CI/CD / preconditions (pull_request) Successful in 2s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m0s
CI/CD / test_and_check_app (pull_request) Successful in 7m2s
CI/CD / Publish immutable image (pull_request) Skipped
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m35s
222 lines
6.9 KiB
JavaScript
222 lines
6.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import {
|
|
existsSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
symlinkSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import test from 'node:test';
|
|
import { updateSource } from '../update-manifest.mjs';
|
|
import {
|
|
packageNotarizedApp,
|
|
releaseMetadata,
|
|
verifyNotarizedApp,
|
|
} from '../macos-ci.mjs';
|
|
|
|
function fixture(t) {
|
|
const directory = mkdtempSync(join(tmpdir(), 'kanbangit-ci-test-'));
|
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
|
mkdirSync(join(directory, 'src-tauri'));
|
|
const config = {
|
|
version: '1.2.3',
|
|
plugins: { updater: { pubkey: updateSource.publicKey } },
|
|
bundle: { macOS: { bundleVersion: '123' } },
|
|
productName: 'KanbanGit',
|
|
identifier: 'de.kanbangit.desktop',
|
|
};
|
|
writeFileSync(
|
|
join(directory, 'src-tauri/tauri.conf.json'),
|
|
JSON.stringify(config),
|
|
);
|
|
writeFileSync(
|
|
join(directory, 'package.json'),
|
|
JSON.stringify({ version: '1.2.3', releaseNotes: 'Release notes' }),
|
|
);
|
|
writeFileSync(
|
|
join(directory, 'src-tauri/Cargo.toml'),
|
|
'[package]\nname = "kanbangit-native"\nversion = "1.2.3"\n[dependencies]\nother = "2"\n',
|
|
);
|
|
const env = {
|
|
RELEASE_TAG: 'macos-v1.2.3',
|
|
APPLE_SIGNING_IDENTITY:
|
|
'Developer ID Application: Example (ZZX2KZGTHU)',
|
|
};
|
|
const bundle = join(
|
|
directory,
|
|
'src-tauri/target/aarch64-apple-darwin/release/bundle',
|
|
);
|
|
mkdirSync(join(bundle, 'dmg'), { recursive: true });
|
|
mkdirSync(join(bundle, 'macos'), { recursive: true });
|
|
writeFileSync(
|
|
join(bundle, 'dmg/KanbanGit_1.2.3_aarch64.dmg'),
|
|
'notarized archive',
|
|
);
|
|
writeFileSync(
|
|
join(bundle, 'macos/KanbanGit.app.tar.gz'),
|
|
'notarized updater',
|
|
);
|
|
writeFileSync(join(bundle, 'macos/KanbanGit.app.tar.gz.sig'), 'signature');
|
|
const calls = [];
|
|
const run = (command, args) => {
|
|
calls.push({ command, args });
|
|
if (command === 'codesign' && args.includes('--display')) {
|
|
return 'CodeDirectory flags=0x10000(runtime)\nCDHash=abcdef123456';
|
|
}
|
|
if (command === 'plutil') {
|
|
return '1.2.3';
|
|
}
|
|
if (command === 'lipo') {
|
|
return 'arm64';
|
|
}
|
|
if (command === 'hdiutil' && args[0] === 'attach') {
|
|
symlinkSync('/Applications', join(args.at(-1), 'Applications'));
|
|
}
|
|
return '';
|
|
};
|
|
return { directory, env, config, run, calls };
|
|
}
|
|
|
|
test('release metadata binds tag, all app versions, identity and architecture', (t) => {
|
|
const f = fixture(t);
|
|
assert.deepEqual(releaseMetadata(f.env, f.directory), {
|
|
version: '1.2.3',
|
|
notes: 'Release notes',
|
|
target: 'aarch64-apple-darwin',
|
|
arch: 'arm64',
|
|
filename: 'KanbanGit-1.2.3-aarch64-apple-darwin.dmg',
|
|
updaterFilename: 'KanbanGit-1.2.3-aarch64-apple-darwin.app.tar.gz',
|
|
});
|
|
assert.equal(
|
|
releaseMetadata(
|
|
{ ...f.env, MACOS_RELEASE_TARGET: 'x86_64-apple-darwin' },
|
|
f.directory,
|
|
).arch,
|
|
'x86_64',
|
|
);
|
|
});
|
|
|
|
for (const env of [
|
|
{ RELEASE_TAG: 'v1.2.3' },
|
|
{ RELEASE_TAG: 'macos-v1.2.4' },
|
|
{ RELEASE_TAG: 'macos-v1.2.3; echo bad' },
|
|
{ MACOS_RELEASE_TARGET: 'x86_64-unknown-linux-gnu' },
|
|
{ APPLE_SIGNING_IDENTITY: '-' },
|
|
{ APPLE_SIGNING_IDENTITY: 'Apple Development: Example (ZZX2KZGTHU)' },
|
|
{
|
|
APPLE_SIGNING_IDENTITY:
|
|
'Developer ID Application: Example (OLDTEAM123)',
|
|
},
|
|
]) {
|
|
test(`rejects invalid release inputs ${JSON.stringify(env)}`, (t) => {
|
|
const f = fixture(t);
|
|
assert.throws(() => releaseMetadata({ ...f.env, ...env }, f.directory));
|
|
});
|
|
}
|
|
|
|
for (const [file, contents] of [
|
|
['package.json', '{"version":"1.2.4"}'],
|
|
['src-tauri/Cargo.toml', '[package]\nversion = "1.2.4"'],
|
|
['src-tauri/tauri.conf.json', '{"version":"01.2.3"}'],
|
|
[
|
|
'src-tauri/tauri.conf.json',
|
|
'{"version":"1.2.3","identifier":"other","productName":"KanbanGit"}',
|
|
],
|
|
]) {
|
|
test(`rejects inconsistent metadata in ${file}: ${contents}`, (t) => {
|
|
const f = fixture(t);
|
|
writeFileSync(join(f.directory, file), contents);
|
|
assert.throws(() => releaseMetadata(f.env, f.directory));
|
|
});
|
|
}
|
|
|
|
test('packages only a verified app and checks the extracted download again', (t) => {
|
|
const f = fixture(t);
|
|
const output = packageNotarizedApp(f);
|
|
assert.equal(
|
|
readFileSync(join(output.directory, output.filename), 'utf8'),
|
|
'notarized archive',
|
|
);
|
|
assert.equal(
|
|
readFileSync(
|
|
join(output.directory, `${output.filename}.sha256`),
|
|
'utf8',
|
|
),
|
|
`${output.checksum} ${output.filename}\n`,
|
|
);
|
|
assert.equal(f.calls.filter((call) => call.command === 'spctl').length, 3);
|
|
assert.equal(f.calls.filter((call) => call.command === 'xcrun').length, 3);
|
|
assert.match(
|
|
f.calls.findLast((call) => call.command === 'spctl').args.at(-1),
|
|
/extracted\/KanbanGit\.app$/,
|
|
);
|
|
assert.throws(() => packageNotarizedApp(f), /already exists/);
|
|
});
|
|
|
|
for (const failure of [
|
|
'codesign',
|
|
'lipo',
|
|
'xcrun',
|
|
'spctl',
|
|
'hdiutil',
|
|
'tar',
|
|
]) {
|
|
test(`does not publish local output when ${failure} verification fails`, (t) => {
|
|
const f = fixture(t);
|
|
const run = f.run;
|
|
f.run = (command, args) => {
|
|
if (command === failure) {
|
|
throw new Error('verification failed');
|
|
}
|
|
return run(command, args);
|
|
};
|
|
assert.throws(() => packageNotarizedApp(f), /verification failed/);
|
|
assert.equal(
|
|
existsSync(
|
|
join(
|
|
f.directory,
|
|
'src-tauri/target/macos-release/1.2.3-aarch64-apple-darwin',
|
|
),
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
}
|
|
|
|
test('rejects an app built for a different architecture', (t) => {
|
|
const f = fixture(t);
|
|
const metadata = releaseMetadata(f.env, f.directory);
|
|
assert.throws(
|
|
() =>
|
|
verifyNotarizedApp('KanbanGit.app', metadata, (command, args) =>
|
|
command === 'lipo' ? 'x86_64' : f.run(command, args),
|
|
),
|
|
/architecture/,
|
|
);
|
|
});
|
|
|
|
test('failed validation of the extracted app discards staged download', (t) => {
|
|
const f = fixture(t);
|
|
const run = f.run;
|
|
f.run = (command, args) => {
|
|
if (command === 'spctl' && args.at(-1).includes('extracted/')) {
|
|
throw new Error('roundtrip invalid');
|
|
}
|
|
return run(command, args);
|
|
};
|
|
assert.throws(() => packageNotarizedApp(f), /roundtrip invalid/);
|
|
assert.equal(
|
|
existsSync(
|
|
join(
|
|
f.directory,
|
|
'src-tauri/target/macos-release/1.2.3-aarch64-apple-darwin',
|
|
),
|
|
),
|
|
false,
|
|
);
|
|
});
|