From b1bbcd6c4629923c7b518ef4b827c08bd9b12d2b Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 09:57:25 +0330 Subject: [PATCH 1/9] use mock relay in nip42 tests --- nip42.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nip42.test.ts b/nip42.test.ts index c415662..270fd79 100644 --- a/nip42.test.ts +++ b/nip42.test.ts @@ -1,14 +1,16 @@ -import { test, expect } from 'bun:test' +import { expect, test } from 'bun:test' import { makeAuthEvent } from './nip42.ts' import { Relay } from './relay.ts' +import { newMockRelay } from './test-helpers.ts' test('auth flow', async () => { - const relay = await Relay.connect('wss://nostr.wine') - + const { url } = newMockRelay() + const relay = await Relay.connect(url) const auth = makeAuthEvent(relay.url, 'chachacha') + expect(auth.tags).toHaveLength(2) - expect(auth.tags[0]).toEqual(['relay', 'wss://nostr.wine/']) + expect(auth.tags[0]).toEqual(['relay', url]) expect(auth.tags[1]).toEqual(['challenge', 'chachacha']) expect(auth.kind).toEqual(22242) }) From 829633b0d6451856bb73a9b3211ebb02a38cc3dc Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 10:40:15 +0330 Subject: [PATCH 2/9] inhance mock relay and refactor to a class --- test-helpers.ts | 198 +++++++++++++++++++++++++++++++----------------- 1 file changed, 130 insertions(+), 68 deletions(-) diff --git a/test-helpers.ts b/test-helpers.ts index 580169c..61be221 100644 --- a/test-helpers.ts +++ b/test-helpers.ts @@ -16,80 +16,142 @@ export function buildEvent(params: Partial): Event { } } -let serial = 0 +/** + * A mock Relay class for testing purposes. + * This mock relay returns some events before eose and then will be ok with everything. + * @class + * @example + * const mockRelay = new MockRelay() + * const relay = new Relay(mockRelay.getUrl()) + * await relay.connect() + * // Do some testing + * relay.close() + * mockRelay.close() + * mockRelay.stop() + */ +export class MockRelay { + private _url: string + private _server: Server + private _secretKeys: Uint8Array[] + private _preloadedEvents: Event[] -// the mock relay will always return some events before eose and then be ok with everything -export function newMockRelay(): { url: string; authors: string[]; ids: string[] } { - serial++ - const url = `wss://mock.relay.url/${serial}` - const relay = new Server(url) - const secretKeys = [generateSecretKey(), generateSecretKey(), generateSecretKey(), generateSecretKey()] - const preloadedEvents = secretKeys.map(sk => - finalizeEvent( - { - kind: 1, - content: '', - created_at: Math.floor(Date.now() / 1000), - tags: [], - }, - sk, - ), - ) + constructor() { + this._url = `wss://mock.relay.url/` + this._server = new Server(this._url) + this._secretKeys = [generateSecretKey(), generateSecretKey(), generateSecretKey(), generateSecretKey()] + this._preloadedEvents = this._secretKeys.map(sk => + finalizeEvent( + { + kind: 1, + content: '', + created_at: Math.floor(Date.now() / 1000), + tags: [], + }, + sk, + ), + ) - relay.on('connection', (conn: any) => { - let subs: { [subId: string]: { conn: any; filters: Filter[] } } = {} + this._server.on('connection', (conn: any) => { + let subs: { [subId: string]: { conn: any; filters: Filter[] } } = {} - conn.on('message', (message: string) => { - const data = JSON.parse(message) - switch (data[0]) { - case 'REQ': { - let subId = data[1] - let filters = data.slice(2) - subs[subId] = { conn, filters } + conn.on('message', (message: string) => { + const data = JSON.parse(message) - preloadedEvents.forEach(event => { - conn.send(JSON.stringify(['EVENT', subId, event])) - }) + switch (data[0]) { + case 'REQ': { + let subId = data[1] + let filters = data.slice(2) + subs[subId] = { conn, filters } - filters.forEach((filter: Filter) => { - const kinds = filter.kinds?.length ? filter.kinds : [1] - kinds.forEach(kind => { - secretKeys.forEach(sk => { - const event = finalizeEvent( - { - kind, - content: '', - created_at: Math.floor(Date.now() / 1000), - tags: [], - }, - sk, - ) - conn.send(JSON.stringify(['EVENT', subId, event])) + this._preloadedEvents.forEach(event => { + conn.send(JSON.stringify(['EVENT', subId, event])) + }) + + filters.forEach((filter: Filter) => { + const kinds = filter.kinds?.length ? filter.kinds : [1] + + kinds.forEach(kind => { + this._secretKeys.forEach(sk => { + const event = finalizeEvent( + { + kind, + content: '', + created_at: Math.floor(Date.now() / 1000), + tags: [], + }, + sk, + ) + + conn.send(JSON.stringify(['EVENT', subId, event])) + }) }) }) - }) - conn.send(JSON.stringify(['EOSE', subId])) - break - } - case 'CLOSE': { - let subId = data[1] - delete subs[subId] - break - } - case 'EVENT': { - let event = data[1] - conn.send(JSON.stringify(['OK', event.id, 'true'])) - for (let subId in subs) { - const { filters, conn: listener } = subs[subId] - if (matchFilters(filters, event)) { - listener.send(JSON.stringify(['EVENT', subId, event])) - } - } - break - } - } - }) - }) - return { url, authors: secretKeys.map(getPublicKey), ids: preloadedEvents.map(evt => evt.id) } + conn.send(JSON.stringify(['EOSE', subId])) + + break + } + case 'CLOSE': { + let subId = data[1] + delete subs[subId] + + break + } + case 'EVENT': { + let event = data[1] + + conn.send(JSON.stringify(['OK', event.id, 'true'])) + + for (let subId in subs) { + const { filters, conn: listener } = subs[subId] + + if (matchFilters(filters, event)) { + listener.send(JSON.stringify(['EVENT', subId, event])) + } + } + + break + } + } + }) + }) + } + + /** + * Get the URL of the mock relay. + * @returns The URL of the mock relay. + */ + getUrl() { + return this._url + } + + /** + * Get the public keys of the authors of the events. + * @returns An array of public keys. + */ + getAuthors() { + return this._secretKeys.map(getPublicKey) + } + + /** + * Get the IDs of the events. + * @returns An array of event IDs. + */ + getEventsIds() { + return this._preloadedEvents.map(evt => evt.id) + } + + /** + * Close the mock relay server. + */ + close() { + this._server.close() + } + + /** + * Stop the mock relay server. + */ + stop() { + this._server.stop() + } } From dbad25b2fad82fa37f4ba7ee1f3cfb806e220a2e Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 10:41:05 +0330 Subject: [PATCH 3/9] use new MockRelay class in relay.test.ts --- relay.test.ts | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/relay.test.ts b/relay.test.ts index e4e484e..c43dfbe 100644 --- a/relay.test.ts +++ b/relay.test.ts @@ -2,43 +2,57 @@ import { expect, test } from 'bun:test' import { finalizeEvent, generateSecretKey, getPublicKey } from './pure.ts' import { Relay } from './relay.ts' -import { newMockRelay } from './test-helpers.ts' +import { MockRelay } from './test-helpers.ts' test('connectivity', async () => { - const { url } = newMockRelay() - const relay = new Relay(url) + const mockRelay = new MockRelay() + + const relay = new Relay(mockRelay.getUrl()) await relay.connect() + expect(relay.connected).toBeTrue() + relay.close() + mockRelay.close() + mockRelay.stop() }) test('connectivity, with Relay.connect()', async () => { - const { url } = newMockRelay() - const relay = await Relay.connect(url) + const mockRelay = new MockRelay() + + const relay = await Relay.connect(mockRelay.getUrl()) + expect(relay.connected).toBeTrue() + relay.close() + mockRelay.close() + mockRelay.stop() }) test('querying', async done => { - const { url, authors } = newMockRelay() + const mockRelay = new MockRelay() + const kind = 0 - const relay = new Relay(url) + const relay = new Relay(mockRelay.getUrl()) await relay.connect() relay.subscribe( [ { - authors: authors, + authors: mockRelay.getAuthors(), kinds: [kind], }, ], { onevent(event) { - expect(authors).toContain(event.pubkey) + expect(mockRelay.getAuthors()).toContain(event.pubkey) expect(event).toHaveProperty('kind', kind) relay.close() + mockRelay.close() + mockRelay.stop() + done() }, }, @@ -46,12 +60,13 @@ test('querying', async done => { }) test('listening and publishing and closing', async done => { + const mockRelay = new MockRelay() + const sk = generateSecretKey() const pk = getPublicKey(sk) const kind = 23571 - const { url } = newMockRelay() - const relay = new Relay(url) + const relay = new Relay(mockRelay.getUrl()) await relay.connect() let sub = relay.subscribe( @@ -66,11 +81,15 @@ test('listening and publishing and closing', async done => { expect(event).toHaveProperty('pubkey', pk) expect(event).toHaveProperty('kind', kind) expect(event).toHaveProperty('content', 'content') - sub.close() + + sub.close() // close the subscription and will trigger onclose() }, - oneose() {}, onclose() { relay.close() + + mockRelay.close() + mockRelay.stop() + done() }, }, From 10b800db3adab215e15102b535a49dd8f0c42c79 Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 11:14:57 +0330 Subject: [PATCH 4/9] randomize relay urls in mock relays --- test-helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-helpers.ts b/test-helpers.ts index 61be221..6cf40c8 100644 --- a/test-helpers.ts +++ b/test-helpers.ts @@ -35,8 +35,8 @@ export class MockRelay { private _secretKeys: Uint8Array[] private _preloadedEvents: Event[] - constructor() { - this._url = `wss://mock.relay.url/` + constructor(url?: string | undefined) { + this._url = url ?? `wss://random.mock.relay/${Math.floor(Math.random() * 100)}` this._server = new Server(this._url) this._secretKeys = [generateSecretKey(), generateSecretKey(), generateSecretKey(), generateSecretKey()] this._preloadedEvents = this._secretKeys.map(sk => From 2e9798b8abff195c92b5dc9eda6f6a540a0e4b5a Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 11:21:29 +0330 Subject: [PATCH 5/9] increase random range for mock relay urls --- test-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-helpers.ts b/test-helpers.ts index 6cf40c8..61fe877 100644 --- a/test-helpers.ts +++ b/test-helpers.ts @@ -36,7 +36,7 @@ export class MockRelay { private _preloadedEvents: Event[] constructor(url?: string | undefined) { - this._url = url ?? `wss://random.mock.relay/${Math.floor(Math.random() * 100)}` + this._url = url ?? `wss://random.mock.relay/${Math.floor(Math.random() * 10000)}` this._server = new Server(this._url) this._secretKeys = [generateSecretKey(), generateSecretKey(), generateSecretKey(), generateSecretKey()] this._preloadedEvents = this._secretKeys.map(sk => From dd0014aee321649f6db13dcaffae3c09c85c3312 Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 11:29:46 +0330 Subject: [PATCH 6/9] refactor pool.test.ts and update with new mock relay class --- pool.test.ts | 98 ++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/pool.test.ts b/pool.test.ts index dc323af..b02ac65 100644 --- a/pool.test.ts +++ b/pool.test.ts @@ -1,35 +1,32 @@ -import { test, expect, afterAll } from 'bun:test' +import { afterEach, beforeEach, expect, test } from 'bun:test' -import { finalizeEvent, type Event } from './pure.ts' -import { generateSecretKey, getPublicKey } from './pure.ts' import { SimplePool } from './pool.ts' -import { newMockRelay } from './test-helpers.ts' +import { finalizeEvent, generateSecretKey, getPublicKey, type Event } from './pure.ts' +import { MockRelay } from './test-helpers.ts' -let pool = new SimplePool() +let pool: SimplePool +let mockRelays: MockRelay[] +let relayURLs: string[] -let mockRelays = [newMockRelay(), newMockRelay(), newMockRelay(), newMockRelay()] -let relays = mockRelays.map(mr => mr.url) -let authors = mockRelays.flatMap(mr => mr.authors) -let ids = mockRelays.flatMap(mr => mr.ids) +beforeEach(() => { + pool = new SimplePool() + mockRelays = Array.from({ length: 10 }, () => new MockRelay()) + relayURLs = mockRelays.map(mr => mr.getUrl()) +}) -afterAll(() => { - pool.close(relays) +afterEach(() => { + pool.close(relayURLs) + + for (let mr of mockRelays) { + mr.close() + mr.stop() + } }) test('removing duplicates when subscribing', async () => { let priv = generateSecretKey() let pub = getPublicKey(priv) - - pool.subscribeMany(relays, [{ authors: [pub] }], { - onevent(event: Event) { - // this should be called only once even though we're listening - // to multiple relays because the events will be caught and - // deduplicated efficiently (without even being parsed) - received.push(event) - }, - }) let received: Event[] = [] - let event = finalizeEvent( { created_at: Math.round(Date.now() / 1000), @@ -40,8 +37,17 @@ test('removing duplicates when subscribing', async () => { priv, ) - await Promise.any(pool.publish(relays, event)) - await new Promise(resolve => setTimeout(resolve, 1500)) + pool.subscribeMany(relayURLs, [{ authors: [pub] }], { + onevent(event: Event) { + // this should be called only once even though we're listening + // to multiple relays because the events will be caught and + // deduplicated efficiently (without even being parsed) + received.push(event) + }, + }) + + await Promise.any(pool.publish(relayURLs, event)) + await new Promise(resolve => setTimeout(resolve, 200)) // wait for the new published event to be received expect(received).toHaveLength(1) expect(received[0]).toEqual(event) @@ -51,12 +57,12 @@ test('same with double subs', async () => { let priv = generateSecretKey() let pub = getPublicKey(priv) - pool.subscribeMany(relays, [{ authors: [pub] }], { + pool.subscribeMany(relayURLs, [{ authors: [pub] }], { onevent(event) { received.push(event) }, }) - pool.subscribeMany(relays, [{ authors: [pub] }], { + pool.subscribeMany(relayURLs, [{ authors: [pub] }], { onevent(event) { received.push(event) }, @@ -74,47 +80,47 @@ test('same with double subs', async () => { priv, ) - await Promise.any(pool.publish(relays, event)) - await new Promise(resolve => setTimeout(resolve, 1500)) + await Promise.any(pool.publish(relayURLs, event)) + await new Promise(resolve => setTimeout(resolve, 200)) // wait for the new published event to be received expect(received).toHaveLength(2) }) test('query a bunch of events and cancel on eose', async () => { let events = new Set() + await new Promise(resolve => { - pool.subscribeManyEose( - [...relays, ...relays, 'wss://relayable.org', 'wss://relay.noswhere.com', 'wss://nothing.com'], - [{ kinds: [0, 1, 2, 3, 4, 5, 6], limit: 40 }], - { - onevent(event) { - events.add(event.id) - }, - onclose: resolve as any, + pool.subscribeManyEose(relayURLs, [{ kinds: [0, 1, 2, 3, 4, 5, 6], limit: 40 }], { + onevent(event) { + events.add(event.id) }, - ) + onclose: resolve as any, + }) }) + expect(events.size).toBeGreaterThan(50) }) test('querySync()', async () => { - let events = await pool.querySync( - [...relays.slice(0, 2), ...relays.slice(0, 2), 'wss://offchain.pub', 'wss://eden.nostr.land'], - { - authors: authors.slice(0, 2), - kinds: [1], - limit: 2, - }, - ) + let authors = mockRelays.flatMap(mr => mr.getAuthors()) + + let events = await pool.querySync(relayURLs, { + authors: authors, + kinds: [1], + limit: 2, + }) + + const uniqueEventCount = new Set(events.map(evt => evt.id)).size // the actual received number will be greater than 2, but there will be no duplicates expect(events.length).toBeGreaterThan(2) - const uniqueEventCount = new Set(events.map(evt => evt.id)).size expect(events).toHaveLength(uniqueEventCount) }) test('get()', async () => { - let event = await pool.get(relays, { + let ids = mockRelays.flatMap(mr => mr.getEventsIds()) + + let event = await pool.get(relayURLs, { ids: [ids[0]], }) From 0357e035f46246137bd00507413014711eaa684f Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 11:29:59 +0330 Subject: [PATCH 7/9] fix nip11 broken test --- nip11.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nip11.test.ts b/nip11.test.ts index bb07322..770f955 100644 --- a/nip11.test.ts +++ b/nip11.test.ts @@ -8,7 +8,7 @@ describe('requesting relay as for NIP11', () => { test('testing a relay', async () => { const info = await fetchRelayInformation('wss://atlas.nostr.land') expect(info.name).toEqual('nostr.land') - expect(info.description).toEqual('nostr.land family of relays (us-or-01)') + expect(info.description).toEqual('nostr.land family of relays (fi-01 [leopard])') expect(info.fees).toBeTruthy() expect(info.supported_nips).toEqual([1, 2, 4, 9, 11, 12, 16, 20, 22, 28, 33, 40]) expect(info.software).toEqual('custom') From fbcfccda01aa02472634cf1d6c526a57b08d41a9 Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 11:31:18 +0330 Subject: [PATCH 8/9] update nip42.test.ts with new mock relay class --- nip42.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nip42.test.ts b/nip42.test.ts index 270fd79..53f8f9d 100644 --- a/nip42.test.ts +++ b/nip42.test.ts @@ -2,15 +2,15 @@ import { expect, test } from 'bun:test' import { makeAuthEvent } from './nip42.ts' import { Relay } from './relay.ts' -import { newMockRelay } from './test-helpers.ts' +import { MockRelay } from './test-helpers.ts' test('auth flow', async () => { - const { url } = newMockRelay() - const relay = await Relay.connect(url) + const mockRelay = new MockRelay() + const relay = await Relay.connect(mockRelay.getUrl()) const auth = makeAuthEvent(relay.url, 'chachacha') expect(auth.tags).toHaveLength(2) - expect(auth.tags[0]).toEqual(['relay', url]) + expect(auth.tags[0]).toEqual(['relay', mockRelay.getUrl()]) expect(auth.tags[1]).toEqual(['challenge', 'chachacha']) expect(auth.kind).toEqual(22242) }) From c453bc5ec319d3ea3a6f80997a51663ec5fde5fb Mon Sep 17 00:00:00 2001 From: Sepehr Safari Date: Sat, 20 Jan 2024 11:50:05 +0330 Subject: [PATCH 9/9] revert nip11.test.ts with a todo flag --- nip11.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nip11.test.ts b/nip11.test.ts index 770f955..3c04fff 100644 --- a/nip11.test.ts +++ b/nip11.test.ts @@ -2,13 +2,14 @@ import { describe, test, expect } from 'bun:test' import fetch from 'node-fetch' import { useFetchImplementation, fetchRelayInformation } from './nip11' +// TODO: replace with a mock describe('requesting relay as for NIP11', () => { useFetchImplementation(fetch) test('testing a relay', async () => { const info = await fetchRelayInformation('wss://atlas.nostr.land') expect(info.name).toEqual('nostr.land') - expect(info.description).toEqual('nostr.land family of relays (fi-01 [leopard])') + expect(info.description).toEqual('nostr.land family of relays (us-or-01)') expect(info.fees).toBeTruthy() expect(info.supported_nips).toEqual([1, 2, 4, 9, 11, 12, 16, 20, 22, 28, 33, 40]) expect(info.software).toEqual('custom')