Add live English and German interface switching, animated navigation and history controls, improved update changelogs, remembered upload folders, reliable repeated hot reloads, and refreshed public documentation.
161 lines
6.0 KiB
JavaScript
161 lines
6.0 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, fetchGithubReleaseNotes, 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('matching GitHub release notes replace the private release body', async () => {
|
|
const calls = [];
|
|
const notes = await fetchGithubReleaseNotes('2.1.0', 'Private fallback', async (url, options) => {
|
|
calls.push({ url, options });
|
|
return { ok: true, json: async () => ({ tag_name: 'v2.1.0', body: '## Public changes\n\n- English UI' }) };
|
|
});
|
|
|
|
assert.equal(notes, '## Public changes\n\n- English UI');
|
|
assert.equal(calls.length, 1);
|
|
assert.equal(calls[0].url, 'https://api.github.com/repos/Sucukdeluxe/Multi-Hoster-Upload/releases/tags/v2.1.0');
|
|
});
|
|
|
|
test('GitHub release-note failures preserve the private release body', async () => {
|
|
const notes = await fetchGithubReleaseNotes('2.1.0', 'Private fallback', async () => ({ ok: false }));
|
|
assert.equal(notes, 'Private fallback');
|
|
});
|
|
|
|
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.7', '--transport-tag', 'v3.3.115', 'Update', 'visibility']));
|
|
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.7',
|
|
transportTag: 'v3.3.115',
|
|
releaseTitle: 'Multi-Hoster-Upload v2.0.7',
|
|
releaseBody: 'Update visibility',
|
|
expectedArtifacts: [
|
|
'Multi-Hoster-Upload Setup 2.0.7.exe',
|
|
'Multi-Hoster-Upload 2.0.7.exe',
|
|
'Multi-Hoster-Upload Setup 2.0.7.exe.blockmap',
|
|
'latest.yml'
|
|
],
|
|
latestYml: "version: 2.0.7\nfiles:\n - url: Multi-Hoster-Upload Setup 2.0.7.exe\n sha512: abc123\n size: 456\npath: Multi-Hoster-Upload Setup 2.0.7.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"/
|
|
);
|
|
});
|