forked from heavy-duty/stoke
114 lines
4.7 KiB
JavaScript
114 lines
4.7 KiB
JavaScript
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-release.sh');
|
|
const TOKEN = 'release-token-that-must-not-enter-argv';
|
|
|
|
function runScenario({ viewStatus = 0, changelog = '## 2.0.0\n\n### Added\n\n- New release flow.\n' } = {}) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-release-test-'));
|
|
try {
|
|
const runnerTemp = path.join(dir, 'runner-temp');
|
|
const log = path.join(dir, 'calls.jsonl');
|
|
const stub = path.join(dir, 'stoke-stub.js');
|
|
const deb = path.join(dir, 'stoke_2.0.0_all.deb');
|
|
fs.mkdirSync(runnerTemp);
|
|
fs.writeFileSync(path.join(dir, 'CHANGELOG.md'), changelog);
|
|
fs.writeFileSync(deb, 'package');
|
|
fs.writeFileSync(stub, `#!/usr/bin/env node
|
|
const fs = require('node:fs');
|
|
const args = process.argv.slice(2);
|
|
const tokenIndex = args.indexOf('--token-file');
|
|
const configIndex = args.indexOf('--config');
|
|
const record = { args };
|
|
if (tokenIndex !== -1) {
|
|
const tokenFile = args[tokenIndex + 1];
|
|
record.tokenFile = tokenFile;
|
|
record.token = fs.readFileSync(tokenFile, 'utf8');
|
|
record.tokenMode = fs.statSync(tokenFile).mode & 0o777;
|
|
}
|
|
if (configIndex !== -1) record.config = args[configIndex + 1];
|
|
fs.appendFileSync(process.env.STOKE_CALL_LOG, JSON.stringify(record) + '\\n');
|
|
if (args.includes('release') && args.includes('view')) process.exit(Number(process.env.VIEW_STATUS));
|
|
`);
|
|
fs.chmodSync(stub, 0o755);
|
|
|
|
const result = spawnSync('bash', [SCRIPT, 'v2.0.0', '2.0.0', deb, 'heavy-duty', 'stoke'], {
|
|
cwd: dir,
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
RELEASE_TOKEN: TOKEN,
|
|
GITHUB_SERVER_URL: 'https://forge.example.test',
|
|
RUNNER_TEMP: runnerTemp,
|
|
STOKE: stub,
|
|
STOKE_CALL_LOG: log,
|
|
VIEW_STATUS: String(viewStatus),
|
|
},
|
|
});
|
|
const calls = fs.existsSync(log)
|
|
? fs.readFileSync(log, 'utf8').trim().split('\n').filter(Boolean).map(JSON.parse)
|
|
: [];
|
|
return { result, calls, runnerTemp };
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
}
|
|
|
|
function command(call) {
|
|
const index = call.args.indexOf('release');
|
|
return index === -1 ? '' : call.args[index + 1];
|
|
}
|
|
|
|
test('existing release uploads the asset without creating another release', () => {
|
|
const scenario = runScenario({ viewStatus: 0 });
|
|
|
|
assert.equal(scenario.result.status, 0, scenario.result.stderr);
|
|
assert.deepEqual(scenario.calls.map(command).filter(Boolean), ['view', 'upload']);
|
|
assert.equal(scenario.calls.some((call) => command(call) === 'create'), false);
|
|
const upload = scenario.calls.find((call) => command(call) === 'upload');
|
|
assert.ok(upload.args.includes('--tag'));
|
|
assert.ok(upload.args.includes('v2.0.0'));
|
|
assert.ok(upload.args.includes('--asset'));
|
|
assert.ok(upload.args.some((arg) => arg.endsWith('stoke_2.0.0_all.deb')));
|
|
});
|
|
|
|
test('missing release creates it with changelog notes and the asset', () => {
|
|
const scenario = runScenario({ viewStatus: 1 });
|
|
|
|
assert.equal(scenario.result.status, 0, scenario.result.stderr);
|
|
assert.deepEqual(scenario.calls.map(command).filter(Boolean), ['view', 'create']);
|
|
const create = scenario.calls.find((call) => command(call) === 'create');
|
|
assert.ok(create.args.includes('--title'));
|
|
assert.ok(create.args.includes('v2.0.0'));
|
|
assert.ok(create.args.includes('--body-file'));
|
|
assert.ok(create.args.includes('--asset'));
|
|
});
|
|
|
|
test('authentication uses a 0600 token file and never puts the token in argv', () => {
|
|
const scenario = runScenario();
|
|
|
|
assert.equal(scenario.result.status, 0, scenario.result.stderr);
|
|
const auth = scenario.calls[0];
|
|
assert.ok(auth.args.includes('auth'));
|
|
assert.ok(auth.args.includes('login'));
|
|
assert.ok(auth.args.includes('--token-file'));
|
|
assert.equal(auth.token, TOKEN);
|
|
assert.equal(auth.tokenMode, 0o600);
|
|
assert.equal(auth.args.includes('https://forge.example.test'), true);
|
|
assert.equal(scenario.calls.every((call) => call.args.every((arg) => !arg.includes(TOKEN))), true);
|
|
assert.equal(scenario.calls.every((call) => call.config === auth.config), true);
|
|
assert.equal(fs.existsSync(auth.tokenFile), false, 'temporary credential file must be removed');
|
|
});
|
|
|
|
test('missing changelog section aborts before any release command', () => {
|
|
const scenario = runScenario({ changelog: '## 1.0.0\n\n- Old release.\n' });
|
|
|
|
assert.equal(scenario.result.status, 1);
|
|
assert.match(scenario.result.stderr, /no section for '2\.0\.0'/);
|
|
assert.deepEqual(scenario.calls.map(command).filter(Boolean), []);
|
|
});
|