import {ctx, now} from "@welshman/lib" import {COMMENT, PROFILE, RELAYS, TrustedEvent} from "@welshman/util" import {beforeEach, describe, expect, it, vi} from "vitest" import {relaysByUrl} from "../src/relays" import {relaySelectionsByPubkey} from "../src/relaySelections" import { RelayMode, Router, addMaximalFallbacks, addMinimalFallbacks, addNoFallbacks, getFilterSelections, getPubkeyRelays, getRelayQuality, makeRouter, } from "../src/router" // Mock dependencies vi.mock(import("@welshman/lib"), async imports => ({ ...(await imports()), ctx: { net: { pool: { has: vi.fn(), }, }, app: { indexerRelays: ["wss://indexer1.com", "wss://indexer2.com"], }, }, })) vi.mock(import("../src/relays"), async imports => ({ ...(await imports()), relaysByUrl: { get: vi.fn(), }, })) vi.mock(import("../src/relaySelections"), async imports => ({ ...(await imports()), relaySelectionsByPubkey: { get: vi.fn().mockReturnValue(new Map()), }, inboxRelaySelectionsByPubkey: { get: vi.fn().mockReturnValue(new Map()), }, })) describe("Router", () => { const id = "00".repeat(32) const pubkey = "aa".repeat(32) const pubkey1 = "bb".repeat(32) const pubkey2 = "cc".repeat(32) let router: Router const mockEvent: TrustedEvent = { id, pubkey, created_at: now(), kind: COMMENT, tags: [ ["E", "11".repeat(32), "wss://relay.com", pubkey1], ["P", pubkey2, "wss://relay2.com"], ], content: "test content", sig: "test-sig", } beforeEach(() => { vi.clearAllMocks() router = makeRouter({ getUserPubkey: () => pubkey, getPubkeyRelays: (user: string, mode?: RelayMode) => [`wss://${mode}.${user.slice(-4)}.com`], getFallbackRelays: () => ["wss://fallback1.com", "wss://fallback2.com"], getRelayQuality: () => 1, getLimit: () => 2, }) ctx.app.router = router }) describe("Basic Router Functions", () => { it("should create router with default options", () => { const router = makeRouter() expect(router).toBeInstanceOf(Router) }) it("should respect limit option", () => { const urls = router.FromRelays(["wss://1.com", "wss://2.com", "wss://3.com"]).getUrls() expect(urls).toHaveLength(2) }) it("should filter invalid relay URLs", () => { const urls = router.FromRelays(["invalid", "wss://valid.com"]).getUrls() expect(urls).toHaveLength(2) // invalid should be filtered out expect(urls.includes("invalid")).toBe(false) // one of the relay should be a fallback expect(urls.some(url => url.startsWith("wss://fallback"))).toBe(true) expect(urls[0]).toBe("wss://valid.com/") }) }) describe("Fallback Policies", () => { it("should implement no fallbacks policy", () => { expect(addNoFallbacks(1, 3)).toBe(0) expect(addNoFallbacks(0, 3)).toBe(0) }) it("should implement minimal fallbacks policy", () => { expect(addMinimalFallbacks(1, 3)).toBe(0) expect(addMinimalFallbacks(0, 3)).toBe(1) }) it("should implement maximal fallbacks policy", () => { expect(addMaximalFallbacks(1, 3)).toBe(2) expect(addMaximalFallbacks(0, 3)).toBe(3) }) }) describe("RouterScenario", () => { it("should apply weight to selections", () => { const scenario = router.FromRelays(["wss://1.com", "wss://2.com"]).weight(0.5) expect(scenario.selections[0].weight).toBe(0.5) }) it("should merge scenarios", () => { const scenario1 = router.FromRelays(["wss://1.com"]) const scenario2 = router.FromRelays(["wss://2.com"]) const merged = router.merge([scenario1, scenario2]) expect(merged.selections).toHaveLength(2) }) it("should respect security options", () => { const urls = router .FromRelays(["ws://insecure.com", "wss://secure.com"]) .allowInsecure(false) .getUrls() expect(urls).toContain("wss://secure.com/") expect(urls).not.toContain("ws://insecure.com/") }) }) describe("Routing Scenarios", () => { describe("ForUser/FromUser", () => { it("should handle user routing", () => { const readUrls = router.ForUser().getUrls() const writeUrls = router.FromUser().getUrls() expect(readUrls).toContain(`wss://read.${pubkey.slice(-4)}.com/`) expect(writeUrls).toContain(`wss://write.${pubkey.slice(-4)}.com/`) }) }) describe("Event Routing", () => { it("should route for event author", () => { const urls = router.Event(mockEvent).getUrls() expect(urls[0]).toBe(`wss://write.${mockEvent.pubkey.slice(-4)}.com/`) expect(urls.length).toBeGreaterThan(0) }) it("should handle event replies", () => { const urls = router.Replies(mockEvent).getUrls() expect(urls[0]).toBe(`wss://read.${mockEvent.pubkey.slice(-4)}.com/`) expect(urls.length).toBeGreaterThan(0) }) it("should handle event ancestors", () => { const urls = router.EventRoots(mockEvent).getUrls() // should have the relay of the mention and the relay of the parent expect(urls.length).toBe(2) // @check, super random results // expect(urls).contains("wss://relay.com/") // expect(urls).contains("wss://relay2.com/") }) }) describe("Pubkey Routing", () => { it("should route for single pubkey", () => { const urls = router.ForPubkey("test-pubkey").getUrls() expect(urls.length).toBeGreaterThan(0) }) it("should route for multiple pubkeys", () => { const urls = router.ForPubkeys(["pubkey1", "pubkey2"]).getUrls() expect(urls.length).toBeGreaterThan(0) }) }) }) describe("Relay Quality", () => { beforeEach(() => { vi.mocked(relaysByUrl.get).mockReturnValue( new Map([ [ "wss://relay.com", { url: "wss://relay.com", stats: { recent_errors: [], }, }, ], [ "wss://error.com", { url: "wss://error.com", stats: { recent_errors: [Date.now()], }, }, ], ]), ) }) it("should score connected relays highly", () => { vi.mocked(ctx.net.pool.has).mockReturnValue(true) expect(getRelayQuality("wss://relay.com")).toBe(1) }) it("should penalize relays with recent errors", () => { expect(getRelayQuality("wss://error.com")).toBe(0) }) it("should handle relays without stats", () => { vi.mocked(ctx.net.pool.has).mockReturnValue(false) expect(getRelayQuality("wss://new.com")).toBe(0.8) }) }) describe("Relay Selection", () => { beforeEach(() => { vi.mocked(relaySelectionsByPubkey.get).mockReturnValue( new Map([ [ "pubkey1", { event: {pubkey: "pubkey1"}, publicTags: [ ["r", "wss://read.com", "read"], ["r", "wss://write.com", "write"], ], }, ], ]), ) }) it("should get read relays for pubkey", () => { const relays = getPubkeyRelays("pubkey1", RelayMode.Read) expect(relays).toContain("wss://read.com/") }) it("should get write relays for pubkey", () => { const relays = getPubkeyRelays("pubkey1", RelayMode.Write) expect(relays).toContain("wss://write.com/") }) it("should handle missing relay selections", () => { const relays = getPubkeyRelays("unknown-pubkey") expect(relays).toEqual([]) }) }) describe("Filter Selections", () => { it("should handle search filters", () => { const selections = getFilterSelections([ { search: "test", }, ]) expect(selections.length).toBeGreaterThan(0) }) it("should handle author filters", () => { const selections = getFilterSelections([ { authors: ["pubkey1", "pubkey2"], }, ]) expect(selections.length).toBeGreaterThan(0) }) it("should handle indexed kinds", () => { const selections = getFilterSelections([ { kinds: [PROFILE, RELAYS], }, ]) expect(selections.length).toBeGreaterThan(0) }) }) })