release-macos-manifest.test.mjs
132 lines 5.6 KB
Raw
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700 feat(companion): add production macOS distribution path Human minor ⚠ breaking 24 days ago
1 import assert from 'node:assert/strict';
2 import fs from 'node:fs';
3 import os from 'node:os';
4 import path from 'node:path';
5 import { spawnSync } from 'node:child_process';
6 import test from 'node:test';
7
8 function makeApp(root) {
9 const resources = path.join(root, 'Knowtation.app/Contents/Resources');
10 fs.mkdirSync(path.join(resources, 'runtime'), { recursive: true });
11 fs.writeFileSync(path.join(resources, 'runtime/data.txt'), 'deterministic payload');
12 fs.writeFileSync(path.join(resources, 'runtime/tool'), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
13 return path.dirname(path.dirname(resources));
14 }
15
16 function generate(app, output) {
17 return spawnSync(process.execPath, [
18 'scripts/release/macos/generate-manifest.mjs',
19 '--stage', 'unsigned',
20 '--app', app,
21 '--output', output,
22 ], {
23 encoding: 'utf8',
24 env: { ...process.env, KNOWTATION_ALLOW_UNCOMMITTED_BUILD: '1' },
25 });
26 }
27
28 test('unsigned runtime manifest is deterministic and records mode, size, and digest', () => {
29 const one = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-manifest-one-'));
30 const two = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-manifest-two-'));
31 try {
32 const appOne = makeApp(one);
33 const appTwo = makeApp(two);
34 const outputOne = path.join(appOne, 'Contents/Resources/runtime-manifest.json');
35 const outputTwo = path.join(appTwo, 'Contents/Resources/runtime-manifest.json');
36 assert.equal(generate(appOne, outputOne).status, 0);
37 assert.equal(generate(appTwo, outputTwo).status, 0);
38 assert.equal(fs.readFileSync(outputOne, 'utf8'), fs.readFileSync(outputTwo, 'utf8'));
39 const manifest = JSON.parse(fs.readFileSync(outputOne, 'utf8'));
40 assert.equal(manifest.files.length, 2);
41 assert.equal(manifest.sourceRevision.status, 'uncommitted');
42 assert.equal(manifest.files.find((entry) => entry.path === 'runtime/tool').executable, true);
43 assert.match(manifest.files[0].sha256, /^[0-9a-f]{64}$/);
44 } finally {
45 fs.rmSync(one, { recursive: true, force: true });
46 fs.rmSync(two, { recursive: true, force: true });
47 }
48 });
49
50 test('release manifest generation rejects an uncommitted source identity', () => {
51 const root = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-final-manifest-'));
52 try {
53 const pkg = path.join(root, 'Knowtation-0.1.0-100-macos-arm64.pkg');
54 fs.writeFileSync(pkg, 'fixture');
55 fs.writeFileSync(path.join(root, 'Knowtation-0.1.0-100-macos-arm64.zip'), 'zip fixture');
56 fs.writeFileSync(path.join(root, 'sbom.spdx.json'), '{}\n');
57 fs.writeFileSync(path.join(root, 'LICENSE'), 'fixture license\n');
58 fs.writeFileSync(path.join(root, 'THIRD-PARTY-NOTICES.txt'), 'fixture\n');
59 const result = spawnSync(process.execPath, [
60 'scripts/release/macos/generate-manifest.mjs',
61 '--stage', 'final',
62 '--dist', root,
63 '--output', path.join(root, 'release-manifest.json'),
64 ], {
65 encoding: 'utf8',
66 env: {
67 ...process.env,
68 KNOWTATION_ALLOW_UNCOMMITTED_BUILD: '1',
69 KNOWTATION_UPDATE_ISSUED_AT: '1000',
70 KNOWTATION_UPDATE_EXPIRES_AT: '2000',
71 KNOWTATION_UPDATE_KEY_ID: 'test-release-key',
72 KNOWTATION_UPDATE_SOURCE_MIN_BUILD: '0',
73 KNOWTATION_UPDATE_SOURCE_MAX_BUILD: '99',
74 KNOWTATION_SOURCE_MUSE_REVISION: '',
75 KNOWTATION_SOURCE_GIT_REVISION: '',
76 },
77 });
78 assert.notEqual(result.status, 0);
79 assert.match(result.stderr, /exact KNOWTATION_SOURCE_MUSE_REVISION/);
80 } finally {
81 fs.rmSync(root, { recursive: true, force: true });
82 }
83 });
84
85 test('manifest generation rejects symbolic links', () => {
86 const root = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-manifest-link-'));
87 try {
88 const app = makeApp(root);
89 fs.symlinkSync('/etc/hosts', path.join(app, 'Contents/Resources/runtime/substitution'));
90 const result = generate(app, path.join(app, 'Contents/Resources/runtime-manifest.json'));
91 assert.notEqual(result.status, 0);
92 assert.match(result.stderr, /symbolic links are forbidden/);
93 } finally {
94 fs.rmSync(root, { recursive: true, force: true });
95 }
96 });
97
98 test('runtime verifier accepts the exact generated resource tree', () => {
99 const root = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-manifest-exact-'));
100 try {
101 const app = makeApp(root);
102 const resources = path.join(app, 'Contents/Resources');
103 fs.copyFileSync('release/macos/release-lock.json', path.join(resources, 'release-lock.json'));
104 const output = path.join(resources, 'runtime-manifest.json');
105 assert.equal(generate(app, output).status, 0);
106 const result = spawnSync(process.execPath, [
107 'scripts/release/macos/verify-runtime-manifest.mjs', '--app', app,
108 ], { encoding: 'utf8' });
109 assert.equal(result.status, 0, result.stderr);
110 } finally {
111 fs.rmSync(root, { recursive: true, force: true });
112 }
113 });
114
115 test('runtime verifier rejects an added file after manifest generation', () => {
116 const root = fs.mkdtempSync(path.join(os.tmpdir(), 'knowtation-manifest-extra-'));
117 try {
118 const app = makeApp(root);
119 const resources = path.join(app, 'Contents/Resources');
120 fs.copyFileSync('release/macos/release-lock.json', path.join(resources, 'release-lock.json'));
121 const output = path.join(resources, 'runtime-manifest.json');
122 assert.equal(generate(app, output).status, 0);
123 fs.writeFileSync(path.join(resources, 'substitution.txt'), 'unexpected');
124 const result = spawnSync(process.execPath, [
125 'scripts/release/macos/verify-runtime-manifest.mjs', '--app', app,
126 ], { encoding: 'utf8' });
127 assert.notEqual(result.status, 0);
128 assert.match(result.stderr, /runtime manifest file closure mismatch/);
129 } finally {
130 fs.rmSync(root, { recursive: true, force: true });
131 }
132 });
File History 1 commit
sha256:bd1eb24434d281fe65142038c1bf217493421f5a86880c22dff7e6037d2d4700 feat(companion): add production macOS distribution path Human minor ⚠ 24 days ago