Redesign the desktop workspace with task sidebars, live filters, clearer settings, and an accessible update dialog. Harden encrypted backup imports, configuration persistence, history retention, queue snapshots, shutdown recovery, and update installation ordering. Publish verified Windows artifacts and refreshed English documentation.
144 lines
5.2 KiB
JavaScript
144 lines
5.2 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const { pathToFileURL } = require('node:url');
|
|
|
|
const { isNewer, resolveReleaseVersion, prepareUpdate, launchPreparedUpdate } = require('../lib/updater');
|
|
const releasePlanUrl = pathToFileURL(path.resolve(__dirname, '../scripts/release-plan.mjs')).href;
|
|
|
|
test('bridge title resolves product version instead of transport tag', () => {
|
|
assert.equal(resolveReleaseVersion({ name: 'Multi-Hoster-Upload v2.0.1', tag_name: 'v3.3.109' }), '2.0.1');
|
|
assert.equal(isNewer('2.0.1', '2.0.1'), false);
|
|
assert.equal(isNewer('2.0.2', '2.0.1'), true);
|
|
});
|
|
|
|
test('release arguments reject a malformed transport tag', async () => {
|
|
const { parseReleaseArgs } = await import(releasePlanUrl);
|
|
assert.throws(
|
|
() => parseReleaseArgs(['2.0.1', '--transport-tag', '3.3.109', 'Bridge', '--dry-run']),
|
|
/--transport-tag must match vX\.Y\.Z/
|
|
);
|
|
});
|
|
|
|
test('update preparation writes a verified installer without launching it', async () => {
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mhu-updater-test-'));
|
|
const installer = Buffer.alloc(128 * 1024, 0);
|
|
installer[0] = 0x4d;
|
|
installer[1] = 0x5a;
|
|
let reads = 0;
|
|
const progress = [];
|
|
try {
|
|
const prepared = await prepareUpdate(value => progress.push(value), {
|
|
checkResult: {
|
|
available: true,
|
|
assetUrl: 'https://update.invalid/setup.exe',
|
|
assetName: 'setup.exe',
|
|
assetSize: installer.length,
|
|
latestYmlUrl: null
|
|
},
|
|
tempDir,
|
|
fetchImpl: async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
body: {
|
|
getReader: () => ({
|
|
read: async () => {
|
|
reads++;
|
|
return reads === 1 ? { done: false, value: installer } : { done: true };
|
|
}
|
|
})
|
|
}
|
|
})
|
|
});
|
|
|
|
assert.equal(prepared.installerPath, path.join(tempDir, 'setup.exe'));
|
|
assert.deepEqual(fs.readFileSync(prepared.installerPath), installer);
|
|
assert.equal(progress.at(-1).stage, 'prepared');
|
|
} finally {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test('a prepared installer launches at most once', () => {
|
|
const calls = [];
|
|
const child = { unrefCalls: 0, unref() { this.unrefCalls++; } };
|
|
const prepared = { installerPath: 'C:\\Temp\\mhu-setup.exe' };
|
|
const spawnImpl = (...args) => {
|
|
calls.push(args);
|
|
return child;
|
|
};
|
|
|
|
assert.equal(launchPreparedUpdate(prepared, { spawnImpl }), true);
|
|
assert.equal(launchPreparedUpdate(prepared, { spawnImpl }), false);
|
|
assert.equal(calls.length, 1);
|
|
assert.deepEqual(calls[0], [
|
|
prepared.installerPath,
|
|
['/S', '--updated', '--force-run'],
|
|
{ detached: true, stdio: 'ignore' }
|
|
]);
|
|
assert.equal(child.unrefCalls, 1);
|
|
});
|
|
|
|
test('release plan keeps product artifacts separate from the transport tag', async () => {
|
|
const { createReleasePlan, parseReleaseArgs, renderLatestYml } = await import(releasePlanUrl);
|
|
const plan = createReleasePlan(parseReleaseArgs(['2.0.6', '--transport-tag', 'v3.3.114', 'Interface', 'redesign']));
|
|
const latestYml = renderLatestYml(plan, 'abc123', 456, '2026-08-07T12:00:00.000Z');
|
|
assert.deepEqual({
|
|
version: plan.version,
|
|
transportTag: plan.transportTag,
|
|
releaseTitle: plan.releaseTitle,
|
|
releaseBody: plan.releaseBody,
|
|
expectedArtifacts: plan.expectedArtifacts,
|
|
latestYml
|
|
}, {
|
|
version: '2.0.6',
|
|
transportTag: 'v3.3.114',
|
|
releaseTitle: 'Multi-Hoster-Upload v2.0.6',
|
|
releaseBody: 'Interface redesign',
|
|
expectedArtifacts: [
|
|
'Multi-Hoster-Upload Setup 2.0.6.exe',
|
|
'Multi-Hoster-Upload 2.0.6.exe',
|
|
'Multi-Hoster-Upload Setup 2.0.6.exe.blockmap',
|
|
'latest.yml'
|
|
],
|
|
latestYml: "version: 2.0.6\nfiles:\n - url: Multi-Hoster-Upload Setup 2.0.6.exe\n sha512: abc123\n size: 456\npath: Multi-Hoster-Upload Setup 2.0.6.exe\nsha512: abc123\nreleaseDate: '2026-08-07T12:00:00.000Z'\n"
|
|
});
|
|
});
|
|
|
|
test('compatible existing release preserves the recovery id', async () => {
|
|
const { createReleasePlan, parseReleaseArgs, resolveExistingReleaseId } = await import(releasePlanUrl);
|
|
const plan = createReleasePlan(parseReleaseArgs(['2.0.1', '--transport-tag', 'v3.3.109', 'Bridge notes']));
|
|
const release = {
|
|
id: 81,
|
|
tag_name: 'v3.3.109',
|
|
name: 'Multi-Hoster-Upload v2.0.1',
|
|
body: 'Bridge notes',
|
|
draft: false,
|
|
prerelease: false,
|
|
assets: []
|
|
};
|
|
|
|
assert.equal(resolveExistingReleaseId(plan, release), 81);
|
|
});
|
|
|
|
test('incompatible existing release title fails closed', async () => {
|
|
const { createReleasePlan, parseReleaseArgs, resolveExistingReleaseId } = await import(releasePlanUrl);
|
|
const plan = createReleasePlan(parseReleaseArgs(['2.0.1', '--transport-tag', 'v3.3.109', 'Bridge notes']));
|
|
const release = {
|
|
id: 81,
|
|
tag_name: 'v3.3.109',
|
|
name: 'Multi-Hoster-Upload v3.3.109',
|
|
body: 'Old transport release',
|
|
draft: false,
|
|
prerelease: false,
|
|
assets: []
|
|
};
|
|
|
|
assert.throws(
|
|
() => resolveExistingReleaseId(plan, release),
|
|
/Refusing recovery for v3\.3\.109: existing release title "Multi-Hoster-Upload v3\.3\.109" does not match "Multi-Hoster-Upload v2\.0\.1"/
|
|
);
|
|
});
|