import assert from "node:assert/strict"; import test from "node:test"; import { createServerJobOutputReceipt, redactServerJobOutput, SERVER_JOB_OUTPUT_LIMITS } from "../../../tools/web/server-job-output.mjs"; test("M13-04F redacts credentials and filesystem paths before publishing output", () => { const receipt = createServerJobOutputReceipt({ stdout: 'authorization: Bearer abc123 token="secret-value" source=/home/user/private.blend', stderr: "failed at C:\\Users\\alice\\job\\source.blend file:///tmp/internal.log", }); assert.equal(receipt.execution, "DISABLED"); assert.ok(receipt.totalRedactions >= 5); assert.doesNotMatch(receipt.stdout.text, /abc123|secret-value|\/home\/user|C:\\Users|file:\/\//u); assert.doesNotMatch(receipt.stderr.text, /alice|internal\.log/u); assert.match(receipt.stdout.text, //u); assert.match(receipt.stderr.text, //u); }); test("M13-04F truncates at UTF-8 byte boundaries and reports the source size", () => { const value = "模型".repeat(100); const receipt = createServerJobOutputReceipt({ stdout: value, stderr: "" }, { ...SERVER_JOB_OUTPUT_LIMITS, stdoutBytes: 32, stderrBytes: 32, totalBytes: 64 }); assert.equal(receipt.stdout.truncated, true); assert.ok(receipt.stdout.emittedBytes <= 32); assert.equal(Buffer.from(receipt.stdout.text, "utf8").toString("utf8"), receipt.stdout.text); assert.equal(receipt.stdout.originalBytes, Buffer.byteLength(value, "utf8")); assert.match(receipt.stdout.text, /$/u); }); test("M13-04F rejects invalid stream budgets and leaves empty streams explicit", () => { assert.deepEqual(createServerJobOutputReceipt({ stdout: "", stderr: "" }).stdout, { text: "", originalBytes: 0, emittedBytes: 0, redactionCount: 0, truncated: false, }); assert.throws(() => createServerJobOutputReceipt({ stdout: "x", stderr: "" }, { stdoutBytes: 10, stderrBytes: 10, totalBytes: 10 }), /SERVER_JOB_OUTPUT_INVALID/); assert.throws(() => redactServerJobOutput(42, 10), /SERVER_JOB_OUTPUT_INVALID/); });