sync-transport.test.ts

2.9 KB · raw

1// Subnet-probe rate-limit tests. The full Noise NK handshake side of the2// transport requires a live WebSocket peer, so we exercise the cooldown3// ledger directly — it is the only Phase 2 invariant we want to pin4// regardless of network reachability.56import { afterEach, beforeEach, describe, expect, it } from "vitest";7import {8  _resetSubnetProbeCooldown,9  probeSubnetForPeer,10  subnetProbeOnCooldown,11  SUBNET_PROBE_COOLDOWN_MS,12  type SyncPeerIdentity,13} from "./sync-transport";1415const PEER_PUBKEY = "ab".repeat(32);16const TEST_IDENTITY: SyncPeerIdentity = {17  deviceId: "test-device",18  deviceName: "Test",19  deviceType: "extension",20  longTermPublicKeyHex: "11".repeat(32),21};2223describe("subnetProbeOnCooldown", () => {24  beforeEach(() => _resetSubnetProbeCooldown());25  afterEach(() => _resetSubnetProbeCooldown());2627  it("returns false before any probe has happened", () => {28    expect(subnetProbeOnCooldown(PEER_PUBKEY)).toBe(false);29  });3031  it("returns true within the cooldown window after a probe", async () => {32    const now = 1_000_000;33    // Fire-and-forget; the probe itself fails fast because no peer exists.34    // We only care about the ledger side-effect of recording `now`.35    await probeSubnetForPeer(36      "10.0.0",37      49142,38      TEST_IDENTITY,39      PEER_PUBKEY,40      { batch: 254, timeoutMs: 1, now },41    );42    expect(subnetProbeOnCooldown(PEER_PUBKEY, now + 1)).toBe(true);43    expect(subnetProbeOnCooldown(PEER_PUBKEY, now + SUBNET_PROBE_COOLDOWN_MS - 1)).toBe(true);44  });4546  it("releases the cooldown after the window expires", async () => {47    const now = 1_000_000;48    await probeSubnetForPeer(49      "10.0.0",50      49142,51      TEST_IDENTITY,52      PEER_PUBKEY,53      { batch: 254, timeoutMs: 1, now },54    );55    expect(subnetProbeOnCooldown(PEER_PUBKEY, now + SUBNET_PROBE_COOLDOWN_MS)).toBe(false);56  });5758  it("returns null and skips the scan when on cooldown", async () => {59    const now = 1_000_000;60    await probeSubnetForPeer(61      "10.0.0",62      49142,63      TEST_IDENTITY,64      PEER_PUBKEY,65      { batch: 254, timeoutMs: 1, now },66    );67    // Second call within the window must short-circuit before any68    // WebSocket attempt is made. We can't easily observe "no WebSocket"69    // directly here, but null + cooldown=true is the contract.70    const result = await probeSubnetForPeer(71      "10.0.0",72      49142,73      TEST_IDENTITY,74      PEER_PUBKEY,75      { batch: 254, timeoutMs: 1, now: now + 1_000 },76    );77    expect(result).toBeNull();78  });7980  it("tracks cooldown independently per peer pubkey", async () => {81    const now = 1_000_000;82    const other = "cd".repeat(32);83    await probeSubnetForPeer(84      "10.0.0",85      49142,86      TEST_IDENTITY,87      PEER_PUBKEY,88      { batch: 254, timeoutMs: 1, now },89    );90    expect(subnetProbeOnCooldown(PEER_PUBKEY, now + 1)).toBe(true);91    expect(subnetProbeOnCooldown(other, now + 1)).toBe(false);92  });93});