Files
welshman/packages/app/__tests__/relay.test.ts
T
2025-02-28 14:18:02 -08:00

168 lines
5.8 KiB
TypeScript

import {describe, it, expect, vi, beforeEach, afterEach} from "vitest"
import {ConnectionEvent} from "@welshman/net"
import type {Connection} from "@welshman/net"
import {now} from "@welshman/lib"
import {Relay, relays} from "../src/relays"
import {trackRelayStats} from "../src/relays"
import {get} from "svelte/store"
describe("Relay Stats", () => {
const mockUrl = "wss://test.relay"
let mockConnection: Connection
beforeEach(() => {
vi.useFakeTimers()
// Reset relays store
relays.set([])
// Create mock connection
mockConnection = {
url: mockUrl,
state: {
pendingPublishes: new Map(),
pendingRequests: new Map(),
},
on: vi.fn(),
off: vi.fn(),
emit: vi.fn(),
} as any
})
afterEach(() => {
vi.useRealTimers()
vi.clearAllMocks()
})
describe("trackRelayStats", () => {
it("should subscribe to all connection events", () => {
trackRelayStats(mockConnection)
expect(mockConnection.on).toHaveBeenCalledWith(ConnectionEvent.Open, expect.any(Function))
expect(mockConnection.on).toHaveBeenCalledWith(ConnectionEvent.Close, expect.any(Function))
expect(mockConnection.on).toHaveBeenCalledWith(ConnectionEvent.Send, expect.any(Function))
expect(mockConnection.on).toHaveBeenCalledWith(ConnectionEvent.Receive, expect.any(Function))
expect(mockConnection.on).toHaveBeenCalledWith(ConnectionEvent.Error, expect.any(Function))
})
it("should unsubscribe from all events when cleanup is called", () => {
const cleanup = trackRelayStats(mockConnection)
cleanup()
expect(mockConnection.off).toHaveBeenCalledWith(ConnectionEvent.Open, expect.any(Function))
expect(mockConnection.off).toHaveBeenCalledWith(ConnectionEvent.Close, expect.any(Function))
expect(mockConnection.off).toHaveBeenCalledWith(ConnectionEvent.Send, expect.any(Function))
expect(mockConnection.off).toHaveBeenCalledWith(ConnectionEvent.Receive, expect.any(Function))
expect(mockConnection.off).toHaveBeenCalledWith(ConnectionEvent.Error, expect.any(Function))
})
})
describe("Connection Event Handlers", () => {
let eventHandlers: Record<string, Function> = {}
beforeEach(() => {
eventHandlers = {}
mockConnection.on.mockImplementation((event, handler) => {
eventHandlers[event] = handler
})
trackRelayStats(mockConnection)
// Add initial relay to the store
relays.set([{url: mockUrl}])
// Allow batched updates to process
vi.runAllTimers()
})
it("should update stats on connection open", () => {
eventHandlers[ConnectionEvent.Open](mockConnection)
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.open_count).toBe(1)
expect(updatedRelays[0].stats?.last_open).toBeGreaterThan(0)
})
it("should update stats on connection close", () => {
eventHandlers[ConnectionEvent.Close](mockConnection)
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.close_count).toBe(1)
expect(updatedRelays[0].stats?.last_close).toBeGreaterThan(0)
})
it("should update stats on REQ send", () => {
eventHandlers[ConnectionEvent.Send](mockConnection, ["REQ", "test"])
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.request_count).toBe(1)
expect(updatedRelays[0].stats?.last_request).toBeGreaterThanOrEqual(now() - 1)
})
it("should update stats on EVENT send", () => {
eventHandlers[ConnectionEvent.Send](mockConnection, ["EVENT", {}])
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.publish_count).toBe(1)
expect(updatedRelays[0].stats?.last_publish).toBeGreaterThanOrEqual(now() - 1)
})
it("should update stats on OK receive with success", () => {
const eventId = "test-event"
mockConnection.state.pendingPublishes.set(eventId, {sent: now() - 1000})
eventHandlers[ConnectionEvent.Receive](mockConnection, ["OK", eventId, true])
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.publish_success_count).toBe(1)
expect(updatedRelays[0].stats?.publish_timer).toBe(1000)
})
it("should update stats on OK receive with failure", () => {
const eventId = "test-event"
mockConnection.state.pendingPublishes.set(eventId, {sent: Date.now() - 1000})
eventHandlers[ConnectionEvent.Receive](mockConnection, ["OK", eventId, false])
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.publish_failure_count).toBe(1)
})
it("should update stats on EOSE receive", () => {
const subId = "test-sub"
mockConnection.state.pendingRequests.set(subId, {sent: now() - 1000})
eventHandlers[ConnectionEvent.Receive](mockConnection, ["EOSE", subId])
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.eose_count).toBe(1)
expect(updatedRelays[0].stats?.eose_timer).toBe(1000)
})
it("should update stats on error", () => {
eventHandlers[ConnectionEvent.Error](mockConnection)
vi.runAllTimers()
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.last_error).toBeGreaterThan(0)
expect(updatedRelays[0].stats?.recent_errors).toHaveLength(1)
})
it("should limit recent errors to 10", () => {
// Trigger 12 errors
for (let i = 0; i < 12; i++) {
eventHandlers[ConnectionEvent.Error](mockConnection)
vi.advanceTimersByTime(1000)
}
const updatedRelays = get(relays) as Relay[]
expect(updatedRelays[0].stats?.recent_errors).toHaveLength(10)
})
})
})