aps-agent/tests/node/nsis-smoke.test.mjs

205 lines
8.2 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
parseArgs,
assertSafeTempDir,
expectedSha256FromManifest,
findInstaller,
findUninstaller,
verifyInstallLayout,
listContents,
} from '../../scripts/nsis-install-smoke.mjs';
import { sha256File } from '../../scripts/offline-install-check.mjs';
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
function tempRoot() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'aps-nsis-test-'));
}
test('nsis-smoke: parseArgs accepts positional installer and flags', () => {
const args = parseArgs([
'C:/tmp/aps-agent-desktop-0.1.0.exe',
'--run-uninstaller',
'--launch',
'--launch-seconds', '6',
'--timeout', '120000',
'--keep-temp-dir',
'--expected-sha256', 'b'.repeat(64),
]);
assert.equal(args.installer, path.resolve('C:/tmp/aps-agent-desktop-0.1.0.exe'));
assert.equal(args.runUninstaller, true);
assert.equal(args.launch, true);
assert.equal(args.launchSeconds, 6);
assert.equal(args.timeoutMs, 120000);
assert.equal(args.keepTempDir, true);
assert.equal(args.expectedSha256, 'b'.repeat(64));
});
test('nsis-smoke: --no-launch is the explicit default', () => {
assert.equal(parseArgs(['--launch', '--no-launch']).launch, false);
assert.equal(parseArgs([]).launch, false);
});
test('nsis-smoke: parseArgs rejects bad hash and unknown options', () => {
assert.throws(() => parseArgs(['--expected-sha256', 'zz']));
assert.throws(() => parseArgs(['--nope']));
});
test('nsis-smoke: assertSafeTempDir accepts only own prefixed dirs under the temp root', () => {
const root = tempRoot();
try {
const own = fs.mkdtempSync(path.join(root, 'aps-nsis-smoke-'));
const guarded = assertSafeTempDir(own, root);
assert.equal(guarded.resolved, path.resolve(own));
assert.ok(guarded.relative.startsWith('aps-nsis-smoke-'));
assert.throws(() => assertSafeTempDir(root, root), /temp root itself/);
assert.throws(() => assertSafeTempDir(path.join(os.tmpdir(), 'elsewhere'), root), /outside/);
const wrongPrefix = fs.mkdtempSync(path.join(root, 'other-prefix-'));
assert.throws(() => assertSafeTempDir(wrongPrefix, root), /prefix/);
assert.throws(() => assertSafeTempDir(path.join(root, '..', 'escape'), root), /outside/);
fs.rmSync(wrongPrefix, { recursive: true, force: true });
fs.rmSync(own, { recursive: true, force: true });
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: verifyInstallLayout validates main-exe electron-builder layout', () => {
const root = tempRoot();
try {
const installDir = path.join(root, 'aps-nsis-smoke-layout');
fs.mkdirSync(path.join(installDir, 'resources', 'sidecar'), { recursive: true });
fs.writeFileSync(path.join(installDir, '工业智核 APS.exe'), 'x');
fs.writeFileSync(path.join(installDir, 'resources', 'app.asar'), 'x');
fs.writeFileSync(path.join(installDir, 'resources', 'sidecar', 'aps-sidecar.exe'), 'x');
fs.writeFileSync(path.join(installDir, 'Uninstall 工业智核 APS.exe'), 'x');
const layout = verifyInstallLayout(installDir, '工业智核 APS');
assert.equal(layout.valid, true);
assert.equal(layout.mainExe, true);
assert.equal(layout.appAsar, true);
assert.equal(layout.sidecar, true);
assert.equal(path.basename(layout.uninstaller), 'Uninstall 工业智核 APS.exe');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: verifyInstallLayout reports broken layouts', () => {
const root = tempRoot();
try {
const dir = path.join(root, 'aps-nsis-smoke-broken');
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(path.join(dir, '工业智核 APS.exe'), 'x');
const layout = verifyInstallLayout(dir, '工业智核 APS');
assert.equal(layout.valid, false);
assert.ok(layout.reasons.length > 0);
assert.equal(verifyInstallLayout(path.join(root, 'nope'), '工业智核 APS').valid, false);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: expectedSha256FromManifest reads the nsis-installer entry', () => {
const hash = expectedSha256FromManifest({ entries: [{ id: 'nsis-installer', sha256: 'CD'.repeat(32) }] });
assert.equal(hash, 'cd'.repeat(32));
assert.equal(expectedSha256FromManifest(null), null);
assert.equal(expectedSha256FromManifest({ entries: [] }), null);
assert.equal(expectedSha256FromManifest({ entries: [{ id: 'other', sha256: 'a'.repeat(64) }] }), null);
assert.equal(expectedSha256FromManifest({ entries: [{ id: 'nsis-installer', sha256: 'not-hex' }] }), null);
});
test('nsis-smoke: findInstaller resolves explicit paths only', () => {
const root = tempRoot();
try {
const missing = findInstaller({ root, explicit: path.join(root, 'nope.exe'), minBytes: 1 });
assert.equal(missing.exists, false);
assert.deepEqual(missing.candidates, [path.join(root, 'nope.exe')]);
const file = path.join(root, 'fake.exe');
fs.writeFileSync(file, Buffer.alloc(1024, 1));
const found = findInstaller({ root, explicit: file, minBytes: 1 });
assert.equal(found.exists, true);
assert.equal(found.source, 'explicit');
assert.equal(found.bytes, 1024);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: findInstaller uses the manifest entry when present', () => {
const root = tempRoot();
try {
const releaseDir = path.join(root, 'apps', 'desktop', 'release');
fs.mkdirSync(releaseDir, { recursive: true });
const file = path.join(releaseDir, 'aps-agent-desktop-0.1.0.exe');
fs.writeFileSync(file, Buffer.alloc(2048, 2));
const manifest = { baseDir: root, entries: [{ id: 'nsis-installer', file: 'apps/desktop/release/aps-agent-desktop-0.1.0.exe' }] };
const found = findInstaller({ root, manifest, minBytes: 1 });
assert.equal(found.exists, true);
assert.equal(found.source, 'manifest');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: findUninstaller matches the electron-builder naming', () => {
const root = tempRoot();
try {
const dir = path.join(root, 'aps-nsis-smoke-u');
fs.mkdirSync(dir, { recursive: true });
assert.equal(findUninstaller(dir, '工业智核 APS'), null);
fs.writeFileSync(path.join(dir, 'Uninstall 工业智核 APS.exe'), 'x');
assert.equal(path.basename(findUninstaller(dir, '工业智核 APS')), 'Uninstall 工业智核 APS.exe');
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: listContents walks nested files and sorts by size', () => {
const root = tempRoot();
try {
const dir = path.join(root, 'aps-nsis-smoke-contents');
fs.mkdirSync(path.join(dir, 'resources', 'sidecar'), { recursive: true });
fs.writeFileSync(path.join(dir, 'small.bin'), Buffer.alloc(10, 1));
fs.writeFileSync(path.join(dir, 'resources', 'big.bin'), Buffer.alloc(120, 2));
fs.writeFileSync(path.join(dir, 'resources', 'sidecar', 'mid.bin'), Buffer.alloc(60, 3));
const result = listContents(dir, 2);
assert.equal(result.fileCount, 3);
assert.equal(result.totalBytes, 190);
assert.equal(result.top.length, 2);
assert.equal(result.top[0].bytes, 120);
assert.equal(result.top[1].bytes, 60);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: sha256File is deterministic and matches hex format', async () => {
const root = tempRoot();
try {
const file = path.join(root, 'sample.bin');
fs.writeFileSync(file, Buffer.alloc(4096, 7));
const first = await sha256File(file);
const second = await sha256File(file);
assert.equal(first, second);
assert.match(first, /^[0-9a-f]{64}$/);
} finally {
fs.rmSync(root, { recursive: true, force: true });
}
});
test('nsis-smoke: auto-discovery finds the real installer on this machine', (t) => {
const found = findInstaller({ root: repoRoot });
if (!found.exists) {
t.skip('NSIS installer not built on this machine');
return;
}
assert.ok(found.bytes >= 50 * 1024 * 1024);
assert.ok(found.file.endsWith('.exe'));
});