Files
KanbanGit/native/tests/macos-workflow.test.js
Twyco 4ee047311f
CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m5s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m32s
CI/CD / test_and_check_app (pull_request) Successful in 6m53s
CI/CD / Publish immutable image (pull_request) Skipped
fix(KAN-65): allow rebuilding unpublished macOS releases
2026-09-28 07:51:56 +02:00

209 lines
7.2 KiB
JavaScript

import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import {
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import process from 'node:process';
import test from 'node:test';
const workflow = readFileSync(
new URL('../../.github/workflows/macos-release.yml', import.meta.url),
'utf8',
);
const selectRef = workflow
.split(
' - name: Select an existing version tag contained in main\n',
)[1]
.split('\n - name:')[0]
.split(' run: |\n')[1]
.split('\n')
.map((line) => line.replace(/^ {10}/, ''))
.join('\n');
function fixture(t) {
const directory = mkdtempSync(join(tmpdir(), 'kanbangit-release-ref-'));
t.after(() => rmSync(directory, { recursive: true, force: true }));
const remote = join(directory, 'remote');
const checkout = join(directory, 'checkout');
mkdirSync(remote);
const env = {
...process.env,
GIT_AUTHOR_NAME: 'Release test',
GIT_COMMITTER_NAME: 'Release test',
GIT_AUTHOR_EMAIL: 'release@example.invalid',
GIT_COMMITTER_EMAIL: 'release@example.invalid',
GIT_CONFIG_NOSYSTEM: '1',
GIT_CONFIG_GLOBAL: '/dev/null',
};
const git = (cwd, ...args) => {
const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' });
assert.equal(result.status, 0, result.stderr);
return result.stdout.trim();
};
git(remote, 'init', '-b', 'main');
writeFileSync(join(remote, 'app.txt'), 'release');
git(remote, 'add', 'app.txt');
git(remote, '-c', 'commit.gpgsign=false', 'commit', '-m', 'Release');
const commit = git(remote, 'rev-parse', 'HEAD');
git(remote, 'tag', 'macos-v1.2.3');
git(directory, 'clone', remote, checkout);
const output = join(directory, 'outputs');
const run = (overrides = {}) => {
writeFileSync(output, '');
const result = spawnSync(
'bash',
['--noprofile', '--norc', '-c', selectRef],
{
cwd: checkout,
encoding: 'utf8',
env: {
...env,
GITHUB_EVENT_NAME: 'push',
GITHUB_REF: 'refs/tags/macos-v1.2.3',
REQUESTED_TAG: '',
MACOS_RUNNER: 'macos-local',
GITHUB_OUTPUT: output,
...overrides,
},
},
);
return { ...result, output: readFileSync(output, 'utf8') };
};
return { run, git, checkout, remote, commit };
}
test('release selection succeeds using Git alone without an administrative API token', (t) => {
const f = fixture(t);
const result = f.run({
GITEA_TOKEN: '',
GITHUB_TOKEN: '',
GITHUB_API_URL: 'http://127.0.0.1:1',
});
assert.equal(result.status, 0, result.stderr);
assert.equal(result.output, `commit=${f.commit}\ntag=macos-v1.2.3\n`);
assert.equal(f.git(f.checkout, 'rev-parse', 'HEAD'), f.commit);
});
test('macOS scheduling uses a repository variable while release steps remain gated by preparation', () => {
const release = workflow.split('\n release:\n')[1];
const runner = release.match(/^ {4}runs-on: (.+)$/m)?.[1];
assert.equal(runner, '${{ vars.MACOS_RUNNER }}');
assert.match(release, /^ {4}needs: prepare$/m);
assert.match(release, /ref: \$\{\{ needs\.prepare\.outputs\.commit \}\}/);
assert.match(
release,
/RELEASE_TAG: \$\{\{ needs\.prepare\.outputs\.tag \}\}/,
);
});
test('manual release on main accepts an existing tag without moving it', (t) => {
const f = fixture(t);
const result = f.run({
GITHUB_EVENT_NAME: 'workflow_dispatch',
GITHUB_REF: 'refs/heads/main',
REQUESTED_TAG: 'macos-v1.2.3',
});
assert.equal(result.status, 0, result.stderr);
assert.equal(f.git(f.checkout, 'rev-parse', 'macos-v1.2.3'), f.commit);
assert.doesNotMatch(workflow, /inputs\.mode|RELEASE_MODE|\n {2}resume:/);
assert.match(workflow, /--ci --target .* --bundles app,dmg/);
assert.match(workflow, /node native\/macos-ci.mjs package/);
assert.match(workflow, /node native\/publish-macos.mjs upload/);
});
test('a moved macOS tag selects the new main commit for a full rebuild', (t) => {
const f = fixture(t);
f.git(
f.remote,
'-c',
'commit.gpgsign=false',
'commit',
'--allow-empty',
'-m',
'Fix failed release',
);
const rebuilt = f.git(f.remote, 'rev-parse', 'HEAD');
f.git(f.remote, 'tag', '--force', 'macos-v1.2.3');
f.git(
f.checkout,
'fetch',
'--force',
'origin',
'refs/tags/macos-v1.2.3:refs/tags/macos-v1.2.3',
);
const result = f.run();
assert.equal(result.status, 0, result.stderr);
assert.equal(result.output, `commit=${rebuilt}\ntag=macos-v1.2.3\n`);
assert.notEqual(rebuilt, f.commit);
assert.match(workflow, /tags:\n\s+- "macos-v\*"/);
assert.match(workflow, /^ {2}group: macos-release$/m);
assert.match(workflow, /^ {2}cancel-in-progress: false$/m);
});
test('release DMG packaging disables Finder automation in the Tauri action environment', () => {
const build = workflow
.split(' - name: Build, sign and notarize with Tauri\n')[1]
.split('\n - name:')[0];
const environment = build
.split(' env:\n')[1]
.split(' with:\n')[0];
assert.match(environment, /^ {10}CI: "true"$/m);
assert.match(environment, /^ {10}TAURI_BUNDLER_DMG_IGNORE_CI: "false"$/m);
assert.match(build, /--bundles app,dmg/);
assert.match(build, /"createUpdaterArtifacts":true/);
});
for (const [name, overrides] of [
['wrong tag case', { GITHUB_REF: 'refs/tags/macOS-V1.2.3' }],
['web release tag', { GITHUB_REF: 'refs/tags/v1.2.3' }],
['missing tag', { GITHUB_REF: 'refs/tags/macos-v9.9.9' }],
[
'shell characters in tag',
{ GITHUB_REF: 'refs/tags/macos-v1.2.3;exit 0' },
],
['missing runner label', { MACOS_RUNNER: '' }],
[
'output injection in runner label',
{ MACOS_RUNNER: 'macos-local\ncommit=other' },
],
[
'manual release outside main',
{
GITHUB_EVENT_NAME: 'workflow_dispatch',
GITHUB_REF: 'refs/heads/feature',
REQUESTED_TAG: 'macos-v1.2.3',
},
],
]) {
test(`release selection rejects ${name} before producing job outputs`, (t) => {
const result = fixture(t).run(overrides);
assert.notEqual(result.status, 0);
assert.equal(result.output, '');
});
}
test('release selection rejects a tag whose commit is outside main', (t) => {
const f = fixture(t);
f.git(f.checkout, 'switch', '-c', 'unmerged');
f.git(
f.checkout,
'-c',
'commit.gpgsign=false',
'commit',
'--allow-empty',
'-m',
'Unmerged',
);
f.git(f.checkout, 'tag', 'macos-v1.2.4');
const result = f.run({ GITHUB_REF: 'refs/tags/macos-v1.2.4' });
assert.notEqual(result.status, 0);
assert.match(result.stderr, /Release tag must belong to main/);
assert.equal(result.output, '');
});