mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
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]>
115 lines
4.2 KiB
JavaScript
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})`,
|
|
);
|
|
});
|
|
});
|