/*
Unit tests for lifecycle covering lifecycle state machine. It also guards the documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the lifecycle suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect } from "vitest";
import {
LifecycleMachine,
isValidTransition,
transition,
isTerminal,
canAcceptNewCalls,
canCancel,
} from "../lifecycle.ts";
import type { ComponentLifecycleState } from "../lifecycle.ts";
import type { ResolvedComponentIdentityV1 } from "@warpgogol/werkstatt-shared/component";
const VALID_SHA = "sha256:" + "a".repeat(64);
function makeIdentity(
overrides: Partial = {},
): ResolvedComponentIdentityV1 {
return {
componentId: "werkstatt/engine",
version: "1.0.0",
artifactHash: VALID_SHA,
...overrides,
};
}
describe("lifecycle state machine", () => {
it("allows valid forward transitions", () => {
const lm = new LifecycleMachine(makeIdentity());
expect(lm.state).toBe("declared");
expect(lm.transitionTo("waiting").ok).toBe(true);
expect(lm.state).toBe("waiting");
expect(lm.transitionTo("loading").ok).toBe(true);
expect(lm.state).toBe("loading");
expect(lm.transitionTo("active").ok).toBe(true);
expect(lm.state).toBe("active");
expect(lm.transitionTo("draining").ok).toBe(true);
expect(lm.state).toBe("draining");
expect(lm.transitionTo("unloading").ok).toBe(true);
expect(lm.state).toBe("unloading");
expect(lm.transitionTo("disposed").ok).toBe(true);
expect(lm.state).toBe("disposed");
expect(lm.isTerminal).toBe(true);
});
it("rejects invalid transitions", () => {
const lm = new LifecycleMachine(makeIdentity());
const result = lm.transitionTo("active");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.rule).toBe("LIFECYCLE-02");
}
expect(lm.state).toBe("declared");
});
it("rejects self-transitions", () => {
const result = transition("active", "active");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.rule).toBe("LIFECYCLE-01");
}
});
it("allows failed transition from any non-terminal state", () => {
const states: ComponentLifecycleState[] = [
"declared",
"waiting",
"loading",
"active",
"draining",
"unloading",
];
for (const s of states) {
expect(isValidTransition(s, "failed")).toBe(true);
}
});
it("allows quarantined from failed", () => {
expect(isValidTransition("failed", "quarantined")).toBe(true);
});
it("terminal states have no outgoing transitions", () => {
expect(isValidTransition("disposed", "active")).toBe(false);
expect(isValidTransition("quarantined", "active")).toBe(false);
expect(isValidTransition("disposed", "failed")).toBe(false);
expect(isValidTransition("quarantined", "failed")).toBe(false);
});
it("canAcceptNewCalls only in active state", () => {
expect(canAcceptNewCalls("active")).toBe(true);
expect(canAcceptNewCalls("declared")).toBe(false);
expect(canAcceptNewCalls("draining")).toBe(false);
expect(canAcceptNewCalls("disposed")).toBe(false);
});
it("canCancel in active or draining", () => {
expect(canCancel("active")).toBe(true);
expect(canCancel("draining")).toBe(true);
expect(canCancel("loading")).toBe(false);
expect(canCancel("disposed")).toBe(false);
});
it("isTerminal for disposed and quarantined", () => {
expect(isTerminal("disposed")).toBe(true);
expect(isTerminal("quarantined")).toBe(true);
expect(isTerminal("active")).toBe(false);
expect(isTerminal("failed")).toBe(false);
});
it("transitionTo does not mutate state on failure", () => {
const lm = new LifecycleMachine(makeIdentity(), "active");
const result = lm.transitionTo("loading");
expect(result.ok).toBe(false);
expect(lm.state).toBe("active");
});
});