UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

167 lines (131 loc) 6.73 kB
// NDKRelaySubscription.test.ts import debug from "debug"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { NDKRelay } from "../index.js"; import { NDK } from "../ndk/index.js"; import type { NDKFilter, NDKSubscriptionInternalId } from "../subscription/index.js"; import { NDKSubscription } from "../subscription/index.js"; import { NDKRelaySubscription, NDKRelaySubscriptionStatus } from "./subscription.js"; const ndk = new NDK(); const relay = new NDKRelay("wss://fake-relay.com", undefined, ndk); const filters: NDKFilter[] = [{ kinds: [1] }]; // mock relay.req = vi.fn(); // Mock classes for NDKSubscription and NDKFilter class MockNDKSubscription extends NDKSubscription { internalId: NDKSubscriptionInternalId; private _groupableDelay: number; private _groupableDelayType: "at-most" | "at-least"; public groupable = true; constructor(internalId: NDKSubscriptionInternalId, delay: number, delayType: "at-most" | "at-least") { super(ndk, filters); this.internalId = internalId; this._groupableDelay = delay; this._groupableDelayType = delayType; } get groupableDelay() { return this._groupableDelay; } get groupableDelayType() { return this._groupableDelayType; } public isGroupable(): boolean { return this.groupable; } } describe("NDKRelaySubscription", () => { let ndkRelaySubscription: NDKRelaySubscription; beforeEach(() => { ndkRelaySubscription = new NDKRelaySubscription(relay, null, ndk.subManager); ndkRelaySubscription.debug = debug("test"); vi.useFakeTimers(); }); afterEach(() => { vi.restoreAllMocks(); vi.useRealTimers(); }); it("should initialize with status INITIAL", () => { expect(ndkRelaySubscription.status).toBe(NDKRelaySubscriptionStatus.INITIAL); }); it("should add item and schedule execution", () => { const subscription = new MockNDKSubscription("sub1", 1000, "at-least"); ndkRelaySubscription.addItem(subscription, filters); expect(ndkRelaySubscription.items.size).toBe(1); expect(ndkRelaySubscription.items.get("sub1")).toEqual({ subscription, filters }); expect(ndkRelaySubscription.status).toBe(NDKRelaySubscriptionStatus.PENDING); }); it("should execute immediately if subscription is not groupable", () => { const subscription = new MockNDKSubscription("sub2", 1000, "at-least"); vi.spyOn(subscription, "isGroupable").mockReturnValue(false); const executeSpy = vi.spyOn(ndkRelaySubscription as any, "execute"); ndkRelaySubscription.addItem(subscription, filters); expect(executeSpy).toHaveBeenCalled(); }); it("should not add items to a closed subscription", () => { const subscription = new MockNDKSubscription("sub4", 1000, "at-least"); ndkRelaySubscription.status = NDKRelaySubscriptionStatus.CLOSED; expect(() => { ndkRelaySubscription.addItem(subscription, []); }).toThrow("Cannot add new items to a closed subscription"); }); it("should schedule execution correctly", () => { const subscription = new MockNDKSubscription("sub5", 1000, "at-least"); ndkRelaySubscription.addItem(subscription, filters); expect(ndkRelaySubscription.fireTime).toBeGreaterThan(Date.now()); }); it("should execute subscription", () => { const executeSpy = vi.spyOn(ndkRelaySubscription as any, "execute"); const subscription = new MockNDKSubscription("sub6", 1000, "at-least"); ndkRelaySubscription.addItem(subscription, filters); vi.advanceTimersByTime(1000); expect(executeSpy).toHaveBeenCalled(); }); it("should reschedule execution when a new subscription with a longer delay is added", () => { const subscription1 = new MockNDKSubscription("sub7", 5000, "at-least"); const subscription2 = new MockNDKSubscription("sub8", 10000, "at-least"); ndkRelaySubscription.addItem(subscription1, filters); const initialTimer = ndkRelaySubscription.executionTimer; ndkRelaySubscription.addItem(subscription2, filters); const rescheduledTimer = ndkRelaySubscription.executionTimer; expect(ndkRelaySubscription.fireTime).toBeGreaterThan(Date.now() + 5000); expect(rescheduledTimer).not.toBe(initialTimer); }); it('should reset timer to shorter "at-most" delay when added after an "at-least" delay', () => { const subscription1 = new MockNDKSubscription("sub9", 5000, "at-least"); const subscription2 = new MockNDKSubscription("sub10", 3000, "at-most"); ndkRelaySubscription.addItem(subscription1, filters); ndkRelaySubscription.addItem(subscription2, filters); // Since the second subscription is "at-most", the timer should be reset to 3000ms expect(ndkRelaySubscription.fireTime).toBeLessThanOrEqual(Date.now() + 3000); }); it('should maintain timer for shorter "at-most" delay when an "at-least" delay is added afterwards', () => { const subscription1 = new MockNDKSubscription("sub11", 3000, "at-most"); const subscription2 = new MockNDKSubscription("sub12", 5000, "at-least"); ndkRelaySubscription.addItem(subscription1, filters); const initialTimer = ndkRelaySubscription.executionTimer; ndkRelaySubscription.addItem(subscription2, filters); const rescheduledTimer = ndkRelaySubscription.executionTimer; // Since the first subscription is "at-most", it should not change when "at-least" is added expect(ndkRelaySubscription.fireTime).toBeLessThanOrEqual(Date.now() + 3000); expect(rescheduledTimer).toBe(initialTimer); }); it("should not close until we have reached EOSE", () => { const sub = new MockNDKSubscription("sub11", 0, "at-most"); sub.groupable = false; ndkRelaySubscription.addItem(sub, filters); const closeSpy = vi.spyOn(ndkRelaySubscription as any, "close"); sub.stop(); expect(closeSpy).not.toHaveBeenCalled(); }); it("it should close when we reach EOSE if the subscription asked for close on EOSE", () => { const sub = new MockNDKSubscription("sub11", 0, "at-most"); sub.groupable = false; sub.closeOnEose = true; ndkRelaySubscription.addItem(sub, filters); const closeSpy = vi.spyOn(ndkRelaySubscription as any, "close"); sub.stop(); expect(closeSpy).not.toHaveBeenCalled(); ndkRelaySubscription.oneose(ndkRelaySubscription.subId); expect(closeSpy).toHaveBeenCalled(); }); });