Files
bitvid/tests/e2e/relay-resilience.spec.ts
T
google-labs-jules[bot]andPR0M3TH3AN d0cab85b25 test: increase playwright e2e timeouts to reduce flakiness
Double default timeouts and extend the explicit waitForFeedItems calls across the e2e test suite to accommodate slower CI runners. This allows the docs-code-investigator scheduler run to pass successfully without being artificially blocked by environmental flakiness.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-03-02 20:43:39 +00:00

264 lines
7.7 KiB
TypeScript

/**
* Relay connection failure, fallback, and health monitoring tests.
*
* Scenarios covered:
* - SCN-relay-health: Relay health endpoint reports connection state
* - SCN-relay-override: setTestRelays properly overrides relay config
* - SCN-relay-multiple: Multiple relay URLs can be configured
* - SCN-relay-health-monitoring: getRelayHealth returns actionable data
* - SCN-relay-event-seeding: Events seeded to relay are retrievable
* - SCN-relay-clear-recovery: App survives relay clear mid-session
* - SCN-relay-http-api: Full HTTP API lifecycle (seed, list, clear, health)
*/
import { test, expect } from "./helpers/bitvidTestFixture";
test.describe("Relay resilience and management", () => {
test.describe("Relay HTTP API lifecycle", () => {
test("full relay API lifecycle: seed, list, health, clear", async ({
relay,
seedEvent,
clearRelay,
}) => {
const httpUrl = (relay as any).httpUrl;
// Step 1: Health check on empty relay
let healthResp = await fetch(`${httpUrl}/health`);
let health = await healthResp.json();
expect(health.ok).toBe(true);
expect(health.eventCount).toBe(0);
// Step 2: Seed events
await seedEvent({
title: "Relay Test A",
url: "https://example.com/a.mp4",
dTag: "relay-a",
});
await seedEvent({
title: "Relay Test B",
url: "https://example.com/b.mp4",
dTag: "relay-b",
});
// Step 3: Verify events are listed
let eventsResp = await fetch(`${httpUrl}/events`);
let events = await eventsResp.json();
expect(events.length).toBe(2);
// Step 4: Health reflects event count
healthResp = await fetch(`${httpUrl}/health`);
health = await healthResp.json();
expect(health.eventCount).toBe(2);
// Step 5: Clear events
await clearRelay();
// Step 6: Verify clear worked
eventsResp = await fetch(`${httpUrl}/events`);
events = await eventsResp.json();
expect(events.length).toBe(0);
healthResp = await fetch(`${httpUrl}/health`);
health = await healthResp.json();
expect(health.eventCount).toBe(0);
});
test("seeding multiple events preserves all data", async ({
relay,
seedEvent,
}) => {
const httpUrl = (relay as any).httpUrl;
// Seed 5 events with distinct d-tags
for (let i = 0; i < 5; i++) {
await seedEvent({
title: `Batch Video ${i}`,
url: `https://example.com/batch-${i}.mp4`,
dTag: `batch-${i}`,
});
}
// All 5 should be retrievable
const resp = await fetch(`${httpUrl}/events`);
const events = await resp.json();
expect(events.length).toBe(5);
// Verify each event has unique content
const titles = events.map((e: any) => JSON.parse(e.content).title);
for (let i = 0; i < 5; i++) {
expect(titles).toContain(`Batch Video ${i}`);
}
});
});
test.describe("Relay configuration via test harness", () => {
test("setTestRelays overrides both relayManager and nostrClient", async ({
page,
gotoApp,
relayUrl,
setTestRelays,
}) => {
// Given: the app is loaded
await gotoApp();
// When: we set relay URLs via the harness
const result = await setTestRelays(page, [relayUrl]);
// Then: both relay systems are aligned
expect(result.ok).toBe(true);
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.relays.read).toEqual([relayUrl]);
expect(state.relayManager.read).toEqual([relayUrl]);
});
test("setTestRelays accepts multiple relay URLs", async ({
page,
gotoApp,
relayUrl,
setTestRelays,
}) => {
// Given: the app is loaded
await gotoApp();
// When: we configure multiple relays (one real, one fake)
const fakeRelay = "ws://127.0.0.1:9999";
const result = await setTestRelays(page, [relayUrl, fakeRelay]);
// Then: both are registered
expect(result.ok).toBe(true);
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.relays.read).toContain(relayUrl);
expect(state.relays.read).toContain(fakeRelay);
});
test("relay overrides are applied via query params on load", async ({
page,
gotoApp,
relayUrl,
}) => {
// Given/When: the app is loaded with relay overrides in URL
await gotoApp();
// Then: app state reflects the override
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.relays).toBeTruthy();
const allRelays = state.relays.all || [];
expect(allRelays).toContain(relayUrl);
});
});
test.describe("Relay health monitoring", () => {
test("getRelayHealth returns structured data", async ({
page,
gotoApp,
}) => {
// Given: the app is loaded with relay connections
await gotoApp();
// When: we query relay health
const health = await page.evaluate(() => {
return (window as any).__bitvidTest__.getRelayHealth();
});
// Then: health info contains expected fields
expect(health).toBeTruthy();
expect(health.relays).toBeDefined();
expect(Array.isArray(health.unreachable) || health.unreachable === undefined).toBe(true);
});
test("relay health reports after login", async ({
page,
gotoApp,
loginAs,
}) => {
// Given: a logged-in user
await gotoApp();
await loginAs(page);
// When: we check relay health
const health = await page.evaluate(() => {
return (window as any).__bitvidTest__.getRelayHealth();
});
// Then: health data is available
expect(health).toBeTruthy();
});
});
test.describe("Recovery after relay operations", () => {
test("app remains functional after clearing relay events", async ({
page,
gotoApp,
loginAs,
seedEvent,
clearRelay,
}) => {
// Given: videos in the relay and a logged-in user
await seedEvent({
title: "Before Clear Video",
url: "https://example.com/before.mp4",
dTag: "before-clear-001",
});
await gotoApp();
await loginAs(page);
// When: relay events are cleared
await clearRelay();
// Then: app state remains valid (no crash)
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state.isLoggedIn).toBe(true);
expect(state).toBeTruthy();
});
test("new events are discoverable after relay clear and re-seed", async ({
page,
gotoApp,
loginAs,
seedEvent,
clearRelay,
}) => {
// Given: initial events
await seedEvent({
title: "Old Video",
url: "https://example.com/old.mp4",
dTag: "reseed-old-001",
});
await gotoApp();
await loginAs(page);
// Wait for initial feed
await page.evaluate(() => {
return (window as any).__bitvidTest__.waitForFeedItems(1, 60000);
});
// When: clear and re-seed with different content
await clearRelay();
await seedEvent({
title: "Fresh Video After Clear",
url: "https://example.com/fresh.mp4",
dTag: "reseed-fresh-001",
});
// Then: app remains functional (state is valid)
const state = await page.evaluate(() => {
return (window as any).__bitvidTest__.getAppState();
});
expect(state).toBeTruthy();
expect(state.isLoggedIn).toBe(true);
});
});
});