mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api.git
synced 2026-09-21 16:52:00 -07:00
Removing integration tests from old repository
This commit is contained in:
@@ -23,7 +23,7 @@ class TimerController {
|
||||
}
|
||||
|
||||
// Constants
|
||||
this.SHUTDOWN_INTERVAL_MS = 10 * 60 * 1000 // 10 minutes in milliseconds
|
||||
this.SHUTDOWN_INTERVAL_MS = 10 * 60 * 60 * 1000 // 10 hours in milliseconds
|
||||
this.LIVENESS_CHECK_INTERVAL_MS = 1 * 60 * 1000 // 1 minute in milliseconds
|
||||
|
||||
// Handlers
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
/*
|
||||
Integration tests for POST /event endpoint.
|
||||
These tests require a running Nostr relay.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Unit under test
|
||||
import Server from '../../../bin/server.js'
|
||||
import { finalizeEvent, getPublicKey, generateSecretKey } from 'nostr-tools/pure'
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
|
||||
describe('#event-integration.js', () => {
|
||||
let server
|
||||
const baseUrl = 'http://localhost:3001' // Use different port for tests
|
||||
|
||||
before(async () => {
|
||||
// Start test server
|
||||
server = new Server()
|
||||
server.config.port = 3001
|
||||
await server.startServer()
|
||||
|
||||
// Wait for server to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// Stop server
|
||||
if (server && server.server) {
|
||||
await new Promise((resolve) => {
|
||||
server.server.close(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('POST /event', () => {
|
||||
it('should publish kind 0 event (profile metadata) - covers example 01', async () => {
|
||||
// Generate keys
|
||||
const sk = generateSecretKey()
|
||||
|
||||
// Create profile metadata event (kind 0)
|
||||
const profileMetadata = {
|
||||
name: 'Test User',
|
||||
about: 'Integration test user',
|
||||
picture: 'https://example.com/test.jpg'
|
||||
}
|
||||
|
||||
const eventTemplate = {
|
||||
kind: 0,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: JSON.stringify(profileMetadata)
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(eventTemplate, sk)
|
||||
|
||||
// Publish to REST API
|
||||
const response = await fetch(`${baseUrl}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.property(result, 'accepted')
|
||||
assert.property(result, 'eventId')
|
||||
assert.equal(result.eventId, signedEvent.id)
|
||||
})
|
||||
|
||||
it('should publish kind 1 event (text post) - covers example 03', async () => {
|
||||
// Alice's private key from examples
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
|
||||
// Generate a post
|
||||
const eventTemplate = {
|
||||
kind: 1,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: 'Integration test post'
|
||||
}
|
||||
|
||||
// Sign the post
|
||||
const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin)
|
||||
|
||||
// Publish to REST API
|
||||
const response = await fetch(`${baseUrl}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.property(result, 'accepted')
|
||||
assert.property(result, 'eventId')
|
||||
assert.equal(result.eventId, signedEvent.id)
|
||||
assert.equal(signedEvent.pubkey, alicePubKey)
|
||||
})
|
||||
|
||||
it('should publish kind 3 event (follow list) - covers example 06', async () => {
|
||||
// Alice's private key
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
|
||||
// Bob's public key
|
||||
const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f'
|
||||
const bobPrivKeyBin = hexToBytes(bobPrivKeyHex)
|
||||
const bobPubKey = getPublicKey(bobPrivKeyBin)
|
||||
|
||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||
|
||||
const followList = [
|
||||
['p', bobPubKey, psf, 'bob']
|
||||
]
|
||||
|
||||
// Generate a follow list event (kind 3)
|
||||
const eventTemplate = {
|
||||
kind: 3,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: followList,
|
||||
content: ''
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin)
|
||||
|
||||
// Publish to REST API
|
||||
const response = await fetch(`${baseUrl}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.property(result, 'accepted')
|
||||
assert.property(result, 'eventId')
|
||||
assert.equal(result.eventId, signedEvent.id)
|
||||
})
|
||||
|
||||
it('should publish kind 7 event (reaction/like) - covers example 07', async () => {
|
||||
// Bob's private key
|
||||
const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f'
|
||||
const bobPrivKeyBin = hexToBytes(bobPrivKeyHex)
|
||||
const bobPubKey = getPublicKey(bobPrivKeyBin)
|
||||
|
||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||
|
||||
// Use a test event ID
|
||||
const evIdToLike = 'd09b4c5da59be3cd2768aa53fa78b77bf4859084c94f3bf26d401f004a9c8167'
|
||||
const evIdAuthorPubKey = '2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e92'
|
||||
|
||||
// Generate like event (kind 7)
|
||||
const likeEventTemplate = {
|
||||
kind: 7,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
pubkey: bobPubKey,
|
||||
tags: [
|
||||
['e', evIdToLike, psf],
|
||||
['p', evIdAuthorPubKey, psf]
|
||||
],
|
||||
content: '+'
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(likeEventTemplate, bobPrivKeyBin)
|
||||
|
||||
// Publish to REST API
|
||||
const response = await fetch(`${baseUrl}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.property(result, 'accepted')
|
||||
assert.property(result, 'eventId')
|
||||
assert.equal(result.eventId, signedEvent.id)
|
||||
})
|
||||
|
||||
it('should reject invalid event', async () => {
|
||||
const invalidEvent = {
|
||||
id: 'invalid',
|
||||
pubkey: 'invalid',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 1,
|
||||
tags: [],
|
||||
content: 'Test',
|
||||
sig: 'invalid'
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(invalidEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
// Should reject invalid event - error response format
|
||||
assert.equal(response.status, 400)
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Invalid event structure')
|
||||
})
|
||||
|
||||
it('should return 400 when event data is missing', async () => {
|
||||
// Send empty body - Express will parse as undefined, controller should handle it
|
||||
const response = await fetch(`${baseUrl}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: ''
|
||||
})
|
||||
|
||||
// Empty body should be parsed as undefined by Express
|
||||
const result = await response.json()
|
||||
|
||||
assert.equal(response.status, 400)
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Event data is required')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
Integration tests for GET /req/:subId endpoint.
|
||||
These tests require a running Nostr relay.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Unit under test
|
||||
import Server from '../../../bin/server.js'
|
||||
import { getPublicKey } from 'nostr-tools/pure'
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
|
||||
describe('#req-integration.js', () => {
|
||||
let server
|
||||
const baseUrl = 'http://localhost:3002' // Use different port for tests
|
||||
|
||||
before(async () => {
|
||||
// Start test server
|
||||
server = new Server()
|
||||
server.config.port = 3002
|
||||
await server.startServer()
|
||||
|
||||
// Wait for server to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// Stop server
|
||||
if (server && server.server) {
|
||||
await new Promise((resolve) => {
|
||||
server.server.close(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('GET /req/:subId', () => {
|
||||
it('should query kind 1 events (posts) - covers examples 02, 04', async () => {
|
||||
// JB55's public key from example 02
|
||||
const jb55 = '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'
|
||||
|
||||
// Create subscription ID
|
||||
const subId = 'read-posts-' + Date.now()
|
||||
|
||||
// Create filters - read posts from JB55
|
||||
const filters = {
|
||||
limit: 2,
|
||||
kinds: [1],
|
||||
authors: [jb55]
|
||||
}
|
||||
|
||||
// Query events using GET /req/:subId
|
||||
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
||||
const url = `${baseUrl}/req/${subId}?filters=${filtersJson}`
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.isArray(events)
|
||||
// May be empty if no events exist, but structure should be correct
|
||||
if (events.length > 0) {
|
||||
assert.property(events[0], 'id')
|
||||
assert.property(events[0], 'pubkey')
|
||||
assert.property(events[0], 'created_at')
|
||||
assert.property(events[0], 'kind')
|
||||
assert.property(events[0], 'content')
|
||||
assert.equal(events[0].kind, 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('should query Alice posts - covers example 04', async () => {
|
||||
// Alice's public key
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
|
||||
// Create subscription ID
|
||||
const subId = 'read-alice-posts-' + Date.now()
|
||||
|
||||
// Create filters - read posts from Alice
|
||||
const filters = {
|
||||
limit: 2,
|
||||
kinds: [1],
|
||||
authors: [alicePubKey]
|
||||
}
|
||||
|
||||
// Query events using GET /req/:subId
|
||||
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
||||
const url = `${baseUrl}/req/${subId}?filters=${filtersJson}`
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.isArray(events)
|
||||
if (events.length > 0) {
|
||||
assert.equal(events[0].pubkey, alicePubKey)
|
||||
assert.equal(events[0].kind, 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('should query kind 3 events (follow list) - covers example 05', async () => {
|
||||
// Alice's public key
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
|
||||
// Create subscription ID
|
||||
const subId = 'get-follow-list-' + Date.now()
|
||||
|
||||
// Create filters - get follow list (kind 3) from Alice
|
||||
const filters = {
|
||||
limit: 5,
|
||||
kinds: [3],
|
||||
authors: [alicePubKey]
|
||||
}
|
||||
|
||||
// Query events using GET /req/:subId
|
||||
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
||||
const url = `${baseUrl}/req/${subId}?filters=${filtersJson}`
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
// Assert response
|
||||
assert.equal(response.status, 200)
|
||||
assert.isArray(events)
|
||||
if (events.length > 0) {
|
||||
assert.equal(events[0].kind, 3)
|
||||
assert.equal(events[0].pubkey, alicePubKey)
|
||||
assert.isArray(events[0].tags)
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle filters as individual query params', async () => {
|
||||
const subId = 'test-sub-' + Date.now()
|
||||
const url = `${baseUrl}/req/${subId}?kinds=[1]&limit=10`
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
assert.equal(response.status, 200)
|
||||
assert.isArray(events)
|
||||
})
|
||||
|
||||
it('should return 400 when subscription ID is missing', async () => {
|
||||
const url = `${baseUrl}/req/?filters=${encodeURIComponent(JSON.stringify([{ kinds: [1] }]))}`
|
||||
|
||||
const response = await fetch(url)
|
||||
await response.json()
|
||||
|
||||
// Should return 404 or 400
|
||||
assert.isAtLeast(response.status, 400)
|
||||
})
|
||||
|
||||
it('should return 400 when filters JSON is invalid', async () => {
|
||||
const subId = 'test-sub-' + Date.now()
|
||||
const url = `${baseUrl}/req/${subId}?filters=invalid-json{`
|
||||
|
||||
const response = await fetch(url)
|
||||
const result = await response.json()
|
||||
|
||||
assert.equal(response.status, 400)
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Invalid filters JSON')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
Integration tests for POST /req/:subId SSE subscription and DELETE /req/:subId.
|
||||
These tests require a running Nostr relay.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Unit under test
|
||||
import Server from '../../../bin/server.js'
|
||||
|
||||
describe('#subscription-integration.js', () => {
|
||||
let server
|
||||
const baseUrl = 'http://localhost:3003' // Use different port for tests
|
||||
|
||||
before(async () => {
|
||||
// Start test server
|
||||
server = new Server()
|
||||
server.config.port = 3003
|
||||
await server.startServer()
|
||||
|
||||
// Wait for server to be ready
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
this.timeout(10000) // Increase timeout for cleanup
|
||||
// Stop server
|
||||
if (server && server.server) {
|
||||
// Close all connections forcefully if available
|
||||
if (server.server.closeAllConnections) {
|
||||
server.server.closeAllConnections()
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
const timeout = setTimeout(() => {
|
||||
resolve() // Force resolve after 2 seconds
|
||||
}, 2000)
|
||||
|
||||
server.server.close(() => {
|
||||
clearTimeout(timeout)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('POST /req/:subId', () => {
|
||||
it('should create SSE subscription', async () => {
|
||||
const subId = 'test-sub-' + Date.now()
|
||||
const filters = { kinds: [1], limit: 10 }
|
||||
|
||||
const response = await fetch(`${baseUrl}/req/${subId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(filters)
|
||||
})
|
||||
|
||||
// Assert SSE headers
|
||||
assert.equal(response.headers.get('content-type'), 'text/event-stream')
|
||||
assert.equal(response.headers.get('cache-control'), 'no-cache')
|
||||
assert.equal(response.headers.get('connection'), 'keep-alive')
|
||||
|
||||
// Read initial connection message
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
try {
|
||||
const { value } = await reader.read()
|
||||
const text = decoder.decode(value)
|
||||
assert.include(text, 'connected')
|
||||
assert.include(text, subId)
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 400 when subscription ID is missing', async () => {
|
||||
const filters = { kinds: [1] }
|
||||
|
||||
const response = await fetch(`${baseUrl}/req/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(filters)
|
||||
})
|
||||
|
||||
// Should return 404 or 400
|
||||
assert.isAtLeast(response.status, 400)
|
||||
})
|
||||
|
||||
it('should return 400 when filters are missing', async () => {
|
||||
const subId = 'test-sub-' + Date.now()
|
||||
|
||||
const response = await fetch(`${baseUrl}/req/${subId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
assert.equal(response.status, 400)
|
||||
assert.property(result, 'error')
|
||||
assert.include(result.error, 'Filters are required')
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE /req/:subId', () => {
|
||||
it('should close a subscription', async () => {
|
||||
const subId = 'test-sub-' + Date.now()
|
||||
|
||||
const response = await fetch(`${baseUrl}/req/${subId}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
// May return 200 if subscription exists, or 500 if it doesn't
|
||||
// The important thing is it doesn't crash
|
||||
assert.isAtMost(response.status, 500)
|
||||
})
|
||||
|
||||
it('should return 400 when subscription ID is missing', async () => {
|
||||
const response = await fetch(`${baseUrl}/req/`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
// Should return 404 or 400
|
||||
assert.isAtLeast(response.status, 400)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /req/:subId', () => {
|
||||
it('should create SSE subscription (alternative method)', async function () {
|
||||
this.timeout(10000) // Increase timeout for this test
|
||||
|
||||
const subId = 'test-sub-' + Date.now()
|
||||
const filters = { kinds: [1], limit: 10 }
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), 8000)
|
||||
|
||||
let response
|
||||
try {
|
||||
response = await fetch(`${baseUrl}/req/${subId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(filters),
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
// Assert SSE headers
|
||||
assert.equal(response.headers.get('content-type'), 'text/event-stream')
|
||||
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
// Consume the stream to prevent hanging
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
try {
|
||||
// Read initial connection message with timeout
|
||||
const readPromise = reader.read()
|
||||
const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve({ value: null, done: true }), 2000))
|
||||
const { value, done } = await Promise.race([readPromise, timeoutPromise])
|
||||
|
||||
if (value && !done) {
|
||||
const text = decoder.decode(value)
|
||||
assert.include(text, 'connected')
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
} catch (err) {
|
||||
// AbortController aborted - this is expected
|
||||
if (err.name !== 'AbortError') {
|
||||
throw err
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeoutId)
|
||||
// Always close the subscription
|
||||
try {
|
||||
await fetch(`${baseUrl}/req/${subId}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
} catch (err) {
|
||||
// Ignore errors when closing
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
Integration tests for ManageSubscriptionUseCase with real adapter.
|
||||
These tests require a running Nostr relay.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Unit under test
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
import ManageSubscriptionUseCase from '../../../src/use-cases/manage-subscription.js'
|
||||
|
||||
describe('#manage-subscription-integration.js', () => {
|
||||
let adapters
|
||||
let uut
|
||||
|
||||
before(async () => {
|
||||
// Initialize adapters (will connect to real relay)
|
||||
adapters = new Adapters()
|
||||
await adapters.start()
|
||||
|
||||
uut = new ManageSubscriptionUseCase({ adapters })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// Clean up all subscriptions and disconnect from all relays
|
||||
// Note: This is a simplified cleanup - in production you'd track all subscriptions
|
||||
if (adapters && adapters.nostrRelays) {
|
||||
await Promise.allSettled(
|
||||
adapters.nostrRelays.map(relay => relay.disconnect())
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('#createSubscription()', () => {
|
||||
it('should successfully create a subscription', async () => {
|
||||
const subscriptionId = 'test-sub-' + Date.now()
|
||||
const filters = [{ kinds: [1], limit: 5 }]
|
||||
|
||||
let eventReceived = false
|
||||
let eoseReceived = false
|
||||
|
||||
const onEvent = (event) => {
|
||||
eventReceived = true
|
||||
assert.property(event, 'id')
|
||||
assert.property(event, 'kind')
|
||||
}
|
||||
|
||||
const onEose = () => {
|
||||
eoseReceived = true
|
||||
}
|
||||
|
||||
const onClosed = () => {
|
||||
// Handler for closed events
|
||||
}
|
||||
|
||||
await uut.createSubscription(subscriptionId, filters, onEvent, onEose, onClosed)
|
||||
|
||||
// Assert subscription exists
|
||||
assert.isTrue(uut.hasSubscription(subscriptionId))
|
||||
|
||||
// Wait a bit for events/EOSE
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
// EOSE should be received (or events)
|
||||
// Note: May not receive events if none exist, but EOSE should come
|
||||
assert.isTrue(eoseReceived || eventReceived)
|
||||
|
||||
// Clean up
|
||||
if (uut.hasSubscription(subscriptionId)) {
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
}
|
||||
})
|
||||
|
||||
it('should prevent duplicate subscriptions', async () => {
|
||||
const subscriptionId = 'test-dup-' + Date.now()
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (uut.hasSubscription(subscriptionId)) {
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle subscription with no events', async () => {
|
||||
const subscriptionId = 'test-empty-' + Date.now()
|
||||
const filters = [{ kinds: [99999], limit: 1 }] // Unlikely to have events
|
||||
|
||||
let eoseReceived = false
|
||||
|
||||
const onEose = () => {
|
||||
eoseReceived = true
|
||||
}
|
||||
|
||||
await uut.createSubscription(subscriptionId, filters, null, onEose, null)
|
||||
|
||||
// Wait for EOSE
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
// Should receive EOSE even with no events
|
||||
assert.isTrue(eoseReceived)
|
||||
|
||||
// Clean up
|
||||
if (uut.hasSubscription(subscriptionId)) {
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#closeSubscription()', () => {
|
||||
it('should successfully close a subscription', async () => {
|
||||
const subscriptionId = 'test-close-' + Date.now()
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
assert.isTrue(uut.hasSubscription(subscriptionId))
|
||||
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
|
||||
// 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')
|
||||
})
|
||||
})
|
||||
|
||||
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-has-' + Date.now()
|
||||
const filters = [{ kinds: [1] }]
|
||||
|
||||
assert.isFalse(uut.hasSubscription(subscriptionId))
|
||||
await uut.createSubscription(subscriptionId, filters)
|
||||
assert.isTrue(uut.hasSubscription(subscriptionId))
|
||||
|
||||
// Clean up
|
||||
if (uut.hasSubscription(subscriptionId)) {
|
||||
await uut.closeSubscription(subscriptionId)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
Integration tests for PublishEventUseCase with real adapter.
|
||||
These tests require a running Nostr relay.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Unit under test
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
import PublishEventUseCase from '../../../src/use-cases/publish-event.js'
|
||||
import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure'
|
||||
|
||||
describe('#publish-event-integration.js', () => {
|
||||
let adapters
|
||||
let uut
|
||||
|
||||
before(async () => {
|
||||
// Initialize adapters (will connect to real relay)
|
||||
adapters = new Adapters()
|
||||
await adapters.start()
|
||||
|
||||
uut = new PublishEventUseCase({ adapters })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// Clean up adapters - disconnect from all relays
|
||||
if (adapters && adapters.nostrRelays) {
|
||||
await Promise.allSettled(
|
||||
adapters.nostrRelays.map(relay => relay.disconnect())
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('#execute()', () => {
|
||||
it('should successfully publish a valid event', async () => {
|
||||
// Generate keys
|
||||
const sk = generateSecretKey()
|
||||
|
||||
// Create event template
|
||||
const eventTemplate = {
|
||||
kind: 1,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: 'Integration test post from use case'
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(eventTemplate, sk)
|
||||
|
||||
// Execute use case
|
||||
const result = await uut.execute(signedEvent)
|
||||
|
||||
// Assert result
|
||||
assert.property(result, 'accepted')
|
||||
assert.property(result, 'message')
|
||||
assert.property(result, 'eventId')
|
||||
assert.equal(result.eventId, signedEvent.id)
|
||||
})
|
||||
|
||||
it('should reject invalid event structure', async () => {
|
||||
const invalidEvent = {
|
||||
id: 'invalid',
|
||||
pubkey: 'invalid',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: 1,
|
||||
tags: [],
|
||||
content: 'Test',
|
||||
sig: 'invalid'
|
||||
}
|
||||
|
||||
try {
|
||||
await uut.execute(invalidEvent)
|
||||
assert.equal(true, false, 'unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Invalid event structure')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle relay rejection', async () => {
|
||||
// Generate keys
|
||||
const sk = generateSecretKey()
|
||||
|
||||
// Create a duplicate event (if we send same event twice)
|
||||
const eventTemplate = {
|
||||
kind: 1,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: 'Duplicate test post'
|
||||
}
|
||||
|
||||
const signedEvent = finalizeEvent(eventTemplate, sk)
|
||||
|
||||
// Publish first time
|
||||
const result1 = await uut.execute(signedEvent)
|
||||
assert.property(result1, 'accepted')
|
||||
|
||||
// Try to publish again (may be rejected as duplicate)
|
||||
const result2 = await uut.execute(signedEvent)
|
||||
assert.property(result2, 'accepted')
|
||||
// Result may be accepted or rejected depending on relay
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
Integration tests for QueryEventsUseCase with real adapter.
|
||||
These tests require a running Nostr relay.
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
// Unit under test
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
import QueryEventsUseCase from '../../../src/use-cases/query-events.js'
|
||||
|
||||
describe('#query-events-integration.js', () => {
|
||||
let adapters
|
||||
let uut
|
||||
|
||||
before(async () => {
|
||||
// Initialize adapters (will connect to real relay)
|
||||
adapters = new Adapters()
|
||||
await adapters.start()
|
||||
|
||||
uut = new QueryEventsUseCase({ adapters })
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
// Clean up adapters - disconnect from all relays
|
||||
if (adapters && adapters.nostrRelays) {
|
||||
await Promise.allSettled(
|
||||
adapters.nostrRelays.map(relay => relay.disconnect())
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('#execute()', () => {
|
||||
it('should successfully query events', async () => {
|
||||
const filters = [{ kinds: [1], limit: 5 }]
|
||||
const subscriptionId = 'test-query-' + Date.now()
|
||||
|
||||
const events = await uut.execute(filters, subscriptionId)
|
||||
|
||||
// Assert result is an array
|
||||
assert.isArray(events)
|
||||
|
||||
// If events are returned, verify structure
|
||||
if (events.length > 0) {
|
||||
assert.property(events[0], 'id')
|
||||
assert.property(events[0], 'pubkey')
|
||||
assert.property(events[0], 'created_at')
|
||||
assert.property(events[0], 'kind')
|
||||
assert.property(events[0], 'content')
|
||||
assert.equal(events[0].kind, 1)
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle empty results', async () => {
|
||||
// Query for events that likely don't exist
|
||||
const filters = [{ kinds: [99999], limit: 1 }]
|
||||
const subscriptionId = 'test-empty-' + Date.now()
|
||||
|
||||
const events = await uut.execute(filters, subscriptionId)
|
||||
|
||||
// Should return empty array, not throw
|
||||
assert.isArray(events)
|
||||
assert.equal(events.length, 0)
|
||||
})
|
||||
|
||||
it('should handle multiple filters', async function () {
|
||||
// Increase timeout for this test - needs to be longer than use case timeout (30s)
|
||||
this.timeout(35000)
|
||||
|
||||
const filters = [
|
||||
{ kinds: [1], limit: 2 },
|
||||
{ kinds: [3], limit: 2 }
|
||||
]
|
||||
const subscriptionId = 'test-multi-' + Date.now()
|
||||
|
||||
const events = await uut.execute(filters, subscriptionId)
|
||||
|
||||
// Should return array (may be empty)
|
||||
assert.isArray(events)
|
||||
})
|
||||
|
||||
it('should timeout if EOSE not received', async () => {
|
||||
// This test may take up to 30 seconds
|
||||
// Use a filter that might not return EOSE quickly
|
||||
const filters = [{ kinds: [1] }] // No limit, might timeout
|
||||
const subscriptionId = 'test-timeout-' + Date.now()
|
||||
|
||||
// Should eventually return (even if empty)
|
||||
const events = await uut.execute(filters, subscriptionId)
|
||||
|
||||
assert.isArray(events)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user