mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-21 16:52:00 -07:00
first commit
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
Unit tests for ManageSubscriptionUseCase.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Mocking data libraries
|
||||
import { mockKind1Event } from '../mocks/event-mocks.js'
|
||||
|
||||
// Unit under test
|
||||
import ManageSubscriptionUseCase from '../../../src/use-cases/manage-subscription.js'
|
||||
|
||||
describe('#manage-subscription.js', () => {
|
||||
let sandbox
|
||||
let mockAdapters
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
// Create mock adapters with multiple relays support
|
||||
const mockRelay1 = {
|
||||
relayUrl: 'wss://relay1.example.com',
|
||||
sendReq: sandbox.stub(),
|
||||
sendClose: sandbox.stub()
|
||||
}
|
||||
const mockRelay2 = {
|
||||
relayUrl: 'wss://relay2.example.com',
|
||||
sendReq: sandbox.stub(),
|
||||
sendClose: sandbox.stub()
|
||||
}
|
||||
|
||||
mockAdapters = {
|
||||
nostrRelays: [mockRelay1, mockRelay2]
|
||||
}
|
||||
|
||||
uut = new ManageSubscriptionUseCase({ adapters: mockAdapters })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#createSubscription()', () => {
|
||||
it('should successfully create a subscription across all relays', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
let onEventCalled = false
|
||||
let onEoseCalled = false
|
||||
let onClosedCalled = false
|
||||
|
||||
const onEvent = (event) => {
|
||||
onEventCalled = true
|
||||
}
|
||||
const onEose = () => {
|
||||
onEoseCalled = true
|
||||
}
|
||||
const onClosed = (message) => {
|
||||
onClosedCalled = true
|
||||
}
|
||||
|
||||
// Mock adapters to resolve
|
||||
mockAdapters.nostrRelays[0].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
|
||||
await uut.createSubscription(subscriptionId, filters, onEvent, onEose, onClosed)
|
||||
|
||||
// Assert adapters were called for both relays
|
||||
assert.isTrue(mockAdapters.nostrRelays[0].sendReq.calledOnce)
|
||||
assert.isTrue(mockAdapters.nostrRelays[1].sendReq.calledOnce)
|
||||
|
||||
// Assert subscription is tracked
|
||||
assert.isTrue(uut.hasSubscription(subscriptionId))
|
||||
|
||||
// Test handlers - get them from the subscription info
|
||||
const subscriptionInfo = uut.activeSubscriptions.get(subscriptionId)
|
||||
const handlers = subscriptionInfo.handlers
|
||||
|
||||
// Test event handler (should de-duplicate)
|
||||
handlers.onEvent(mockKind1Event)
|
||||
assert.isTrue(onEventCalled)
|
||||
|
||||
// Simulate EOSE from both relays
|
||||
const relayStatuses = subscriptionInfo.relayStatuses
|
||||
relayStatuses[0].eoseReceived = true
|
||||
relayStatuses[1].eoseReceived = true
|
||||
handlers.onEose()
|
||||
assert.isTrue(onEoseCalled)
|
||||
|
||||
handlers.onClosed('test message')
|
||||
assert.isTrue(onClosedCalled)
|
||||
assert.isFalse(uut.hasSubscription(subscriptionId))
|
||||
})
|
||||
|
||||
it('should prevent duplicate subscriptions', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
mockAdapters.nostrRelays[0].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
|
||||
try {
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
assert.equal(true, false, 'unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'already exists')
|
||||
}
|
||||
})
|
||||
|
||||
it('should clean up subscription on error', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
mockAdapters.nostrRelays[0].sendReq.rejects(new Error('Connection error'))
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
|
||||
try {
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
assert.equal(true, false, 'unexpected result')
|
||||
} catch (err) {
|
||||
// Should clean up even if some relays fail
|
||||
assert.isFalse(uut.hasSubscription(subscriptionId))
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle missing callbacks gracefully', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
mockAdapters.nostrRelays[0].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
|
||||
await uut.createSubscription(subscriptionId, filters, null, null, null)
|
||||
|
||||
// Should not throw when handlers are null
|
||||
const subscriptionInfo = uut.activeSubscriptions.get(subscriptionId)
|
||||
const handlers = subscriptionInfo.handlers
|
||||
handlers.onEvent(mockKind1Event)
|
||||
handlers.onEose()
|
||||
handlers.onClosed('test')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#closeSubscription()', () => {
|
||||
it('should successfully close a subscription across all relays', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
mockAdapters.nostrRelays[0].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[0].sendClose.resolves()
|
||||
mockAdapters.nostrRelays[1].sendClose.resolves()
|
||||
|
||||
// Create subscription first
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
assert.isTrue(uut.hasSubscription(subscriptionId))
|
||||
|
||||
// Close subscription
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
|
||||
// Assert adapters were called for both relays
|
||||
assert.isTrue(mockAdapters.nostrRelays[0].sendClose.calledOnce)
|
||||
assert.isTrue(mockAdapters.nostrRelays[1].sendClose.calledOnce)
|
||||
|
||||
// Assert subscription is removed
|
||||
assert.isFalse(uut.hasSubscription(subscriptionId))
|
||||
})
|
||||
|
||||
it('should return successfully when closing non-existent subscription (idempotent)', async () => {
|
||||
const subscriptionId = 'non-existent-sub'
|
||||
|
||||
// Should not throw - idempotent operation
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
|
||||
// Should return successfully without error
|
||||
assert.isTrue(true, 'closeSubscription should succeed for non-existent subscription')
|
||||
})
|
||||
|
||||
it('should clean up subscription even on error', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
mockAdapters.nostrRelays[0].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[0].sendClose.rejects(new Error('Close error'))
|
||||
mockAdapters.nostrRelays[1].sendClose.resolves()
|
||||
|
||||
// Create subscription first
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
|
||||
// Close should succeed even if one relay fails
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
|
||||
// Should still clean up
|
||||
assert.isFalse(uut.hasSubscription(subscriptionId))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hasSubscription()', () => {
|
||||
it('should return false for non-existent subscription', () => {
|
||||
assert.isFalse(uut.hasSubscription('non-existent'))
|
||||
})
|
||||
|
||||
it('should return true for existing subscription', async () => {
|
||||
const subscriptionId = 'test-sub-123'
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
mockAdapters.nostrRelays[0].sendReq.resolves()
|
||||
mockAdapters.nostrRelays[1].sendReq.resolves()
|
||||
|
||||
assert.isFalse(uut.hasSubscription(subscriptionId))
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
assert.isTrue(uut.hasSubscription(subscriptionId))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should require adapters instance', () => {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new ManageSubscriptionUseCase()
|
||||
assert.equal(true, false, 'unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Adapters instance required')
|
||||
}
|
||||
})
|
||||
|
||||
it('should require NostrRelay adapters array', () => {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new ManageSubscriptionUseCase({ adapters: {} })
|
||||
assert.equal(true, false, 'unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'NostrRelay adapters array required')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user