import { test } from "node:test"; import assert from "node:assert/strict"; import { parseScenario } from "../src/declaration/parse.ts"; import { assertSupported, UnsupportedError } from "../src/lifecycle/supported.ts"; /** * A declaration the runtime silently ignores is the fault this lab exists to catch — * novox/hq 04-ISSUES/003, where a firewall key is declared in five manifests and read by * no code. These tests exist so the lab never commits it. */ const withGateway = `scenario: x segments: pub: { kind: public, cidr: [192.0.2.0/24] } home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } machines: { a: { at: { segment: home, address: [192.168.1.9] } } }`; test("a scenario with no unimplemented features is raisable", () => { const scenario = parseScenario(`scenario: x segments: { net: { kind: public, cidr: [192.0.2.0/24] } } machines: { a: { at: { segment: net, address: [192.0.2.1] } } }`); assert.doesNotThrow(() => assertSupported(scenario)); }); test("a declared gateway is refused rather than silently absent", () => { assert.throws(() => assertSupported(parseScenario(withGateway)), UnsupportedError); }); test("the refusal names every missing capability, not just the first", () => { const scenario = parseScenario(`scenario: x segments: pub: { kind: public, cidr: [192.0.2.0/24] } home: { kind: private, cidr: [192.168.1.0/24], gateway: { to: pub, address: [192.0.2.5], nat: [v4] } } policy: [{ from: home, to: pub, allow: false }] machines: a: at: { segment: home, address: [192.168.1.9] } published: [{ port: 443, on: home }] inbound: deny place: { all: [host] }`); try { assertSupported(scenario); assert.fail("should have refused"); } catch (err) { const missing = (err as UnsupportedError).missing; assert.ok(missing.length >= 5, `expected every gap named, got ${missing.length}`); assert.match(err instanceof Error ? err.message : "", /silently lacks them/); } }); test("inbound: allow is not a missing capability — only deny needs enforcing", () => { const scenario = parseScenario(`scenario: x segments: { net: { kind: public, cidr: [192.0.2.0/24] } } machines: { a: { at: { segment: net, address: [192.0.2.1] }, inbound: allow } }`); assert.doesNotThrow(() => assertSupported(scenario)); }); test("validation and raisability are different questions", () => { // The declaration model is complete; the runtime is not. A scenario may be valid and // still not raisable, and conflating the two would hide the gap. assert.doesNotThrow(() => parseScenario(withGateway), "should validate"); assert.throws(() => assertSupported(parseScenario(withGateway)), "should not raise"); });