import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { OptimizationCancelledError, OptimizerClient, } from "../../src/optimization/optimizer-client"; import type { OptimizationRequest, OptimizationResult, } from "../../src/optimization/optimization.types"; class TestWorker extends EventTarget { static instances: TestWorker[] = []; readonly messages: unknown[] = []; terminateCount = 0; constructor() { super(); TestWorker.instances.push(this); } postMessage(message: unknown): void { this.messages.push(message); } terminate(): void { this.terminateCount += 1; } respond(response: OptimizationResult): void { this.dispatchEvent(new MessageEvent("message", { data: response })); } } function resultFor(worker: TestWorker, source = ""): OptimizationResult { const request = worker.messages[0] as OptimizationRequest; return { type: "result", jobId: request.jobId, source, profile: request.profile, optionalPlugins: request.optionalPlugins, inputBytes: source.length, outputBytes: source.length, elapsedMs: 1, }; } describe("OptimizerClient cancellation", () => { beforeEach(() => { TestWorker.instances = []; vi.useFakeTimers(); vi.stubGlobal("Worker", TestWorker); }); afterEach(() => { vi.useRealTimers(); vi.unstubAllGlobals(); }); it("rejects a cancelled job immediately and cleans up exactly once", async () => { const client = new OptimizerClient(); const pending = client.optimize("", "conservative"); const rejected = expect(pending).rejects.toBeInstanceOf( OptimizationCancelledError, ); const worker = TestWorker.instances[0]!; expect(vi.getTimerCount()).toBe(1); client.cancel(); client.cancel(); await rejected; expect(worker.terminateCount).toBe(1); expect(vi.getTimerCount()).toBe(0); worker.respond(resultFor(worker, '')); expect(worker.terminateCount).toBe(1); }); it("promptly rejects a superseded job while the replacement can finish", async () => { const client = new OptimizerClient(); const first = client.optimize('', "conservative"); const firstRejected = expect(first).rejects.toBeInstanceOf( OptimizationCancelledError, ); const firstWorker = TestWorker.instances[0]!; const second = client.optimize('', "standard"); const secondWorker = TestWorker.instances[1]!; await firstRejected; expect(firstWorker.terminateCount).toBe(1); expect(secondWorker.terminateCount).toBe(0); expect(vi.getTimerCount()).toBe(1); const expected = resultFor(secondWorker, ''); secondWorker.respond(expected); await expect(second).resolves.toEqual(expected); expect(secondWorker.terminateCount).toBe(1); expect(vi.getTimerCount()).toBe(0); client.cancel(); expect(secondWorker.terminateCount).toBe(1); }); it("uses the same race-safe cleanup for AbortSignal cancellation", async () => { const controller = new AbortController(); const client = new OptimizerClient(); const pending = client.optimize( "", "aggressive", [], controller.signal, ); const rejected = expect(pending).rejects.toBeInstanceOf( OptimizationCancelledError, ); const worker = TestWorker.instances[0]!; controller.abort(); await rejected; expect(worker.terminateCount).toBe(1); expect(vi.getTimerCount()).toBe(0); client.cancel(); expect(worker.terminateCount).toBe(1); }); });