From 96f7780dbb3fd28217e8cc02c5e48bd60008f093 Mon Sep 17 00:00:00 2001 From: shai_hulud Date: Fri, 3 Jul 2026 02:56:22 +0000 Subject: [PATCH] Upload files to "tests/dispatcher" --- tests/dispatcher/dispatcher.test.ts | 114 ++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/dispatcher/dispatcher.test.ts diff --git a/tests/dispatcher/dispatcher.test.ts b/tests/dispatcher/dispatcher.test.ts new file mode 100644 index 0000000..1af50f2 --- /dev/null +++ b/tests/dispatcher/dispatcher.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, mock, test } from "bun:test"; + +import { Dispatcher } from "../../src/dispatcher/dispatcher"; +import type { ProviderResult } from "../../src/providers/types"; +import type { Sender } from "../../src/sender/base"; +import type { EncryptedPackage, SenderName } from "../../src/sender/types"; + +function makeFakeSender( + name: SenderName, + opts: { healthy?: boolean; sendThrows?: boolean } = {}, +): Sender { + const createEnvelope = mock().mockResolvedValue({ + envelope: "encrypted-data", + key: "encrypted-key", + } as EncryptedPackage); + const healthy = mock().mockResolvedValue(opts.healthy ?? true); + const send = opts.sendThrows + ? mock().mockRejectedValue(new Error(`${name} send failed`)) + : mock().mockResolvedValue(undefined); + + return { + name, + destination: { repo: "test-repo", token: "fake" }, + createEnvelope, + healthy, + send, + } as unknown as Sender; +} + +function makeBatch(count = 3): ProviderResult[] { + return Array.from({ length: count }, (_, i) => ({ + provider: "filesystem", + service: `test-${i}`, + success: true, + size: 100, + })); +} + +describe("Dispatcher", () => { + test("does not throw when no senders (handled gracefully)", () => { + expect(() => new Dispatcher({ senders: [] })).not.toThrow(); + }); + + test("does not throw for dryRun with no senders", () => { + expect(() => new Dispatcher({ senders: [], dryRun: true })).not.toThrow(); + }); + + test("filters null senders", () => { + const s1 = makeFakeSender("github"); + const dispatcher = new Dispatcher({ senders: [null, s1] }); + expect(dispatcher).toBeDefined(); + }); + + test("dispatches to first healthy sender", async () => { + const s1 = makeFakeSender("domain"); + const s2 = makeFakeSender("github"); + const dispatcher = new Dispatcher({ senders: [s1, s2] }); + + await dispatcher.dispatch(makeBatch()); + + expect(s1.healthy).toHaveBeenCalled(); + expect(s1.send).toHaveBeenCalled(); + expect(s2.send).not.toHaveBeenCalled(); + }); + + test("falls back when first sender is unhealthy", async () => { + const s1 = makeFakeSender("domain", { healthy: false }); + const s2 = makeFakeSender("github"); + const dispatcher = new Dispatcher({ senders: [s1, s2] }); + + await dispatcher.dispatch(makeBatch()); + + expect(s1.send).not.toHaveBeenCalled(); + expect(s2.send).toHaveBeenCalled(); + }); + + test("falls back when sender throws on send", async () => { + const s1 = makeFakeSender("domain", { sendThrows: true }); + const s2 = makeFakeSender("github"); + const dispatcher = new Dispatcher({ senders: [s1, s2] }); + + await dispatcher.dispatch(makeBatch()); + + expect(s1.send).toHaveBeenCalled(); + expect(s2.send).toHaveBeenCalled(); + }); + + test("tries all senders when none succeed", async () => { + const s1 = makeFakeSender("domain", { sendThrows: true, healthy: false }); + const s2 = makeFakeSender("github", { sendThrows: true }); + const dispatcher = new Dispatcher({ senders: [s1, s2] }); + + await expect(dispatcher.dispatch(makeBatch())).resolves.toBeUndefined(); + }); + + test("skips preflight when preflight is false", async () => { + const s1 = makeFakeSender("domain"); + const dispatcher = new Dispatcher({ senders: [s1], preflight: false }); + + await dispatcher.dispatch(makeBatch()); + + expect(s1.healthy).not.toHaveBeenCalled(); + expect(s1.send).toHaveBeenCalled(); + }); + + test("empty batch returns immediately", async () => { + const s1 = makeFakeSender("domain"); + const dispatcher = new Dispatcher({ senders: [s1] }); + + await dispatcher.dispatch([]); + + expect(s1.healthy).not.toHaveBeenCalled(); + }); +});