mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-21 16:52:00 -07:00
305 lines
8.2 KiB
JavaScript
305 lines
8.2 KiB
JavaScript
/*
|
|||
|
|
Unit tests for NostrRelayAdapter.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
// npm libraries
|
||
|
|
import { assert } from 'chai'
|
||
|
|
import sinon from 'sinon'
|
||
|
|
|
||
|
|
// Mocking data libraries
|
||
|
|
import {
|
||
|
|
mockKind1Event,
|
||
|
|
validEventId
|
||
|
|
} from '../mocks/event-mocks.js'
|
||
|
|
import {
|
||
|
|
mockOkAccepted,
|
||
|
|
mockEventMessage,
|
||
|
|
mockEoseMessage,
|
||
|
|
mockClosedMessage
|
||
|
|
} from '../mocks/nostr-relay-mocks.js'
|
||
|
|
|
||
|
|
// Unit under test
|
||
|
|
// Note: WebSocket mocking for ES modules is complex. These tests focus on
|
||
|
|
// testing the adapter's logic that can be tested without full WebSocket mocking.
|
||
|
|
import NostrRelayAdapter from '../../../src/adapters/nostr-relay.js'
|
||
|
|
|
||
|
|
describe('#nostr-relay.js', () => {
|
||
|
|
let sandbox
|
||
|
|
let uut
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
sandbox = sinon.createSandbox()
|
||
|
|
|
||
|
|
uut = new NostrRelayAdapter({
|
||
|
|
relayUrl: 'wss://test-relay.example.com'
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
sandbox.restore()
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#connect()', () => {
|
||
|
|
it('should return immediately if already connected', async () => {
|
||
|
|
// Manually set connection state
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.ws = { close: sandbox.stub() }
|
||
|
|
|
||
|
|
await uut.connect()
|
||
|
|
|
||
|
|
// Should not create new connection
|
||
|
|
assert.isTrue(uut.isConnected)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Note: Full WebSocket connection testing requires integration tests
|
||
|
|
// due to ES module import limitations
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#sendEvent()', () => {
|
||
|
|
it('should queue message when disconnected', async () => {
|
||
|
|
uut.isConnected = false
|
||
|
|
uut.ws = null
|
||
|
|
|
||
|
|
// Mock connect to resolve immediately
|
||
|
|
uut.connect = sandbox.stub().resolves()
|
||
|
|
|
||
|
|
// Start sending (will queue)
|
||
|
|
uut.sendEvent(mockKind1Event).catch(() => {
|
||
|
|
// Expected to fail or timeout without real WebSocket
|
||
|
|
})
|
||
|
|
|
||
|
|
// Should queue message and attempt connection
|
||
|
|
// Wait a bit for async operations
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 10))
|
||
|
|
assert.isTrue(uut.pendingMessages.length > 0 || uut.connect.called)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should set up event resolver', async () => {
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.ws = { send: sandbox.stub() }
|
||
|
|
// Mock sendMessage to resolve immediately
|
||
|
|
uut.sendMessage = sandbox.stub().resolves()
|
||
|
|
|
||
|
|
// Start sending
|
||
|
|
const sendPromise = uut.sendEvent(mockKind1Event).catch(() => {
|
||
|
|
// Expected without real WebSocket response
|
||
|
|
})
|
||
|
|
|
||
|
|
// Wait a tick for Promise constructor to run
|
||
|
|
await new Promise(resolve => setImmediate(resolve))
|
||
|
|
|
||
|
|
// Verify resolver was set up
|
||
|
|
assert.isTrue(uut.eventResolvers.has(mockKind1Event.id))
|
||
|
|
|
||
|
|
// Clean up
|
||
|
|
uut.eventResolvers.delete(mockKind1Event.id)
|
||
|
|
// Prevent timeout error
|
||
|
|
sendPromise.catch(() => {})
|
||
|
|
})
|
||
|
|
|
||
|
|
// Note: Full sendEvent testing with WebSocket responses requires integration tests
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#sendReq()', () => {
|
||
|
|
it('should store handlers for subscription', async () => {
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.ws = { send: sandbox.stub() }
|
||
|
|
uut.connect = sandbox.stub().resolves()
|
||
|
|
|
||
|
|
const subscriptionId = 'test-sub-123'
|
||
|
|
const filters = [{ kinds: [1] }]
|
||
|
|
const handlers = {
|
||
|
|
onEvent: sandbox.stub(),
|
||
|
|
onEose: sandbox.stub(),
|
||
|
|
onClosed: sandbox.stub()
|
||
|
|
}
|
||
|
|
|
||
|
|
await uut.sendReq(subscriptionId, filters, handlers)
|
||
|
|
|
||
|
|
// Assert handlers were stored
|
||
|
|
assert.isTrue(uut.subscriptionHandlers.has(subscriptionId))
|
||
|
|
assert.deepEqual(uut.subscriptionHandlers.get(subscriptionId), handlers)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should connect before sending if disconnected', async () => {
|
||
|
|
uut.isConnected = false
|
||
|
|
uut.connect = sandbox.stub().resolves()
|
||
|
|
|
||
|
|
const subscriptionId = 'test-sub-123'
|
||
|
|
const filters = [{ kinds: [1] }]
|
||
|
|
const handlers = {}
|
||
|
|
|
||
|
|
await uut.sendReq(subscriptionId, filters, handlers)
|
||
|
|
|
||
|
|
assert.isTrue(uut.connect.called)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#sendClose()', () => {
|
||
|
|
it('should clean up handlers for subscription', async () => {
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.ws = { send: sandbox.stub() }
|
||
|
|
|
||
|
|
const subscriptionId = 'test-sub-123'
|
||
|
|
uut.subscriptionHandlers.set(subscriptionId, {})
|
||
|
|
uut.messageHandlers.set(subscriptionId, {})
|
||
|
|
|
||
|
|
await uut.sendClose(subscriptionId)
|
||
|
|
|
||
|
|
// Assert handlers were cleaned up
|
||
|
|
assert.isFalse(uut.subscriptionHandlers.has(subscriptionId))
|
||
|
|
assert.isFalse(uut.messageHandlers.has(subscriptionId))
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#handleMessage()', () => {
|
||
|
|
it('should handle EVENT message', () => {
|
||
|
|
// Use the subscription ID from the mock message
|
||
|
|
const subscriptionId = 'subscription-id-123'
|
||
|
|
const onEventHandler = sandbox.stub()
|
||
|
|
uut.subscriptionHandlers.set(subscriptionId, {
|
||
|
|
onEvent: onEventHandler
|
||
|
|
})
|
||
|
|
|
||
|
|
const message = mockEventMessage
|
||
|
|
uut.handleMessage(message)
|
||
|
|
|
||
|
|
assert.isTrue(onEventHandler.calledOnce)
|
||
|
|
assert.deepEqual(onEventHandler.getCall(0).args[0], mockKind1Event)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should handle EOSE message', () => {
|
||
|
|
// Use the subscription ID from the mock message
|
||
|
|
const subscriptionId = 'subscription-id-123'
|
||
|
|
const onEoseHandler = sandbox.stub()
|
||
|
|
uut.subscriptionHandlers.set(subscriptionId, {
|
||
|
|
onEose: onEoseHandler
|
||
|
|
})
|
||
|
|
|
||
|
|
const message = mockEoseMessage
|
||
|
|
uut.handleMessage(message)
|
||
|
|
|
||
|
|
assert.isTrue(onEoseHandler.calledOnce)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should handle CLOSED message', () => {
|
||
|
|
// Use the subscription ID from the mock message
|
||
|
|
const subscriptionId = 'subscription-id-123'
|
||
|
|
const onClosedHandler = sandbox.stub()
|
||
|
|
uut.subscriptionHandlers.set(subscriptionId, {
|
||
|
|
onClosed: onClosedHandler
|
||
|
|
})
|
||
|
|
|
||
|
|
const message = mockClosedMessage
|
||
|
|
uut.handleMessage(message)
|
||
|
|
|
||
|
|
assert.isTrue(onClosedHandler.calledOnce)
|
||
|
|
assert.equal(onClosedHandler.getCall(0).args[0], 'subscription closed')
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should handle OK message', () => {
|
||
|
|
const eventId = validEventId
|
||
|
|
let resolver = null
|
||
|
|
uut.eventResolvers.set(eventId, (result) => {
|
||
|
|
resolver = result
|
||
|
|
})
|
||
|
|
|
||
|
|
const message = mockOkAccepted
|
||
|
|
uut.handleMessage(message)
|
||
|
|
|
||
|
|
assert.isNotNull(resolver)
|
||
|
|
assert.isTrue(resolver.accepted)
|
||
|
|
assert.isFalse(uut.eventResolvers.has(eventId))
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should handle NOTICE message', () => {
|
||
|
|
const message = ['NOTICE', 'rate limited']
|
||
|
|
// Should not throw
|
||
|
|
uut.handleMessage(message)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should ignore invalid message format', () => {
|
||
|
|
const message = 'invalid'
|
||
|
|
// Should not throw
|
||
|
|
uut.handleMessage(message)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should ignore empty messages', () => {
|
||
|
|
const message = []
|
||
|
|
// Should not throw
|
||
|
|
uut.handleMessage(message)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#disconnect()', () => {
|
||
|
|
it('should disconnect from relay', async () => {
|
||
|
|
const mockWs = { close: sandbox.stub() }
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.ws = mockWs
|
||
|
|
|
||
|
|
await uut.disconnect()
|
||
|
|
|
||
|
|
assert.isTrue(mockWs.close.called)
|
||
|
|
assert.isFalse(uut.isConnected)
|
||
|
|
assert.isNull(uut.ws)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should handle disconnect when already disconnected', async () => {
|
||
|
|
uut.isConnected = false
|
||
|
|
uut.ws = null
|
||
|
|
|
||
|
|
await uut.disconnect()
|
||
|
|
|
||
|
|
assert.isFalse(uut.isConnected)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#handleError()', () => {
|
||
|
|
it('should handle WebSocket errors', () => {
|
||
|
|
uut.isConnected = true
|
||
|
|
const error = new Error('WebSocket error')
|
||
|
|
|
||
|
|
uut.handleError(error)
|
||
|
|
|
||
|
|
assert.isFalse(uut.isConnected)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
describe('#handleClose()', () => {
|
||
|
|
it('should attempt reconnection on close', async () => {
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.reconnectAttempts = 0
|
||
|
|
uut.maxReconnectAttempts = 5
|
||
|
|
|
||
|
|
// Mock connect to avoid actual connection
|
||
|
|
uut.connect = sandbox.stub().resolves()
|
||
|
|
|
||
|
|
uut.handleClose()
|
||
|
|
|
||
|
|
// Wait for reconnection attempt
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 110))
|
||
|
|
|
||
|
|
// Should attempt reconnection
|
||
|
|
assert.equal(uut.reconnectAttempts, 1)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('should stop reconnecting after max attempts', async () => {
|
||
|
|
uut.isConnected = true
|
||
|
|
uut.reconnectAttempts = 5
|
||
|
|
uut.maxReconnectAttempts = 5
|
||
|
|
|
||
|
|
uut.connect = sandbox.stub().resolves()
|
||
|
|
|
||
|
|
uut.handleClose()
|
||
|
|
|
||
|
|
await new Promise(resolve => setTimeout(resolve, 110))
|
||
|
|
|
||
|
|
// Should not increment beyond max
|
||
|
|
assert.equal(uut.reconnectAttempts, 5)
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
*/
|