Files
bitvid/tests/nostr/subscribeVideosRelayFallback.test.mjs
thePR0M3TH3ANandClaude Opus 4.8 6b63625d88 test(feed): update subscribeVideos spec for reserved live defaults
Spec correction following de515462 (feed always reserves live default
aggregators). The existing "uses the healthy relays when they are available"
case asserted deepEqual(subRelayArgs, healthy) — i.e. ONLY the healthy relays.
That expectation is now wrong: the feed deliberately also reserves known-good
defaults so a healthy-looking but dead user list can't hang the feed. Replaced
with a stricter behavioral assertion (healthy relays still present AND a default
reserved AND bounded to the read cap). Mutation-verified: reverting
buildFeedRelaySet to the old healthy-only logic fails the updated case.

test_integrity_note:
  change_type: ["spec_correction"]
  scenarios:
    - id: SCN-feed-reserve-live-defaults
      given: "the user has healthy-looking relays that may actually be dead"
      when: "the feed subscription is created via subscribeVideos"
      then: "the relay set includes the user's healthy relays AND at least one default aggregator, bounded to MAX_SUBSCRIBE_RELAYS"
  observable_outcomes:
    - "the relay array passed to pool.sub()"
  determinism_controls:
    - "mocked pool.sub captures the boundary relay list; getHealthyRelays stubbed"
  anti_cheat_rationale:
    prevents:
      - "over-mocking internal logic"
      - "snapshot rubber-stamping"
  relaxation:
    did_relax_any_assertion: false
    if_true_explain_spec_basis: "old deepEqual replaced with stricter superset+liveness+bound assertions"

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 12:25:27 -04:00

115 lines
4.2 KiB
JavaScript

// Regression test for the first-load feed race + the dead-relay liveness
// backstop.
//
// Scenario (SCN-subscribe-videos-empty-relay-fallback):
// Given the initial relay connect probe timed out on a cold first load, so no
// relays are currently marked healthy,
// When the feed subscription is created via subscribeVideos,
// Then it must subscribe to the default relay set (not an empty relay list),
// because a zero-relay subscription silently never delivers events and never
// self-heals when relays reconnect — forcing the user to refresh the page.
//
// Scenario (SCN-feed-reserve-live-defaults):
// Given the user has healthy-looking relays that may actually be dead,
// When the feed subscription is created,
// Then it must include the user's healthy relays AND reserve known-good default
// aggregators, so the feed reaches a live relay regardless of the user's list.
//
// Observable outcome asserted at the boundary: the relay list passed to
// pool.sub().
import { describe, it, beforeEach, afterEach, mock } from "node:test";
import assert from "node:assert/strict";
import { NostrClient } from "../../js/nostr/client.js";
import {
DEFAULT_RELAY_URLS,
MAX_SUBSCRIBE_RELAYS,
} from "../../js/nostr/toolkit.js";
if (!globalThis.WebSocket) {
globalThis.WebSocket = class MockWebSocket {};
}
describe("subscribeVideos relay fallback", () => {
let client;
let mockPool;
let subRelayArgs;
beforeEach(() => {
client = new NostrClient();
subRelayArgs = null;
mockPool = {
sub: mock.fn((relays) => {
subRelayArgs = relays;
return { on: mock.fn(), unsub: mock.fn() };
}),
};
client.pool = mockPool;
client.isInitialized = true;
client.relays = ["wss://relay.example.com", "wss://relay2.example.com"];
});
afterEach(() => {
mock.reset();
});
it("falls back to the default relay set when no relays are healthy", () => {
// Simulate the cold-start race: the connect probe timed out, so every relay
// is currently filtered out as unhealthy.
client.getHealthyRelays = () => [];
client.subscribeVideos(() => {});
assert.equal(mockPool.sub.mock.callCount(), 1);
assert.ok(Array.isArray(subRelayArgs), "pool.sub must receive a relay array");
assert.ok(
subRelayArgs.length > 0,
"must never subscribe to ZERO relays when healthy relays are empty",
);
for (const url of subRelayArgs) {
assert.match(
url,
/^wss?:\/\//,
`fallback relay should be a websocket URL, got ${url}`,
);
}
});
// Spec correction (SCN-feed-reserve-live-defaults): the feed no longer
// subscribes to ONLY the currently-healthy relays. A user's relays can pass
// the liveness filter yet still be dead (not actually probed this session), in
// which case a healthy-only set would hang the feed forever on "Fetching…".
// The feed now always reserves a couple of known-good default aggregators
// alongside the user's relays (see buildFeedRelaySet). The user's healthy
// relays must still be present (original intent preserved) AND at least one
// default must be reserved (new liveness guarantee), bounded to the read cap.
it("uses the healthy relays AND reserves live default aggregators", () => {
const healthy = ["wss://relay.example.com"];
client.getHealthyRelays = () => healthy;
client.subscribeVideos(() => {});
assert.equal(mockPool.sub.mock.callCount(), 1);
assert.ok(Array.isArray(subRelayArgs), "pool.sub must receive a relay array");
// Original intent: the user's healthy relays are not ignored.
for (const url of healthy) {
assert.ok(
subRelayArgs.includes(url),
`healthy relay ${url} must remain in the feed set`,
);
}
// New guarantee: at least one reliable default is always reserved, so the
// feed can reach a live relay even if the user's own relays are dead.
assert.ok(
subRelayArgs.some((url) => DEFAULT_RELAY_URLS.includes(url)),
"must reserve at least one default aggregator alongside healthy relays",
);
// Still bounded so a huge user list can't flood REQ fan-out.
assert.ok(
subRelayArgs.length <= MAX_SUBSCRIBE_RELAYS,
`feed set must stay within the read cap (${MAX_SUBSCRIBE_RELAYS})`,
);
});
});