first commit

This commit is contained in:
Chris Troutner
2025-11-09 16:16:37 -08:00
commit ba2afd47a4
61 changed files with 13847 additions and 0 deletions
@@ -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')
}
})
})
})