Fix some tests

This commit is contained in:
Jon Staab
2025-03-31 14:06:52 -07:00
parent 1524d128e3
commit 8bc336ae5d
8 changed files with 69 additions and 251 deletions
-2
View File
@@ -206,8 +206,6 @@ describe("tags", () => {
// a[0] should be the address of the replaceable event // a[0] should be the address of the replaceable event
expect(a[0][1]).toBe(getAddress(replaceableEvent)) expect(a[0][1]).toBe(getAddress(replaceableEvent))
console.log(result)
}) })
}) })
+27 -170
View File
@@ -1,10 +1,13 @@
import {now} from "@welshman/lib" import {now} from "@welshman/lib"
import {publish, PublishStatus} from "@welshman/net" import {publish, PublishStatus, MockAdapter} from "@welshman/net"
import {NOTE} from "@welshman/util" import {NOTE, makeEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {LOCAL_RELAY_URL} from "@welshman/relay"
import {getPubkey, makeSecret} from "@welshman/signer"
import {EventEmitter} from "events" import {EventEmitter} from "events"
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest" import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"
import {repository, tracker} from "../src/core" import {repository, tracker} from "../src/core"
import * as sessionModule from "../src/session" import {addSession, dropSession} from "../src/session"
import { import {
abortThunk, abortThunk,
makeThunk, makeThunk,
@@ -16,100 +19,31 @@ import {
walkThunks, walkThunks,
} from "../src/thunk" } from "../src/thunk"
// Mock dependencies const secret = makeSecret()
vi.mock("@welshman/net", () => ({
publish: vi.fn().mockReturnValue({emitter: {on: vi.fn()}}),
PublishStatus: {
Pending: "pending",
Success: "success",
Failure: "failure",
Timeout: "timeout",
Aborted: "aborted",
},
}))
vi.mock("../src/session", () => ({ const pubkey = getPubkey(secret)
pubkey: {
get: vi.fn().mockReturnValue("aa".repeat(32)),
},
getSession: vi.fn(),
getSigner: vi.fn(),
}))
vi.mock("../src/core", () => ({
repository: {
publish: vi.fn(),
removeEvent: vi.fn(),
getEvent: vi.fn(),
},
tracker: {
track: vi.fn(),
},
}))
const pubkey = "aa".repeat(32)
const id = "00".repeat(32)
const mockEvent = {
id,
pubkey,
kind: NOTE,
created_at: now(),
content: "test content",
tags: [],
}
const mockRequest = { const mockRequest = {
event: mockEvent, event: prepEvent({...makeEvent(NOTE), pubkey}),
relays: ["relay1", "relay2"], relays: [LOCAL_RELAY_URL],
} }
describe("thunk", () => { describe("thunk", () => {
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers() vi.useFakeTimers()
vi.clearAllMocks() addSession({method: 'nip01', secret, pubkey})
}) })
afterEach(() => { afterEach(() => {
vi.useRealTimers() vi.useRealTimers()
vi.resetModules() vi.clearAllMocks()
thunkWorker.clear() thunkWorker.clear()
thunkWorker.pause() // clear timeout thunkWorker.pause()
thunkWorker.resume() thunkWorker.resume()
}) dropSession(pubkey)
describe("prepEvent", () => {
it("should prepare an event with stamp, own, and hash", () => {
const result = prepEvent(mockEvent)
expect(result).toHaveProperty("id")
expect(result).toHaveProperty("pubkey")
expect(result).toHaveProperty("created_at")
})
})
describe("makeThunk", () => {
it("should create a thunk with required properties", () => {
const thunk = makeThunk(mockRequest)
expect(thunk).toHaveProperty("event")
expect(thunk).toHaveProperty("request")
expect(thunk).toHaveProperty("controller")
expect(thunk).toHaveProperty("result")
expect(thunk).toHaveProperty("status")
})
}) })
describe("mergeThunks", () => { describe("mergeThunks", () => {
it("should merge multiple thunks", () => {
const thunk1 = makeThunk(mockRequest)
const thunk2 = makeThunk(mockRequest)
const merged = mergeThunks([thunk1, thunk2])
expect(merged).toHaveProperty("thunks")
expect(merged.thunks).toHaveLength(2)
expect(merged).toHaveProperty("controller")
expect(merged).toHaveProperty("result")
expect(merged).toHaveProperty("status")
})
it("should abort all thunks when merged controller aborts", () => { it("should abort all thunks when merged controller aborts", () => {
const thunk1 = makeThunk(mockRequest) const thunk1 = makeThunk(mockRequest)
const thunk2 = makeThunk(mockRequest) const thunk2 = makeThunk(mockRequest)
@@ -135,18 +69,21 @@ describe("thunk", () => {
describe("publishThunk", () => { describe("publishThunk", () => {
it("should create and publish a thunk", async () => { it("should create and publish a thunk", async () => {
const publishSpy = vi.spyOn(repository, 'publish')
const result = publishThunk(mockRequest) const result = publishThunk(mockRequest)
expect(repository.publish).toHaveBeenCalled() expect(publishSpy).toHaveBeenCalled()
expect(result).toHaveProperty("event") expect(result).toHaveProperty("event")
expect(result).toHaveProperty("request") expect(result).toHaveProperty("request")
}) })
it("should handle abort", () => { it("should handle abort", () => {
const removeEventSpy = vi.spyOn(repository, 'removeEvent')
const thunk = publishThunk(mockRequest) const thunk = publishThunk(mockRequest)
thunk.controller.abort() thunk.controller.abort()
expect(repository.removeEvent).toHaveBeenCalledWith(thunk.event.id) expect(removeEventSpy).toHaveBeenCalledWith(thunk.event.id)
}) })
}) })
@@ -163,62 +100,18 @@ describe("thunk", () => {
describe("abortThunk", () => { describe("abortThunk", () => {
it("should abort a thunk and clean up", () => { it("should abort a thunk and clean up", () => {
const thunk = makeThunk(mockRequest) const thunk = makeThunk(mockRequest)
abortThunk(thunk) abortThunk(thunk)
expect(repository.removeEvent).toHaveBeenCalledWith(thunk.event.id) expect(repository.removeEvent).toHaveBeenCalledWith(thunk.event.id)
}) })
}) })
})
describe("thunkWorker", async () => {
beforeEach(() => {
vi.useFakeTimers()
vi.clearAllMocks()
})
afterEach(() => {
vi.useRealTimers()
vi.resetModules()
thunkWorker.clear()
})
const mockSigner = {
sign: vi.fn().mockResolvedValue({...mockEvent, sig: "test-sig"}),
}
vi.mocked(sessionModule.getSigner).mockReturnValue(mockSigner)
it("should handle publishing events", async () => {
const thunk = makeThunk(mockRequest)
thunkWorker.push(thunk)
await vi.runAllTimersAsync()
expect(mockSigner.sign).toHaveBeenCalled()
})
it("should handle delayed publishing", async () => {
const thunk = makeThunk({...mockRequest, delay: 100})
const startTime = Date.now()
thunkWorker.push(thunk)
await vi.runAllTimersAsync()
const endTime = Date.now()
// worker delays work by 50ms, so total delay should be 150ms
expect(endTime - startTime).toBe(150)
})
it("should update status during publishing", async () => { it("should update status during publishing", async () => {
// Create mock emitter const send = vi.fn()
const mockEmitter = new EventEmitter() const track = vi.spyOn(tracker, 'track')
vi.mocked(publish).mockReturnValue({
emitter: mockEmitter,
})
const thunk = makeThunk(mockRequest)
const statuses: Map<string, any> = new Map<string, any>() const statuses: Map<string, any> = new Map<string, any>()
const thunk = makeThunk(mockRequest)
// Subscribe to status updates // Subscribe to status updates
thunk.status.subscribe(status => { thunk.status.subscribe(status => {
@@ -233,55 +126,19 @@ describe("thunkWorker", async () => {
// Wait for initial async operations // Wait for initial async operations
await vi.runAllTimersAsync() await vi.runAllTimersAsync()
// Simulate publish status updates expect(statuses.get(LOCAL_RELAY_URL)).toEqual({
mockEmitter.emit("*", PublishStatus.Pending, "relay1", "Connecting...")
await vi.runAllTimersAsync()
expect(statuses.get("relay1")).toEqual({
status: PublishStatus.Pending,
message: "Connecting...",
})
mockEmitter.emit("*", PublishStatus.Success, "relay1", "Published")
await vi.runAllTimersAsync()
expect(statuses.get("relay1")).toEqual({
status: PublishStatus.Success, status: PublishStatus.Success,
message: "Published", message: "",
}) })
// Verify tracker was called on success // Verify tracker was called on success
expect(tracker.track).toHaveBeenCalledWith(thunk.event.id, "relay1") expect(track).toHaveBeenCalledWith(thunk.event.id, LOCAL_RELAY_URL)
// Verify all relays complete resolves the result
mockEmitter.emit("*", PublishStatus.Success, "relay2", "Published")
await vi.runAllTimersAsync() await vi.runAllTimersAsync()
const finalStatus = await thunk.result const finalStatus = await thunk.result
expect(finalStatus).toEqual({ expect(finalStatus).toEqual({
relay1: {status: PublishStatus.Success, message: "Published"}, [LOCAL_RELAY_URL]: {status: PublishStatus.Success, message: ""},
relay2: {status: PublishStatus.Success, message: "Published"},
}) })
}) })
it("should handle publish failures", async () => {
const mockSigner = {
sign: vi.fn().mockRejectedValue("Signing failed"),
}
vi.mocked(sessionModule.getSigner).mockReturnValue(mockSigner)
const thunk = makeThunk(mockRequest)
thunkWorker.push(thunk)
await vi.runAllTimersAsync()
expect(mockSigner.sign).toHaveBeenCalled()
// in case of failure, the worker will just stop its task, event is not removed
})
}) })
+6 -1
View File
@@ -26,6 +26,7 @@ export type ThunkRequest = {
event: ThunkEvent event: ThunkEvent
relays: string[] relays: string[]
delay?: number delay?: number
context?: AdapterContext,
} }
export type ThunkStatus = { export type ThunkStatus = {
@@ -221,7 +222,11 @@ thunkWorker.addGlobalHandler((thunk: Thunk) => {
} }
// Send it off // Send it off
const pub = new MultiPublish({event: signedEvent, relays: thunk.request.relays}) const pub = new MultiPublish({
event: signedEvent,
relays: thunk.request.relays,
context: thunk.request.context,
})
// Copy the signature over since we had deferred it // Copy the signature over since we had deferred it
const savedEvent = repository.getEvent(signedEvent.id) as SignedEvent const savedEvent = repository.getEvent(signedEvent.id) as SignedEvent
+9 -27
View File
@@ -1,30 +1,12 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest" import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
import { EventEmitter } from "events" import { EventEmitter } from "events"
import { Unicast, Multicast, PublishEvent, PublishStatus, unicast, multicast } from "../src/publish" import { SinglePublish, MultiPublish, PublishEvent, PublishStatus, } from "../src/publish"
import { AbstractAdapter, AdapterEvent } from "../src/adapter" import { AbstractAdapter, AdapterEvent, MockAdapter } from "../src/adapter"
import { ClientMessageType, RelayMessage } from "../src/message" import { ClientMessageType, RelayMessage } from "../src/message"
import { SignedEvent, makeEvent } from "@welshman/util" import { SignedEvent, makeEvent } from "@welshman/util"
import { Nip01Signer } from '@welshman/signer' import { Nip01Signer } from '@welshman/signer'
class MockAdapter extends AbstractAdapter { describe("SinglePublish", () => {
constructor(readonly url: string, readonly send) {
super()
}
get sockets() {
return []
}
get urls() {
return [this.url]
}
receive = (message: RelayMessage) => {
this.emit(AdapterEvent.Receive, message, this.url)
}
}
describe("Unicast", () => {
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers() vi.useFakeTimers()
}) })
@@ -39,7 +21,7 @@ describe("Unicast", () => {
const signer = Nip01Signer.ephemeral() const signer = Nip01Signer.ephemeral()
const event = await signer.sign(makeEvent(1)) const event = await signer.sign(makeEvent(1))
const pub = unicast({ const pub = new SinglePublish({
relay: '1', relay: '1',
context: {getAdapter: () => adapter}, context: {getAdapter: () => adapter},
event, event,
@@ -72,7 +54,7 @@ describe("Unicast", () => {
const signer = Nip01Signer.ephemeral() const signer = Nip01Signer.ephemeral()
const event = await signer.sign(makeEvent(1)) const event = await signer.sign(makeEvent(1))
const pub = unicast({ const pub = new SinglePublish({
relay: '1', relay: '1',
context: {getAdapter: () => adapter}, context: {getAdapter: () => adapter},
event, event,
@@ -105,7 +87,7 @@ describe("Unicast", () => {
const signer = Nip01Signer.ephemeral() const signer = Nip01Signer.ephemeral()
const event = await signer.sign(makeEvent(1)) const event = await signer.sign(makeEvent(1))
const pub = unicast({ const pub = new SinglePublish({
relay: '1', relay: '1',
context: {getAdapter: () => adapter}, context: {getAdapter: () => adapter},
event, event,
@@ -139,7 +121,7 @@ describe("Unicast", () => {
const signer = Nip01Signer.ephemeral() const signer = Nip01Signer.ephemeral()
const event = await signer.sign(makeEvent(1)) const event = await signer.sign(makeEvent(1))
const pub = unicast({ const pub = new SinglePublish({
relay: '1', relay: '1',
context: {getAdapter: () => adapter}, context: {getAdapter: () => adapter},
event, event,
@@ -170,7 +152,7 @@ describe("Unicast", () => {
}) })
}) })
describe("Multicast", () => { describe("MultiPublish", () => {
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers() vi.useFakeTimers()
}) })
@@ -189,7 +171,7 @@ describe("Multicast", () => {
const signer = Nip01Signer.ephemeral() const signer = Nip01Signer.ephemeral()
const event = await signer.sign(makeEvent(1)) const event = await signer.sign(makeEvent(1))
const pub = multicast({ const pub = new MultiPublish({
event, event,
relays: ['1', '2', '3'], relays: ['1', '2', '3'],
context: { context: {
+11 -24
View File
@@ -1,10 +1,11 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
import { Socket, SocketEvent } from "../src/socket"
import { Relay, LOCAL_RELAY_URL, isRelayUrl } from "@welshman/util"
import { AdapterEvent, SocketAdapter, LocalAdapter, getAdapter } from "../src/adapter"
import { Pool } from "../src/pool"
import { ClientMessage, RelayMessage } from "../src/message"
import EventEmitter from "events" import EventEmitter from "events"
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
import { isRelayUrl } from "@welshman/util"
import { LocalRelay, LOCAL_RELAY_URL } from "@welshman/relay"
import { AdapterEvent, SocketAdapter, LocalAdapter, getAdapter } from "../src/adapter"
import { ClientMessage, RelayMessage } from "../src/message"
import { Socket, SocketEvent } from "../src/socket"
import { Pool } from "../src/pool"
vi.mock('isomorphic-ws', () => { vi.mock('isomorphic-ws', () => {
const WebSocket = vi.fn(function () { const WebSocket = vi.fn(function () {
@@ -69,7 +70,7 @@ describe("SocketAdapter", () => {
}) })
describe("LocalAdapter", () => { describe("LocalAdapter", () => {
let relay: Relay & EventEmitter let relay: LocalRelay & EventEmitter
let adapter: LocalAdapter let adapter: LocalAdapter
beforeEach(() => { beforeEach(() => {
@@ -78,7 +79,7 @@ describe("LocalAdapter", () => {
send: vi.fn(), send: vi.fn(),
removeAllListeners: vi.fn() removeAllListeners: vi.fn()
}) })
relay = mockRelay as unknown as Relay & EventEmitter relay = mockRelay as unknown as LocalRelay & EventEmitter
adapter = new LocalAdapter(relay) adapter = new LocalAdapter(relay)
}) })
@@ -119,11 +120,11 @@ describe("LocalAdapter", () => {
describe("getAdapter", () => { describe("getAdapter", () => {
let pool: Pool let pool: Pool
let relay: Relay let relay: LocalRelay
beforeEach(() => { beforeEach(() => {
pool = new Pool() pool = new Pool()
relay = new Relay() relay = new LocalRelay()
pool.get = vi.fn().mockReturnValue(new Socket("wss://test.relay")) pool.get = vi.fn().mockReturnValue(new Socket("wss://test.relay"))
}) })
@@ -143,20 +144,6 @@ describe("getAdapter", () => {
expect(adapter).toBeInstanceOf(SocketAdapter) expect(adapter).toBeInstanceOf(SocketAdapter)
}) })
it("should throw error for invalid relay URL", () => {
expect(() => getAdapter("invalid-url", {})).toThrow("Invalid relay url invalid-url")
})
it("should throw error for local relay URL without relay context", () => {
const url = LOCAL_RELAY_URL
expect(() => getAdapter(url, {})).toThrow(`Unable to get local relay for ${url}`)
})
it("should throw error for remote relay URL without pool context", () => {
const url = "wss://test.relay"
expect(() => getAdapter(url, {})).toThrow(`Unable to get socket for ${url}`)
})
it("should use custom adapter if provided", () => { it("should use custom adapter if provided", () => {
const customAdapter = new SocketAdapter(new Socket("wss://test.relay")) const customAdapter = new SocketAdapter(new Socket("wss://test.relay"))
const getCustomAdapter = vi.fn().mockReturnValue(customAdapter) const getCustomAdapter = vi.fn().mockReturnValue(customAdapter)
+4 -22
View File
@@ -2,28 +2,10 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"
import { Nip01Signer } from '@welshman/signer' import { Nip01Signer } from '@welshman/signer'
import { LOCAL_RELAY_URL, makeEvent } from '@welshman/util' import { LOCAL_RELAY_URL, makeEvent } from '@welshman/util'
import { ClientMessageType, RelayMessage } from "../src/message" import { ClientMessageType, RelayMessage } from "../src/message"
import { AdapterContext, AbstractAdapter, AdapterEvent } from "../src/adapter" import { AdapterContext, AbstractAdapter, AdapterEvent, MockAdapter } from "../src/adapter"
import { unireq, multireq, RequestEvent } from "../src/request" import { SingleRequest, MultiRequest, RequestEvent } from "../src/request"
import { Tracker } from "../src/tracker" import { Tracker } from "../src/tracker"
class MockAdapter extends AbstractAdapter {
constructor(readonly url: string, readonly send) {
super()
}
get sockets() {
return []
}
get urls() {
return [this.url]
}
receive = (message: RelayMessage) => {
this.emit(AdapterEvent.Receive, message, this.url)
}
}
describe("Unireq", () => { describe("Unireq", () => {
beforeEach(() => { beforeEach(() => {
vi.useFakeTimers() vi.useFakeTimers()
@@ -36,7 +18,7 @@ describe("Unireq", () => {
it("everything basically works", async () => { it("everything basically works", async () => {
const sendSpy = vi.fn() const sendSpy = vi.fn()
const adapter = new MockAdapter('1', sendSpy) const adapter = new MockAdapter('1', sendSpy)
const req = unireq({ const req = new SingleRequest({
relay: 'whatever', relay: 'whatever',
filter: {kinds: [1]}, filter: {kinds: [1]},
context: {getAdapter: () => adapter}, context: {getAdapter: () => adapter},
@@ -100,7 +82,7 @@ describe("Multireq", () => {
const adapter1 = new MockAdapter('1', send1Spy) const adapter1 = new MockAdapter('1', send1Spy)
const send2Spy = vi.fn() const send2Spy = vi.fn()
const adapter2 = new MockAdapter('2', send2Spy) const adapter2 = new MockAdapter('2', send2Spy)
const req = multireq({ const req = new MultiRequest({
autoClose: true, autoClose: true,
relays: ['1', '2'], relays: ['1', '2'],
filter: {kinds: [1]}, filter: {kinds: [1]},
+12 -3
View File
@@ -78,16 +78,25 @@ export class LocalAdapter extends AbstractAdapter {
} }
} }
export class EmptyAdapter extends AbstractAdapter { export class MockAdapter extends AbstractAdapter {
constructor(
readonly url: string,
readonly send: (message: ClientMessage) => void,
) {
super()
}
get sockets() { get sockets() {
return [] return []
} }
get urls() { get urls() {
return [] return [this.url]
} }
send(message: ClientMessage) {} receive = (message: RelayMessage) => {
this.emit(AdapterEvent.Receive, message, this.url)
}
} }
export type AdapterContext = { export type AdapterContext = {
-2
View File
@@ -205,9 +205,7 @@ export const socketPolicyReopenActive = (socket: Socket) => {
// If the socket closed and we have no error, reopen it but don't flap // If the socket closed and we have no error, reopen it but don't flap
if (newStatus === SocketStatus.Closed && pending.size) { if (newStatus === SocketStatus.Closed && pending.size) {
console.log("1")
sleep(Math.max(0, 30_000 - (Date.now() - lastOpen))).then(() => { sleep(Math.max(0, 30_000 - (Date.now() - lastOpen))).then(() => {
console.log("2")
for (const message of pending.values()) { for (const message of pending.values()) {
socket.send(message) socket.send(message)
} }