import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import test from 'node:test'; const workflow = fs.readFileSync(new URL('../.gitea/workflows/build-and-deploy.yaml', import.meta.url), 'utf8'); const commit = 'a'.repeat(40); function script(name, attribute = 'run') { const lines = workflow.split('\n'); const start = lines.findIndex(line => line.trim() === `- name: ${name}`); assert.notEqual(start, -1); const offset = lines.findIndex((line, index) => index > start && line.trim() === `${attribute}: |-`); assert.notEqual(offset, -1); const indent = lines[offset].search(/\S/) + 2; const result = []; for (const line of lines.slice(offset + 1)) { if (line.trim() && line.search(/\S/) < indent) break; result.push(line.slice(indent)); } return result.join('\n'); } function fixture(t) { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'wegwichtel-deploy-')); t.after(() => fs.rmSync(directory, { recursive: true, force: true })); const output = path.join(directory, 'output'); const env = { ...process.env, EXPECTED_COMMIT: commit, WORKFLOW_RUN: '1828', WORKFLOW_ATTEMPT: '1', RUN_EVENT: 'push', SELECTED_JOB: '', GITHUB_OUTPUT: output }; function run(source, extra = {}) { return spawnSync('sh', ['-s'], { input: source, cwd: directory, env: { ...env, ...extra }, encoding: 'utf8' }); } function proof(value = `${commit}:1828:1`) { fs.mkdirSync(path.join(directory, 'deployment-approval'), { recursive: true }); fs.writeFileSync(path.join(directory, 'deployment-approval', 'quality-gate-passed.txt'), value); } return { directory, output, env, run, proof }; } test('a deployment approval is written only after the Quality Gate step succeeded', t => { const h = fixture(t), source = script('Write deployment approval'); for (const outcome of ['', 'failure', 'skipped', 'cancelled']) { assert.notEqual(h.run(source, { GATE_OUTCOME: outcome }).status, 0); assert.equal(fs.existsSync(path.join(h.directory, 'quality-gate-passed.txt')), false); } assert.equal(h.run(source, { GATE_OUTCOME: 'success' }).status, 0); assert.equal(fs.readFileSync(path.join(h.directory, 'quality-gate-passed.txt'), 'utf8'), `${commit}:1828:1\n`); }); test('push deployments reject missing, mismatching and previous-attempt approval artifacts', t => { const h = fixture(t), source = script('Verify deployment approval'); assert.notEqual(h.run(source).status, 0); for (const proof of ['wrong', `${'b'.repeat(40)}:1828:1`, `${commit}:1827:1`, `${commit}:1828:0`]) { h.proof(proof); assert.notEqual(h.run(source).status, 0); assert.equal(fs.existsSync(h.output), false); } h.proof(); assert.equal(h.run(source).status, 0); assert.equal(fs.readFileSync(h.output, 'utf8'), `commit=${commit}\n`); }); test('only an explicit manual deploy can use the existing manual override', t => { const h = fixture(t), source = script('Verify deployment approval'); assert.notEqual(h.run(source, { RUN_EVENT: 'push', SELECTED_JOB: 'deploy' }).status, 0); assert.notEqual(h.run(source, { RUN_EVENT: 'workflow_dispatch', SELECTED_JOB: 'all' }).status, 0); assert.equal(h.run(source, { RUN_EVENT: 'workflow_dispatch', SELECTED_JOB: 'deploy' }).status, 0); assert.equal(fs.readFileSync(h.output, 'utf8'), `commit=${commit}\n`); }); test('the remote script refuses missing approval before git access and restarts only the approved commit', t => { const h = fixture(t), source = script('Deploy Wegwichtel', 'script'); const log = path.join(h.directory, 'calls'); const stubs = String.raw` cd() { return 0; } git() { printf 'git %s\n' "$*" >> "$TASK_LOG" if [ "$1" = merge ] && [ "$MERGE_FAILS" = yes ]; then return 1; fi if [ "$1" = rev-parse ]; then printf '%s\n' "$ACTUAL_COMMIT"; fi } systemctl() { printf 'systemctl %s\n' "$*" >> "$TASK_LOG"; } `; const invoke = (approval, actual = commit, mergeFails = 'no') => h.run(stubs + source, { DEPLOY_APPROVED_COMMIT: approval, ACTUAL_COMMIT: actual, TASK_LOG: log, MERGE_FAILS: mergeFails }); assert.notEqual(invoke('').status, 0); assert.equal(fs.existsSync(log), false); assert.notEqual(invoke('b'.repeat(40)).status, 0); assert.equal(fs.existsSync(log), false); assert.notEqual(invoke(commit, 'b'.repeat(40)).status, 0); assert.doesNotMatch(fs.readFileSync(log, 'utf8'), /systemctl/); fs.rmSync(log); assert.notEqual(invoke(commit, commit, 'yes').status, 0); assert.doesNotMatch(fs.readFileSync(log, 'utf8'), /systemctl/); fs.rmSync(log); assert.equal(invoke(commit).status, 0); const calls = fs.readFileSync(log, 'utf8'); assert.match(calls, new RegExp(`git merge --ff-only ${commit}`)); assert.match(calls, /systemctl --user restart wegwichtel.service/); });