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
123 lines
3.5 KiB
JavaScript
123 lines
3.5 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { Buffer } from 'node:buffer';
|
|
import { signedFixture } from './updater-fixture.mjs';
|
|
import test from 'node:test';
|
|
import {
|
|
compareVersions,
|
|
updateManifest,
|
|
verifyUpdateSignature,
|
|
} from '../update-manifest.mjs';
|
|
import { updatePresentation } from '../ui/updates-state.js';
|
|
|
|
test('compares stable versions numerically and rejects prerelease or malformed versions', () => {
|
|
assert.equal(compareVersions('1.10.0', '1.9.9'), 1);
|
|
assert.equal(compareVersions('1.0.0', '2.0.0'), -1);
|
|
assert.equal(compareVersions('1.0.0', '1.0.0'), 0);
|
|
for (const invalid of [
|
|
'v1.0.0',
|
|
'1.0.0-beta',
|
|
'01.0.0',
|
|
'1.0.0+2',
|
|
'',
|
|
'1.2',
|
|
]) {
|
|
assert.throws(() => compareVersions(invalid, '1.0.0'));
|
|
}
|
|
});
|
|
test('verifies minisign archive and trusted comment with the permanent key', () => {
|
|
const bytes = Buffer.from('signed app');
|
|
const fixture = signedFixture(bytes);
|
|
assert.doesNotThrow(() =>
|
|
verifyUpdateSignature(bytes, fixture.signature, fixture.publicKey),
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
verifyUpdateSignature(
|
|
Buffer.from('tampered app'),
|
|
fixture.signature,
|
|
fixture.publicKey,
|
|
),
|
|
/signature/,
|
|
);
|
|
assert.throws(
|
|
() =>
|
|
verifyUpdateSignature(
|
|
bytes,
|
|
fixture.signature,
|
|
signedFixture(bytes).publicKey,
|
|
),
|
|
/signature/,
|
|
);
|
|
for (const signature of [
|
|
'',
|
|
'invalid',
|
|
Buffer.from(
|
|
Buffer.from(fixture.signature, 'base64')
|
|
.toString()
|
|
.replace('timestamp:123456', 'timestamp:999999'),
|
|
).toString('base64'),
|
|
]) {
|
|
assert.throws(
|
|
() => verifyUpdateSignature(bytes, signature, fixture.publicKey),
|
|
/signature/,
|
|
);
|
|
}
|
|
});
|
|
test('manifest selects the exact supported architecture and embeds release notes', () => {
|
|
const metadata = {
|
|
version: '1.2.3',
|
|
target: 'aarch64-apple-darwin',
|
|
updaterFilename: 'KanbanGit.app.tar.gz',
|
|
};
|
|
assert.deepEqual(
|
|
updateManifest(
|
|
metadata,
|
|
'https://release.example/1.2.3',
|
|
'signed',
|
|
'Release notes',
|
|
),
|
|
{
|
|
version: '1.2.3',
|
|
notes: 'Release notes',
|
|
platforms: {
|
|
'darwin-aarch64': {
|
|
url: 'https://release.example/1.2.3/KanbanGit.app.tar.gz',
|
|
signature: 'signed',
|
|
},
|
|
},
|
|
},
|
|
);
|
|
assert.throws(() =>
|
|
updateManifest(
|
|
{ ...metadata, target: 'android' },
|
|
'https://example.com',
|
|
'signed',
|
|
),
|
|
);
|
|
});
|
|
test('update controls prevent concurrent operations and allow failed downloads to retry', () => {
|
|
for (const phase of [
|
|
'checking',
|
|
'downloading',
|
|
'verifying',
|
|
'installing',
|
|
'restarting',
|
|
]) {
|
|
const view = updatePresentation({ phase, version: '2.0.0' });
|
|
assert.equal(view.busy, true);
|
|
assert.equal(view.canInstall, false);
|
|
}
|
|
assert.equal(
|
|
updatePresentation({ phase: 'available', version: '2.0.0' }).canInstall,
|
|
true,
|
|
);
|
|
assert.equal(
|
|
updatePresentation({ phase: 'error', version: '2.0.0' }).canInstall,
|
|
true,
|
|
);
|
|
assert.equal(
|
|
updatePresentation({ phase: 'current', version: null }).canInstall,
|
|
false,
|
|
);
|
|
});
|