UNPKG

@bsv/message-box-client

Version:

A client for P2P messaging and payments

741 lines (598 loc) 26 kB
/* eslint-env jest */ import { MessageBoxClient } from '../MessageBoxClient.js' import { WalletClient, AuthFetch, Transaction, LockingScript } from '@bsv/sdk' // MOCK: WalletClient methods globally jest.spyOn(WalletClient.prototype, 'createHmac').mockResolvedValue({ hmac: Array.from(new Uint8Array([1, 2, 3])) }) jest.spyOn(WalletClient.prototype, 'getPublicKey').mockResolvedValue({ publicKey: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' }) jest.spyOn(WalletClient.prototype, 'encrypt').mockResolvedValue({ ciphertext: [9, 9, 9, 9] }) jest.spyOn(WalletClient.prototype, 'decrypt').mockResolvedValue({ plaintext: [1, 2, 3, 4, 5] }) jest.spyOn(WalletClient.prototype, 'createSignature').mockResolvedValue({ signature: [1, 2, 3, 4, 5] // <-- any dummy byte array }) jest.spyOn(WalletClient.prototype, 'connectToSubstrate').mockImplementation(async function () { this.substrate = { createSignature: async (message: Uint8Array) => { return Array.from(new Uint8Array([1, 2, 3])) // Return mock signature } } }) const minimalTx = new Transaction() minimalTx.addOutput({ satoshis: 1, lockingScript: new LockingScript([]) // ✅ must wrap in LockingScript class }) const realAtomicBEEF = minimalTx.toAtomicBEEF() jest.spyOn(WalletClient.prototype, 'createAction').mockResolvedValue({ txid: 'mocked-txid', tx: realAtomicBEEF }) // MOCK: AuthFetch responses const defaultMockResponse: Partial<Response> = { json: async () => ({ status: 'success', message: 'Mocked response' }), headers: new Headers(), ok: true, status: 200 } jest.spyOn(MessageBoxClient.prototype as any, 'anointHost').mockImplementation(async () => { return { txid: 'mocked-anoint-txid' } }) jest.spyOn(MessageBoxClient.prototype as any, 'queryAdvertisements') .mockResolvedValue([] as string[]) jest.spyOn(AuthFetch.prototype, 'fetch') .mockResolvedValue(defaultMockResponse as Response) // MOCK: WebSocket behavior const socketOnMap: Record<string, (...args: any[]) => void> = {} const mockSocket = { on: jest.fn((event, callback) => { socketOnMap[event] = callback }), emit: jest.fn(), disconnect: jest.fn(), connected: true, off: jest.fn() } jest.mock('@bsv/authsocket-client', () => ({ AuthSocketClient: jest.fn(() => mockSocket) })) // Optional: Global WebSocket override (not strictly needed with AuthSocketClient) class MockWebSocket { static CONNECTING = 0 static OPEN = 1 static CLOSING = 2 static CLOSED = 3 readyState = MockWebSocket.OPEN on = jest.fn() send = jest.fn() close = jest.fn() } global.WebSocket = MockWebSocket as unknown as typeof WebSocket describe('MessageBoxClient', () => { let mockWalletClient: WalletClient beforeEach(() => { mockWalletClient = new WalletClient() jest.clearAllMocks() // (Optional, but if you want per-test control, you could move mocks here instead of globally.) }) const VALID_LIST_AND_READ_RESULT = { body: JSON.stringify({ status: 200, messages: [ { sender: 'mockSender', messageId: 42, body: {} }, { sender: 'mockSender', messageId: 43, body: {} } ] }) } const VALID_ACK_RESULT = { body: JSON.stringify({ status: 200, message: 'Messages marked as acknowledged!' }) } it('Creates an instance of the MessageBoxClient class', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() expect(messageBoxClient).toHaveProperty('host', 'https://messagebox.babbage.systems') // Ensure the socket is initialized as undefined before connecting expect(messageBoxClient.testSocket).toBeUndefined() }) it('Initializes WebSocket connection', async () => { await new Promise(resolve => setTimeout(resolve, 100)) const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() const connection = messageBoxClient.initializeConnection() // Simulate server response setTimeout(() => { socketOnMap.authenticationSuccess?.({ status: 'ok' }) }, 100) await expect(connection).resolves.toBeUndefined() }, 10000) it('Falls back to HTTP when WebSocket is not initialized', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() // Bypass the real connection logic jest.spyOn(messageBoxClient, 'initializeConnection').mockImplementation(async () => { }) // Manually set identity key ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' // Simulate WebSocket not initialized ; (messageBoxClient as any).socket = null // Expect it to fall back to HTTP and succeed const result = await messageBoxClient.sendLiveMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: 'Test message' }) expect(result).toEqual({ status: 'success', message: 'Mocked response', messageId: '010203' }) }) it('Listens for live messages', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() const connection = messageBoxClient.initializeConnection() setTimeout(() => { socketOnMap.authenticationSuccess?.({ status: 'ok' }) }, 100) await connection const mockOnMessage = jest.fn() await messageBoxClient.listenForLiveMessages({ messageBox: 'test_inbox', onMessage: mockOnMessage }) expect(messageBoxClient.testSocket?.emit).toHaveBeenCalledWith( 'joinRoom', '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4-test_inbox' ) }, 10000) it('Sends a live message', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() const connection = messageBoxClient.initializeConnection() // Simulate WebSocket auth success setTimeout(() => { socketOnMap.authenticationSuccess?.({ status: 'ok' }) }, 100) await connection const emitSpy = jest.spyOn(messageBoxClient.testSocket as any, 'emit') // Kick off sending a message (this sets up the ack listener) const sendPromise = messageBoxClient.sendLiveMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: 'Test message' }) // Simulate WebSocket acknowledgment setTimeout(() => { socketOnMap['sendMessageAck-02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4-test_inbox']?.({ status: 'success', messageId: 'mocked123' }) }, 100) const result = await sendPromise // Check that WebSocket emit happened correctly expect(emitSpy).toHaveBeenCalledWith( 'sendMessage', expect.objectContaining({ roomId: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4-test_inbox', message: expect.objectContaining({ messageId: '010203', recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', body: expect.stringMatching(/encrypted/) }) }) ) // Check the resolved result expect(result).toEqual({ status: 'success', messageId: 'mocked123' }) }, 15000) it('Sends a message', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' jest.spyOn(messageBoxClient.authFetch, 'fetch').mockResolvedValue({ json: async () => ({ status: 'success', message: 'Your message has been sent!' }), headers: new Headers(), ok: true, status: 200 } as unknown as Response) const result = await messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: { data: 'test' } }) expect(result).toHaveProperty('message', 'Your message has been sent!') }) it('Lists available messages', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' jest.spyOn(messageBoxClient.authFetch, 'fetch').mockResolvedValue({ json: async () => JSON.parse(VALID_LIST_AND_READ_RESULT.body), headers: new Headers(), ok: true, status: 200 } as unknown as Response) const result = await messageBoxClient.listMessages({ messageBox: 'test_inbox' }) expect(result).toEqual(JSON.parse(VALID_LIST_AND_READ_RESULT.body).messages) }) it('Acknowledges a message', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' jest.spyOn(messageBoxClient.authFetch, 'fetch').mockResolvedValue({ json: async () => JSON.parse(VALID_ACK_RESULT.body), headers: new Headers(), ok: true, status: 200 } as unknown as Response) const result = await messageBoxClient.acknowledgeMessage({ messageIds: ['42'] }) expect(result).toEqual(200) }) it('Throws an error when sendMessage() API fails', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' jest.spyOn(messageBoxClient.authFetch, 'fetch') .mockResolvedValue({ status: 500, statusText: 'Internal Server Error', ok: false, json: async () => ({ status: 'error', description: 'Internal Server Error' }), headers: new Headers() } as unknown as Response) await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: 'Test Message' })).rejects.toThrow('Message sending failed: HTTP 500 - Internal Server Error') }) it('throws when every host fails', async () => { const client = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://primary', enableLogging: false }) await client.init() // Pretend there are no advertised replicas jest.spyOn(client as any, 'queryAdvertisements').mockResolvedValue([]) // Primary host responds with 500 jest.spyOn(client.authFetch, 'fetch').mockResolvedValue({ ok: false, status: 500, statusText: 'Internal Server Error', json: async () => ({ status: 'error', description: 'DB down' }) } as unknown as Response) await expect(client.listMessages({ messageBox: 'inbox' })) .rejects.toThrow('Failed to retrieve messages from any host') }) it('returns [] when at least one host succeeds but has no messages', async () => { const client = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://primary', enableLogging: false }) await client.init() // One failing replica, one healthy replica jest.spyOn(client as any, 'queryAdvertisements').mockResolvedValue([{ host: 'https://replica' }]) jest.spyOn(client.authFetch, 'fetch') .mockImplementation(async url => // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions url.startsWith('https://primary') ? await Promise.resolve({ ok: false, status: 500, statusText: 'Internal Server Error', json: async () => ({ status: 'error', description: 'DB down' }) } as unknown as Response) : await Promise.resolve({ ok: true, status: 200, json: async () => ({ status: 'success', messages: [] }) } as unknown as Response) ) await expect(client.listMessages({ messageBox: 'inbox' })).resolves.toEqual([]) }) it('Throws an error when acknowledgeMessage() API fails', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() ; (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' jest.spyOn(messageBoxClient.authFetch, 'fetch') .mockResolvedValue({ status: 500, json: async () => ({ status: 'error', description: 'Failed to acknowledge messages' }) } as unknown as Response) await expect(messageBoxClient.acknowledgeMessage({ messageIds: ['42'] })) .rejects.toThrow('Failed to acknowledge messages') }) it('Emits joinRoom event and listens for incoming messages', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() // Mock identity key properly jest.spyOn(mockWalletClient, 'getPublicKey').mockResolvedValue({ publicKey: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' }) // Mock socket with `on` method capturing event handlers const mockSocket = { emit: jest.fn(), on: jest.fn() } as any // Mock `initializeConnection` so it assigns `socket` & identity key jest.spyOn(messageBoxClient, 'initializeConnection').mockImplementation(async () => { Object.defineProperty(messageBoxClient, 'testIdentityKey', { get: () => '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' }) Object.defineProperty(messageBoxClient, 'testSocket', { get: () => mockSocket }); (messageBoxClient as any).socket = mockSocket; // Ensures internal socket is set (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' // Ensures identity key is set }) const onMessageMock = jest.fn() await messageBoxClient.listenForLiveMessages({ onMessage: onMessageMock, messageBox: 'test_inbox' }) // Ensure `joinRoom` event was emitted with the correct identity key expect(mockSocket.emit).toHaveBeenCalledWith('joinRoom', '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4-test_inbox') // Simulate receiving a message const receivedMessage = { text: 'Hello, world!' } // Extract & invoke the callback function stored in `on` const sendMessageCallback = mockSocket.on.mock.calls.find( ([eventName]) => eventName === 'sendMessage-02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4-test_inbox' )?.[1] // Extract the callback function if (typeof sendMessageCallback === 'function') { sendMessageCallback(receivedMessage) } // Ensure `onMessage` was called with the received message expect(onMessageMock).toHaveBeenCalledWith(receivedMessage) }) it('Handles WebSocket connection and disconnection events', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() // Simulate identity key jest.spyOn(mockWalletClient, 'getPublicKey').mockResolvedValue({ publicKey: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' }) // Simulate connection + disconnection + auth success setTimeout(() => { socketOnMap.connect?.() socketOnMap.disconnect?.() socketOnMap.authenticationSuccess?.({ status: 'ok' }) }, 100) await messageBoxClient.initializeConnection() // Verify event listeners were registered expect(mockSocket.on).toHaveBeenCalledWith('connect', expect.any(Function)) expect(mockSocket.on).toHaveBeenCalledWith('disconnect', expect.any(Function)) }, 10000) it('throws an error when recipient is empty in sendLiveMessage', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() // Mock `initializeConnection` so it assigns `socket` & identity key jest.spyOn(messageBoxClient, 'initializeConnection').mockImplementation(async () => { Object.defineProperty(messageBoxClient, 'testIdentityKey', { get: () => '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' }) Object.defineProperty(messageBoxClient, 'testSocket', { get: () => mockSocket }); (messageBoxClient as any).socket = mockSocket; // Ensures internal socket is set (messageBoxClient as any).myIdentityKey = '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4' // Ensures identity key is set }) // Mock socket to ensure WebSocket validation does not fail const mockSocket = { emit: jest.fn() } as any jest.spyOn(messageBoxClient, 'testSocket', 'get').mockReturnValue(mockSocket) await expect(messageBoxClient.sendLiveMessage({ recipient: ' ', messageBox: 'test_inbox', body: 'Test message' })).rejects.toThrow('[MB CLIENT ERROR] Recipient identity key is required') }) it('throws an error when recipient is missing in sendMessage', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() await expect(messageBoxClient.sendMessage({ recipient: '', // Empty recipient messageBox: 'test_inbox', body: 'Test message' })).rejects.toThrow('You must provide a message recipient!') await expect(messageBoxClient.sendMessage({ recipient: ' ', // Whitespace recipient messageBox: 'test_inbox', body: 'Test message' })).rejects.toThrow('You must provide a message recipient!') await expect(messageBoxClient.sendMessage({ recipient: null as any, // Null recipient messageBox: 'test_inbox', body: 'Test message' })).rejects.toThrow('You must provide a message recipient!') }) it('throws an error when messageBox is missing in sendMessage', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: '', // Empty messageBox body: 'Test message' })).rejects.toThrow('You must provide a messageBox to send this message into!') await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: ' ', // Whitespace messageBox body: 'Test message' })).rejects.toThrow('You must provide a messageBox to send this message into!') await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: null as any, // Null messageBox body: 'Test message' })).rejects.toThrow('You must provide a messageBox to send this message into!') }) it('throws an error when message body is missing in sendMessage', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: '' // Empty body })).rejects.toThrow('Every message must have a body!') await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: ' ' // Whitespace body })).rejects.toThrow('Every message must have a body!') await expect(messageBoxClient.sendMessage({ recipient: '02b463b8ef7f03c47fba2679c7334d13e4939b8ca30dbb6bbd22e34ea3e9b1b0e4', messageBox: 'test_inbox', body: null as any // Null body })).rejects.toThrow('Every message must have a body!') }) it('throws an error when messageBox is empty in listMessages', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() await expect(messageBoxClient.listMessages({ messageBox: '' // Empty messageBox })).rejects.toThrow('MessageBox cannot be empty') await expect(messageBoxClient.listMessages({ messageBox: ' ' // Whitespace messageBox })).rejects.toThrow('MessageBox cannot be empty') }) it('throws an error when messageIds is empty in acknowledgeMessage', async () => { const messageBoxClient = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://messagebox.babbage.systems', enableLogging: true }) await messageBoxClient.init() await expect(messageBoxClient.acknowledgeMessage({ messageIds: [] // Empty array })).rejects.toThrow('Message IDs array cannot be empty') await expect(messageBoxClient.acknowledgeMessage({ messageIds: undefined as any // Undefined value })).rejects.toThrow('Message IDs array cannot be empty') await expect(messageBoxClient.acknowledgeMessage({ messageIds: null as any // Null value })).rejects.toThrow('Message IDs array cannot be empty') await expect(messageBoxClient.acknowledgeMessage({ messageIds: 'invalid' as any // Not an array })).rejects.toThrow('Message IDs array cannot be empty') }) it('Uses default host if none is provided', () => { const client = new MessageBoxClient({ walletClient: mockWalletClient }) expect((client as any).initialized).toBe(false) expect((client as any).host).toBe('https://messagebox.babbage.systems') }) it('Calls init() to set up a default host when missing', async () => { const client = new MessageBoxClient({ walletClient: mockWalletClient }) expect((client as any).initialized).toBe(false) await client.init() expect((client as any).initialized).toBe(true) expect(typeof (client as any).host).toBe('string') expect((client as any).host.length).toBeGreaterThan(0) }) it('init() overrides host if passed with override=true', async () => { const client = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://original-host.example' }) expect((client as any).initialized).toBe(false) expect((client as any).host).toBe('https://original-host.example') await client.init('https://new-host.example') expect((client as any).initialized).toBe(true) expect((client as any).host).toBe('https://new-host.example') }) it('does not anoint when advert already exists', async () => { jest.spyOn(MessageBoxClient.prototype as any, 'queryAdvertisements').mockResolvedValue([{ host: 'https://messagebox.babbage.systems' }]) const spy = jest.spyOn(MessageBoxClient.prototype as any, 'anointHost') await new MessageBoxClient({ walletClient: mockWalletClient }).init() expect(spy).not.toHaveBeenCalled() }) it('resolveHostForRecipient returns the first advertised host', async () => { const client = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://default.box' }) await client.init() // For this ONE call return two adverts – the first is selected ; (MessageBoxClient.prototype as any).queryAdvertisements .mockResolvedValueOnce([ { host: 'https://peer.box' }, { host: 'https://second.box' }]) const result = await client.resolveHostForRecipient('02aa…deadbeef') expect(result).toBe('https://peer.box') }) it('resolveHostForRecipient falls back to this.host when no adverts exist', async () => { const client = new MessageBoxClient({ walletClient: mockWalletClient, host: 'https://default.box' }) await client.init() ; (MessageBoxClient.prototype as any).queryAdvertisements .mockResolvedValueOnce([]) const result = await client.resolveHostForRecipient('03bb…cafef00d') expect(result).toBe('https://default.box') }) })