mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-29 17:02:28 +00:00
subscribeVideos called pool.sub(getHealthyRelays(this.relays)) with no empty-relay fallback, unlike the fetch paths in the same file. On a cold first load the 5s relay connect probe can time out, marking every relay unreachable; init() only warns and schedules a background reconnect (it does not throw), so boot proceeds and the feed subscribes to ZERO relays. The background reconnect later succeeds but nothing re-issues the subscription, so the feed stays empty until the user refreshes (when the SWR cache renders) — the reported "notes don't load on first visit / need a refresh" symptom. Fall back to DEFAULT_RELAY_URLS when no relays are healthy, mirroring the established pattern. Because the fallback bypasses the unreachable cooldown, the subscription retries those relays directly and self-heals instead of staying dead until refresh. Adds tests/nostr/subscribeVideosRelayFallback.test.mjs: empty healthy set => default relays (never zero), populated healthy set => uses those relays. Co-Authored-By: Claude Opus 4.8 <[email protected]>
81 lines
2.5 KiB
JavaScript
81 lines
2.5 KiB
JavaScript
// Regression test for the first-load feed race.
|
|
//
|
|
// 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.
|
|
//
|
|
// 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";
|
|
|
|
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}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
it("uses the healthy relays when they are available", () => {
|
|
const healthy = ["wss://relay.example.com"];
|
|
client.getHealthyRelays = () => healthy;
|
|
|
|
client.subscribeVideos(() => {});
|
|
|
|
assert.equal(mockPool.sub.mock.callCount(), 1);
|
|
assert.deepEqual(
|
|
subRelayArgs,
|
|
healthy,
|
|
"should subscribe using the healthy relays, not the default fallback",
|
|
);
|
|
});
|
|
});
|