Files
CurrenciCombo/orchestrator/tests/unit/planValidation.instrument.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

83 lines
2.8 KiB
TypeScript

import { describe, it, expect } from "@jest/globals";
import { validatePlan } from "../../src/services/planValidation";
import type { InstrumentTerms, Plan } from "../../src/types/plan";
const goodTerms: InstrumentTerms = {
applicant: "Solace Bank Group PLC",
issuingBankBIC: "SOLBAE22",
beneficiaryBankBIC: "MEBLAEAD", // Emirates Islamic BIC prefix example
beneficiaryName: "Acme Trading LLC",
beneficiaryAccount: "AE070331234567890123456",
amount: 1_000_000,
currency: "USD",
tenor: "90D",
expiryDate: "2026-06-30",
placeOfPresentation: "Dubai, UAE",
governingLaw: "URDG 758",
templateRef: "EIB-SBLC-v3.2",
templateHash:
"a".repeat(64), // dummy sha256
};
function planWith(terms: Partial<InstrumentTerms> | null): Plan {
return {
creator: "solace-ops-01",
steps: [
{
type: "issueInstrument",
amount: terms?.amount ?? 1_000_000,
instrument: terms === null ? undefined : ({ ...goodTerms, ...terms } as InstrumentTerms),
},
],
};
}
describe("validatePlan — issueInstrument step", () => {
it("accepts a well-formed SBLC step", () => {
const result = validatePlan(planWith({}));
expect(result.valid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it("rejects a step missing the instrument object", () => {
const result = validatePlan(planWith(null));
expect(result.valid).toBe(false);
expect(result.errors[0]).toMatch(/missing instrument terms/);
});
it("rejects an invalid BIC", () => {
const result = validatePlan(planWith({ issuingBankBIC: "NOTABIC" }));
expect(result.valid).toBe(false);
expect(result.errors.join("\n")).toMatch(/issuingBankBIC is not a valid BIC/);
});
it("rejects a non-ISO-4217 currency", () => {
const result = validatePlan(planWith({ currency: "usd" }));
expect(result.valid).toBe(false);
expect(result.errors.join("\n")).toMatch(/currency must be ISO 4217/);
});
it("rejects a non-ISO-8601 expiry date", () => {
const result = validatePlan(planWith({ expiryDate: "30-06-2026" }));
expect(result.valid).toBe(false);
expect(result.errors.join("\n")).toMatch(/expiryDate must be YYYY-MM-DD/);
});
it("rejects a non-sha256 template hash", () => {
const result = validatePlan(planWith({ templateHash: "deadbeef" }));
expect(result.valid).toBe(false);
expect(result.errors.join("\n")).toMatch(/templateHash must be 64 hex chars/);
});
it("rejects an instrument with non-positive amount", () => {
const result = validatePlan(planWith({ amount: 0 }));
expect(result.valid).toBe(false);
expect(result.errors.join("\n")).toMatch(/instrument.amount must be > 0/);
});
it("accepts 11-char branched BIC", () => {
const result = validatePlan(planWith({ issuingBankBIC: "SOLBAE22XXX" }));
expect(result.valid).toBe(true);
});
});