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/.
This commit is contained in:
commit
aa5e262f3a
880 changed files with 111042 additions and 0 deletions
166
apps/api/src/live-monitor/poller/poller.test.ts
Normal file
166
apps/api/src/live-monitor/poller/poller.test.ts
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
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");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue