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 { const [mock, scenarios] = await Promise.all([ vi.importActual("../../../../../packages/connector-mock-livestream/src/index.js"), vi.importActual("../../../../../packages/testing/src/scenarios.js"), ]); return { mock, scenarios }; } function fixture(): { service: LiveMonitorService; get: () => Promise; audits: () => number } { const row: Record = { 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(); 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, 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 = { 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; }, 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"); }); });