/*
Unit tests for activation covering ActivationTransaction. 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 activation suite's contract for test-file scaffold coverage.
*/
import { describe, it, expect } from "vitest";
import { createActivationTransaction } from "../activation.ts";
import { ComponentFiber } from "../fiber.ts";
import type {
ResolvedComponentIdentityV1,
ResolvedComponentSetV1,
} from "@warpgogol/werkstatt-shared/component";
import { computeSetHash } from "../../component/identity.ts";
const VALID_SHA = "sha256:" + "a".repeat(64);
const VALID_SHA_2 = "sha256:" + "b".repeat(64);
function makeIdentity(
overrides: Partial = {},
): ResolvedComponentIdentityV1 {
return {
componentId: "werkstatt/engine",
version: "1.0.0",
artifactHash: VALID_SHA,
...overrides,
};
}
function makeSet(
components: ResolvedComponentIdentityV1[],
overrides: Partial> = {},
): ResolvedComponentSetV1 {
const base = {
schema: "werkstatt/resolved-component-set@1" as const,
profileId: "site",
components,
dependencyGraphHash: VALID_SHA,
grantSetHash: VALID_SHA,
effectPolicyHash: VALID_SHA,
isolationPolicyHash: VALID_SHA,
...overrides,
};
return { ...base, setHash: computeSetHash(base) };
}
function makeFiber(identity: ResolvedComponentIdentityV1): ComponentFiber {
return new ComponentFiber(identity);
}
describe("ActivationTransaction", () => {
it("prepares and commits new set without prior set", async () => {
const set = makeSet([makeIdentity()]);
const tx = createActivationTransaction("tx-1", null, set, makeFiber, { timeoutMs: 1000 });
await tx.prepare();
expect(tx.state).toBe("preparing");
await tx.commit();
expect(tx.isCommitted).toBe(true);
expect(tx.fibers).toHaveLength(1);
expect(tx.fibers[0]!.state).toBe("active");
});
it("prepares, commits, and drains prior set on commit", async () => {
const priorSet = makeSet([makeIdentity({ componentId: "werkstatt/old" })]);
const newSet = makeSet([makeIdentity({ componentId: "werkstatt/new" })]);
const tx = createActivationTransaction("tx-2", priorSet, newSet, makeFiber, {
timeoutMs: 1000,
});
await tx.prepare();
await tx.commit();
expect(tx.isCommitted).toBe(true);
expect(tx.result.drainResults).toHaveLength(1);
expect(tx.result.drainResults[0]!.drained).toBe(true);
});
it("abort restores to aborted state", async () => {
const set = makeSet([makeIdentity()]);
const tx = createActivationTransaction("tx-3", null, set, makeFiber, { timeoutMs: 1000 });
await tx.prepare();
await tx.abort("test abort");
expect(tx.isAborted).toBe(true);
expect(tx.result.error).toBe("test abort");
});
it("prepare throws on invalid state for double prepare", async () => {
const set = makeSet([makeIdentity()]);
const tx = createActivationTransaction("tx-4", null, set, makeFiber, { timeoutMs: 1000 });
await tx.prepare();
await expect(tx.prepare()).rejects.toThrow(/requires idle state/);
});
it("commit throws when not in preparing state", async () => {
const set = makeSet([makeIdentity()]);
const tx = createActivationTransaction("tx-5", null, set, makeFiber, { timeoutMs: 1000 });
await expect(tx.commit()).rejects.toThrow(/requires preparing state/);
});
it("drain timeout on prior set causes abort", async () => {
const priorSet = makeSet([makeIdentity({ componentId: "werkstatt/old" })]);
const newSet = makeSet([makeIdentity({ componentId: "werkstatt/new" })]);
const slowFiber = (id: ResolvedComponentIdentityV1): ComponentFiber => {
const fiber = new ComponentFiber(id);
if (id.componentId === "werkstatt/old") {
fiber.transitionTo("waiting");
fiber.transitionTo("loading");
fiber.transitionTo("active");
fiber
.run({
id: "slow-op",
execute: async () => new Promise(() => {}),
})
.catch(() => {});
}
return fiber;
};
const tx = createActivationTransaction("tx-6", priorSet, newSet, slowFiber, { timeoutMs: 50 });
await tx.prepare();
await expect(tx.commit()).rejects.toThrow(/drain timeout/);
expect(tx.isAborted).toBe(true);
});
it("activation result contains correct hashes", async () => {
const set = makeSet([makeIdentity()]);
const tx = createActivationTransaction("tx-7", null, set, makeFiber, { timeoutMs: 1000 });
await tx.prepare();
await tx.commit();
expect(tx.result.priorSetHash).toBe("sha256:" + "0".repeat(64));
expect(tx.result.proposedSetHash).toBe(set.setHash);
});
it("multiple components activate in dependency order", async () => {
const c1 = makeIdentity({ componentId: "werkstatt/alpha", version: "1.0.0" });
const c2 = makeIdentity({
componentId: "werkstatt/beta",
version: "1.0.0",
artifactHash: VALID_SHA_2,
});
const set = makeSet([c2, c1]);
const tx = createActivationTransaction("tx-8", null, set, makeFiber, { timeoutMs: 1000 });
await tx.prepare();
await tx.commit();
expect(tx.fibers).toHaveLength(2);
expect(tx.fibers[0]!.component.componentId).toBe("werkstatt/alpha");
expect(tx.fibers[1]!.component.componentId).toBe("werkstatt/beta");
});
});