import { expect, test } from "bun:test"; import { performance } from "@chopin/experiment/fixtures"; import { MemoryStorage } from "../storage/memory/adapter"; import { userAndChannel } from "../storage/contract-support"; import { Experiments } from "./service"; test("claims expire, stale workers cannot publish and completion replays atomically", async () => { let storage = new MemoryStorage(); let { channelId, userId, lease } = await userAndChannel(storage); let documentId = crypto.randomUUID(); await storage.channels.create({ id: documentId, repositoryId: "repo", repositoryOwner: "org", repositoryName: "repo", title: "Experiment", createdBy: userId, now: new Date(), }); let now = Date.now(); let published: string[] = []; let service = new Experiments( storage.experiments, () => lease, id => published.push(id), () => now, ); let draft = await service.create(documentId, userId, " Measure startup\n"); let input = { id: draft.id, documentId, requester: userId, authorizer: userId, brief: draft.brief, context: "", source: { repositoryId: "repo", repository: "org/repo", commit: "a".repeat(40) }, }; await service.authorize(draft.id, "connection", input); await expect(service.claim(draft.id, "other")).rejects.toThrow("connection-forbidden"); let claimed = await service.claim(draft.id, "connection"); await service.candidate(draft.id, "connection", claimed.generation, performance); expect((await storage.experiments.get(draft.id))?.result).toBeUndefined(); let completed = await service.complete(draft.id, "connection", claimed.generation); expect(completed.result).toEqual(performance); expect(completed.views.comparison.revision).toBe(0); expect(await service.complete(draft.id, "connection", claimed.generation)).toEqual(completed); let decisionId = crypto.randomUUID(); await service.decide(draft.id, { id: "alice", login: "alice" }, { id: decisionId, view: "comparison", revision: 0, conclusion: "Choose cached", rationale: "Lower median", }); await service.select(draft.id, "bob", "comparison", { mutationId: crypto.randomUUID(), expected: { "filter:workload": 0 }, set: { "filter:workload": ["large"] }, }); let revised = (await service.store.get(draft.id))!; expect(revised.decisions[0].state.revision).toBe(0); expect(revised.views.comparison.revision).toBe(1); await expect( service.decide(draft.id, { id: "alice", login: "alice" }, { id: crypto.randomUUID(), view: "comparison", revision: 0, conclusion: "Stale", rationale: "", }), ).rejects.toThrow("Shared selections changed"); let another = await service.create(documentId, userId, "Try another"); await service.authorize(another.id, "connection", { ...input, id: another.id, brief: another.brief, }); let running = await service.claim(another.id, "connection"); now += 61_000; await expect(service.candidate(another.id, "connection", running.generation, performance)).rejects .toThrow("stale-claim"); await service.recover(); expect((await storage.experiments.get(another.id))?.state).toBe("interrupted"); expect(published).not.toContain(channelId); });