Files
0pt/tests/contracts/dependency-mode.contract.test.ts
T
Simon AxelssonandClaude Fable 5 c47e3b468e
CI (SIAX Cloud) / integration (push) Failing after 22s
CI (SIAX Cloud) / security (push) Successful in 22s
Security Scan (SIAX Cloud) / security (push) Successful in 21s
CI (SIAX Cloud) / quality (push) Successful in 43s
CI (SIAX Cloud) / integration (pull_request) Failing after 3s
CI (SIAX Cloud) / security (pull_request) Successful in 17s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 16s
CI (SIAX Cloud) / quality (pull_request) Successful in 43s
test(integration)+ci+docs: live OpenSearch/ClickHouse suites, CI service containers, STATUS.md (§56)
- tests/integration/: 10 tests against REAL OpenSearch 2.11 (tenant
  isolation, purpose/rights filters, generation rebuild/rollback,
  projection-disaster rebuild §68) + REAL ClickHouse 24.8 (tenant-isolated
  metric DSL, TTL schema, injection-surface refusals). Honest skip without
  env - and CI fails hard if the suites skip instead of running.
- .gitea/workflows/ci.yml: new blocking 'integration' job with
  self-identifying service containers (st0re ci.yml's measured
  alias-collision discipline: run-unique cluster.name / credentials,
  getent-enumerated IP selection, no-match = hard fail).
- STATUS.md: full 8-level Master-DoD classification per capability incl.
  the honest gaps blocking PRODUCTION_READY.
- dependency-mode: NEXT_PHASE=phase-production-build is not a serving
  runtime (next build page-data collection must compile; guard re-arms at
  boot) - pinned by contract test.
- docs/02-architecture.md: superseded the accurate-but-now-fixed 'every
  client runs against its mock' paragraph; .env.example: client URLs now
  actually read from env + new P0 backend vars.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 16:56:33 +02:00

114 lines
5.0 KiB
TypeScript

/**
* Master-DoD §3/§10: "Production kan ALDRIG tyst falla tillbaka till
* mock/in-memory — fail hard med DEPENDENCY_UNAVAILABLE."
*
* These tests drive every client factory (and the store-mode resolver) in a
* production runtime with the dependency unconfigured and assert the HARD
* throw at construction time — plus the non-production behaviour that keeps
* unit tests hermetic.
*/
import { describe, it, expect } from "vitest";
import { resolveDependencyBaseUrl, resolveStoreMode } from "@/src/platform/dependency-mode";
import { SiaxError } from "@/src/platform/errors";
import { createCl0udClient } from "@/src/clients/cl0ud-client";
import { createSt0reClient } from "@/src/clients/st0re-client";
import { createN0dClient } from "@/src/clients/n0d-client";
import { createAud0Client } from "@/src/clients/aud0-client";
const PROD = { NODE_ENV: "production" } as NodeJS.ProcessEnv;
const DEV = { NODE_ENV: "test" } as NodeJS.ProcessEnv;
describe("resolveDependencyBaseUrl (the §3 kill switch)", () => {
it("production + unconfigured => throws SiaxError DEPENDENCY_UNAVAILABLE", () => {
try {
resolveDependencyBaseUrl("st0re", undefined, PROD);
expect.unreachable("must throw");
} catch (err) {
expect(err).toBeInstanceOf(SiaxError);
expect((err as SiaxError).code).toBe("DEPENDENCY_UNAVAILABLE");
expect((err as SiaxError).source).toBe("st0re");
}
});
it("production + empty string => throws (whitespace is not a configuration)", () => {
expect(() => resolveDependencyBaseUrl("aud0", " ", PROD)).toThrowError(SiaxError);
});
it("non-production + unconfigured => undefined (mock allowed for tests/dev only)", () => {
expect(resolveDependencyBaseUrl("n0d", undefined, DEV)).toBeUndefined();
});
it("configured => normalized URL in any runtime (trailing slashes stripped)", () => {
expect(resolveDependencyBaseUrl("cl0ud", "https://x.example.com///", PROD)).toBe("https://x.example.com");
});
it("the Next BUILD phase is not a production runtime (page-data collection must compile; the guard re-arms at boot)", () => {
const BUILD = { NODE_ENV: "production", NEXT_PHASE: "phase-production-build" } as NodeJS.ProcessEnv;
expect(resolveDependencyBaseUrl("st0re", undefined, BUILD)).toBeUndefined();
// Any other phase value stays armed.
const SERVE = { NODE_ENV: "production", NEXT_PHASE: "phase-production-server" } as NodeJS.ProcessEnv;
expect(() => resolveDependencyBaseUrl("st0re", undefined, SERVE)).toThrowError(SiaxError);
});
});
describe("resolveStoreMode (§3 for stateful registries)", () => {
it("production + no database => throws DEPENDENCY_UNAVAILABLE", () => {
expect(() => resolveStoreMode("0pt.source_registry", undefined, PROD)).toThrowError(SiaxError);
});
it("non-production + no database => explicit memory mode", () => {
expect(resolveStoreMode("0pt.source_registry", undefined, DEV)).toEqual({ mode: "memory" });
});
it("database configured => postgres mode", () => {
expect(resolveStoreMode("x", "postgres://u@h/db", PROD)).toEqual({ mode: "postgres", url: "postgres://u@h/db" });
});
});
describe("client factories under NODE_ENV=production without a base URL", () => {
// The factories read process.env.NODE_ENV via isProductionRuntime; drive it
// for real, restoring afterwards.
function inProduction<T>(fn: () => T): T {
const prev = process.env.NODE_ENV;
const prevUrls = {
CL0UD_URL: process.env.CL0UD_URL,
ST0RE_URL: process.env.ST0RE_URL,
N0D_URL: process.env.N0D_URL,
AUD0_URL: process.env.AUD0_URL,
};
delete process.env.CL0UD_URL;
delete process.env.ST0RE_URL;
delete process.env.N0D_URL;
delete process.env.AUD0_URL;
(process.env as Record<string, string>).NODE_ENV = "production";
try {
return fn();
} finally {
if (prev === undefined) delete (process.env as Record<string, string | undefined>).NODE_ENV;
else (process.env as Record<string, string>).NODE_ENV = prev;
for (const [k, v] of Object.entries(prevUrls)) {
if (v === undefined) delete process.env[k];
else process.env[k] = v;
}
}
}
it("createCl0udClient() fails hard (policy may NEVER silently allow via local pass-through)", () => {
inProduction(() => expect(() => createCl0udClient()).toThrowError(SiaxError));
});
it("createSt0reClient() fails hard", () => {
inProduction(() => expect(() => createSt0reClient()).toThrowError(SiaxError));
});
it("createN0dClient() fails hard", () => {
inProduction(() => expect(() => createN0dClient()).toThrowError(SiaxError));
});
it("createAud0Client() fails hard", () => {
inProduction(() => expect(() => createAud0Client()).toThrowError(SiaxError));
});
it("all four factories still return local implementations in a test runtime", () => {
expect(createCl0udClient()).toBeTruthy();
expect(createSt0reClient()).toBeTruthy();
expect(createN0dClient()).toBeTruthy();
expect(createAud0Client()).toBeTruthy();
});
});