import { afterEach, expect, test } from "bun:test"; import { mkdtempSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { rootPath } from "../../src/config"; import { exportCicdOtelPayload, loadCicdOtelRuntimeConfig, selectCicdOtelRepository } from "./otel-runtime-config"; const roots: string[] = []; afterEach(() => { while (roots.length > 0) rmSync(roots.pop()!, { recursive: true, force: true }); }); test("unexpanded Repository inputs fall back to materialized owning YAML", () => { const config = loadCicdOtelRuntimeConfig("sentinel-nc01-v03", { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "{{otel_traces_endpoint}}", OTEL_SERVICE_NAME: "{{otel_service_name}}", OTEL_TRACES_SAMPLER_ARG: "{{otel_sampling_ratio}}", OTEL_EXPORTER_TIMEOUT_MS: "{{otel_exporter_timeout_ms}}", }); expect(config).toMatchObject({ endpoint: "http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces", endpointHostname: "otel-collector.platform-infra.svc.cluster.local", serviceName: "unidesk-cicd-sentinel-nc01", samplingRatio: 1, timeoutMs: 300, }); expect(config.fallbackCodes).toEqual([ "INVALID_ENDPOINT_INPUT", "INVALID_SERVICE_INPUT", "INVALID_SAMPLING_INPUT", "INVALID_TIMEOUT_INPUT", ]); }); test("valid but mismatched runtime inputs cannot override owning YAML", () => { const config = loadCicdOtelRuntimeConfig("sentinel-nc01-v03", { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://collector.example.invalid:4318/v1/traces", OTEL_SERVICE_NAME: "different-service", OTEL_TRACES_SAMPLER_ARG: "0.5", OTEL_EXPORTER_TIMEOUT_MS: "600", }); expect(config.endpointHostname).toBe("otel-collector.platform-infra.svc.cluster.local"); expect(config.serviceName).toBe("unidesk-cicd-sentinel-nc01"); expect(config.samplingRatio).toBe(1); expect(config.timeoutMs).toBe(300); expect(config.fallbackCodes).toEqual([ "MISMATCH_ENDPOINT_INPUT", "MISMATCH_SERVICE_INPUT", "MISMATCH_SAMPLING_INPUT", "MISMATCH_TIMEOUT_INPUT", ]); }); test("legal owning endpoint and non-blocking exporter failure share the bounded helper", async () => { const config = loadCicdOtelRuntimeConfig("sentinel-nc01-v03", { OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces", OTEL_SERVICE_NAME: "unidesk-cicd-sentinel-nc01", OTEL_TRACES_SAMPLER_ARG: "1", OTEL_EXPORTER_TIMEOUT_MS: "300", }); expect(config.fallbackCodes).toEqual([]); const warning = await exportCicdOtelPayload( config, "00-11111111111111111111111111111111-2222222222222222-01", "11111111111111111111111111111111", { resourceSpans: [] }, async () => { throw Object.assign(new TypeError("fetch failed"), { cause: { code: "ENOTFOUND" } }); }, ); expect(warning).toEqual({ event: "cicd.otel.export", warning: true, blocking: false, causeCode: "ENOTFOUND", endpointHostname: "otel-collector.platform-infra.svc.cluster.local", timedOut: false, errorType: "TypeError", timeoutMs: 300, traceId: "11111111111111111111111111111111", valuesRedacted: true, }); }); test("repository identity selection fails closed on zero or multiple exact matches", () => { expect(() => selectCicdOtelRepository([], "sentinel-nc01-v03")).toThrow("matched 0"); expect(() => selectCicdOtelRepository([ { id: "sentinel-nc01-v03", params: {} }, { id: "sentinel-nc01-v03", params: {} }, ], "sentinel-nc01-v03")).toThrow("matched 2"); }); test("actual renderer replaces unexpanded inputs without changing business exit", async () => { const result = await render({ OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "{{otel_traces_endpoint}}" }, false); expect(result.exitCode).toBe(0); expect(result.stderr).toContain('"event":"cicd.otel.config"'); expect(result.stderr).toContain('"blocking":false'); expect(result.stderr).toContain('"endpointHostname":"otel-collector.platform-infra.svc.cluster.local"'); expect(result.stderr).not.toContain("{{otel_traces_endpoint}}"); expect(readFileSync(join(result.outputDir, "source.sh"), "utf8")).toContain("http://otel-collector.platform-infra.svc.cluster.local:4318/v1/traces"); const publish = readFileSync(join(result.outputDir, "publish.sh"), "utf8"); expect(publish).toContain("UNIDESK_PAC_CONSUMER='sentinel-nc01-v03'"); expect(publish).toContain("feature-config-schema-validation"); expect(publish).toContain("cicd.feature_config.schema"); expect(publish).toContain("OTEL_EXPORTER_TIMEOUT_MS='300'"); expect(publish).toContain("feature-config-validator-process-failure"); }); async function render(env: Record, traced = true) { const outputDir = mkdtempSync(join(tmpdir(), "unidesk-render-sentinel-publish-")); roots.push(outputDir); const child = Bun.spawn({ cmd: [ process.execPath, "scripts/native/cicd/render-sentinel-publish-task.ts", "--node", "NC01", "--lane", "v03", "--repository-id", "sentinel-nc01-v03", "--sentinel", "nc01-web-probe-sentinel", "--pipeline-run", "hwlab-web-probe-sentinel-nc01-test", "--source-commit", "a".repeat(40), "--source-stage-ref", `refs/unidesk/snapshots/test/${"a".repeat(40)}`, "--source-authority", "gitea-snapshot", "--output-dir", outputDir, ], cwd: rootPath(), env: { ...process.env, TRACEPARENT: traced ? "00-11111111111111111111111111111111-2222222222222222-01" : "", OTEL_SERVICE_NAME: "unidesk-cicd-test", OTEL_TRACES_SAMPLER_ARG: "1", OTEL_EXPORTER_TIMEOUT_MS: "300", ...env, }, stdout: "pipe", stderr: "pipe", }); const [exitCode, stdout, stderr] = await Promise.all([ child.exited, new Response(child.stdout).text(), new Response(child.stderr).text(), ]); return { exitCode, stdout, stderr, outputDir, }; }