Files
fluxer/fluxer_desktop/src/main/ModuleUpdater.test.mjs
T

1072 lines
40 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import assert from 'node:assert/strict';
import {Buffer} from 'node:buffer';
import {createHash} from 'node:crypto';
import {mkdirSync, mkdtempSync, writeFileSync} from 'node:fs';
import {rm} from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import {after, describe, test} from 'node:test';
import './LocalAppTestSupport.test.mjs';
const {ModuleStore, getModuleStoreRoot} = await import('@electron/main/ModuleStore');
const {ModuleUpdater} = await import('@electron/main/ModuleUpdater');
const SHELL_VERSION = '2026.823.1';
const RELEASE_CHANNEL = 'canary';
const PLATFORM = 'darwin';
const ARCH = 'arm64';
const PACKAGE_ORIGIN = 'https://pkgs.invalid';
const SOURCE_SHA = 'a'.repeat(40);
const NOW = Date.parse('2026-08-23T12:00:00.000Z');
const temporaryRoots = [];
after(async () => {
for (const root of temporaryRoots) {
await rm(root, {recursive: true, force: true});
}
});
function sha256(data) {
return createHash('sha256').update(data).digest('hex');
}
function createUserData() {
const root = mkdtempSync(path.join(os.tmpdir(), 'module-updater-'));
temporaryRoots.push(root);
return root;
}
function installModuleOnDisk(storeRoot, moduleName, digest, entries, buildVersion = SHELL_VERSION) {
const directory = path.join(storeRoot, 'store', moduleName, digest);
mkdirSync(directory, {recursive: true});
const files = entries.map(([relative, body]) => {
writeFileSync(path.join(directory, relative), body);
return {path: relative, sha256: sha256(Buffer.from(body, 'utf8')), bytes: Buffer.byteLength(body, 'utf8')};
});
writeFileSync(
path.join(directory, 'module.json'),
`${JSON.stringify(
{
module: moduleName,
build_version: buildVersion,
release_channel: RELEASE_CHANNEL,
source_sha: SOURCE_SHA,
files,
},
null,
'\t',
)}\n`,
);
return directory;
}
function manifestBytes(
modules,
{
shellLatest = SHELL_VERSION,
shellMinimum = SHELL_VERSION,
required = null,
bounds = {},
metadataVersion = 1,
platform = PLATFORM,
} = {},
) {
return Buffer.from(
JSON.stringify({
manifest_version: 1,
release_channel: RELEASE_CHANNEL,
platform,
arch: ARCH,
build_version: SHELL_VERSION,
pub_date: '2026-08-23T00:00:00.000Z',
metadata_version: metadataVersion,
shell: {latest_version: shellLatest, minimum_version: shellMinimum},
modules: Object.fromEntries(
Object.entries(modules).map(([moduleName, digest]) => [
moduleName,
{
sha256: digest,
bytes: 1024,
url: `${PACKAGE_ORIGIN}/desktop/${RELEASE_CHANNEL}/modules/${moduleName}/${digest}/package.br`,
...(bounds[moduleName] ?? {}),
},
]),
),
required_modules: required ?? Object.keys(modules),
}),
'utf8',
);
}
async function openStore(userDataPath) {
return await ModuleStore.open({
root: getModuleStoreRoot(userDataPath),
shellVersion: SHELL_VERSION,
releaseChannel: RELEASE_CHANNEL,
});
}
function createUpdater(store, modules, options = {}) {
const bytes = manifestBytes(modules, options);
const requests = [];
const sleeps = [];
const reports = [];
const updater = new ModuleUpdater({
store,
shellVersion: SHELL_VERSION,
releaseChannel: RELEASE_CHANNEL,
platform: options.platform ?? PLATFORM,
arch: ARCH,
packageOrigin: PACKAGE_ORIGIN,
hasOfflineRenderer: options.bundledRendererVersion != null,
bundledRendererVersion: options.bundledRendererVersion ?? null,
forceStartupUpdate: options.forceStartupUpdate ?? false,
selfUpdateShellFirst: options.selfUpdateShellFirst,
onState: options.onState,
fetch: async (url, init) => {
requests.push(url);
const response = options.respond == null ? null : await options.respond(url, init);
return response ?? new Response(bytes, {status: 200, headers: {etag: '"manifest-1"'}});
},
sleep: async (ms) => {
sleeps.push(ms);
},
random: () => 0,
now: () => NOW,
report: (report) => {
reports.push(report);
},
});
return {updater, requests, sleeps, reports};
}
function failNthInstallationCheck(store, nth, failure) {
let checks = 0;
return new Proxy(store, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof value !== 'function') {
return value;
}
if (property !== 'isInstalled') {
return value.bind(target);
}
return async (moduleName, digest) => {
checks += 1;
if (checks === nth) {
throw failure;
}
return await value.call(target, moduleName, digest);
};
},
});
}
const RENDERER_SHA = sha256('renderer-one');
const NEXT_RENDERER_SHA = sha256('renderer-two');
const OVERLAY_SHA = sha256('overlay-one');
const NEXT_OVERLAY_SHA = sha256('overlay-two');
function failInstallModule(store, failure) {
return new Proxy(store, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof value !== 'function') {
return value;
}
if (property !== 'installModule') {
return value.bind(target);
}
return async () => {
throw failure;
};
},
});
}
function failInstallModuleFor(store, moduleName, failure) {
return new Proxy(store, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof value !== 'function') {
return value;
}
if (property !== 'installModule') {
return value.bind(target);
}
return async (request) => {
if (request.module === moduleName) {
throw failure;
}
return await value.call(target, request);
};
},
});
}
function mergeDuringInstall(store, entries) {
let merged = false;
return new Proxy(store, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof value !== 'function') {
return value;
}
if (property !== 'installModule') {
return value.bind(target);
}
return async (request) => {
if (!merged) {
merged = true;
await target.mergeCommitted(entries);
}
return await value.call(target, request);
};
},
});
}
function recordInstalls(store, installs) {
return new Proxy(store, {
get(target, property) {
const value = Reflect.get(target, property, target);
if (typeof value !== 'function') {
return value;
}
if (property !== 'installModule') {
return value.bind(target);
}
return async (request) => {
installs.push(request.module);
return await value.call(target, request);
};
},
});
}
function unreachableFeed() {
return async () => {
throw new Error('module feed is unreachable');
};
}
function missingPackages() {
return async (url) => (url.endsWith('.br') ? new Response('', {status: 404}) : null);
}
function seedCachedPackage(store, digest) {
const packagePath = store.getPackagePath(digest);
mkdirSync(path.dirname(packagePath), {recursive: true});
writeFileSync(packagePath, 'cached');
}
describe('ModuleUpdater.run', () => {
test('launches the committed set when the manifest asks for nothing new', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const {updater, requests, sleeps} = createUpdater(store, {fluxer_renderer: RENDERER_SHA});
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
assert.equal(outcome.rolledBack, false);
assert.deepEqual(outcome.belowFloor, []);
assert.equal(Object.hasOwn(outcome, 'installed'), false);
assert.equal(requests.length, 1);
assert.deepEqual(sleeps, []);
assert.deepEqual(store.getState().floor, {fluxer_renderer: RENDERER_SHA});
assert.equal(store.getState().boot_attempt, 1);
});
test('a store failure at the floor check is retried instead of escaping run', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const failure = Object.assign(new Error('failed to inspect installed module path'), {
name: 'ModuleStoreIOError',
});
const {updater, sleeps, reports} = createUpdater(failNthInstallationCheck(store, 2, failure), {
fluxer_renderer: RENDERER_SHA,
});
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
assert.deepEqual(sleeps, [500]);
assert.deepEqual(
reports.map((report) => [report.type, report.message]),
[['network-error', failure.message]],
);
});
test('a manifest that no longer carries the committed module blocks instead of launching nothing', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const {updater} = createUpdater(store, {});
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-update-required');
assert.equal(outcome.reason, 'nothing-installed');
});
test('a shell floor above this shell blocks on the shell update instead of installing modules', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const {updater, requests} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{shellLatest: '2026.824.0', shellMinimum: '2026.824.0'},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-shell-update');
assert.equal(outcome.latestVersion, '2026.824.0');
assert.equal(outcome.minimumVersion, '2026.824.0');
assert.equal(outcome.requiredSecurityUpdate, false);
assert.deepEqual(requests, [`${PACKAGE_ORIGIN}/desktop/${RELEASE_CHANNEL}/${PLATFORM}/${ARCH}/modules.json`]);
assert.deepEqual(store.getCommitted(), {fluxer_renderer: RENDERER_SHA});
});
test('a required module that outruns this shell blocks on the shell update', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const {updater, requests} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{bounds: {fluxer_renderer: {minimum_shell_version: '2026.824.0'}}},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-shell-update');
assert.equal(outcome.latestVersion, SHELL_VERSION);
assert.equal(outcome.minimumVersion, SHELL_VERSION);
assert.equal(requests.length, 1, 'a shell block must not start downloading packages');
});
test('an optional module this shell has outgrown keeps its committed copy and still launches', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
await store.commit({fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
const {updater, requests} = createUpdater(
store,
{fluxer_overlay: NEXT_OVERLAY_SHA, fluxer_renderer: RENDERER_SHA},
{required: ['fluxer_renderer'], bounds: {fluxer_overlay: {minimum_shell_version: '2026.824.0'}}},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
assert.equal(requests.length, 1, 'a module this shell cannot run must never be downloaded');
});
test('an unreachable feed with no recorded fetch blocks instead of launching whatever is on disk', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const {updater, sleeps} = createUpdater(store, {fluxer_renderer: RENDERER_SHA}, {respond: unreachableFeed()});
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-update-required');
assert.equal(outcome.reason, 'nothing-installed');
assert.deepEqual(sleeps, [500, 1000, 2000, 4000, 8000]);
assert.equal(store.getState().boot_attempt, 0);
});
test('an unreachable feed launches the set a previous boot already verified without waiting out the backoff', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
assert.equal((await createUpdater(store, {fluxer_renderer: RENDERER_SHA}).updater.run()).status, 'launching');
const {updater, sleeps} = createUpdater(store, {fluxer_renderer: RENDERER_SHA}, {respond: unreachableFeed()});
const outcome = await updater.run();
assert.equal(outcome.status, 'unreachable-launch');
assert.deepEqual(sleeps, []);
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
assert.equal(outcome.launchAttempt.committed.fluxer_renderer, RENDERER_SHA);
assert.equal(store.getState().boot_attempt, 2);
});
test('a manifest older than the one already seen launches the verified set without retrying', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const seen = createUpdater(store, {fluxer_renderer: RENDERER_SHA}, {metadataVersion: 5});
assert.equal((await seen.updater.run()).status, 'launching');
const {updater, sleeps, reports} = createUpdater(store, {fluxer_renderer: NEXT_RENDERER_SHA}, {metadataVersion: 4});
const outcome = await updater.run();
assert.equal(outcome.status, 'unreachable-launch');
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
assert.deepEqual(sleeps, []);
assert.deepEqual(
reports.map((report) => report.type),
['manifest-rollback'],
);
});
test('a package the server no longer has demotes an optional module to its committed digest', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
await store.commit({fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
const {updater, reports} = createUpdater(
store,
{fluxer_overlay: NEXT_OVERLAY_SHA, fluxer_renderer: RENDERER_SHA},
{required: ['fluxer_renderer'], respond: missingPackages()},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
assert.deepEqual(
reports.filter((report) => report.type !== 'network-error').map((report) => [report.type, report.module]),
[['package-missing', 'fluxer_overlay']],
);
});
test('an install that runs out of disk space reports the storage error and commits nothing', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
installModuleOnDisk(getModuleStoreRoot(userDataPath), 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
const failure = Object.assign(new Error('no space left on device'), {code: 'ENOSPC'});
const {updater, reports} = createUpdater(failInstallModule(store, failure), {
fluxer_renderer: NEXT_RENDERER_SHA,
});
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-update-required');
assert.equal(outcome.reason, 'unreachable-below-floor');
assert.equal(outcome.message, 'no space left to install fluxer_renderer: no space left on device');
assert.equal(outcome.updateServerUnreachable, false);
assert.deepEqual(store.getCommitted(), {fluxer_renderer: RENDERER_SHA});
assert.deepEqual(
[...new Set(reports.filter((report) => report.type === 'storage-error').map((report) => report.module))],
['fluxer_renderer', null],
);
assert.equal(updater.getLastState().detail, 'Not enough disk space to install the update');
});
});
describe('ModuleUpdater checks without downloading and installs only when asked', () => {
async function storeOnRenderer() {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
return {store, storeRoot};
}
test('a check reads only the manifest and leaves the committed set, the floor and the boot attempt alone', async () => {
const {store} = await storeOnRenderer();
const before = store.getState();
const installs = [];
const {updater, requests} = createUpdater(recordInstalls(store, installs), {fluxer_renderer: NEXT_RENDERER_SHA});
const check = await updater.checkForUpdate();
assert.deepEqual(check, {shellLatestVersion: SHELL_VERSION, shellNewer: false, modulesChanged: true});
assert.deepEqual(
requests.map((url) => String(url).split('/').pop()),
['modules.json'],
);
assert.deepEqual(installs, []);
assert.deepEqual(store.getCommitted(), {fluxer_renderer: RENDERER_SHA});
assert.equal(store.getState().boot_attempt, before.boot_attempt);
assert.deepEqual(store.getState().floor, before.floor);
assert.equal(store.getState().last_manifest_fetch, before.last_manifest_fetch);
});
test('a check reports a newer shell and an unchanged module set', async () => {
const {store} = await storeOnRenderer();
const {updater} = createUpdater(store, {fluxer_renderer: RENDERER_SHA}, {shellLatest: '2026.824.1'});
assert.deepEqual(await updater.checkForUpdate(), {
shellLatestVersion: '2026.824.1',
shellNewer: true,
modulesChanged: false,
});
});
test('a module that needs the next shell reads as a shell update, not as a module update', async () => {
const {store} = await storeOnRenderer();
const {updater} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{shellLatest: '2026.824.1', bounds: {fluxer_renderer: {minimum_shell_version: '2026.824.1'}}},
);
assert.deepEqual(await updater.checkForUpdate(), {
shellLatestVersion: '2026.824.1',
shellNewer: true,
modulesChanged: false,
});
});
test('the click installs the new set, reports progress and arms a launch attempt for the next window', async () => {
const {store, storeRoot} = await storeOnRenderer();
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
seedCachedPackage(store, NEXT_RENDERER_SHA);
const statuses = [];
const {updater} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{onState: (state) => statuses.push(state.status)},
);
const attempt = await updater.installPending();
assert.deepEqual(attempt?.committed, {fluxer_renderer: NEXT_RENDERER_SHA});
assert.deepEqual(store.getCommitted(), {fluxer_renderer: NEXT_RENDERER_SHA});
assert.equal(store.getState().boot_attempt, 1);
assert.equal(statuses[0], 'checking');
assert.equal(statuses.at(-1), 'verifying');
await updater.markLaunchSucceeded(attempt);
assert.equal(store.getState().boot_attempt, 0);
});
test('the click keeps a module installed on demand while it was downloading', async () => {
const {store, storeRoot} = await storeOnRenderer();
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
seedCachedPackage(store, NEXT_RENDERER_SHA);
const {updater} = createUpdater(mergeDuringInstall(store, {fluxer_overlay: OVERLAY_SHA}), {
fluxer_renderer: NEXT_RENDERER_SHA,
});
const attempt = await updater.installPending();
assert.deepEqual(attempt?.committed, {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA});
assert.deepEqual(store.getCommitted(), {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA});
});
test('the click installs nothing when the manifest now needs a newer shell', async () => {
const {store} = await storeOnRenderer();
const installs = [];
const {updater} = createUpdater(
recordInstalls(store, installs),
{fluxer_renderer: NEXT_RENDERER_SHA},
{shellLatest: '2026.824.1', shellMinimum: '2026.824.1'},
);
assert.equal(await updater.installPending(), null);
assert.deepEqual(installs, []);
assert.deepEqual(store.getCommitted(), {fluxer_renderer: RENDERER_SHA});
});
test('a module the manifest advertises but the CDN never got does not strand the rest of the update', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
seedCachedPackage(store, NEXT_RENDERER_SHA);
const {updater, reports} = createUpdater(
store,
{fluxer_overlay: NEXT_OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA},
{
required: ['fluxer_renderer'],
respond: async (url) =>
url.endsWith(`${NEXT_OVERLAY_SHA}/package.br`) ? new Response('', {status: 404}) : null,
},
);
const attempt = await updater.installPending();
assert.deepEqual(attempt?.committed, {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA});
assert.deepEqual(
[...new Set(reports.filter((report) => report.type === 'package-missing').map((report) => report.module))],
['fluxer_overlay'],
);
});
test('a committed optional module whose tree is gone and whose package 404s still lets the renderer activate', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
await rm(path.join(storeRoot, 'store', 'fluxer_overlay', OVERLAY_SHA), {recursive: true, force: true});
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
seedCachedPackage(store, NEXT_RENDERER_SHA);
const {updater} = createUpdater(
store,
{fluxer_overlay: OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA},
{
required: ['fluxer_renderer'],
respond: async (url) => (url.endsWith(`${OVERLAY_SHA}/package.br`) ? new Response('', {status: 404}) : null),
},
);
await updater.installPending();
assert.deepEqual(store.getCommitted(), {fluxer_renderer: NEXT_RENDERER_SHA});
});
test('a package failure with no deterministic cause rejects the click and keeps the installed set', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
installModuleOnDisk(storeRoot, 'fluxer_overlay', NEXT_OVERLAY_SHA, [['index.js', 'overlay-two']]);
seedCachedPackage(store, NEXT_OVERLAY_SHA);
const {updater} = createUpdater(
store,
{fluxer_overlay: NEXT_OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA},
{
required: ['fluxer_renderer'],
respond: async (url) =>
url.endsWith(`${NEXT_RENDERER_SHA}/package.br`) ? new Response('', {status: 503}) : null,
},
);
await assert.rejects(updater.installPending(), /failed to download module fluxer_renderer/u);
assert.deepEqual(store.getCommitted(), {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
});
test('a click that runs out of disk space aborts instead of activating the part that fit', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_overlay', OVERLAY_SHA, [['index.js', 'overlay']]);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
await store.commit({fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
installModuleOnDisk(storeRoot, 'fluxer_overlay', NEXT_OVERLAY_SHA, [['index.js', 'overlay-two']]);
seedCachedPackage(store, NEXT_OVERLAY_SHA);
const failure = Object.assign(new Error('no space left on device'), {code: 'ENOSPC'});
const {updater} = createUpdater(
failInstallModuleFor(store, 'fluxer_renderer', failure),
{fluxer_overlay: NEXT_OVERLAY_SHA, fluxer_renderer: NEXT_RENDERER_SHA},
{required: ['fluxer_renderer']},
);
await assert.rejects(updater.installPending(), /no space left on device/u);
assert.deepEqual(store.getCommitted(), {fluxer_overlay: OVERLAY_SHA, fluxer_renderer: RENDERER_SHA});
});
test('a boot with a newer shell updates the shell before downloading any module', async () => {
const {store, storeRoot} = await storeOnRenderer();
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
seedCachedPackage(store, NEXT_RENDERER_SHA);
const steps = [];
const {updater} = createUpdater(
recordInstalls(store, steps),
{fluxer_renderer: NEXT_RENDERER_SHA},
{
shellLatest: '2026.824.1',
forceStartupUpdate: true,
selfUpdateShellFirst: async (latestVersion) => {
steps.push(`shell ${latestVersion}`);
},
},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(steps, ['shell 2026.824.1', 'fluxer_renderer']);
});
test('a boot with the newest shell never runs the shell update', async () => {
const {store, storeRoot} = await storeOnRenderer();
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
seedCachedPackage(store, NEXT_RENDERER_SHA);
const steps = [];
const {updater} = createUpdater(
recordInstalls(store, steps),
{fluxer_renderer: NEXT_RENDERER_SHA},
{
forceStartupUpdate: true,
selfUpdateShellFirst: async (latestVersion) => {
steps.push(`shell ${latestVersion}`);
},
},
);
await updater.run();
assert.deepEqual(steps, ['fluxer_renderer']);
});
test('a Linux boot right after a shell update fetches the modules instead of launching the old set', async () => {
const optional = await storeOnRenderer();
const quiet = createUpdater(optional.store, {fluxer_renderer: NEXT_RENDERER_SHA}, {platform: 'linux'});
const skipped = await quiet.updater.run();
assert.equal(skipped.status, 'launching');
assert.deepEqual(skipped.committed, {fluxer_renderer: RENDERER_SHA});
assert.deepEqual(quiet.requests, []);
const forced = await storeOnRenderer();
installModuleOnDisk(forced.storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer-two']]);
seedCachedPackage(forced.store, NEXT_RENDERER_SHA);
const {updater, requests} = createUpdater(
forced.store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{platform: 'linux', forceStartupUpdate: true},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {fluxer_renderer: NEXT_RENDERER_SHA});
assert.ok(requests.some((url) => String(url).endsWith('/modules.json')));
});
test('a Linux boot forced only by a shell change launches the installed set at once when the download fails', async () => {
const {store} = await storeOnRenderer();
const {updater, sleeps} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{
platform: 'linux',
forceStartupUpdate: true,
respond: async (url) => (String(url).endsWith('package.br') ? new Response('', {status: 503}) : null),
},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'unreachable-launch');
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
assert.deepEqual(
sleeps,
[],
'a package or deb upgrade never waits through the backoff for a module it can live without',
);
});
});
describe('ModuleUpdater explains why a launch is blocked', () => {
test('a first launch that cannot reach the feed blocks as unreachable rather than as a missing update', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const {updater} = createUpdater(store, {fluxer_renderer: RENDERER_SHA}, {respond: unreachableFeed()});
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-update-required');
assert.equal(outcome.reason, 'nothing-installed');
assert.equal(outcome.updateServerUnreachable, true);
});
test('a package url the policy refuses names its cause in the block and in a report', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const url = `http://192.168.139.107:48780/desktop/canary/modules/fluxer_renderer/${RENDERER_SHA}/package.br`;
const {updater, reports} = createUpdater(
store,
{fluxer_renderer: RENDERER_SHA},
{bounds: {fluxer_renderer: {url}}},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'blocked-update-required');
assert.equal(outcome.reason, 'required-module-unavailable');
assert.equal(outcome.updateServerUnreachable, false);
assert.match(outcome.message, /not https: http:\/\/192\.168\.139\.107/u);
const rejected = reports.find((report) => report.type === 'package-rejected');
assert.equal(rejected?.module, 'fluxer_renderer');
assert.match(rejected?.message ?? '', /192\.168\.139\.107/u);
const blocked = reports.find((report) => report.type === 'blocked');
assert.notEqual(blocked?.error, null);
});
});
describe('ModuleUpdater after a crash-loop rollback', () => {
async function rollBackFrom(store, modules) {
for (let launch = 0; launch < 2; launch += 1) {
assert.equal((await createUpdater(store, modules).updater.run()).status, 'launching');
}
const reverted = await createUpdater(store, modules).updater.run();
assert.equal(reverted.rolledBack, true);
return reverted;
}
test('the next boot keeps the reverted set instead of reinstalling the build that crashed', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer two']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
await store.commit({fluxer_renderer: NEXT_RENDERER_SHA});
const modules = {fluxer_renderer: NEXT_RENDERER_SHA};
await rollBackFrom(store, modules);
const {updater, requests} = createUpdater(store, modules);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.equal(outcome.rolledBack, false);
assert.deepEqual(outcome.committed, {fluxer_renderer: RENDERER_SHA});
assert.deepEqual(
requests.filter((url) => String(url).endsWith('.br')),
[],
);
});
test('a manifest that moves past the rejected build clears the rejection', async () => {
const userDataPath = createUserData();
const storeRoot = getModuleStoreRoot(userDataPath);
const store = await openStore(userDataPath);
installModuleOnDisk(storeRoot, 'fluxer_renderer', RENDERER_SHA, [['index.js', 'renderer']]);
installModuleOnDisk(storeRoot, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.js', 'renderer two']]);
await store.commit({fluxer_renderer: RENDERER_SHA});
await store.commit({fluxer_renderer: NEXT_RENDERER_SHA});
await rollBackFrom(store, {fluxer_renderer: NEXT_RENDERER_SHA});
assert.equal(store.isRejected('fluxer_renderer', NEXT_RENDERER_SHA), true);
await createUpdater(store, {fluxer_renderer: OVERLAY_SHA}, {metadataVersion: 2, respond: missingPackages()})
.updater.run()
.catch(() => undefined);
assert.equal(store.isRejected('fluxer_renderer', NEXT_RENDERER_SHA), false);
});
});
describe('ModuleUpdater with a renderer bundled in the shell', () => {
const OLDER_VERSION = '2026.822.9';
async function storeWithOlderRenderer() {
const store = await openStore(createUserData());
installModuleOnDisk(store.root, 'fluxer_renderer', RENDERER_SHA, [['index.html', 'renderer one']], OLDER_VERSION);
await store.commit({fluxer_renderer: RENDERER_SHA});
await store.recordManifestFetch({
etag: null,
fetchedAt: new Date(NOW).toISOString(),
manifest: {
feed: {releaseChannel: RELEASE_CHANNEL, platform: PLATFORM, arch: ARCH},
metadataVersion: 1,
manifestSha256: 'c'.repeat(64),
},
floor: {fluxer_renderer: NEXT_RENDERER_SHA},
});
return store;
}
test('a new shell over an older installed renderer launches the bundle with the feed down', async () => {
const store = await storeWithOlderRenderer();
const {updater, reports} = createUpdater(
store,
{},
{bundledRendererVersion: SHELL_VERSION, forceStartupUpdate: true, respond: unreachableFeed()},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'unreachable-launch');
assert.deepEqual(outcome.committed, {});
assert.deepEqual(store.getCommitted(), {});
assert.deepEqual(store.getState().previous, {});
assert.equal(
reports.some((report) => report.type === 'bundled-renderer-preferred'),
true,
);
assert.deepEqual((await updater.selectServedModules(outcome.committed)).renderer, {
source: 'bundled',
version: SHELL_VERSION,
bundledVersion: SHELL_VERSION,
});
});
test('a feed renderer the bundle already covers is never downloaded', async () => {
const store = await storeWithOlderRenderer();
const {updater, requests} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{bundledRendererVersion: SHELL_VERSION, forceStartupUpdate: true, metadataVersion: 2},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {});
assert.equal(
requests.some((url) => String(url).endsWith('package.br')),
false,
);
assert.deepEqual(store.getState().floor, {});
assert.deepEqual(await updater.checkForUpdate(), {
shellLatestVersion: SHELL_VERSION,
shellNewer: false,
modulesChanged: false,
});
});
test('a feed renderer newer than the bundle is still fetched', async () => {
const store = await openStore(createUserData());
const {updater, requests} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{bundledRendererVersion: OLDER_VERSION, respond: missingPackages()},
);
await updater.run();
assert.equal(
requests.some((url) => String(url).endsWith(`${NEXT_RENDERER_SHA}/package.br`)),
true,
);
});
test('a renderer module that keeps failing to boot is dropped for the bundle, never relaunched forever', async () => {
const store = await openStore(createUserData());
installModuleOnDisk(store.root, 'fluxer_renderer', NEXT_RENDERER_SHA, [['index.html', 'broken']], '2026.900.1');
await store.commit({fluxer_renderer: NEXT_RENDERER_SHA});
const served = [];
for (let launch = 0; launch < 4; launch += 1) {
const {updater} = createUpdater(
store,
{},
{bundledRendererVersion: SHELL_VERSION, platform: 'linux', respond: unreachableFeed()},
);
const outcome = await updater.run();
served.push((await updater.selectServedModules(outcome.committed)).renderer.source);
}
assert.deepEqual(served, ['module', 'module', 'bundled', 'bundled']);
assert.equal(store.isRejected('fluxer_renderer', NEXT_RENDERER_SHA), true);
});
test('a committed on-demand module whose files are gone never blocks an offline launch', async () => {
for (const platform of ['darwin', 'linux']) {
const store = await openStore(createUserData());
const directory = installModuleOnDisk(store.root, 'fluxer_fonts_jp', OVERLAY_SHA, [['a.woff2', 'font']], '0.0.0');
await store.commit({fluxer_fonts_jp: OVERLAY_SHA});
await rm(directory, {recursive: true, force: true});
const {updater} = createUpdater(
store,
{},
{bundledRendererVersion: SHELL_VERSION, platform, respond: unreachableFeed()},
);
const outcome = await updater.run();
assert.equal(outcome.status, platform === 'linux' ? 'launching' : 'unreachable-launch', platform);
assert.deepEqual(outcome.committed, {}, platform);
}
});
test('a newer feed renderer whose package is missing launches the bundle instead of blocking', async () => {
const store = await openStore(createUserData());
const {updater} = createUpdater(
store,
{fluxer_renderer: NEXT_RENDERER_SHA},
{bundledRendererVersion: OLDER_VERSION, respond: missingPackages()},
);
const outcome = await updater.run();
assert.equal(outcome.status, 'launching');
assert.deepEqual(outcome.committed, {});
assert.deepEqual(await updater.checkForUpdate(), {
shellLatestVersion: SHELL_VERSION,
shellNewer: false,
modulesChanged: true,
});
});
test('a packaged bundle with no readable version still outranks an installed module', async () => {
const store = await openStore(createUserData());
installModuleOnDisk(store.root, 'fluxer_renderer', RENDERER_SHA, [['index.html', 'old']], OLDER_VERSION);
await store.commit({fluxer_renderer: RENDERER_SHA});
const updater = new ModuleUpdater({
store,
shellVersion: SHELL_VERSION,
releaseChannel: RELEASE_CHANNEL,
platform: 'linux',
arch: ARCH,
packageOrigin: PACKAGE_ORIGIN,
hasOfflineRenderer: true,
bundledRendererVersion: 'dev',
preferUnversionedBundle: true,
fetch: unreachableFeed(),
sleep: async () => {},
random: () => 0,
now: () => NOW,
});
const outcome = await updater.run();
assert.equal((await updater.selectServedModules(outcome.committed)).renderer.source, 'bundled');
assert.deepEqual(store.getCommitted(), {});
});
test('a Linux launch never waits on a shell download, a required security update still does', async () => {
const attempts = [];
const selfUpdateShellFirst = async (latestVersion) => {
attempts.push(latestVersion);
};
const store = await openStore(createUserData());
const {updater} = createUpdater(
store,
{},
{
bundledRendererVersion: SHELL_VERSION,
platform: 'linux',
forceStartupUpdate: true,
shellLatest: '2026.900.1',
shellMinimum: '0.0.0',
selfUpdateShellFirst,
},
);
assert.equal((await updater.run()).status, 'launching');
assert.deepEqual(attempts, []);
const mac = createUpdater(
await openStore(createUserData()),
{},
{
bundledRendererVersion: SHELL_VERSION,
forceStartupUpdate: true,
shellLatest: '2026.900.1',
shellMinimum: '0.0.0',
selfUpdateShellFirst,
},
);
await mac.updater.run();
assert.deepEqual(attempts, ['2026.900.1']);
});
});