T-HUB/apps/api/src/live-monitor/poller/poller.test.ts
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

166 lines
8.5 KiB
TypeScript

import type { PoolClient } from "pg";
import { describe, expect, it, vi } from "vitest";
import { LiveMonitorService, type LiveMonitorDependencies } from "../live-monitor.service.js";
import type { MonitorObservation, MonitorRowState } from "../reducer.js";
import { MockPollDriver, type MockDriverPackages } from "./mock-driver.js";
import { PeerTubePollDriver } from "./peertube-driver.js";
import { jitteredInterval, LiveMonitorPoller, parsePollSources, type PollerDependencies } from "./poller.js";
const epoch = "2026-01-01T00:00:00.000Z";
async function mockPackages(): Promise<MockDriverPackages> {
const [mock, scenarios] = await Promise.all([
vi.importActual<MockDriverPackages["mock"]>("../../../../../packages/connector-mock-livestream/src/index.js"),
vi.importActual<MockDriverPackages["scenarios"]>("../../../../../packages/testing/src/scenarios.js"),
]);
return { mock, scenarios };
}
function fixture(): { service: LiveMonitorService; get: () => Promise<MonitorRowState>; audits: () => number } {
const row: Record<string, unknown> = {
id: "source", workspace_id: "workspace", connection_id: "connection", remote_ref: "live:1",
state: "OFFLINE", version: 0, authoritative_tier: null,
last_media_health_at: null, last_hls_probe_at: null, last_webhook_at: null, last_api_poll_at: null,
tier_status: {}, ok_streak: 0, fail_streak: 0, state_entered_at: epoch,
hold_until: null, cooldown_until: null, previous_state: null, last_transition_at: null,
last_transition_reason: null, created_at: epoch, updated_at: epoch,
};
const seen = new Set<string>();
let auditCount = 0;
const client = {
async query(sql: string, params: unknown[] = []) {
if (sql.startsWith("SELECT * FROM source_monitor_states")) return { rowCount: 1, rows: [{ ...row }] };
if (sql.startsWith("INSERT INTO monitor_seen_events")) {
const id = String(params[1]);
if (seen.has(id)) return { rowCount: 0, rows: [] };
seen.add(id);
return { rowCount: 1, rows: [{ provider_event_id: id }] };
}
if (sql.startsWith("UPDATE source_monitor_states")) {
if (Number(params[18]) !== Number(row.version)) return { rowCount: 0, rows: [] };
const columns = ["state", "version", "authoritative_tier", "last_media_health_at", "last_hls_probe_at",
"last_webhook_at", "last_api_poll_at", "tier_status", "ok_streak", "fail_streak", "state_entered_at",
"hold_until", "cooldown_until", "previous_state", "last_transition_at", "last_transition_reason", "updated_at"];
for (const [index, column] of columns.entries()) row[column] = params[index + 1];
return { rowCount: 1, rows: [{ ...row }] };
}
if (sql.startsWith("SELECT audit_append")) {
auditCount += 1;
return { rowCount: 1, rows: [{}] };
}
throw new Error(`unexpected query: ${sql}`);
},
} as unknown as PoolClient;
const dependencies: LiveMonitorDependencies = { tenantTransaction: async (_workspace, fn) => fn(client) };
const service = new LiveMonitorService(dependencies);
return { service, get: () => service.getState("workspace", "source"), audits: () => auditCount };
}
describe("live monitor poller", () => {
it("drives dod-flap through the service and reducer without rapid terminal cuts", async () => {
const { service, get, audits } = fixture();
const observations: MonitorObservation[] = [];
const driver = await MockPollDriver.create({
scenario: "dod-flap", source: "A", sourceRef: "live:1", workspaceId: "workspace",
connectionId: "connection", epoch,
ingestNow: () => new Date(Date.parse(epoch) + observations.length * 10_000).toISOString(),
}, await mockPackages());
const states: string[] = [];
for (let tick = 0; tick < 10; tick += 1) {
const [observation] = await driver.poll();
if (observation === undefined) throw new Error("mock poll emitted no observation");
observations.push(observation);
const state = await service.recordObservation("workspace", "source", observation);
expect(state.version).toBe(tick + 1);
states.push(state.state);
}
expect(states).toEqual(["PROBING", "LIVE", "LIVE", "LIVE", "ERROR", "ERROR", "ERROR", "PROBING", "LIVE", "LIVE"]);
expect(observations.map((item) => item.observedAt)).toEqual(
Array.from({ length: 10 }, (_, tick) => new Date(Date.parse(epoch) + tick * 10_000).toISOString()),
);
expect(observations.every((item) => item.tier === "api-poll")).toBe(true);
expect((await get()).version).toBe(10);
expect(audits()).toBe(5);
const replay = observations[8];
if (replay === undefined) throw new Error("missing replay event");
expect((await service.recordObservation("workspace", "source", replay)).version).toBe(10);
expect(audits()).toBe(5);
});
it("plays the single-live scenario and keeps a replayed event from confirming LIVE", async () => {
const { service } = fixture();
const driver = await MockPollDriver.create({
scenario: "single-live", source: "A", sourceRef: "live:1", workspaceId: "workspace",
connectionId: "connection", epoch,
}, await mockPackages());
const [first] = await driver.poll();
if (first === undefined) throw new Error("missing first observation");
expect((await service.recordObservation("workspace", "source", first)).state).toBe("PROBING");
expect((await service.recordObservation("workspace", "source", first)).version).toBe(1);
const [second] = await driver.poll();
if (second === undefined) throw new Error("missing second observation");
const live = await service.recordObservation("workspace", "source", second);
expect(live.state).toBe("LIVE");
expect(live.version).toBe(2);
});
it("applies the same provider event only once across two poll ticks", async () => {
const { service, get } = fixture();
const observation: MonitorObservation = {
tier: "api-poll", observedAt: epoch, receivedAt: epoch, providerEventId: "same-event",
payload: { outcome: "ok" },
};
const poller = new LiveMonitorPoller(service, {
enabled: false,
sources: [{ provider: "mock-livestream", sourceRef: "live:1", workspaceId: "workspace", connectionId: "connection" }],
schedule: () => 1 as unknown as ReturnType<typeof setTimeout>,
cancel: () => undefined,
resolveSourceId: async () => "source",
makeDriver: async () => ({ poll: async () => [observation] }),
});
poller.start();
await poller.pollOnce();
await poller.pollOnce();
expect((await get()).version).toBe(1);
expect((await get()).state).toBe("PROBING");
await poller.stop();
});
it("uses a 10 second interval with bounded ±20% jitter and schedules the next tick", async () => {
expect([0, 0.5, 1].map((value) => jitteredInterval(10_000, value))).toEqual([8_000, 10_000, 12_000]);
const scheduled: Array<{ callback: () => void; delay: number }> = [];
const recorded: MonitorObservation[] = [];
const observation: MonitorObservation = {
tier: "api-poll", observedAt: epoch, receivedAt: epoch, providerEventId: "event-1",
payload: { outcome: "ok" },
};
const monitor = { recordObservation: async (_workspace: string, _source: string, value: MonitorObservation) => {
recorded.push(value);
} } as unknown as LiveMonitorService;
const overrides: Partial<PollerDependencies> = {
enabled: false, intervalMs: 10_000, random: () => 0,
sources: [{ provider: "mock-livestream", sourceRef: "live:1", workspaceId: "workspace", connectionId: "connection" }],
schedule: (callback, delay) => {
scheduled.push({ callback, delay });
return 1 as unknown as ReturnType<typeof setTimeout>;
},
cancel: () => undefined,
resolveSourceId: async () => "source",
makeDriver: async () => ({ poll: async () => [observation] }),
};
const poller = new LiveMonitorPoller(monitor, overrides);
poller.start();
expect(scheduled[0]?.delay).toBe(0);
scheduled[0]?.callback();
await new Promise((resolve) => setImmediate(resolve));
expect(recorded).toEqual([observation]);
expect(scheduled[1]?.delay).toBe(8_000);
await poller.stop();
});
it("parses source config and leaves the real probe stubbed", async () => {
expect(parsePollSources(JSON.stringify([{ provider: "mock-livestream", sourceRef: "live:1",
workspaceId: "workspace", connectionId: "connection" }]))).toHaveLength(1);
await expect(new PeerTubePollDriver("video-id").poll()).rejects.toThrow("NOT_IMPLEMENTED");
});
});