import { test, expect } from "bun:test"; // broadcast.ts pulls in the runtime `config`, which requires DISCORD_GUILD_ID. // Set it before the dynamic import so the module loads without a real .env. process.env.DISCORD_GUILD_ID ||= "test-guild"; const { wireBroadcast, autoStartBroadcast } = await import("./broadcast.ts"); /** Minimal in-memory ScreenStreamer that records start/stop calls. */ function fakeStreamer(active = false) { const calls: string[] = []; let on = active; return { calls, kind: "selfbot" as const, isActive: () => on, async start() { calls.push("start"); on = true; return "ok"; }, async stop() { calls.push("stop"); on = false; }, }; } const ctx = { guildId: "g", voiceChannelId: "c" }; test("wireBroadcast reports live state to the brain", () => { const s = fakeStreamer(false); const session: any = {}; wireBroadcast(session, s as any, ctx); expect(session.getBroadcasting()).toBe(false); }); test("wireBroadcast lets the brain toggle the stream by voice", async () => { const s = fakeStreamer(false); const session: any = {}; wireBroadcast(session, s as any, ctx); await session.onBroadcastAction("start"); // idle -> start expect(session.getBroadcasting()).toBe(true); await session.onBroadcastAction("start"); // already live -> no-op await session.onBroadcastAction("stop"); // stop expect(session.getBroadcasting()).toBe(false); expect(s.calls).toEqual(["start", "stop"]); }); test("autoStartBroadcast starts the broadcast when idle", async () => { const s = fakeStreamer(false); const ok = await autoStartBroadcast(s as any, ctx, 2, 0); expect(ok).toBe(true); expect(s.calls).toEqual(["start"]); }); test("autoStartBroadcast does not restart an already-live broadcast", async () => { const s = fakeStreamer(true); const ok = await autoStartBroadcast(s as any, ctx, 2, 0); expect(ok).toBe(true); expect(s.calls).toEqual([]); }); test("autoStartBroadcast retries a transient failure and then succeeds", async () => { let on = false; const calls: string[] = []; const s: any = { isActive: () => on, async start() { calls.push("start"); if (calls.length === 1) throw new Error("transient: voice not ready"); on = true; }, async stop() {}, }; const ok = await autoStartBroadcast(s, ctx, 2, 0); expect(ok).toBe(true); expect(calls).toEqual(["start", "start"]); }); test("autoStartBroadcast returns false after exhausting retries (no throw)", async () => { let attempts = 0; const s: any = { isActive: () => false, async start() { attempts++; throw new Error("boom"); }, async stop() {}, }; const ok = await autoStartBroadcast(s, ctx, 2, 0); expect(ok).toBe(false); expect(attempts).toBe(2); // retried, then gave up without throwing });