@bsv/message-box-client
Version:
A client for P2P messaging and payments
741 lines (598 loc) • 26 kB
text/typescript
/* 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')
})
})