1498 lines
47 KiB
TypeScript
1498 lines
47 KiB
TypeScript
import { runInNewContext } from "node:vm";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { WebSocketServer } from "ws";
|
|
|
|
import {
|
|
CdpConnection,
|
|
activeVideoEdgeCount,
|
|
buildBenchmarkFailureEvidence,
|
|
buildBenchmarkInitScript,
|
|
buildRecoveryCheck,
|
|
buildRouteTimingCheck,
|
|
buildRunChecks,
|
|
captureRecoveryViewerBaselines,
|
|
createPage,
|
|
everyViewerAdvanced,
|
|
everyViewerRecoveredMedia,
|
|
joinViewerBurst,
|
|
mergeRecoveryHostPeaks,
|
|
parseBenchmarkCanaryMode,
|
|
parseBenchmarkConfig,
|
|
parseExpectedEndpointCap,
|
|
parseViewerCounts,
|
|
sanitizeFailurePageEvidence,
|
|
summarizeBenchmarkRouteTiming,
|
|
summarizeSamples,
|
|
} from "../scripts/peer-assisted-benchmark";
|
|
import { MAX_VIEWERS_PER_ROOM_LIMIT } from "../src/shared/protocol";
|
|
import type {
|
|
MediaRouteUpstream,
|
|
ParticipantRouteAssignment,
|
|
} from "../src/shared/protocol";
|
|
|
|
const lowQualitySettings = {
|
|
resolution: "720p",
|
|
maxFramerate: 30,
|
|
maxBitrate: 3_000_000,
|
|
degradationPreference: "maintain-resolution",
|
|
} as const;
|
|
|
|
interface Deferred<T> {
|
|
promise: Promise<T>;
|
|
resolve: (value: T) => void;
|
|
}
|
|
|
|
function createDeferred<T>(): Deferred<T> {
|
|
let resolve!: (value: T) => void;
|
|
const promise = new Promise<T>((resolvePromise) => {
|
|
resolve = resolvePromise;
|
|
});
|
|
return { promise, resolve };
|
|
}
|
|
|
|
function page(
|
|
role: "host" | "viewer",
|
|
label: string,
|
|
sendEdges: number,
|
|
receiveEdges: number,
|
|
) {
|
|
return {
|
|
label,
|
|
role,
|
|
viewerIndex: role === "viewer" ? Number(label.split("-")[1]) : null,
|
|
roomId: "1",
|
|
peerId: `${label}-peer`,
|
|
authenticatedAtEpochMs: 1_000,
|
|
authenticateSentAtEpochMs: 900,
|
|
signalingConnected: true,
|
|
qualitySettings: lowQualitySettings,
|
|
maxActiveOutboundMediaEdges: sendEdges,
|
|
maxAssignedChildren: sendEdges,
|
|
routeRevision: 1 as number | null,
|
|
routeAssignment: {
|
|
upstream:
|
|
role === "viewer"
|
|
? ({ kind: "peer", peerId: "parent-peer" } as MediaRouteUpstream)
|
|
: ({ kind: "none" } as MediaRouteUpstream),
|
|
childPeerIds: Array.from(
|
|
{ length: sendEdges },
|
|
(_, index) => `child-peer-${index}`,
|
|
),
|
|
sfuPublicationGeneration: null as string | null,
|
|
},
|
|
firstDecodedAtEpochMs: role === "viewer" ? 1_500 : null,
|
|
firstRenderedAtEpochMs: role === "viewer" ? 1_550 : null,
|
|
renderedFrames: role === "viewer" ? 10 : 0,
|
|
connections: [
|
|
...Array.from({ length: sendEdges }, (_, index) => ({
|
|
index,
|
|
createdAtEpochMs: 1_000,
|
|
connectionId: `send-${index}` as string | null,
|
|
remotePeerId: `child-${index}` as string | null,
|
|
connectionState: "connected",
|
|
iceConnectionState: "connected",
|
|
hasOutboundVideo: true,
|
|
hasInboundVideo: false,
|
|
send: {},
|
|
receive: null,
|
|
sendTotals: { id: `send-${index}`, framesTotal: 10, bytesTotal: 100 },
|
|
receiveTotals: null,
|
|
error: undefined as string | undefined,
|
|
})),
|
|
...Array.from({ length: receiveEdges }, (_, index) => ({
|
|
index: sendEdges + index,
|
|
createdAtEpochMs: 1_000,
|
|
connectionId: `receive-${index}` as string | null,
|
|
remotePeerId: "parent-peer",
|
|
connectionState: "connected",
|
|
iceConnectionState: "connected",
|
|
hasOutboundVideo: false,
|
|
hasInboundVideo: true,
|
|
send: null,
|
|
receive: {},
|
|
sendTotals: null,
|
|
receiveTotals: { id: `receive-${index}`, framesTotal: 10, bytesTotal: 100 },
|
|
error: undefined as string | undefined,
|
|
})),
|
|
],
|
|
};
|
|
}
|
|
|
|
class FakeWebSocket {
|
|
readonly sent: string[] = [];
|
|
private readonly listeners = new Map<
|
|
string,
|
|
Array<(event: { data?: string }) => void>
|
|
>();
|
|
|
|
send(data: string): void {
|
|
this.sent.push(data);
|
|
}
|
|
|
|
addEventListener(
|
|
type: string,
|
|
listener: (event: { data?: string }) => void,
|
|
): void {
|
|
const listeners = this.listeners.get(type) ?? [];
|
|
listeners.push(listener);
|
|
this.listeners.set(type, listeners);
|
|
}
|
|
|
|
emitMessage(message: unknown): void {
|
|
for (const listener of this.listeners.get("message") ?? []) {
|
|
listener({ data: JSON.stringify(message) });
|
|
}
|
|
}
|
|
|
|
emitClose(): void {
|
|
for (const listener of this.listeners.get("close") ?? []) {
|
|
listener({});
|
|
}
|
|
}
|
|
}
|
|
|
|
interface ObserverSnapshot {
|
|
peerId: string | null;
|
|
signalingConnected: boolean;
|
|
routeRevision: number | null;
|
|
routeAssignment: ParticipantRouteAssignment | null;
|
|
maxAssignedChildren: number;
|
|
}
|
|
|
|
function createObserverHarness(
|
|
expectedEndpointCap = 2,
|
|
role: "host" | "viewer" = "host",
|
|
) {
|
|
const context: Record<string, unknown> = {
|
|
WebSocket: FakeWebSocket,
|
|
RTCPeerConnection: class FakePeerConnection {},
|
|
navigator: { mediaDevices: {} },
|
|
document: { querySelector: () => null },
|
|
localStorage: { removeItem: () => undefined },
|
|
performance: { timeOrigin: 0 },
|
|
setInterval: () => 1,
|
|
clearInterval: () => undefined,
|
|
queueMicrotask,
|
|
};
|
|
const source = buildBenchmarkInitScript({
|
|
label: role === "host" ? "host-1" : "viewer-1",
|
|
role,
|
|
viewerIndex: role === "viewer" ? 1 : null,
|
|
clearHostRoom: false,
|
|
width: 1280,
|
|
height: 720,
|
|
frameRate: 30,
|
|
expectedEndpointCap,
|
|
}).replace(
|
|
'const statsModule = import("/src/client/webrtc/stats.ts");',
|
|
"const statsModule = new Promise(() => undefined);",
|
|
);
|
|
runInNewContext(source, context);
|
|
const WebSocketConstructor = context.WebSocket as new () => FakeWebSocket;
|
|
const api = context.__SCREENER_BENCHMARK__ as {
|
|
snapshot: () => ObserverSnapshot;
|
|
canarySnapshot: () => Record<string, unknown>;
|
|
requestRouteDiagnosticSnapshot: () => boolean;
|
|
routeDiagnosticTimingSamples: () => Record<
|
|
string,
|
|
Array<number | null>
|
|
> | null;
|
|
};
|
|
return {
|
|
socket: () => new WebSocketConstructor(),
|
|
snapshot: () => structuredClone(api.snapshot()),
|
|
canarySnapshot: () => structuredClone(api.canarySnapshot()),
|
|
requestRouteDiagnosticSnapshot: () =>
|
|
api.requestRouteDiagnosticSnapshot(),
|
|
routeDiagnosticTimingSamples: () =>
|
|
structuredClone(api.routeDiagnosticTimingSamples()),
|
|
};
|
|
}
|
|
|
|
function authenticate(
|
|
socket: FakeWebSocket,
|
|
role: "host" | "viewer" = "host",
|
|
): void {
|
|
socket.send(
|
|
JSON.stringify({
|
|
type: "authenticate",
|
|
roomId: "1",
|
|
role,
|
|
}),
|
|
);
|
|
}
|
|
|
|
function routeAssignment(
|
|
childPeerIds: string[],
|
|
generation: string | null = null,
|
|
): ParticipantRouteAssignment {
|
|
return {
|
|
upstream: { kind: "none" },
|
|
childPeerIds,
|
|
sfuPublicationGeneration: generation,
|
|
};
|
|
}
|
|
|
|
function markSfuMedia(observation: ReturnType<typeof page>): void {
|
|
const connection = observation.connections.find((candidate) =>
|
|
observation.role === "host"
|
|
? candidate.hasOutboundVideo
|
|
: candidate.hasInboundVideo,
|
|
);
|
|
if (!connection) {
|
|
throw new Error("An SFU media connection is required");
|
|
}
|
|
connection.connectionId = null;
|
|
connection.remotePeerId = null;
|
|
}
|
|
|
|
describe("peer topology loopback configuration", () => {
|
|
it("bounds a CDP command that never receives a response", async () => {
|
|
const server = new WebSocketServer({ port: 0 });
|
|
await new Promise<void>((resolveListening) => {
|
|
server.once("listening", resolveListening);
|
|
});
|
|
const address = server.address();
|
|
if (!address || typeof address === "string") {
|
|
throw new Error("Unexpected WebSocket address");
|
|
}
|
|
const cdp = await CdpConnection.connect(
|
|
`ws://127.0.0.1:${address.port}`,
|
|
500,
|
|
);
|
|
await expect(cdp.call("Never.responds")).rejects.toThrow(
|
|
"CDP command timed out: Never.responds",
|
|
);
|
|
await cdp.close();
|
|
await new Promise<void>((resolveClose, rejectClose) => {
|
|
server.close((error) => error ? rejectClose(error) : resolveClose());
|
|
});
|
|
});
|
|
|
|
it("keeps the real-relay canary opt-in", () => {
|
|
expect(parseBenchmarkCanaryMode(undefined)).toBe("none");
|
|
expect(parseBenchmarkCanaryMode("viewer-mbb")).toBe("viewer-mbb");
|
|
expect(parseBenchmarkConfig({
|
|
CHROME_PATH: "chrome",
|
|
BENCHMARK_CANARY: "viewer-mbb",
|
|
BENCHMARK_VIEWERS: "3",
|
|
}).canaryMode).toBe("viewer-mbb");
|
|
expect(() => parseBenchmarkConfig({
|
|
CHROME_PATH: "chrome",
|
|
BENCHMARK_CANARY: "viewer-mbb",
|
|
})).toThrow(/selected viewer count of 3/);
|
|
expect(() => parseBenchmarkCanaryMode("signaling")).toThrow(/BENCHMARK_CANARY/);
|
|
});
|
|
|
|
it("runs the exact 20-Viewer acceptance case by default", () => {
|
|
expect(parseViewerCounts(undefined)).toEqual([
|
|
MAX_VIEWERS_PER_ROOM_LIMIT,
|
|
]);
|
|
});
|
|
|
|
it("deduplicates configured viewer counts without changing their order", () => {
|
|
expect(parseViewerCounts("8, 3,3,1")).toEqual([8, 3, 1]);
|
|
});
|
|
|
|
it("accepts the room viewer ceiling and rejects values above it", () => {
|
|
expect(parseViewerCounts(String(MAX_VIEWERS_PER_ROOM_LIMIT))).toEqual([
|
|
MAX_VIEWERS_PER_ROOM_LIMIT,
|
|
]);
|
|
expect(() =>
|
|
parseViewerCounts(String(MAX_VIEWERS_PER_ROOM_LIMIT + 1)),
|
|
).toThrow(
|
|
new RegExp(`1 to ${MAX_VIEWERS_PER_ROOM_LIMIT}`),
|
|
);
|
|
});
|
|
|
|
it("starts every Viewer join before waiting for burst media", async () => {
|
|
const creations = Array.from({ length: 3 }, () =>
|
|
createDeferred<string>(),
|
|
);
|
|
const media = Array.from({ length: 3 }, () => createDeferred<void>());
|
|
const creationStarts: number[] = [];
|
|
const mediaStarts: number[] = [];
|
|
|
|
const burst = joinViewerBurst(
|
|
3,
|
|
(viewerIndex) => {
|
|
creationStarts.push(viewerIndex);
|
|
return creations[viewerIndex - 1]!.promise;
|
|
},
|
|
(_viewer, viewerIndex) => {
|
|
mediaStarts.push(viewerIndex);
|
|
return media[viewerIndex - 1]!.promise;
|
|
},
|
|
);
|
|
expect(creationStarts).toEqual([1, 2, 3]);
|
|
expect(mediaStarts).toEqual([]);
|
|
|
|
creations[0]!.resolve("viewer-1");
|
|
creations[1]!.resolve("viewer-2");
|
|
await Promise.resolve();
|
|
expect(mediaStarts).toEqual([]);
|
|
creations[2]!.resolve("viewer-3");
|
|
await vi.waitFor(() => expect(mediaStarts).toEqual([1, 2, 3]));
|
|
|
|
media[2]!.resolve();
|
|
media[0]!.resolve();
|
|
media[1]!.resolve();
|
|
await expect(burst).resolves.toEqual([
|
|
"viewer-1",
|
|
"viewer-2",
|
|
"viewer-3",
|
|
]);
|
|
});
|
|
|
|
it("summarizes all four route timing keys without a pass threshold", () => {
|
|
const values = Array.from({ length: 20 }, (_, index) =>
|
|
index % 5 === 0 ? null : (20 - index) * 10,
|
|
);
|
|
const summary = summarizeBenchmarkRouteTiming({
|
|
queueWaitMs: values,
|
|
candidateStartMs: values,
|
|
firstDecodedFrameMs: values,
|
|
finalMs: values,
|
|
});
|
|
|
|
for (const timing of Object.values(summary)) {
|
|
expect(timing).toEqual({
|
|
sampleCount: 16,
|
|
pendingCount: 4,
|
|
rawMs: [
|
|
10, 20, 30, 40, 60, 70, 80, 90, 110, 120, 130, 140, 160,
|
|
170, 180, 190,
|
|
],
|
|
p50Ms: 90,
|
|
p95Ms: 190,
|
|
maxMs: 190,
|
|
});
|
|
expect(Object.keys(timing)).not.toContain("passed");
|
|
}
|
|
expect(buildRouteTimingCheck("captured", summary)).toMatchObject({
|
|
passed: true,
|
|
actual: 4,
|
|
});
|
|
expect(buildRouteTimingCheck("unavailable", summary).passed).toBe(false);
|
|
|
|
const incomplete = {
|
|
...summary,
|
|
finalMs: { ...summary.finalMs, pendingCount: 3 },
|
|
};
|
|
expect(buildRouteTimingCheck("captured", incomplete)).toMatchObject({
|
|
passed: false,
|
|
actual: 3,
|
|
});
|
|
});
|
|
|
|
it("closes the exact CDP target when page initialization fails", async () => {
|
|
const call = vi.fn(
|
|
async (method: string): Promise<Record<string, unknown>> => {
|
|
if (method === "Target.createTarget") {
|
|
return { targetId: "target-viewer-17" };
|
|
}
|
|
if (method === "Target.attachToTarget") {
|
|
return { sessionId: "session-viewer-17" };
|
|
}
|
|
if (method === "Page.navigate") {
|
|
return { errorText: "navigation rejected" };
|
|
}
|
|
return {};
|
|
},
|
|
);
|
|
|
|
await expect(
|
|
createPage(
|
|
{ call } as unknown as Parameters<typeof createPage>[0],
|
|
"http://127.0.0.1:3000/r/1234",
|
|
{
|
|
label: "viewer-17",
|
|
role: "viewer",
|
|
viewerIndex: 17,
|
|
clearHostRoom: false,
|
|
width: 1280,
|
|
height: 720,
|
|
frameRate: 30,
|
|
expectedEndpointCap: 2,
|
|
},
|
|
new AbortController().signal,
|
|
),
|
|
).rejects.toThrow("Navigation failed: navigation rejected");
|
|
expect(call).toHaveBeenCalledWith("Target.closeTarget", {
|
|
targetId: "target-viewer-17",
|
|
});
|
|
});
|
|
|
|
it("requires recovery to target one selected case", () => {
|
|
expect(() =>
|
|
parseBenchmarkConfig({
|
|
CHROME_PATH: "chrome",
|
|
BENCHMARK_VIEWERS: "1,3",
|
|
BENCHMARK_RECOVERY_VIEWERS: "5",
|
|
}),
|
|
).toThrow(/selected viewer count/);
|
|
});
|
|
|
|
it("defaults to cap two and accepts the deployment range", () => {
|
|
expect(parseExpectedEndpointCap(undefined)).toBe(2);
|
|
expect(parseExpectedEndpointCap("1")).toBe(1);
|
|
expect(parseExpectedEndpointCap("3")).toBe(3);
|
|
for (const value of ["0", "2.5", "4"]) {
|
|
expect(() => parseExpectedEndpointCap(value)).toThrow(/integer from 1 to 3/);
|
|
}
|
|
});
|
|
|
|
it("requires a relay-sized case for the optional quality control smoke", () => {
|
|
expect(() =>
|
|
parseBenchmarkConfig({
|
|
CHROME_PATH: "chrome",
|
|
BENCHMARK_VIEWERS: "1",
|
|
BENCHMARK_QUALITY_SMOKE: "1",
|
|
}),
|
|
).toThrow(/at least 3/);
|
|
expect(
|
|
parseBenchmarkConfig({
|
|
CHROME_PATH: "chrome",
|
|
BENCHMARK_VIEWERS: "3",
|
|
BENCHMARK_QUALITY_SMOKE: "1",
|
|
}).qualityControlSmoke,
|
|
).toBe(true);
|
|
});
|
|
|
|
it("writes a report file by default and reserves '-' for stdout", () => {
|
|
expect(parseBenchmarkConfig({ CHROME_PATH: "chrome" }).outputPath).toBe(
|
|
"benchmark-results/peer-assisted.json",
|
|
);
|
|
expect(
|
|
parseBenchmarkConfig({ CHROME_PATH: "chrome", BENCHMARK_OUTPUT: "-" })
|
|
.outputPath,
|
|
).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("peer topology loopback observations", () => {
|
|
it("bounds failure evidence and removes media-path identifiers and raw errors", () => {
|
|
const unsafe = page("viewer", "viewer-11", 0, 1);
|
|
unsafe.roomId = "room-secret";
|
|
unsafe.peerId = "peer-secret";
|
|
unsafe.routeAssignment.upstream = {
|
|
kind: "peer",
|
|
peerId: "parent-secret",
|
|
};
|
|
unsafe.connections = Array.from({ length: 9 }, (_, index) => ({
|
|
...structuredClone(unsafe.connections[0]!),
|
|
index,
|
|
connectionId: `connection-secret-${index}`,
|
|
remotePeerId: `remote-secret-${index}`,
|
|
error: `private-error-${index}`,
|
|
}));
|
|
|
|
const failure = buildBenchmarkFailureEvidence([unsafe], 21);
|
|
expect(failure).toMatchObject({
|
|
expectedPageCount: 21,
|
|
observedPageCount: 1,
|
|
pages: [
|
|
{
|
|
label: "viewer-11",
|
|
authenticated: true,
|
|
upstreamKind: "peer",
|
|
connectionCount: 9,
|
|
},
|
|
],
|
|
});
|
|
expect(failure.pages[0]?.connections).toHaveLength(6);
|
|
expect(sanitizeFailurePageEvidence(unsafe)).toEqual(failure.pages[0]);
|
|
const serialized = JSON.stringify(failure);
|
|
for (const secret of [
|
|
"room-secret",
|
|
"peer-secret",
|
|
"parent-secret",
|
|
"connection-secret",
|
|
"remote-secret",
|
|
"private-error",
|
|
]) {
|
|
expect(serialized).not.toContain(secret);
|
|
}
|
|
});
|
|
|
|
it("requests one Host-only route snapshot and retains only timing samples", () => {
|
|
const observer = createObserverHarness();
|
|
const socket = observer.socket();
|
|
authenticate(socket);
|
|
socket.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "host_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 1,
|
|
routeAssignment: routeAssignment([]),
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
|
|
expect(observer.requestRouteDiagnosticSnapshot()).toBe(true);
|
|
expect(observer.requestRouteDiagnosticSnapshot()).toBe(false);
|
|
expect(
|
|
socket.sent
|
|
.map((value) => JSON.parse(value) as { type?: string })
|
|
.filter((message) => message.type === "request-route-diagnostic"),
|
|
).toHaveLength(1);
|
|
|
|
const children = Array.from(
|
|
{ length: MAX_VIEWERS_PER_ROOM_LIMIT },
|
|
(_, index) => ({
|
|
ordinal: index + 1,
|
|
...(index === 0
|
|
? {
|
|
peerId: "must_not_escape",
|
|
connectionId: "must_not_escape",
|
|
}
|
|
: {}),
|
|
queueWaitMs: index === 0 ? 30 : null,
|
|
candidateStartMs: index === 0 ? 50 : index === 1 ? 20 : null,
|
|
firstDecodedFrameMs: index === 0 ? 80 : null,
|
|
finalMs: index === 0 ? 90 : null,
|
|
}),
|
|
);
|
|
socket.emitMessage({
|
|
type: "route-diagnostic-snapshot",
|
|
snapshot: { children },
|
|
});
|
|
|
|
const timing = observer.routeDiagnosticTimingSamples();
|
|
expect(timing?.queueWaitMs).toHaveLength(MAX_VIEWERS_PER_ROOM_LIMIT);
|
|
expect(timing?.queueWaitMs.slice(0, 2)).toEqual([30, null]);
|
|
expect(timing?.candidateStartMs.slice(0, 2)).toEqual([50, 20]);
|
|
expect(timing?.firstDecodedFrameMs.slice(0, 2)).toEqual([80, null]);
|
|
expect(timing?.finalMs.slice(0, 2)).toEqual([90, null]);
|
|
expect(
|
|
JSON.stringify(observer.routeDiagnosticTimingSamples()),
|
|
).not.toContain("must_not_escape");
|
|
});
|
|
|
|
it("captures a partial failure snapshot without satisfying exact 20", () => {
|
|
const observer = createObserverHarness();
|
|
const socket = observer.socket();
|
|
authenticate(socket);
|
|
socket.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "host_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 1,
|
|
routeAssignment: routeAssignment([]),
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
expect(observer.requestRouteDiagnosticSnapshot()).toBe(true);
|
|
socket.emitMessage({
|
|
type: "route-diagnostic-snapshot",
|
|
snapshot: {
|
|
children: Array.from(
|
|
{ length: MAX_VIEWERS_PER_ROOM_LIMIT - 1 },
|
|
(_, index) => ({
|
|
ordinal: index + 1,
|
|
queueWaitMs: null,
|
|
candidateStartMs: null,
|
|
firstDecodedFrameMs: null,
|
|
finalMs: null,
|
|
}),
|
|
),
|
|
},
|
|
});
|
|
const timing = observer.routeDiagnosticTimingSamples();
|
|
expect(timing?.queueWaitMs).toHaveLength(
|
|
MAX_VIEWERS_PER_ROOM_LIMIT - 1,
|
|
);
|
|
expect(
|
|
buildRouteTimingCheck(
|
|
"captured",
|
|
summarizeBenchmarkRouteTiming(
|
|
timing as Parameters<typeof summarizeBenchmarkRouteTiming>[0],
|
|
),
|
|
).passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("does not expose the route diagnostic request on a Viewer page", () => {
|
|
const observer = createObserverHarness(2, "viewer");
|
|
const socket = observer.socket();
|
|
authenticate(socket, "viewer");
|
|
socket.emitMessage({
|
|
type: "authenticated",
|
|
role: "viewer",
|
|
peerId: "viewer_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 1,
|
|
routeAssignment: {
|
|
upstream: { kind: "peer", peerId: "host_peer_0001" },
|
|
childPeerIds: [],
|
|
sfuPublicationGeneration: null,
|
|
},
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
|
|
expect(observer.requestRouteDiagnosticSnapshot()).toBe(false);
|
|
expect(
|
|
socket.sent
|
|
.map((value) => JSON.parse(value) as { type?: string })
|
|
.some((message) => message.type === "request-route-diagnostic"),
|
|
).toBe(false);
|
|
});
|
|
|
|
it.each([
|
|
["an SFU upstream without a generation", { kind: "sfu" }, null],
|
|
[
|
|
"a peer upstream with a generation",
|
|
{ kind: "peer", peerId: "host_peer_0001" },
|
|
"publication_generation_12345678",
|
|
],
|
|
])("rejects %s in the v9 route observer", (_label, upstream, generation) => {
|
|
const observer = createObserverHarness(2, "viewer");
|
|
const socket = observer.socket();
|
|
authenticate(socket, "viewer");
|
|
socket.emitMessage({
|
|
type: "authenticated",
|
|
role: "viewer",
|
|
peerId: "viewer_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 1,
|
|
routeAssignment: {
|
|
upstream,
|
|
childPeerIds: [],
|
|
sfuPublicationGeneration: generation,
|
|
},
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
|
|
expect(observer.snapshot()).toMatchObject({
|
|
routeRevision: null,
|
|
routeAssignment: null,
|
|
});
|
|
});
|
|
|
|
it("counts only active media connections in the requested direction", () => {
|
|
const host = page("host", "host", 2, 0);
|
|
host.connections[0]!.connectionState = "closed";
|
|
expect(activeVideoEdgeCount(host, "send")).toBe(1);
|
|
expect(activeVideoEdgeCount(host, "receive")).toBe(0);
|
|
});
|
|
|
|
it("summarizes raw fanout and first-frame fields without a score", () => {
|
|
const initialPages = [
|
|
page("host", "host", 2, 0),
|
|
page("viewer", "viewer-1", 1, 1),
|
|
page("viewer", "viewer-2", 0, 1),
|
|
page("viewer", "viewer-3", 0, 1),
|
|
];
|
|
const finalPages = structuredClone(initialPages);
|
|
for (const viewer of finalPages.filter((entry) => entry.role === "viewer")) {
|
|
viewer.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
}
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initialPages },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: finalPages },
|
|
],
|
|
3,
|
|
);
|
|
expect(summary.maxHostActiveMediaEdges).toBe(2);
|
|
expect(summary.maxRelayActiveMediaEdges).toBe(1);
|
|
expect(summary.everyViewerDecoded).toBe(true);
|
|
expect(summary.firstFrames[0]?.decodedAfterAuthenticateMs).toBe(600);
|
|
expect(summary.maxFirstDecodedAfterAuthenticateMs).toBe(600);
|
|
expect(summary.senderEvidence.host.bitrateKbps.mean).toBeNull();
|
|
expect(summary.browserProcessResources.measuredCpuTimeSeconds).toBeNull();
|
|
expect(
|
|
summary.finalTopology.every(
|
|
(entry) =>
|
|
JSON.stringify(entry.qualitySettings) ===
|
|
JSON.stringify(lowQualitySettings),
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("summarizes identified sender evidence and rejects reset process intervals", () => {
|
|
const makePages = (bitrate: number, fps: number, intervalFrames: number | null, intervalTime: number | null, reason: string) => {
|
|
const pages = [page("host", "host", 1, 0), page("viewer", "viewer-1", 1, 1)];
|
|
pages[0]!.connections[0]!.send = {
|
|
rtpStatsId: "host-rtp",
|
|
bitrateKbps: bitrate,
|
|
framesPerSecond: fps,
|
|
resolution: fps === 25 ? "640x360" : "1280x720",
|
|
availableOutgoingKbps: bitrate * 4,
|
|
intervalFramesEncoded: intervalFrames,
|
|
intervalEncodeTimeMs: intervalTime,
|
|
qualityLimitationReason: reason,
|
|
};
|
|
pages[1]!.connections[0]!.send = {
|
|
...pages[0]!.connections[0]!.send,
|
|
rtpStatsId: "relay-rtp",
|
|
bitrateKbps: bitrate / 2,
|
|
};
|
|
return pages;
|
|
};
|
|
const processSample = (browserCpu: number, rendererCpu: number) => ({ processes: [
|
|
{ type: "browser", id: 1, cpuTimeSeconds: browserCpu }, { type: "renderer", id: 2, cpuTimeSeconds: rendererCpu },
|
|
] });
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: makePages(1_000, 25, null, null, "none"), browserProcesses: processSample(10, 2) },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: makePages(1_200, 30, 20, 40, "none"), browserProcesses: processSample(10.4, 2.6) },
|
|
{ atEpochMs: 6_000, elapsedMs: 4_000, pages: makePages(1_400, 30, 30, 75, "cpu"), browserProcesses: processSample(9, 3) },
|
|
],
|
|
1,
|
|
);
|
|
|
|
expect(summary.senderEvidence.host).toMatchObject({
|
|
uniqueSenderCount: 1,
|
|
unknownIdentitySamples: 0,
|
|
bitrateKbps: { sampleCount: 3, min: 1_000, max: 1_400, mean: 1_200 },
|
|
framesPerSecond: { sampleCount: 3, min: 25, max: 30 },
|
|
resolutions: ["1280x720", "640x360"],
|
|
encodeIntervals: { sampleCount: 2, framesEncoded: 50, encodeTimeMs: 115 },
|
|
qualityLimitationReasonSamples: { none: 2, cpu: 1 },
|
|
});
|
|
expect(summary.senderEvidence.host.encodeIntervals.meanEncodeMsPerFrame).toBe(2.3);
|
|
expect(summary.senderEvidence.relay.bitrateKbps.mean).toBe(600);
|
|
expect(summary.browserProcessResources).toMatchObject({
|
|
validCpuIntervals: 1,
|
|
invalidCpuIntervals: 1,
|
|
measuredWallTimeSeconds: 2,
|
|
peakResidentSetBytes: null,
|
|
});
|
|
expect(summary.browserProcessResources.measuredCpuTimeSeconds).toBeCloseTo(1);
|
|
expect(summary.browserProcessResources.averageCpuUtilizationPercent).toBeCloseTo(50);
|
|
});
|
|
|
|
it("checks one endpoint cap for Host and Viewer senders", () => {
|
|
const initial = [
|
|
page("host", "host", 2, 0),
|
|
...Array.from({ length: 3 }, (_, index) =>
|
|
page("viewer", `viewer-${index + 1}`, index === 0 ? 1 : 0, 1),
|
|
),
|
|
];
|
|
const final = structuredClone(initial);
|
|
for (const viewer of final.filter((entry) => entry.role === "viewer")) {
|
|
viewer.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
}
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
3,
|
|
);
|
|
const checks = buildRunChecks(summary, 3, "720p30", 2);
|
|
expect(checks.find((check) => check.name === "host-assigned-children")?.passed).toBe(true);
|
|
expect(checks.find((check) => check.name === "relay-active-media-edges")?.passed).toBe(true);
|
|
const overused = {
|
|
...summary,
|
|
maxRelayActiveMediaEdges: 3,
|
|
};
|
|
expect(
|
|
buildRunChecks(overused, 3, "720p30", 2).find(
|
|
(check) => check.name === "relay-active-media-edges",
|
|
)?.passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("fails continuity when frames freeze or stats collection errors", () => {
|
|
const frozenInitial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
const frozenFinal = structuredClone(frozenInitial);
|
|
expect(
|
|
summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: frozenInitial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: frozenFinal },
|
|
],
|
|
1,
|
|
).everyViewerDecoded,
|
|
).toBe(false);
|
|
|
|
const errorFinal = structuredClone(frozenFinal);
|
|
errorFinal[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
errorFinal[1]!.connections[0]!.error = "getStats failed";
|
|
expect(
|
|
summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: frozenInitial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: errorFinal },
|
|
],
|
|
1,
|
|
).everyViewerDecoded,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("accepts an authoritative SFU root and records its route generation", () => {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
initial[0]!.routeRevision = 7;
|
|
initial[0]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
initial[1]!.routeRevision = 7;
|
|
initial[1]!.routeAssignment.upstream = { kind: "sfu" };
|
|
initial[1]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
markSfuMedia(initial[0]!);
|
|
markSfuMedia(initial[1]!);
|
|
|
|
const final = structuredClone(initial);
|
|
final[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
1,
|
|
);
|
|
|
|
expect(summary.everyViewerDecoded).toBe(true);
|
|
expect(summary.viewerContinuity[0]).toMatchObject({
|
|
assigned: true,
|
|
routeRevision: 7,
|
|
upstreamKind: "sfu",
|
|
});
|
|
expect(summary.finalTopology).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
role: "host",
|
|
routeRevision: 7,
|
|
routeUpstream: { kind: "none" },
|
|
sfuPublicationGeneration: "publication_generation_12345678",
|
|
}),
|
|
expect.objectContaining({
|
|
role: "viewer",
|
|
parentPeerId: null,
|
|
routeRevision: 7,
|
|
routeUpstream: { kind: "sfu" },
|
|
}),
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("accepts admitted SFU subscriptions and detects excess Host media edges", () => {
|
|
const generation = "publication_generation_12345678";
|
|
const host = page("host", "host", 2, 0);
|
|
const direct = page("viewer", "viewer-1", 0, 1);
|
|
const firstRoot = page("viewer", "viewer-2", 0, 1);
|
|
const secondRoot = page("viewer", "viewer-3", 0, 1);
|
|
host.routeRevision = 7;
|
|
host.routeAssignment.childPeerIds = [direct.peerId];
|
|
host.routeAssignment.sfuPublicationGeneration = generation;
|
|
host.maxAssignedChildren = 1;
|
|
direct.routeRevision = 7;
|
|
direct.routeAssignment.upstream = {
|
|
kind: "peer",
|
|
peerId: host.peerId,
|
|
};
|
|
for (const root of [firstRoot, secondRoot]) {
|
|
root.routeRevision = 7;
|
|
root.routeAssignment.upstream = { kind: "sfu" };
|
|
root.routeAssignment.sfuPublicationGeneration = generation;
|
|
}
|
|
for (const participant of [host, firstRoot, secondRoot]) {
|
|
markSfuMedia(participant);
|
|
}
|
|
|
|
const initial = [host, direct, firstRoot, secondRoot];
|
|
const final = structuredClone(initial);
|
|
for (const viewer of final.slice(1)) {
|
|
viewer.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
}
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
3,
|
|
);
|
|
const checks = buildRunChecks(summary, 3, "720p30");
|
|
|
|
expect(summary).toMatchObject({
|
|
maxHostActiveMediaEdges: 2,
|
|
maxHostAssignedChildren: 1,
|
|
sfuRootCount: 2,
|
|
sfuPublicationCoherent: true,
|
|
everyViewerDecoded: true,
|
|
});
|
|
expect(
|
|
checks.find((check) => check.name === "sfu-route-consistency")?.passed,
|
|
).toBe(true);
|
|
expect(
|
|
checks.find((check) => check.name === "host-active-media-edges")?.passed,
|
|
).toBe(true);
|
|
|
|
const thirdRoot = page("viewer", "viewer-4", 0, 1);
|
|
thirdRoot.routeRevision = 7;
|
|
thirdRoot.routeAssignment.upstream = { kind: "sfu" };
|
|
thirdRoot.routeAssignment.sfuPublicationGeneration = generation;
|
|
markSfuMedia(thirdRoot);
|
|
const threeRootInitial = [...structuredClone(initial), thirdRoot];
|
|
const threeRootFinal = structuredClone(threeRootInitial);
|
|
for (const viewer of threeRootFinal.slice(1)) {
|
|
viewer.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
}
|
|
const threeRootSummary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: threeRootInitial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: threeRootFinal },
|
|
],
|
|
4,
|
|
);
|
|
expect(threeRootSummary.sfuRootCount).toBe(3);
|
|
expect(
|
|
buildRunChecks(threeRootSummary, 4, "720p30").find(
|
|
(check) => check.name === "sfu-route-consistency",
|
|
)?.passed,
|
|
).toBe(true);
|
|
|
|
const excessiveHostInitial = structuredClone(initial);
|
|
const excessiveHostFinal = structuredClone(final);
|
|
excessiveHostInitial[0]!.maxActiveOutboundMediaEdges = 3;
|
|
excessiveHostFinal[0]!.maxActiveOutboundMediaEdges = 3;
|
|
const excessiveHostSummary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: excessiveHostInitial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: excessiveHostFinal },
|
|
],
|
|
3,
|
|
);
|
|
expect(
|
|
buildRunChecks(excessiveHostSummary, 3, "720p30").find(
|
|
(check) => check.name === "host-active-media-edges",
|
|
)?.passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("fails SFU continuity without one same-revision Host publication", () => {
|
|
function summarizeSfu(
|
|
hostGeneration: string | null,
|
|
hostRevision: number,
|
|
viewerRevision: number,
|
|
viewerGeneration: string | null = null,
|
|
) {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
initial[0]!.routeRevision = hostRevision;
|
|
initial[0]!.routeAssignment.sfuPublicationGeneration = hostGeneration;
|
|
initial[1]!.routeRevision = viewerRevision;
|
|
initial[1]!.routeAssignment.upstream = { kind: "sfu" };
|
|
initial[1]!.routeAssignment.sfuPublicationGeneration = viewerGeneration;
|
|
markSfuMedia(initial[0]!);
|
|
markSfuMedia(initial[1]!);
|
|
const final = structuredClone(initial);
|
|
final[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
return summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
1,
|
|
);
|
|
}
|
|
|
|
const missing = summarizeSfu(null, 7, 7);
|
|
expect(missing.sfuPublicationCoherent).toBe(false);
|
|
expect(missing.everyViewerDecoded).toBe(false);
|
|
|
|
const mismatchedRevision = summarizeSfu(
|
|
"publication_generation_12345678",
|
|
7,
|
|
8,
|
|
);
|
|
expect(mismatchedRevision.sfuPublicationCoherent).toBe(false);
|
|
expect(mismatchedRevision.everyViewerDecoded).toBe(false);
|
|
|
|
const mismatchedOwner = summarizeSfu(
|
|
"publication_generation_12345678",
|
|
7,
|
|
7,
|
|
"viewer_generation_12345678",
|
|
);
|
|
expect(mismatchedOwner.sfuPublicationCoherent).toBe(false);
|
|
expect(mismatchedOwner.everyViewerDecoded).toBe(false);
|
|
});
|
|
|
|
it("requires every SFU-rooted participant to share the active revision", () => {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 1, 1),
|
|
page("viewer", "viewer-2", 0, 1),
|
|
];
|
|
initial[0]!.routeRevision = 7;
|
|
initial[0]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
initial[1]!.routeRevision = 7;
|
|
initial[1]!.routeAssignment.upstream = { kind: "sfu" };
|
|
initial[1]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
initial[2]!.routeRevision = 6;
|
|
initial[2]!.routeAssignment.upstream = {
|
|
kind: "peer",
|
|
peerId: initial[1]!.peerId,
|
|
};
|
|
markSfuMedia(initial[0]!);
|
|
markSfuMedia(initial[1]!);
|
|
|
|
const final = structuredClone(initial);
|
|
final[1]!.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
final[2]!.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
2,
|
|
);
|
|
|
|
expect(summary.sfuPublicationObserved).toBe(true);
|
|
expect(summary.sfuPublicationCoherent).toBe(false);
|
|
expect(summary.everyViewerDecoded).toBe(false);
|
|
});
|
|
|
|
it("does not treat old peer frames as active SFU media", () => {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
initial[0]!.routeRevision = 7;
|
|
initial[0]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
initial[1]!.routeRevision = 7;
|
|
initial[1]!.routeAssignment.upstream = { kind: "sfu" };
|
|
initial[1]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
markSfuMedia(initial[0]!);
|
|
|
|
const final = structuredClone(initial);
|
|
final[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
1,
|
|
);
|
|
|
|
expect(summary.sfuPublicationObserved).toBe(true);
|
|
expect(summary.sfuPublicationCoherent).toBe(false);
|
|
expect(summary.everyViewerDecoded).toBe(false);
|
|
});
|
|
|
|
it("reports only the no-orphan invariant when no SFU root is active", () => {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
const final = structuredClone(initial);
|
|
final[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
1,
|
|
);
|
|
const checks = buildRunChecks(summary, 1, "720p30");
|
|
|
|
expect(summary.sfuPublicationObserved).toBe(false);
|
|
expect(checks.some((check) => check.name === "sfu-route-consistency")).toBe(
|
|
false,
|
|
);
|
|
expect(checks).toContainEqual(
|
|
expect.objectContaining({
|
|
name: "no-orphan-sfu-publication",
|
|
passed: true,
|
|
}),
|
|
);
|
|
|
|
const orphaned = structuredClone(final);
|
|
orphaned[0]!.routeAssignment.sfuPublicationGeneration =
|
|
"publication_generation_12345678";
|
|
const orphanedSummary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: orphaned },
|
|
],
|
|
1,
|
|
);
|
|
expect(orphanedSummary.sfuPublicationObserved).toBe(false);
|
|
expect(orphanedSummary.sfuPublicationCoherent).toBe(false);
|
|
expect(
|
|
buildRunChecks(orphanedSummary, 1, "720p30").find(
|
|
(check) => check.name === "no-orphan-sfu-publication",
|
|
)?.passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("keeps loopback timing diagnostic while gating topology and decoding", () => {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
initial[1]!.authenticateSentAtEpochMs = 1_000;
|
|
const final = structuredClone(initial);
|
|
final[1]!.firstDecodedAtEpochMs = 31_000;
|
|
final[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
const summary = summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 32_000, elapsedMs: 30_000, pages: final },
|
|
],
|
|
1,
|
|
);
|
|
const checks = buildRunChecks(summary, 1, "720p30");
|
|
|
|
expect(summary.maxFirstDecodedAfterAuthenticateMs).toBe(30_000);
|
|
expect(
|
|
checks.some((check) => check.name === "first-decoded-frame"),
|
|
).toBe(false);
|
|
expect(
|
|
checks.find((check) => check.name === "all-viewers-decoded")?.passed,
|
|
).toBe(true);
|
|
expect(
|
|
checks
|
|
.filter((check) => check.name.endsWith("media-edges"))
|
|
.every((check) => check.passed),
|
|
).toBe(true);
|
|
});
|
|
|
|
it("gates recovery on resumed decoding and the two-edge bounds, not elapsed time", () => {
|
|
const recovered = {
|
|
triggered: true,
|
|
recoveredAtEpochMs: 60_000,
|
|
recoveryMs: 50_000,
|
|
maxHostActiveMediaEdges: 2,
|
|
maxHostAssignedChildren: 2,
|
|
};
|
|
|
|
expect(buildRecoveryCheck(recovered).passed).toBe(true);
|
|
expect(
|
|
buildRecoveryCheck({ ...recovered, recoveredAtEpochMs: undefined }).passed,
|
|
).toBe(false);
|
|
expect(
|
|
buildRecoveryCheck({ ...recovered, maxHostActiveMediaEdges: 3 }).passed,
|
|
).toBe(false);
|
|
expect(
|
|
buildRecoveryCheck({ ...recovered, maxHostAssignedChildren: 3 }).passed,
|
|
).toBe(false);
|
|
expect(
|
|
buildRecoveryCheck({
|
|
triggered: true,
|
|
recoveredAtEpochMs: 60_000,
|
|
}).passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("requires unaffected viewers to keep decoding after relay reassignment", () => {
|
|
const before = [
|
|
page("viewer", "viewer-1", 1, 1),
|
|
page("viewer", "viewer-2", 0, 1),
|
|
];
|
|
const baselines = captureRecoveryViewerBaselines(before);
|
|
expect(baselines).not.toBeNull();
|
|
const after = structuredClone(before);
|
|
after[0]!.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
|
|
expect(everyViewerRecoveredMedia(baselines, after)).toBe(false);
|
|
|
|
after[1]!.connections.at(-1)!.receiveTotals!.framesTotal = 20;
|
|
expect(everyViewerRecoveredMedia(baselines, after)).toBe(true);
|
|
|
|
after[1]!.connections.at(-1)!.connectionId = "replacement-connection";
|
|
expect(everyViewerRecoveredMedia(baselines, after)).toBe(false);
|
|
after[1]!.connections.at(-1)!.connectionId = "receive-0";
|
|
after[1]!.connections.at(-1)!.receiveTotals!.id = "replacement-rtp";
|
|
expect(everyViewerRecoveredMedia(baselines, after)).toBe(false);
|
|
});
|
|
|
|
it("does not treat an unavailable recovery baseline as decoded growth", () => {
|
|
const missing = [page("viewer", "viewer-1", 0, 1)];
|
|
missing[0]!.connections = [];
|
|
const current = [page("viewer", "viewer-1", 0, 1)];
|
|
|
|
expect(captureRecoveryViewerBaselines(missing)).toBeNull();
|
|
expect(everyViewerRecoveredMedia(null, current)).toBe(false);
|
|
|
|
const rebased = captureRecoveryViewerBaselines(current);
|
|
expect(rebased).not.toBeNull();
|
|
expect(
|
|
everyViewerRecoveredMedia(rebased, structuredClone(current)),
|
|
).toBe(false);
|
|
|
|
current[0]!.connections[0]!.connectionId = null;
|
|
expect(captureRecoveryViewerBaselines(current)).not.toBeNull();
|
|
|
|
const errored = structuredClone(current);
|
|
errored[0]!.connections[0]!.receiveTotals = null;
|
|
errored[0]!.connections[0]!.error = "getStats failed";
|
|
expect(captureRecoveryViewerBaselines(errored)).toBeNull();
|
|
});
|
|
|
|
it("retains a probe-observed host peak above the current edge count", () => {
|
|
const host = page("host", "host", 2, 0);
|
|
host.maxActiveOutboundMediaEdges = 3;
|
|
host.maxAssignedChildren = 3;
|
|
|
|
const peaks = mergeRecoveryHostPeaks({}, host);
|
|
|
|
expect(peaks).toEqual({
|
|
maxHostActiveMediaEdges: 3,
|
|
maxHostAssignedChildren: 3,
|
|
});
|
|
expect(
|
|
buildRecoveryCheck({
|
|
triggered: true,
|
|
recoveredAtEpochMs: 60_000,
|
|
...peaks,
|
|
}).passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("fails closed when decoded media has no authoritative upstream", () => {
|
|
const initial = [
|
|
page("host", "host", 1, 0),
|
|
page("viewer", "viewer-1", 0, 1),
|
|
];
|
|
initial[1]!.routeAssignment.upstream = { kind: "none" };
|
|
const final = structuredClone(initial);
|
|
final[1]!.connections[0]!.receiveTotals!.framesTotal = 20;
|
|
|
|
expect(
|
|
summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
1,
|
|
).everyViewerDecoded,
|
|
).toBe(false);
|
|
|
|
final[1]!.routeAssignment.upstream = {
|
|
kind: "peer",
|
|
peerId: "parent-peer",
|
|
};
|
|
final[1]!.routeRevision = null;
|
|
expect(
|
|
summarizeSamples(
|
|
[
|
|
{ atEpochMs: 2_000, elapsedMs: 0, pages: initial },
|
|
{ atEpochMs: 4_000, elapsedMs: 2_000, pages: final },
|
|
],
|
|
1,
|
|
).everyViewerDecoded,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("requires every viewer to decode and render after a quality change", () => {
|
|
const before = [
|
|
page("host", "host", 2, 0),
|
|
page("viewer", "viewer-1", 1, 1),
|
|
page("viewer", "viewer-2", 0, 1),
|
|
];
|
|
const after = structuredClone(before);
|
|
for (const viewer of after.filter((entry) => entry.role === "viewer")) {
|
|
viewer.connections.at(-1)!.receiveTotals!.framesTotal += 1;
|
|
viewer.renderedFrames += 1;
|
|
}
|
|
expect(everyViewerAdvanced(before, after)).toBe(true);
|
|
|
|
after[2]!.renderedFrames = before[2]!.renderedFrames;
|
|
expect(everyViewerAdvanced(before, after)).toBe(false);
|
|
after[2]!.renderedFrames += 1;
|
|
after[1]!.connections.at(-1)!.receiveTotals!.framesTotal =
|
|
before[1]!.connections.at(-1)!.receiveTotals!.framesTotal;
|
|
expect(everyViewerAdvanced(before, after)).toBe(false);
|
|
});
|
|
|
|
it("observes only active authoritative route transitions", () => {
|
|
const observer = createObserverHarness();
|
|
const owner = observer.socket();
|
|
const outsider = observer.socket();
|
|
const activeRoute = routeAssignment(["child_peer_0001"]);
|
|
const preparedRoute = routeAssignment(
|
|
["child_peer_0001", "child_peer_0002"],
|
|
"publication_generation_12345678",
|
|
);
|
|
const conflictingRoute = routeAssignment(["conflict_peer_01"]);
|
|
|
|
outsider.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "outsider_peer_01",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 99,
|
|
routeAssignment: conflictingRoute,
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
expect(observer.snapshot().peerId).toBeNull();
|
|
|
|
authenticate(owner);
|
|
owner.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "host_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 4,
|
|
routeAssignment: activeRoute,
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
expect(observer.snapshot()).toMatchObject({
|
|
routeRevision: 4,
|
|
routeAssignment: activeRoute,
|
|
maxAssignedChildren: 1,
|
|
});
|
|
|
|
owner.emitMessage({
|
|
type: "route-update",
|
|
revision: 5,
|
|
phase: "prepare",
|
|
assignment: preparedRoute,
|
|
});
|
|
expect(observer.snapshot()).toMatchObject({
|
|
routeRevision: 4,
|
|
routeAssignment: activeRoute,
|
|
maxAssignedChildren: 1,
|
|
});
|
|
|
|
owner.emitMessage({
|
|
type: "route-update",
|
|
revision: 3,
|
|
phase: "active",
|
|
assignment: preparedRoute,
|
|
});
|
|
owner.emitMessage({
|
|
type: "route-update",
|
|
revision: 5,
|
|
phase: "active",
|
|
assignment: conflictingRoute,
|
|
});
|
|
expect(observer.snapshot().routeAssignment).toEqual(activeRoute);
|
|
|
|
owner.emitMessage({
|
|
type: "route-update",
|
|
revision: 5,
|
|
phase: "active",
|
|
assignment: preparedRoute,
|
|
});
|
|
expect(observer.snapshot()).toMatchObject({
|
|
routeRevision: 5,
|
|
routeAssignment: preparedRoute,
|
|
maxAssignedChildren: 2,
|
|
});
|
|
|
|
owner.emitMessage({
|
|
type: "route-update",
|
|
revision: 5,
|
|
phase: "prepare",
|
|
assignment: preparedRoute,
|
|
});
|
|
owner.emitMessage({
|
|
type: "route-update",
|
|
revision: 5,
|
|
phase: "active",
|
|
assignment: activeRoute,
|
|
});
|
|
expect(observer.snapshot()).toMatchObject({
|
|
routeRevision: 5,
|
|
routeAssignment: preparedRoute,
|
|
maxAssignedChildren: 2,
|
|
});
|
|
});
|
|
|
|
it("accepts a three-child route only for an explicit cap3 observer", () => {
|
|
const assignment = routeAssignment(["child_peer_0001", "child_peer_0002", "child_peer_0003"]);
|
|
for (const [cap, accepted] of [[2, false], [3, true]] as const) {
|
|
const observer = createObserverHarness(cap);
|
|
const socket = observer.socket();
|
|
authenticate(socket);
|
|
socket.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "host_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 1,
|
|
routeAssignment: assignment,
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
expect(observer.snapshot().routeAssignment !== null).toBe(accepted);
|
|
}
|
|
});
|
|
|
|
it("clears hybrid state only from the current authenticated socket", () => {
|
|
const observer = createObserverHarness();
|
|
const previous = observer.socket();
|
|
const current = observer.socket();
|
|
const assignment = routeAssignment(["child_peer_0001"]);
|
|
|
|
authenticate(previous);
|
|
previous.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "host_peer_0001",
|
|
mediaMode: "peer-assisted",
|
|
routeRevision: 4,
|
|
routeAssignment: assignment,
|
|
qualitySettings: lowQualitySettings,
|
|
});
|
|
authenticate(current);
|
|
expect(observer.snapshot()).toMatchObject({
|
|
signalingConnected: false,
|
|
routeRevision: 4,
|
|
routeAssignment: assignment,
|
|
});
|
|
current.emitMessage({
|
|
type: "authenticated",
|
|
role: "host",
|
|
peerId: "host_peer_0001",
|
|
viewerPeerIds: [],
|
|
});
|
|
expect(observer.snapshot()).toMatchObject({
|
|
signalingConnected: true,
|
|
routeRevision: null,
|
|
routeAssignment: null,
|
|
});
|
|
|
|
previous.emitMessage({
|
|
type: "route-update",
|
|
revision: 6,
|
|
phase: "active",
|
|
assignment,
|
|
});
|
|
previous.emitClose();
|
|
expect(observer.snapshot()).toMatchObject({
|
|
signalingConnected: true,
|
|
routeRevision: null,
|
|
routeAssignment: null,
|
|
});
|
|
|
|
current.emitClose();
|
|
expect(observer.snapshot().signalingConnected).toBe(false);
|
|
});
|
|
|
|
it("injects the requested deterministic capture dimensions", () => {
|
|
const source = buildBenchmarkInitScript({
|
|
label: "host-1",
|
|
role: "host",
|
|
viewerIndex: null,
|
|
clearHostRoom: true,
|
|
width: 1280,
|
|
height: 720,
|
|
frameRate: 30,
|
|
expectedEndpointCap: 2,
|
|
});
|
|
expect(source).toContain('"width":1280');
|
|
expect(source).toContain('"frameRate":30');
|
|
expect(source).toContain("getDisplayMedia");
|
|
expect(source).toContain("collectConnectionMetrics");
|
|
expect(source).toContain("set-quality-settings");
|
|
expect(source).toContain("renderedFrames");
|
|
});
|
|
});
|