const { test } = require('node:test'); const assert = require('node:assert/strict'); const { spawnSync } = require('node:child_process'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const ROOT = path.join(__dirname, '..'); const SCRIPT = path.join(ROOT, 'scripts', 'publish-deb.sh'); const TOKEN = 'deb-token-that-must-not-enter-argv'; const UPLOAD_LINE = 'Uploading stoke_2.0.0_all.deb to https://forge.example.test/api/packages/heavy-duty/debian/pool/stable/main/upload\n'; function runScenario({ token = '', httpStatus = 201, responseBody = '', curlStatus = 0 } = {}) { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-publish-deb-test-')); const home = path.join(dir, 'home'); const bin = path.join(dir, 'bin'); const runnerTemp = path.join(dir, 'runner-temp'); const log = path.join(dir, 'curl.json'); const deb = path.join(dir, 'stoke_2.0.0_all.deb'); const legacyBefore = new Set(fs.readdirSync(os.tmpdir()).filter((name) => name.startsWith('stoke-publish-response.'))); let call = null; let result; try { fs.mkdirSync(home); fs.mkdirSync(bin); fs.mkdirSync(runnerTemp); fs.writeFileSync(deb, 'package'); fs.writeFileSync(path.join(bin, 'curl'), `#!/usr/bin/env node const fs = require('node:fs'); const path = require('node:path'); const args = process.argv.slice(2); const headerArg = args[args.indexOf('-H') + 1]; const headerFile = headerArg && headerArg.startsWith('@') ? headerArg.slice(1) : null; const responseFile = args[args.indexOf('-o') + 1]; const record = { args, headerFile, responseFile }; if (headerFile) { record.header = fs.readFileSync(headerFile, 'utf8'); record.headerMode = fs.statSync(headerFile).mode & 0o777; record.tempDir = path.dirname(headerFile); } fs.writeFileSync(responseFile, process.env.CURL_RESPONSE_BODY); fs.writeFileSync(process.env.CURL_CALL_LOG, JSON.stringify(record)); if (Number(process.env.CURL_STATUS)) process.exit(Number(process.env.CURL_STATUS)); process.stdout.write(process.env.CURL_HTTP_STATUS); `); fs.chmodSync(path.join(bin, 'curl'), 0o755); result = spawnSync('bash', [SCRIPT, deb], { encoding: 'utf8', env: { HOME: home, PATH: `${bin}:${process.env.PATH}`, RUNNER_TEMP: runnerTemp, STOKE_CONFIG_FILE: path.join(dir, 'missing-config.json'), STOKE_TOKEN: token, FORGE_URL: 'https://forge.example.test', CURL_CALL_LOG: log, CURL_HTTP_STATUS: String(httpStatus), CURL_RESPONSE_BODY: responseBody, CURL_STATUS: String(curlStatus), }, }); call = fs.existsSync(log) ? JSON.parse(fs.readFileSync(log, 'utf8')) : null; const remainingTempEntries = fs.readdirSync(runnerTemp); const legacyAfter = fs.readdirSync(os.tmpdir()).filter( (name) => name.startsWith('stoke-publish-response.') && !legacyBefore.has(name), ); return { result, call, runnerTemp, remainingTempEntries, legacyAfter, headerExistsAfter: call?.headerFile ? fs.existsSync(call.headerFile) : false, responseExistsAfter: call?.responseFile ? fs.existsSync(call.responseFile) : false, }; } finally { if (call?.responseFile && !call.responseFile.startsWith(`${dir}${path.sep}`)) { fs.rmSync(call.responseFile, { force: true }); } fs.rmSync(dir, { recursive: true, force: true }); } } function assertCleaned(scenario) { assert.deepEqual(scenario.remainingTempEntries, []); assert.deepEqual(scenario.legacyAfter, []); assert.equal(scenario.headerExistsAfter, false); assert.equal(scenario.responseExistsAfter, false); } test('empty token identifies the CI secret before offering the local remedy', () => { const scenario = runScenario(); assert.equal(scenario.result.status, 1); assert.equal(scenario.result.stdout, ''); assert.match(scenario.result.stderr, /^error: no token\./); assert.match(scenario.result.stderr, /STOKE_TOKEN/); assert.match(scenario.result.stderr, /RELEASE_TOKEN/); assert.match(scenario.result.stderr, /empty value.*secret/is); assert.ok(scenario.result.stderr.indexOf('RELEASE_TOKEN') < scenario.result.stderr.indexOf('stoke auth login')); assert.equal(scenario.call, null); assertCleaned(scenario); }); test('curl reads a private authorization header file without receiving the token in argv', () => { const scenario = runScenario({ token: TOKEN }); assert.equal(scenario.result.status, 0, scenario.result.stderr); assert.ok(scenario.call.args.includes('-H')); assert.equal(scenario.call.args.every((arg) => !arg.includes(TOKEN)), true); assert.equal(path.dirname(scenario.call.tempDir), scenario.runnerTemp); assert.ok(scenario.call.headerFile.startsWith(`${scenario.call.tempDir}${path.sep}`)); assert.equal(scenario.call.header, `Authorization: token ${TOKEN}\n`); assert.equal(scenario.call.headerMode, 0o600); assert.equal(path.dirname(scenario.call.responseFile), scenario.call.tempDir); assert.doesNotMatch(scenario.result.stdout, new RegExp(TOKEN)); assert.doesNotMatch(scenario.result.stderr, new RegExp(TOKEN)); assertCleaned(scenario); }); test('201 response preserves the success transcript and removes temporary files', () => { const scenario = runScenario({ token: TOKEN, httpStatus: 201 }); assert.equal(scenario.result.status, 0, scenario.result.stderr); assert.equal(scenario.result.stdout, `${UPLOAD_LINE}Published.\n`); assert.equal(scenario.result.stderr, ''); assertCleaned(scenario); }); test('409 response preserves the already-published transcript and removes temporary files', () => { const scenario = runScenario({ token: TOKEN, httpStatus: 409 }); assert.equal(scenario.result.status, 0, scenario.result.stderr); assert.equal(scenario.result.stdout, `${UPLOAD_LINE}Already published (409): this exact version already exists in the registry.\n`); assert.equal(scenario.result.stderr, ''); assertCleaned(scenario); }); test('HTTP failure preserves the response body on stderr and removes temporary files', () => { const scenario = runScenario({ token: TOKEN, httpStatus: 500, responseBody: 'registry rejected\n' }); assert.equal(scenario.result.status, 1); assert.equal(scenario.result.stdout, UPLOAD_LINE); assert.equal(scenario.result.stderr, 'error: upload failed with HTTP 500\nregistry rejected\n'); assertCleaned(scenario); }); test('curl failure propagates its status and still removes temporary files', () => { const scenario = runScenario({ token: TOKEN, curlStatus: 7, responseBody: 'transport failed\n' }); assert.equal(scenario.result.status, 7); assert.equal(scenario.result.stdout, UPLOAD_LINE); assert.equal(scenario.result.stderr, ''); assertCleaned(scenario); });