Add tests for request

This commit is contained in:
Jon Staab
2025-03-27 13:43:25 -07:00
parent 4723c3f86a
commit ca9eadf8ff
2 changed files with 159 additions and 3 deletions
+141
View File
@@ -0,0 +1,141 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
import { Nip01Signer } from '@welshman/signer'
import { LOCAL_RELAY_URL, makeEvent } from '@welshman/util'
import { ClientMessageType } from "../src/message"
import { AdapterContext, AbstractAdapter, AdapterEventType } from "../src/adapter"
import { unireq, multireq, RequestEventType } from "../src/request"
import { Tracker } from "../src/tracker"
class MockAdapter extends AbstractAdapter {
constructor(readonly send) {
super()
this.sockets = []
this.urls = [LOCAL_RELAY_URL]
}
receive = (message: RelayMessage) => {
this.emit(AdapterEventType.Receive, message, this.url)
}
}
describe("Unireq", () => {
beforeEach(() => {
vi.useFakeTimers()
})
it("everything basically works", async () => {
const sendSpy = vi.fn()
const adapter = new MockAdapter(sendSpy)
const req = unireq({
relay: 'whatever',
filter: {kinds: [1]},
context: {getAdapter: () => adapter},
autoClose: true,
})
const duplicateSpy = vi.fn()
const invalidSpy = vi.fn()
const filteredSpy = vi.fn()
const eventSpy = vi.fn()
const eoseSpy = vi.fn()
const closeSpy = vi.fn()
req.on(RequestEventType.Duplicate, duplicateSpy)
req.on(RequestEventType.Invalid, invalidSpy)
req.on(RequestEventType.Filtered, filteredSpy)
req.on(RequestEventType.Event, eventSpy)
req.on(RequestEventType.Eose, eoseSpy)
req.on(RequestEventType.Close, closeSpy)
await vi.runAllTimers()
expect(sendSpy).toHaveBeenCalledWith([ClientMessageType.Req, req._id, {kinds: [1]}])
const signer = Nip01Signer.ephemeral()
const event1 = await signer.sign(makeEvent(1))
const event2 = await signer.sign(makeEvent(7))
const event3 = makeEvent(1)
adapter.receive(["EVENT", req._id, event1])
adapter.receive(["EVENT", req._id, event2])
adapter.receive(["EVENT", req._id, event1])
adapter.receive(["EVENT", req._id, event3])
await vi.runAllTimers()
expect(duplicateSpy).toHaveBeenCalledWith(event1)
expect(filteredSpy).toHaveBeenCalledWith(event2)
expect(invalidSpy).toHaveBeenCalledWith(event3)
expect(eventSpy).toHaveBeenCalledWith(event1)
expect(eoseSpy).toHaveBeenCalledTimes(0)
adapter.receive(["EOSE", req._id])
expect(eoseSpy).toHaveBeenCalledTimes(1)
expect(closeSpy).toHaveBeenCalledTimes(1)
})
})
describe("Multireq", () => {
it("everything basically works", async () => {
const send1Spy = vi.fn()
const adapter1 = new MockAdapter(send1Spy)
const send2Spy = vi.fn()
const adapter2 = new MockAdapter(send2Spy)
const req = multireq({
autoClose: true,
relays: ['1', '2'],
filter: {kinds: [1]},
context: {
getAdapter: (url: string) => url === '1' ? adapter1 : adapter2
},
})
const duplicateSpy = vi.fn()
const invalidSpy = vi.fn()
const filteredSpy = vi.fn()
const eventSpy = vi.fn()
const eoseSpy = vi.fn()
const closeSpy = vi.fn()
req.on(RequestEventType.Duplicate, duplicateSpy)
req.on(RequestEventType.Invalid, invalidSpy)
req.on(RequestEventType.Filtered, filteredSpy)
req.on(RequestEventType.Event, eventSpy)
req.on(RequestEventType.Eose, eoseSpy)
req.on(RequestEventType.Close, closeSpy)
await vi.runAllTimers()
expect(send1Spy).toHaveBeenCalledWith([ClientMessageType.Req, req._children[0]._id, {kinds: [1]}])
expect(send2Spy).toHaveBeenCalledWith([ClientMessageType.Req, req._children[1]._id, {kinds: [1]}])
const signer = Nip01Signer.ephemeral()
const event1 = await signer.sign(makeEvent(1))
const event2 = await signer.sign(makeEvent(7))
const event3 = makeEvent(1)
const event4 = await signer.sign(makeEvent(1))
adapter1.receive(["EVENT", req._children[0]._id, event1])
adapter1.receive(["EVENT", req._children[0]._id, event2])
adapter1.receive(["EVENT", req._children[0]._id, event3])
adapter2.receive(["EVENT", req._children[1]._id, event1])
adapter2.receive(["EVENT", req._children[1]._id, event4])
await vi.runAllTimers()
expect(duplicateSpy).toHaveBeenCalledWith(event1, '2')
expect(filteredSpy).toHaveBeenCalledWith(event2, '1')
expect(invalidSpy).toHaveBeenCalledWith(event3, '1')
expect(eventSpy).toHaveBeenCalledWith(event1, '1')
expect(eoseSpy).toHaveBeenCalledTimes(0)
adapter1.receive(["EOSE", req._children[0]._id])
adapter2.receive(["EOSE", req._children[1]._id])
expect(eoseSpy).toHaveBeenCalledTimes(2)
expect(closeSpy).toHaveBeenCalledTimes(1)
})
})
+18 -3
View File
@@ -1,4 +1,5 @@
import {EventEmitter} from "events" import {EventEmitter} from "events"
import {verifyEvent as nostrToolsVerifyEvent} from 'nostr-tools'
import {on, call, randomId, yieldThread} from "@welshman/lib" import {on, call, randomId, yieldThread} from "@welshman/lib"
import {Filter, matchFilter, SignedEvent} from "@welshman/util" import {Filter, matchFilter, SignedEvent} from "@welshman/util"
import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js" import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js"
@@ -7,6 +8,14 @@ import {SocketEventType, SocketStatus} from "./socket.js"
import {TypedEmitter, Unsubscriber} from "./util.js" import {TypedEmitter, Unsubscriber} from "./util.js"
import {Tracker} from "./tracker.js" import {Tracker} from "./tracker.js"
export const defaultVerifyEvent = (event: SignedEvent) => {
try {
return nostrToolsVerifyEvent(event)
} catch (e) {
return false
}
}
export enum RequestEventType { export enum RequestEventType {
Close = "request:event:close", Close = "request:event:close",
Disconnect = "request:event:disconnect", Disconnect = "request:event:disconnect",
@@ -48,6 +57,10 @@ export class Unireq extends (EventEmitter as new () => TypedEmitter<UnireqEvents
constructor(readonly options: UnireqOptions) { constructor(readonly options: UnireqOptions) {
super() super()
const tracker = options.tracker || new Tracker()
const verifyEvent = options.verifyEvent || defaultVerifyEvent
// Set up our adapter // Set up our adapter
this._adapter = getAdapter(this.options.relay, this.options.context) this._adapter = getAdapter(this.options.relay, this.options.context)
@@ -59,9 +72,9 @@ export class Unireq extends (EventEmitter as new () => TypedEmitter<UnireqEvents
if (id !== this._id) return if (id !== this._id) return
if (this.options.tracker?.track(event.id, url)) { if (tracker.track(event.id, url)) {
this.emit(RequestEventType.Duplicate, event) this.emit(RequestEventType.Duplicate, event)
} else if (this.options.verifyEvent?.(event) === false) { } else if (verifyEvent?.(event) === false) {
this.emit(RequestEventType.Invalid, event) this.emit(RequestEventType.Invalid, event)
} else if (!matchFilter(this.options.filter, event)) { } else if (!matchFilter(this.options.filter, event)) {
this.emit(RequestEventType.Filtered, event) this.emit(RequestEventType.Filtered, event)
@@ -145,8 +158,10 @@ export class Multireq extends (EventEmitter as new () => TypedEmitter<MultireqEv
constructor({relays, ...options}: MultireqOptions) { constructor({relays, ...options}: MultireqOptions) {
super() super()
const tracker = new Tracker()
for (const relay of relays) { for (const relay of relays) {
const req = new Unireq({relay, ...options}) const req = new Unireq({relay, tracker, ...options})
req.on(RequestEventType.Event, (event: SignedEvent) => { req.on(RequestEventType.Event, (event: SignedEvent) => {
this.emit(RequestEventType.Event, event, relay) this.emit(RequestEventType.Event, event, relay)