import { Effect } from "effect"; import { describe, expect, it } from "vitest"; import { orbActorKey, OrbStateError, transitionOrbState, transitionRunState, } from "./domain"; const runTransition = (from: string, to: string) => Effect.runSync( Effect.flip(transitionOrbState({ from: from as never, to: to as never })) ); const runTransitionOk = (from: string, to: string) => Effect.runSync(transitionOrbState({ from: from as never, to: to as never })); const runRunState = (from: string, to: string) => Effect.runSync( Effect.flip(transitionRunState({ from: from as never, to: to as never })) ); const runRunStateOk = (from: string, to: string) => Effect.runSync(transitionRunState({ from: from as never, to: to as never })); describe("orbActorKey", () => { it("produces a stable key from the identity triple", () => { const key = orbActorKey({ projectId: "prj-1", runId: "run-1", workUnitId: "wrk-1", }); expect(key).toBe("project:prj-1:work:wrk-1:run:run-1"); }); it("preserves order across different identities", () => { expect(orbActorKey({ projectId: "a", runId: "b", workUnitId: "c" })).toBe( "project:a:work:c:run:b" ); }); }); describe("transitionOrbState", () => { it("allows creating -> prepared", () => { expect(runTransitionOk("creating", "prepared")).toBe("prepared"); }); it("allows prepared -> running", () => { expect(runTransitionOk("prepared", "running")).toBe("running"); }); it("allows running -> needs-input", () => { expect(runTransitionOk("running", "needs-input")).toBe("needs-input"); }); it("allows running -> completed", () => { expect(runTransitionOk("running", "completed")).toBe("completed"); }); it("allows completed -> disposed", () => { expect(runTransitionOk("completed", "disposed")).toBe("disposed"); }); it("allows needs-input -> running (resume)", () => { expect(runTransitionOk("needs-input", "running")).toBe("running"); }); it("rejects disposed -> running", () => { const error = runTransition("disposed", "running"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("AlreadyDisposed"); }); it("rejects completed -> creating", () => { const error = runTransition("completed", "creating"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("InvalidTransition"); }); it("rejects prepared -> needs-input (skipping running)", () => { const error = runTransition("prepared", "needs-input"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("InvalidTransition"); }); it("rejects cancelled -> running", () => { const error = runTransition("cancelled", "running"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("InvalidTransition"); }); }); describe("transitionRunState", () => { it("allows queued -> provisioning", () => { expect(runRunStateOk("queued", "provisioning")).toBe("provisioning"); }); it("allows provisioning -> preparing", () => { expect(runRunStateOk("provisioning", "preparing")).toBe("preparing"); }); it("allows running -> verifying", () => { expect(runRunStateOk("running", "verifying")).toBe("verifying"); }); it("allows verifying -> succeeded", () => { expect(runRunStateOk("verifying", "succeeded")).toBe("succeeded"); }); it("allows running -> cancelled", () => { expect(runRunStateOk("running", "cancelled")).toBe("cancelled"); }); it("rejects succeeded -> running (terminal)", () => { const error = runRunState("succeeded", "running"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("InvalidTransition"); }); it("rejects failed -> running (terminal)", () => { const error = runRunState("failed", "running"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("InvalidTransition"); }); it("rejects queued -> succeeded (skipping steps)", () => { const error = runRunState("queued", "succeeded"); expect(error).toBeInstanceOf(OrbStateError); expect(error.reason).toBe("InvalidTransition"); }); });