Skip to content

Commit

Permalink
fix: ipc-wrapper failed when receiving a non-error failure
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Apr 26, 2024
1 parent 3f8db38 commit 8e01ef4
Show file tree
Hide file tree
Showing 2 changed files with 349 additions and 1 deletion.
348 changes: 348 additions & 0 deletions src/host-api/__tests__/ipc-wrapper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,348 @@
import { IpcWrapper } from '../ipc-wrapper.js'

interface TestInbound1 {
recvTest1: (a: number) => void
recvTest2: (a: number) => never
}
interface TestOutbound1 {
sendTest1: (a: number) => void
sendTest2: (a: number) => never
}

function stringifyError(err: Error): string {
return JSON.stringify(err, Object.getOwnPropertyNames(err))
}

describe('IpcWrapper', () => {
const sendMessageFn = jest.fn()

const testRecv1Fn = jest.fn<Promise<void>, []>()
const testRecv2Fn = jest.fn<Promise<void>, []>()
let ipc: IpcWrapper<TestOutbound1, TestInbound1>

beforeAll(() => {
jest.useFakeTimers()
})

beforeEach(() => {
sendMessageFn.mockClear()
testRecv1Fn.mockClear()
testRecv2Fn.mockClear()

ipc = new IpcWrapper<TestOutbound1, TestInbound1>(
{
recvTest1: testRecv1Fn,
recvTest2: testRecv2Fn,
},
sendMessageFn,
100
)
})

test('send without callback', () => {
ipc.sendWithNoCb('sendTest2', 1)

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest2',
payload: '1',
callbackId: undefined,
})
})

describe('send with callback', () => {
test('timeout', async () => {
const result = ipc.sendWithCb('sendTest1', 23)
result.catch(() => null) // suppress unhandled promise rejection warning

expect(result).toBeTruthy() // should be a promise

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest1',
payload: '23',
callbackId: 1,
})

jest.advanceTimersByTime(101)

await expect(result).rejects.toThrow('Call timed out')
})

test('returns success', async () => {
const result = ipc.sendWithCb('sendTest1', 23)
result.catch(() => null) // suppress unhandled promise rejection warning

expect(result).toBeTruthy() // should be a promise

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest1',
payload: '23',
callbackId: 1,
})

ipc.receivedMessage({
direction: 'response',
callbackId: 1,
success: true,
payload: '42',
})

await expect(result).resolves.toEqual(42)
})

test('returns object', async () => {
const result = ipc.sendWithCb('sendTest1', 23)
result.catch(() => null) // suppress unhandled promise rejection warning

expect(result).toBeTruthy() // should be a promise

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest1',
payload: '23',
callbackId: 1,
})

ipc.receivedMessage({
direction: 'response',
callbackId: 1,
success: true,
payload: JSON.stringify({ value: 42 }),
})

await expect(result).resolves.toEqual({ value: 42 })
})

test('throw error', async () => {
const result = ipc.sendWithCb('sendTest1', 23)
result.catch(() => null) // suppress unhandled promise rejection warning

expect(result).toBeTruthy() // should be a promise

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest1',
payload: '23',
callbackId: 1,
})

ipc.receivedMessage({
direction: 'response',
callbackId: 1,
success: false,
payload: stringifyError(new Error('my error')),
})

await expect(result).rejects.toThrow(`my error`)
})

test('throw error as string', async () => {
const result = ipc.sendWithCb('sendTest1', 23)
result.catch(() => null) // suppress unhandled promise rejection warning

expect(result).toBeTruthy() // should be a promise

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest1',
payload: '23',
callbackId: 1,
})

ipc.receivedMessage({
direction: 'response',
callbackId: 1,
success: false,
payload: '"error as string"',
})

await expect(result).rejects.toEqual('error as string')
})

test('throw null', async () => {
const result = ipc.sendWithCb('sendTest1', 23)
result.catch(() => null) // suppress unhandled promise rejection warning

expect(result).toBeTruthy() // should be a promise

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'call',
name: 'sendTest1',
payload: '23',
callbackId: 1,
})

ipc.receivedMessage({
direction: 'response',
callbackId: 1,
success: false,
payload: JSON.stringify(null),
})

await expect(result).rejects.toEqual(null)
})
})

test('receive without callback', () => {
testRecv1Fn.mockImplementation(async () => {
return 67 as any
})

ipc.receivedMessage({
direction: 'call',
name: 'recvTest1',
payload: '42',
callbackId: undefined,
})

expect(testRecv1Fn).toHaveBeenCalledTimes(1)
expect(testRecv1Fn).toHaveBeenCalledWith(42)

jest.advanceTimersByTime(101)
expect(sendMessageFn).toHaveBeenCalledTimes(0)
})

describe('receive with callback', () => {
test('returns success', async () => {
testRecv2Fn.mockImplementation(async () => {
return 67 as any
})

ipc.receivedMessage({
direction: 'call',
name: 'recvTest2',
payload: '42',
callbackId: 456,
})

expect(testRecv2Fn).toHaveBeenCalledTimes(1)
expect(testRecv2Fn).toHaveBeenCalledWith(42)

await jest.advanceTimersByTimeAsync(201)

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'response',
success: true,
payload: '67',
callbackId: 456,
})
})

test('returns object', async () => {
testRecv2Fn.mockImplementation(async () => {
return { value: 88 } as any
})

ipc.receivedMessage({
direction: 'call',
name: 'recvTest2',
payload: '42',
callbackId: 456,
})

expect(testRecv2Fn).toHaveBeenCalledTimes(1)
expect(testRecv2Fn).toHaveBeenCalledWith(42)

await jest.advanceTimersByTimeAsync(201)

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'response',
success: true,
payload: JSON.stringify({ value: 88 }),
callbackId: 456,
})
})

test('throw error', async () => {
let error: Error | undefined
testRecv2Fn.mockImplementation(async () => {
error = new Error('my error')
throw error
})

ipc.receivedMessage({
direction: 'call',
name: 'recvTest2',
payload: '42',
callbackId: 456,
})

expect(testRecv2Fn).toHaveBeenCalledTimes(1)
expect(testRecv2Fn).toHaveBeenCalledWith(42)
expect(error).toBeTruthy()

await jest.advanceTimersByTimeAsync(201)

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'response',
success: false,
payload: stringifyError(error!),
callbackId: 456,
})
})

test('throw error as string', async () => {
testRecv2Fn.mockImplementation(async () => {
throw 'my error message'
})

ipc.receivedMessage({
direction: 'call',
name: 'recvTest2',
payload: '42',
callbackId: 456,
})

expect(testRecv2Fn).toHaveBeenCalledTimes(1)
expect(testRecv2Fn).toHaveBeenCalledWith(42)

await jest.advanceTimersByTimeAsync(201)

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'response',
success: false,
payload: '"my error message"',
callbackId: 456,
})
})

test('throw null', async () => {
testRecv2Fn.mockImplementation(async () => {
throw null
})

ipc.receivedMessage({
direction: 'call',
name: 'recvTest2',
payload: '42',
callbackId: 456,
})

expect(testRecv2Fn).toHaveBeenCalledTimes(1)
expect(testRecv2Fn).toHaveBeenCalledWith(42)

await jest.advanceTimersByTimeAsync(201)

expect(sendMessageFn).toHaveBeenCalledTimes(1)
expect(sendMessageFn).toHaveBeenCalledWith({
direction: 'response',
success: false,
payload: 'null',
callbackId: 456,
})
})
})
})
2 changes: 1 addition & 1 deletion src/host-api/ipc-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export class IpcWrapper<TOutbound extends { [key: string]: any }, TInbound exten
callbacks.resolve(data)
} else {
let err = data
if (data && 'message' in data) {
if (data && typeof data === 'object' && 'message' in data) {
err = new Error(data.message)
if (data.stack) err.stack = data.stack
}
Expand Down

0 comments on commit 8e01ef4

Please sign in to comment.