@nostr-dev-kit/ndk
Version:
NDK - Nostr Development Kit
171 lines (135 loc) • 6.76 kB
text/typescript
// 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();
});
});