import test from "node:test"; import assert from "node:assert/strict"; import { defaultSiteSettings } from "../lib/settings"; import { defaultChannelPriority } from "../lib/channelPriority"; import { emptySchedulerState } from "../jobs/syncSchedulerState"; import { buildScheduleView } from "../jobs/syncScheduler"; import type { ChannelBrief } from "../controller/channels"; import type { ChannelConfig } from "../lib/channelConfig"; import type { ChannelSnapshot } from "../controller/channelSnapshot"; import { emptyOperationCounts, type OperationSnapshotEntry, } from "../lib/operations"; import { buildWidgetSyncPayload, type WidgetSyncInputs } from "./widgetSync"; // THE WIDGET'S CORPUS-WIDE SUMS. Every number here is folded off the snapshot // each brief already carries, which is why the briefs are an argument: this used // to be a corpus walk on a polled endpoint. const NOW = 1_700_000_000_000; const entry = ( over: Partial = {}, ): OperationSnapshotEntry => ({ ...emptyOperationCounts(), ids: [], ...over }); const brief = ( slug: string, snapshot: Partial | null, config: Partial = {}, ): ChannelBrief => ({ slug, config: { name: slug, url: `https://example.com/${slug}`, ...config }, snapshot: snapshot ? ({ generatedAt: "2026-01-01T00:00:00Z", totals: { videos: 0, transcribed: 0, downloaded: 0 }, buckets: {}, ...snapshot, } as ChannelSnapshot) : null, }) as ChannelBrief; function inputs(over: Partial = {}): WidgetSyncInputs { return { settings: defaultSiteSettings(), now: NOW, state: emptySchedulerState(), briefs: [], ...over, }; } test("two briefs are summed, per corpus and per kind", () => { const briefs = [ brief("alpha", { totals: { videos: 100, transcribed: 90, downloaded: 95 }, digestEngines: { ollama: 40, remote: 2 }, backfill: { digest: entry({ missing: 5, blocked: 3, deferred: 1, eligible: 60 }), diarization: entry({ missing: 4, stale: 1, missingInput: 20, eligible: 100 }), "attribution-text": entry({ missing: 9, missingInput: 1, eligible: 100 }), }, }), brief("beta", { totals: { videos: 10, transcribed: 10, downloaded: 10 }, digestEngines: { ollama: 3 }, backfill: { digest: entry({ missing: 1, blocked: 2, deferred: 4, eligible: 8 }), diarization: entry({ missing: 1, missingInput: 5, eligible: 10 }), "attribution-text": entry({ missing: 2, eligible: 10 }), }, }), ]; const payload = buildWidgetSyncPayload(inputs({ briefs })); assert.equal(payload.digest.videos, 110); assert.equal(payload.digest.digested, 45); assert.equal(payload.digest.channelsWithAny, 2); assert.equal(payload.digest.channels, 2); assert.equal(payload.digest.blocked, 5); assert.equal(payload.digest.deferred, 5); // Eligibility MINUS what is blocked on a transcript, per channel: (60-3) + // (8-2). A blocked video is eligible in principle and cannot be digested // today, and counting it would make coverage sag every time new videos land. assert.equal(payload.digest.eligible, 63); // The backfill LANE only: `digest` runs on its own queue key and its coverage // is the figure beside this one, not part of it. assert.equal(payload.backfill.reachable, 4 + 1 + 9 + 1 + 2); assert.equal(payload.backfill.needsMedia, 20 + 1 + 5); assert.deepEqual( payload.backfill.kinds.map((k) => [k.id, k.reachable, k.needsMedia]), [ ["attribution-text", 11, 1], ["diarization", 6, 25], ], ); const diarization = payload.backfill.kinds.find((k) => k.id === "diarization")!; assert.equal(diarization.label, "Speaker diarization"); assert.equal(diarization.eligible, 110); // present = eligible - every work count, summed per channel. assert.equal(diarization.present, 110 - 6 - 25); }); test("one snapshot that cannot report voids that kind's coverage and nothing else", () => { const briefs = [ brief("alpha", { totals: { videos: 10, transcribed: 10, downloaded: 10 }, backfill: { digest: entry({ missing: 1, eligible: 10 }), diarization: entry({ missing: 1, eligible: 10 }), "attribution-text": entry({ missing: 1, eligible: 10 }), }, }), // Written before `eligible` existed. brief("beta", { totals: { videos: 10, transcribed: 10, downloaded: 10 }, backfill: { digest: entry({ missing: 1, eligible: 10 }), diarization: entry({ missing: 1 }), "attribution-text": entry({ missing: 1, eligible: 10 }), }, }), ]; const payload = buildWidgetSyncPayload(inputs({ briefs })); const kind = (id: string) => payload.backfill.kinds.find((k) => k.id === id)!; // A partial sum would be a denominator smaller than its own numerator, so the // kind says "unknown" — and only that kind. assert.equal(kind("diarization").eligible, null); assert.equal(kind("diarization").present, null); assert.equal(kind("attribution-text").eligible, 20); assert.equal(kind("attribution-text").present, 18); // Digest's own denominator is a separate sum and is unaffected. assert.equal(payload.digest.eligible, 20); // Digest goes null the same way, corpus-wide, when a channel cannot report. const noDigestEligible = buildWidgetSyncPayload( inputs({ briefs: [briefs[0], brief("gamma", { backfill: { digest: entry({ missing: 1 }) } })], }), ); assert.equal(noDigestEligible.digest.eligible, null); }); test("kinds are sorted by reachable descending, tie-broken by id", () => { const payload = buildWidgetSyncPayload( inputs({ briefs: [ brief("alpha", { backfill: { diarization: entry({ missing: 7 }), "attribution-text": entry({ missing: 7 }), }, }), ], }), ); assert.deepEqual( payload.backfill.kinds.map((k) => k.id), ["attribution-text", "diarization"], ); const bigger = buildWidgetSyncPayload( inputs({ briefs: [ brief("alpha", { backfill: { diarization: entry({ missing: 8 }), "attribution-text": entry({ missing: 7 }), }, }), ], }), ); assert.deepEqual( bigger.backfill.kinds.map((k) => k.id), ["diarization", "attribution-text"], ); }); test("nextRunAt and overdue come off buildScheduleView, over eligible channels only", () => { const settings = defaultSiteSettings(); settings.syncScheduler = { ...settings.syncScheduler, enabled: true, defaultIntervalMinutes: 60, }; // Synced 30 minutes ago: next due in another 30. const soon = brief("soon", null, { lastSyncedAt: new Date(NOW - 30 * 60_000).toISOString(), }); // Synced two hours ago: overdue now. const late = brief("late", null, { lastSyncedAt: new Date(NOW - 120 * 60_000).toISOString(), }); const briefs = [soon, late]; const payload = buildWidgetSyncPayload(inputs({ settings, briefs })); const view = buildScheduleView({ channels: briefs.map((c) => ({ slug: c.slug, config: c.config })), scheduler: settings.syncScheduler, state: emptySchedulerState(), now: NOW, priority: defaultChannelPriority(), }); assert.deepEqual( payload.scheduler.nextRunAt, Math.min(...view.map((v) => v.nextDueAt!)), ); assert.equal(payload.scheduler.overdue, true); assert.equal(payload.scheduler.enabled, true); // Scheduler off: no channel is eligible, so there is nothing due and nothing // overdue — the schedule view, not a second predicate, decides that. const off = buildWidgetSyncPayload(inputs({ briefs })); assert.equal(off.scheduler.nextRunAt, null); assert.equal(off.scheduler.overdue, false); assert.equal(off.scheduler.enabled, false); }); test("the two freshness markers", () => { const state = emptySchedulerState(); state.lastSyncAllAt = 12_345; state.runs = [{ at: 999, queued: [], skipped: [] }]; const payload = buildWidgetSyncPayload( inputs({ state, briefs: [ brief("alpha", null, { lastSyncedAt: "2026-01-02T00:00:00Z" }), brief("beta", null, { lastSyncedAt: "2026-03-04T00:00:00Z" }), brief("gamma", null, { lastSyncedAt: "not a date" }), brief("delta", null), ], }), ); assert.equal(payload.lastSyncAllAt, 12_345); assert.equal(payload.scheduler.lastRunAt, 999); assert.equal( payload.lastIndividualSyncAt, Date.parse("2026-03-04T00:00:00Z"), ); });