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
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.
83 lines
2.8 KiB
TypeScript
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);
|
|
});
|
|
});
|