Files
CurrenciCombo/orchestrator/tests/unit/transactionState.test.ts
Devin b24a4df983
Some checks failed
Code Quality / SonarQube Analysis (pull_request) Failing after 23s
Code Quality / Code Quality Checks (pull_request) Failing after 11s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 4s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 5s
PR A: 12-state transaction machine + issueInstrument step + SoD matrix
Architecture note steps 1, 2, 10 (data model).

- types/transactionState.ts: 12 states, allowed-transition table, SoD matrix
- types/plan.ts: add InstrumentTerms + 'issueInstrument' PlanStep type
- services/planValidation.ts: validate SBLC step (BIC, ISO-4217, sha256,
  YYYY-MM-DD expiry, >0 amount)
- services/stateMachine.ts: transition() enforces legality + SoD + appends
  to transaction_state_transitions
- db/migrations/002: plans.transaction_state (CHECK) +
  transaction_state_transitions append-only table
- tests/unit: 13 + 8 unit tests (31 total, all pass)

No behaviour change yet: coordinator still uses legacy status field.
PRs B-G will migrate execution paths onto the new machine.
2026-04-22 16:21:36 +00:00

86 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from "@jest/globals";
import {
ALLOWED_TRANSITIONS,
ROLE_FOR_TRANSITION,
SOD_REQUIRED_TRANSITIONS,
TRANSACTION_STATES,
canTransition,
} from "../../src/types/transactionState";
describe("Transaction state machine (architecture note §8§9)", () => {
it("declares the 12 states from §8.1", () => {
expect(TRANSACTION_STATES).toEqual([
"DRAFT",
"INITIATED",
"PRECONDITIONS_PENDING",
"READY_FOR_PREPARE",
"PREPARED",
"EXECUTING",
"PARTIALLY_EXECUTED",
"VALIDATING",
"COMMITTED",
"ABORTED",
"UNWIND_PENDING",
"CLOSED",
]);
});
describe("§9.1 permitted high-level transitions", () => {
// Each of these is listed in the note; canTransition must accept them.
const legal: Array<[string, string]> = [
["DRAFT", "INITIATED"],
["INITIATED", "PRECONDITIONS_PENDING"],
["PRECONDITIONS_PENDING", "READY_FOR_PREPARE"],
["READY_FOR_PREPARE", "PREPARED"],
["PREPARED", "EXECUTING"],
["EXECUTING", "PARTIALLY_EXECUTED"],
["EXECUTING", "VALIDATING"],
["PARTIALLY_EXECUTED", "VALIDATING"],
["VALIDATING", "COMMITTED"],
["VALIDATING", "ABORTED"],
["ABORTED", "UNWIND_PENDING"],
["COMMITTED", "CLOSED"],
["UNWIND_PENDING", "CLOSED"],
];
it.each(legal)("allows %s -> %s", (from, to) => {
expect(canTransition(from as any, to as any)).toBe(true);
});
// A few illegal edges — explicitly not in §9.1.
const illegal: Array<[string, string]> = [
["DRAFT", "COMMITTED"],
["INITIATED", "EXECUTING"],
["CLOSED", "INITIATED"],
["PREPARED", "COMMITTED"],
["COMMITTED", "ABORTED"],
["ABORTED", "COMMITTED"],
];
it.each(illegal)("rejects %s -> %s", (from, to) => {
expect(canTransition(from as any, to as any)).toBe(false);
});
});
it("CLOSED is a terminal state", () => {
expect(ALLOWED_TRANSITIONS.CLOSED).toEqual([]);
});
describe("segregation-of-duties checkpoints (§13)", () => {
it("flags the four SoD-gated transitions", () => {
expect([...SOD_REQUIRED_TRANSITIONS].sort()).toEqual(
[
"ABORTED->UNWIND_PENDING",
"PREPARED->EXECUTING",
"READY_FOR_PREPARE->PREPARED",
"VALIDATING->COMMITTED",
].sort(),
);
});
it("assigns a role to every SoD-gated transition", () => {
for (const key of SOD_REQUIRED_TRANSITIONS) {
expect(ROLE_FOR_TRANSITION[key]).toBeDefined();
}
});
});
});