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

1436 lines
56 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 {
cpSync,
existsSync,
mkdirSync,
mkdtempSync,
readdirSync,
readFileSync,
symlinkSync,
utimesSync,
writeFileSync,
} from 'node:fs';
import fsPromises, {rm} from 'node:fs/promises';
import {registerHooks} from 'node:module';
import os from 'node:os';
import path from 'node:path';
import {after, describe, test} from 'node:test';
import zlib from 'node:zlib';
const DESKTOP_SRC = new URL('../', import.meta.url);
const PACKAGES = new URL('../../../packages/', import.meta.url);
registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier.startsWith('@electron/')) {
return {shortCircuit: true, url: new URL(`${specifier.slice('@electron/'.length)}.ts`, DESKTOP_SRC).href};
}
if (specifier.startsWith('@fluxer/')) {
return {shortCircuit: true, url: new URL(`${specifier.slice('@fluxer/'.length)}.ts`, PACKAGES).href};
}
return nextResolve(specifier, context);
},
});
const {
MODULE_FILE_STAMPS_NAME,
MODULE_INCOMING_STORE_PREFIX,
MODULE_PACKAGE_MAX_AGE_MS,
MODULE_STATE_FILE_NAME,
MODULE_STATE_VERSION,
ModuleManifestEquivocationError,
ModuleManifestRollbackError,
ModuleStore,
ModuleStoreDownloadHashMismatchError,
ModuleStoreGarbageCollectionOrderError,
ModuleStoreInstallationMissingError,
getModuleStoreRoot,
} = await import('./ModuleStore.ts');
const {ModulePackageHashMismatchError} = await import('./ModulePackage.ts');
const BLOCK = 512;
const SHELL_VERSION = '2026.823.1';
const RELEASE_CHANNEL = 'canary';
const SOURCE_SHA = 'a'.repeat(40);
const MANIFEST_FEED = {releaseChannel: RELEASE_CHANNEL, platform: 'darwin', arch: 'arm64'};
const MANIFEST_FETCHED_AT = '2026-08-23T12:00:00.000Z';
const temporaryRoots = [];
after(async () => {
for (const root of temporaryRoots) {
await rm(root, {recursive: true, force: true});
}
});
function createUserData() {
const root = mkdtempSync(path.join(os.tmpdir(), 'module-store-'));
temporaryRoots.push(root);
return root;
}
function sha256(data) {
return createHash('sha256').update(data).digest('hex');
}
function octal(value, width) {
return `${value.toString(8).padStart(width - 1, '0')}\0`;
}
function ustarHeader(name, size) {
const header = Buffer.alloc(BLOCK);
header.write(name, 0, 100, 'utf8');
header.write(octal(0o644, 8), 100, 8, 'ascii');
header.write(octal(0, 8), 108, 8, 'ascii');
header.write(octal(0, 8), 116, 8, 'ascii');
header.write(octal(size, 12), 124, 12, 'ascii');
header.write(octal(0, 12), 136, 12, 'ascii');
header.write('0', 156, 1, 'ascii');
header.write('ustar\0', 257, 6, 'ascii');
header.write('00', 263, 2, 'ascii');
header.fill(0x20, 148, 156);
let sum = 0;
for (const byte of header) {
sum += byte;
}
header.write(octal(sum, 8), 148, 8, 'ascii');
return header;
}
function pad(size) {
const remainder = size % BLOCK;
return remainder === 0 ? Buffer.alloc(0) : Buffer.alloc(BLOCK - remainder);
}
function packModule(moduleName, entries, {declaredSha256 = {}} = {}) {
const files = entries.map(([relative, body]) => ({
path: relative,
sha256: declaredSha256[relative] ?? sha256(Buffer.from(body, 'utf8')),
bytes: Buffer.byteLength(body, 'utf8'),
}));
const manifest = Buffer.from(
`${JSON.stringify(
{
module: moduleName,
build_version: SHELL_VERSION,
release_channel: RELEASE_CHANNEL,
source_sha: SOURCE_SHA,
files,
},
null,
'\t',
)}\n`,
'utf8',
);
const chunks = [ustarHeader('module.json', manifest.length), manifest, pad(manifest.length)];
for (const [relative, body] of entries) {
const payload = Buffer.from(body, 'utf8');
chunks.push(ustarHeader(`files/${relative}`, payload.length), payload, pad(payload.length));
}
chunks.push(Buffer.alloc(BLOCK * 2));
const tar = Buffer.concat(chunks);
const packed = zlib.brotliCompressSync(tar, {
params: {[zlib.constants.BROTLI_PARAM_QUALITY]: 5, [zlib.constants.BROTLI_PARAM_SIZE_HINT]: tar.length},
});
return {module: moduleName, packed, sha256: sha256(packed), files};
}
function streamOf(buffer) {
return {
offset: 0,
chunks: (async function* stream() {
for (let offset = 0; offset < buffer.length; offset += 4096) {
yield buffer.subarray(offset, Math.min(offset + 4096, buffer.length));
}
})(),
};
}
async function openStore(userDataPath) {
return await ModuleStore.open({
root: getModuleStoreRoot(userDataPath),
shellVersion: SHELL_VERSION,
releaseChannel: RELEASE_CHANNEL,
});
}
function deferred() {
let resolve;
const promise = new Promise((resolvePromise) => {
resolve = resolvePromise;
});
return {promise, resolve};
}
async function markLaunched(store) {
return await store.markLaunchAttemptSucceeded(await store.recordLaunchAttempt());
}
async function install(store, packageFile) {
const installed = await store.installModule({
module: packageFile.module,
sha256: packageFile.sha256,
download: () => streamOf(packageFile.packed),
});
await store.settleInstalledModules();
return installed;
}
function manifestObservation(metadataVersion, manifestSha256) {
return {feed: MANIFEST_FEED, metadataVersion, manifestSha256};
}
async function recordManifest(store, metadataVersion, manifestSha256) {
return await store.recordManifestFetch({
etag: null,
fetchedAt: MANIFEST_FETCHED_AT,
manifest: manifestObservation(metadataVersion, manifestSha256),
});
}
function readState(userDataPath) {
return JSON.parse(readFileSync(path.join(getModuleStoreRoot(userDataPath), MODULE_STATE_FILE_NAME), 'utf8'));
}
const RENDERER_V1 = packModule('fluxer_renderer', [
['assets/index-v1.js', 'renderer one'],
['assets/style-v1.css', 'body{}'],
]);
const RENDERER_V2 = packModule('fluxer_renderer', [['assets/index-v2.js', 'renderer two']]);
const GRAMMARS_V1 = packModule('fluxer_grammars', [['assets/rust-v1.wasm', 'grammar one']]);
const RENDERER_BAD_MEMBER = packModule('fluxer_renderer', [['assets/index-v1.js', 'renderer one']], {
declaredSha256: {'assets/index-v1.js': 'e'.repeat(64)},
});
describe('ModuleStore', () => {
test('is beside the account store, never inside it', () => {
const userDataPath = createUserData();
const root = getModuleStoreRoot(userDataPath);
assert.equal(root, path.join(userDataPath, 'modules'));
assert.equal(path.dirname(root), userDataPath);
assert.equal(existsSync(path.join(userDataPath, 'desktop-app-store.sqlite3')), false);
});
test('says when the shell changed version since the store was last opened', async () => {
const userDataPath = createUserData();
assert.equal((await openStore(userDataPath)).shellVersionChanged, false);
assert.equal((await openStore(userDataPath)).shellVersionChanged, false);
const upgraded = await ModuleStore.open({
root: getModuleStoreRoot(userDataPath),
shellVersion: '2026.824.1',
releaseChannel: RELEASE_CHANNEL,
});
assert.equal(upgraded.shellVersionChanged, true);
assert.notEqual(
readState(userDataPath).shell_version,
'2026.824.1',
'a quit or crash before the modules converge has to force the update again on the next boot',
);
const reopened = await ModuleStore.open({
root: getModuleStoreRoot(userDataPath),
shellVersion: '2026.824.1',
releaseChannel: RELEASE_CHANNEL,
});
assert.equal(reopened.shellVersionChanged, true);
await reopened.recordShellVersionConverged();
assert.equal(readState(userDataPath).shell_version, '2026.824.1');
const converged = await ModuleStore.open({
root: getModuleStoreRoot(userDataPath),
shellVersion: '2026.824.1',
releaseChannel: RELEASE_CHANNEL,
});
assert.equal(converged.shellVersionChanged, false);
});
test('writes state.json atomically and leaves no temporary behind', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await install(store, GRAMMARS_V1);
const before = readState(userDataPath);
assert.deepEqual(before.committed, {});
await store.commit({fluxer_renderer: RENDERER_V1.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
const after = readState(userDataPath);
assert.equal(after.state_version, MODULE_STATE_VERSION);
assert.deepEqual(after.committed, {
fluxer_grammars: GRAMMARS_V1.sha256,
fluxer_renderer: RENDERER_V1.sha256,
});
assert.deepEqual(after.previous, {});
assert.equal(after.boot_attempt, 0);
const leftovers = readdirSync(getModuleStoreRoot(userDataPath)).filter((name) => name.endsWith('.tmp'));
assert.deepEqual(leftovers, []);
const reopened = await openStore(userDataPath);
assert.deepEqual(reopened.getCommitted(), after.committed);
});
test('moves the old committed map to previous on the next commit', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V2.sha256});
const state = readState(userDataPath);
assert.deepEqual(state.committed, {fluxer_renderer: RENDERER_V2.sha256});
assert.deepEqual(state.previous, {fluxer_renderer: RENDERER_V1.sha256});
});
test('a crash before the commit point leaves the old set committed', async () => {
const userDataPath = createUserData();
const first = await openStore(userDataPath);
await install(first, RENDERER_V1);
await first.commit({fluxer_renderer: RENDERER_V1.sha256});
const crashing = await openStore(userDataPath);
const staged = await install(crashing, RENDERER_V2);
assert.equal(existsSync(path.join(staged.directory, 'module.json')), true);
const rebooted = await openStore(userDataPath);
assert.deepEqual(rebooted.getCommitted(), {fluxer_renderer: RENDERER_V1.sha256});
const index = await rebooted.buildModuleIndex();
assert.deepEqual([...index.keys()].sort(), ['assets/index-v1.js', 'assets/style-v1.css']);
assert.equal(existsSync(staged.directory), true);
});
test('a download hash mismatch fails that module only and caches nothing', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await assert.rejects(
store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_V1.sha256,
download: () => streamOf(Buffer.from('not the package', 'utf8')),
}),
ModuleStoreDownloadHashMismatchError,
);
assert.equal(existsSync(store.getPackagePath(RENDERER_V1.sha256)), false);
assert.deepEqual(readdirSync(store.incomingDownloadRoot), []);
const installed = await install(store, GRAMMARS_V1);
assert.equal(existsSync(path.join(installed.directory, 'module.json')), true);
await store.commit({fluxer_grammars: GRAMMARS_V1.sha256});
assert.deepEqual(readState(userDataPath).committed, {fluxer_grammars: GRAMMARS_V1.sha256});
});
test('a member digest mismatch leaves the verified package cached for every later attempt', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
let downloads = 0;
const attempt = () =>
store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_BAD_MEMBER.sha256,
download: () => {
downloads += 1;
return streamOf(RENDERER_BAD_MEMBER.packed);
},
});
const memberMismatch = (error) =>
error instanceof ModulePackageHashMismatchError && error.message === 'hash mismatch: assets/index-v1.js';
const packagePath = store.getPackagePath(RENDERER_BAD_MEMBER.sha256);
await assert.rejects(attempt(), memberMismatch);
assert.equal(downloads, 1);
assert.equal(existsSync(packagePath), true);
await assert.rejects(attempt(), memberMismatch);
assert.equal(existsSync(packagePath), true);
await assert.rejects(attempt(), memberMismatch);
assert.equal(downloads, 1);
assert.deepEqual(readdirSync(store.storeRoot), []);
});
test('a cached package whose own digest no longer matches is dropped and downloaded again', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
let downloads = 0;
const attempt = () =>
store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_BAD_MEMBER.sha256,
download: () => {
downloads += 1;
return streamOf(RENDERER_BAD_MEMBER.packed);
},
});
const packagePath = store.getPackagePath(RENDERER_BAD_MEMBER.sha256);
await assert.rejects(attempt(), ModulePackageHashMismatchError);
assert.equal(downloads, 1);
writeFileSync(packagePath, 'not the package');
await assert.rejects(
attempt(),
(error) =>
error instanceof ModulePackageHashMismatchError && error.message.startsWith('package sha256 mismatch:'),
);
assert.equal(existsSync(packagePath), false);
await assert.rejects(attempt(), ModulePackageHashMismatchError);
assert.equal(downloads, 2);
assert.equal(existsSync(packagePath), true);
});
test('commit refuses when a store directory is missing', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await rm(installed.directory, {recursive: true, force: true});
await assert.rejects(store.commit({fluxer_renderer: RENDERER_V1.sha256}), ModuleStoreInstallationMissingError);
assert.deepEqual(readState(userDataPath).committed, {});
});
test('commit refuses to re-affirm a committed module whose directory was deleted', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await rm(installed.directory, {recursive: true, force: true});
await assert.rejects(store.commit({fluxer_renderer: RENDERER_V1.sha256}), ModuleStoreInstallationMissingError);
assert.deepEqual(readState(userDataPath).committed, {fluxer_renderer: RENDERER_V1.sha256});
});
test('commit refuses when module.json is unreadable', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
writeFileSync(path.join(installed.directory, 'module.json'), 'not json');
await assert.rejects(store.commit({fluxer_renderer: RENDERER_V1.sha256}), ModuleStoreInstallationMissingError);
});
test('garbage collection only runs after a successful launch', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await assert.rejects(store.collectGarbage(), ModuleStoreGarbageCollectionOrderError);
});
test('garbage collection preserves committed and previous', async () => {
const userDataPath = createUserData();
const earlierRun = await openStore(userDataPath);
const v1 = await install(earlierRun, RENDERER_V1);
await earlierRun.commit({fluxer_renderer: RENDERER_V1.sha256});
const v2 = await install(earlierRun, RENDERER_V2);
await earlierRun.commit({fluxer_renderer: RENDERER_V2.sha256});
const orphan = await install(earlierRun, GRAMMARS_V1);
const store = await openStore(userDataPath);
const incoming = path.join(store.storeRoot, `${MODULE_INCOMING_STORE_PREFIX}${'b'.repeat(64)}`);
mkdirSync(incoming, {recursive: true});
const stale = path.join(store.downloadRoot, `${'c'.repeat(64)}.br`);
writeFileSync(stale, 'stale');
const staleSeconds = (Date.now() - MODULE_PACKAGE_MAX_AGE_MS - 60_000) / 1000;
utimesSync(stale, staleSeconds, staleSeconds);
const fresh = store.getPackagePath(RENDERER_V2.sha256);
assert.equal(existsSync(fresh), true);
await markLaunched(store);
const result = await store.collectGarbage();
assert.equal(existsSync(v1.directory), true);
assert.equal(existsSync(v2.directory), true);
assert.equal(existsSync(orphan.directory), false);
assert.equal(existsSync(incoming), false);
assert.equal(existsSync(stale), false);
assert.equal(existsSync(fresh), true);
assert.equal(result.removedDirectories.includes(incoming), true);
assert.equal(result.removedPackages.includes(stale), true);
});
test('garbage collection keeps an installation that has not been committed yet', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await markLaunched(store);
const pending = await install(store, GRAMMARS_V1);
await store.collectGarbage();
assert.equal(existsSync(path.join(pending.directory, 'module.json')), true);
await store.mergeCommitted({fluxer_grammars: GRAMMARS_V1.sha256});
assert.deepEqual(readState(userDataPath).committed, {
fluxer_grammars: GRAMMARS_V1.sha256,
fluxer_renderer: RENDERER_V1.sha256,
});
});
test('garbage collection waits for an in-flight install instead of deleting its staging tree', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await markLaunched(store);
const staging = path.join(store.storeRoot, `${MODULE_INCOMING_STORE_PREFIX}${GRAMMARS_V1.sha256}`);
const rename = fsPromises.rename;
const staged = deferred();
const release = deferred();
fsPromises.rename = async (from, to) => {
if (from === staging) {
staged.resolve();
await release.promise;
}
return await rename(from, to);
};
try {
const installing = install(store, GRAMMARS_V1);
await staged.promise;
const collecting = store.collectGarbage();
await new Promise((resolve) => setTimeout(resolve, 50));
assert.equal(existsSync(staging), true);
release.resolve();
const installed = await installing;
const result = await collecting;
assert.equal(existsSync(path.join(installed.directory, 'module.json')), true);
assert.equal(result.removedDirectories.includes(staging), false);
} finally {
fsPromises.rename = rename;
}
});
test('garbage collection keeps a cached package that an install is already using', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await markLaunched(store);
const reclaimed = await install(store, GRAMMARS_V1);
await rm(reclaimed.directory, {recursive: true, force: true});
const cached = store.getPackagePath(GRAMMARS_V1.sha256);
const staleSeconds = (Date.now() - MODULE_PACKAGE_MAX_AGE_MS - 60_000) / 1000;
utimesSync(cached, staleSeconds, staleSeconds);
const readdir = fsPromises.readdir;
const sweeping = deferred();
const release = deferred();
let held = false;
fsPromises.readdir = async (directory, options) => {
if (directory === store.storeRoot && !held) {
held = true;
sweeping.resolve();
await release.promise;
}
return await readdir(directory, options);
};
try {
const collecting = store.collectGarbage();
await sweeping.promise;
const installing = install(store, GRAMMARS_V1);
await new Promise((resolve) => setTimeout(resolve, 50));
release.resolve();
const result = await collecting;
const installed = await installing;
assert.equal(existsSync(cached), true);
assert.equal(result.removedPackages.includes(cached), false);
assert.equal(existsSync(path.join(installed.directory, 'module.json')), true);
} finally {
fsPromises.readdir = readdir;
}
});
test('a second rollback onto the same set reports no rollback, so the update path still runs', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V2.sha256});
for (let boot = 0; boot < 2; boot += 1) {
const failing = await openStore(userDataPath);
await failing.beginBootAttempt();
await failing.recordLaunchAttempt();
}
const rolledBackStore = await openStore(userDataPath);
const rollback = await rolledBackStore.beginBootAttempt();
assert.equal(rollback.rolledBack, true);
assert.deepEqual(rollback.committed, {fluxer_renderer: RENDERER_V1.sha256});
await rolledBackStore.recordLaunchAttempt();
for (let boot = 0; boot < 1; boot += 1) {
const failing = await openStore(userDataPath);
await failing.beginBootAttempt();
await failing.recordLaunchAttempt();
}
const secondRollback = await (await openStore(userDataPath)).beginBootAttempt();
assert.equal(secondRollback.rolledBack, false);
assert.equal(secondRollback.bootAttempt, 0);
assert.deepEqual(secondRollback.committed, {fluxer_renderer: RENDERER_V1.sha256});
assert.equal(readState(userDataPath).boot_attempt, 0);
});
test('a first-ever committed set is never rolled back into an empty committed map', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
for (let boot = 0; boot < 2; boot += 1) {
const failing = await openStore(userDataPath);
await failing.beginBootAttempt();
await failing.recordLaunchAttempt();
}
const rollback = await (await openStore(userDataPath)).beginBootAttempt();
assert.equal(rollback.rolledBack, false);
assert.equal(rollback.bootAttempt, 0);
assert.deepEqual(rollback.committed, {fluxer_renderer: RENDERER_V1.sha256});
assert.deepEqual(readState(userDataPath).committed, {fluxer_renderer: RENDERER_V1.sha256});
});
test('rolls back to the previous set after two failed boots', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V2.sha256});
const firstStore = await openStore(userDataPath);
const firstBoot = await firstStore.beginBootAttempt();
assert.deepEqual(firstBoot, {
rolledBack: false,
bootAttempt: 0,
committed: {fluxer_renderer: RENDERER_V2.sha256},
});
await firstStore.recordLaunchAttempt();
assert.equal(readState(userDataPath).boot_attempt, 1);
const secondStore = await openStore(userDataPath);
const secondBoot = await secondStore.beginBootAttempt();
assert.equal(secondBoot.rolledBack, false);
assert.equal(secondBoot.bootAttempt, 1);
await secondStore.recordLaunchAttempt();
assert.equal(readState(userDataPath).boot_attempt, 2);
const thirdStore = await openStore(userDataPath);
const thirdBoot = await thirdStore.beginBootAttempt();
assert.equal(thirdBoot.rolledBack, true);
assert.equal(thirdBoot.bootAttempt, 0);
assert.deepEqual(thirdBoot.committed, {fluxer_renderer: RENDERER_V1.sha256});
assert.deepEqual(readState(userDataPath).committed, {fluxer_renderer: RENDERER_V1.sha256});
const index = await thirdStore.buildModuleIndex();
assert.deepEqual([...index.keys()].sort(), ['assets/index-v1.js', 'assets/style-v1.css']);
await markLaunched(thirdStore);
assert.equal(readState(userDataPath).boot_attempt, 0);
const fourthBoot = await (await openStore(userDataPath)).beginBootAttempt();
assert.equal(fourthBoot.rolledBack, false);
assert.equal(fourthBoot.bootAttempt, 0);
});
test('a boot that never launches leaves the attempt counter alone, so blocking cannot downgrade anyone', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V2.sha256});
for (let attempt = 0; attempt < 5; attempt += 1) {
const blocked = await openStore(userDataPath);
assert.equal((await blocked.beginBootAttempt()).rolledBack, false);
}
assert.equal(readState(userDataPath).boot_attempt, 0);
assert.deepEqual(readState(userDataPath).committed, {fluxer_renderer: RENDERER_V2.sha256});
});
test('the attempt counter is fsynced before the handoff, not after it', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await store.recordLaunchAttempt();
assert.equal(readState(userDataPath).boot_attempt, 1);
assert.equal(store.getState().boot_attempt, 1);
});
test('builds an index map across every committed module', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const renderer = await install(store, RENDERER_V1);
const grammars = await install(store, GRAMMARS_V1);
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V1.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
const index = await store.buildModuleIndex();
assert.equal(index.size, 3);
assert.equal(index.get('assets/index-v1.js'), path.join(renderer.directory, 'assets', 'index-v1.js'));
assert.equal(index.get('assets/style-v1.css'), path.join(renderer.directory, 'assets', 'style-v1.css'));
assert.equal(index.get('assets/rust-v1.wasm'), path.join(grammars.directory, 'assets', 'rust-v1.wasm'));
assert.equal(index.has('assets/index-v2.js'), false);
for (const absolute of index.values()) {
assert.equal(existsSync(absolute), true);
}
assert.equal(readFileSync(index.get('assets/rust-v1.wasm'), 'utf8'), 'grammar one');
});
test('builds the index from one committed generation even when a merge lands mid-scan', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const renderer = await install(store, RENDERER_V1);
await install(store, GRAMMARS_V1);
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V1.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
const grammarsDirectory = store.getModuleDirectory('fluxer_grammars', GRAMMARS_V1.sha256);
const lstat = fsPromises.lstat;
const scanning = deferred();
const release = deferred();
let held = false;
fsPromises.lstat = async (target, ...rest) => {
if (target === grammarsDirectory && !held) {
held = true;
scanning.resolve();
await release.promise;
}
return await lstat(target, ...rest);
};
try {
const building = store.buildModuleIndex();
await scanning.promise;
await store.mergeCommitted({fluxer_renderer: RENDERER_V2.sha256});
release.resolve();
const index = await building;
assert.deepEqual([...index.keys()].sort(), ['assets/index-v1.js', 'assets/rust-v1.wasm', 'assets/style-v1.css']);
assert.equal(index.get('assets/index-v1.js'), path.join(renderer.directory, 'assets', 'index-v1.js'));
} finally {
fsPromises.lstat = lstat;
}
});
test('a served file swapped for different bytes of the same length is no longer installed', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const served = path.join(installed.directory, 'assets', 'index-v1.js');
writeFileSync(served, 'RENDERER ONE');
assert.equal(readFileSync(served).length, Buffer.byteLength('renderer one', 'utf8'));
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), false);
await assert.rejects(store.buildModuleIndex(), ModuleStoreInstallationMissingError);
await assert.rejects(store.commit({fluxer_renderer: RENDERER_V1.sha256}), ModuleStoreInstallationMissingError);
});
test('a repair reinstall keeps the committed tree served until the replacement is renamed in', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const corrupted = path.join(installed.directory, 'assets', 'index-v1.js');
const untouched = path.join(installed.directory, 'assets', 'style-v1.css');
writeFileSync(corrupted, 'RENDERER ONE');
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), false);
const incoming = path.join(store.storeRoot, `${MODULE_INCOMING_STORE_PREFIX}${RENDERER_V1.sha256}`);
const mkdir = fsPromises.mkdir;
const servedDuringExtraction = [];
fsPromises.mkdir = async (target, ...rest) => {
if (typeof target === 'string' && (target === incoming || target.startsWith(`${incoming}${path.sep}`))) {
servedDuringExtraction.push(existsSync(untouched));
}
return await mkdir(target, ...rest);
};
let repaired;
try {
repaired = await install(store, RENDERER_V1);
} finally {
fsPromises.mkdir = mkdir;
}
assert.equal(servedDuringExtraction.length > 0, true);
assert.deepEqual(
servedDuringExtraction.filter((present) => !present),
[],
);
assert.equal(repaired.directory, installed.directory);
assert.equal(readFileSync(corrupted, 'utf8'), 'renderer one');
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), true);
assert.deepEqual(
readdirSync(store.storeRoot).filter((name) => name.startsWith('.')),
[],
);
const index = await store.buildModuleIndex();
assert.deepEqual([...index.keys()].sort(), ['assets/index-v1.js', 'assets/style-v1.css']);
});
test('a manifest below the highest metadata_version seen for its feed is refused as a downgrade', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await recordManifest(store, 7, 'd'.repeat(64));
assert.throws(
() => store.requireManifestFresh(manifestObservation(6, 'e'.repeat(64))),
ModuleManifestRollbackError,
);
await assert.rejects(recordManifest(store, 6, 'e'.repeat(64)), ModuleManifestRollbackError);
const highWater = readState(userDataPath).manifest_high_water;
assert.equal(highWater.length, 1);
assert.equal(highWater[0].metadata_version, 7);
assert.equal(highWater[0].manifest_sha256, 'd'.repeat(64));
await recordManifest(store, 8, 'f'.repeat(64));
assert.equal(readState(userDataPath).manifest_high_water[0].metadata_version, 8);
});
test('a second manifest body at an already seen metadata_version is refused as equivocation', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await recordManifest(store, 7, 'd'.repeat(64));
assert.throws(
() => store.requireManifestFresh(manifestObservation(7, 'e'.repeat(64))),
ModuleManifestEquivocationError,
);
await assert.rejects(recordManifest(store, 7, 'e'.repeat(64)), ModuleManifestEquivocationError);
store.requireManifestFresh(manifestObservation(7, 'd'.repeat(64)));
await recordManifest(store, 7, 'd'.repeat(64));
const highWater = readState(userDataPath).manifest_high_water;
assert.equal(highWater.length, 1);
assert.equal(highWater[0].manifest_sha256, 'd'.repeat(64));
});
test('a superseded launch attempt cannot clear the boot counter that drives rollback', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const superseded = await store.recordLaunchAttempt();
const active = await store.recordLaunchAttempt();
assert.equal(await store.markLaunchAttemptSucceeded(superseded), false);
assert.equal(readState(userDataPath).boot_attempt, 2);
assert.equal(store.getState().boot_attempt, 2);
assert.equal(await store.markLaunchAttemptSucceeded(active), true);
assert.equal(readState(userDataPath).boot_attempt, 0);
});
test('a store directory whose module.json names another module is not an installation of it', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const renderer = await install(store, RENDERER_V1);
const impostor = store.getModuleDirectory('fluxer_grammars', RENDERER_V1.sha256);
mkdirSync(path.dirname(impostor), {recursive: true});
cpSync(renderer.directory, impostor, {recursive: true});
assert.equal(await store.isInstalled('fluxer_grammars', RENDERER_V1.sha256), false);
assert.equal(await store.getInstalledManifest('fluxer_grammars', RENDERER_V1.sha256), null);
await assert.rejects(store.commit({fluxer_grammars: RENDERER_V1.sha256}), ModuleStoreInstallationMissingError);
});
test('a symlinked store directory is never served, even when it points at a real installation', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const renderer = await install(store, RENDERER_V1);
const outside = path.join(userDataPath, 'outside-the-store');
cpSync(renderer.directory, outside, {recursive: true});
const linkedSha256 = 'b'.repeat(64);
const linked = store.getModuleDirectory('fluxer_renderer', linkedSha256);
symlinkSync(outside, linked, 'dir');
assert.equal(existsSync(path.join(linked, 'module.json')), true);
assert.equal(await store.isInstalled('fluxer_renderer', linkedSha256), false);
await assert.rejects(store.commit({fluxer_renderer: linkedSha256}), ModuleStoreInstallationMissingError);
});
test('an index built over a deleted committed module fails loudly', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await rm(installed.directory, {recursive: true, force: true});
await assert.rejects(store.buildModuleIndex(), ModuleStoreInstallationMissingError);
});
});
function chunksOf(buffer) {
return (async function* stream() {
for (let offset = 0; offset < buffer.length; offset += 4096) {
yield buffer.subarray(offset, Math.min(offset + 4096, buffer.length));
}
})();
}
function interruptedAfter(buffer, bytes) {
return (async function* stream() {
yield buffer.subarray(0, bytes);
throw new Error('connection reset');
})();
}
describe('ModuleStore resumable package downloads', () => {
test('an interrupted download keeps its bytes and the next attempt resumes from them', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const cut = Math.floor(RENDERER_V1.packed.length / 2);
const offsets = [];
await assert.rejects(
store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_V1.sha256,
download: (resumeFrom) => {
offsets.push(resumeFrom);
return {offset: 0, chunks: interruptedAfter(RENDERER_V1.packed, cut)};
},
}),
);
assert.equal(readFileSync(store.getPartialPackagePath(RENDERER_V1.sha256)).length, cut);
const installed = await store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_V1.sha256,
download: (resumeFrom) => {
offsets.push(resumeFrom);
return {offset: resumeFrom, chunks: chunksOf(RENDERER_V1.packed.subarray(resumeFrom))};
},
});
assert.deepEqual(offsets, [0, cut]);
assert.equal(existsSync(path.join(installed.directory, 'module.json')), true);
assert.equal(existsSync(store.getPartialPackagePath(RENDERER_V1.sha256)), false);
assert.equal(existsSync(store.getPackagePath(RENDERER_V1.sha256)), true);
});
test('a server that ignores the range restarts the download from the first byte', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
mkdirSync(store.incomingDownloadRoot, {recursive: true});
writeFileSync(store.getPartialPackagePath(RENDERER_V1.sha256), RENDERER_V1.packed.subarray(0, 100));
const installed = await store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_V1.sha256,
download: () => ({offset: 0, chunks: chunksOf(RENDERER_V1.packed)}),
});
assert.equal(existsSync(path.join(installed.directory, 'module.json')), true);
});
test('a resumed partial that does not hash to the package is dropped so the retry starts clean', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
mkdirSync(store.incomingDownloadRoot, {recursive: true});
const partial = store.getPartialPackagePath(RENDERER_V1.sha256);
writeFileSync(partial, Buffer.alloc(100, 7));
await assert.rejects(
store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_V1.sha256,
download: (resumeFrom) => ({offset: resumeFrom, chunks: chunksOf(RENDERER_V1.packed.subarray(resumeFrom))}),
}),
ModuleStoreDownloadHashMismatchError,
);
assert.equal(existsSync(partial), false);
});
test('a download that resumes at an offset nobody asked for is refused', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await assert.rejects(
store.installModule({
module: 'fluxer_renderer',
sha256: RENDERER_V1.sha256,
download: () => ({offset: 10, chunks: chunksOf(RENDERER_V1.packed.subarray(10))}),
}),
/resumed at byte 10, expected 0/u,
);
});
});
describe('ModuleStore rejected module sets', () => {
async function bootTwiceWithoutReady(userDataPath) {
for (let boot = 0; boot < 2; boot += 1) {
const store = await openStore(userDataPath);
await store.beginBootAttempt();
await store.recordLaunchAttempt();
}
return await openStore(userDataPath);
}
test('a rollback that reverts remembers the module set it rejected', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V2.sha256});
const rebooted = await bootTwiceWithoutReady(userDataPath);
const attempt = await rebooted.beginBootAttempt();
assert.equal(attempt.rolledBack, true);
assert.deepEqual(readState(userDataPath).rejected, {fluxer_renderer: RENDERER_V2.sha256});
assert.equal(rebooted.isRejected('fluxer_renderer', RENDERER_V2.sha256), true);
assert.equal(rebooted.isRejected('fluxer_renderer', RENDERER_V1.sha256), false);
});
test('the rejection holds while the manifest still advertises that build and clears once it moves on', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await install(store, RENDERER_V2);
await store.commit({fluxer_renderer: RENDERER_V2.sha256});
const rebooted = await bootTwiceWithoutReady(userDataPath);
await rebooted.beginBootAttempt();
await rebooted.recordManifestFetch({
etag: null,
fetchedAt: MANIFEST_FETCHED_AT,
manifest: manifestObservation(1, 'a'.repeat(64)),
advertised: {fluxer_renderer: RENDERER_V2.sha256},
});
assert.equal(rebooted.isRejected('fluxer_renderer', RENDERER_V2.sha256), true);
await rebooted.recordManifestFetch({
etag: null,
fetchedAt: MANIFEST_FETCHED_AT,
manifest: manifestObservation(2, 'b'.repeat(64)),
advertised: {fluxer_renderer: 'c'.repeat(64)},
});
assert.equal(rebooted.isRejected('fluxer_renderer', RENDERER_V2.sha256), false);
assert.deepEqual(readState(userDataPath).rejected, {});
});
test('a state file written before rejections existed still opens', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const statePath = path.join(store.root, MODULE_STATE_FILE_NAME);
const legacy = readState(userDataPath);
delete legacy.rejected;
writeFileSync(statePath, JSON.stringify(legacy));
const reopened = await openStore(userDataPath);
assert.deepEqual(reopened.getState().rejected, {});
});
});
describe('ModuleStore shell ranges beside older shells', () => {
test('state written by this shell stays readable by shells that require linux_security_minimum', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const written = readState(userDataPath);
assert.equal(Object.hasOwn(written, 'linux_security_minimum'), true);
assert.equal(written.linux_security_minimum, null);
assert.equal(written.state_version, MODULE_STATE_VERSION);
});
test('state that persisted a security minimum still opens', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const legacy = readState(userDataPath);
legacy.linux_security_minimum = {version: '2026.900.1', required_modules: ['fluxer_renderer']};
delete legacy.shell_ranges;
writeFileSync(path.join(store.root, MODULE_STATE_FILE_NAME), JSON.stringify(legacy));
const reopened = await openStore(userDataPath);
assert.equal(reopened.recoveredUnreadableState, null);
assert.deepEqual(reopened.getCommitted(), {fluxer_renderer: RENDERER_V1.sha256});
assert.deepEqual(reopened.getState().shell_ranges, {});
await reopened.commit({fluxer_renderer: RENDERER_V1.sha256});
assert.equal(readState(userDataPath).linux_security_minimum, null);
});
test('shell ranges survive a reopen and garbage collection prunes them', async () => {
const userDataPath = createUserData();
const earlierRun = await openStore(userDataPath);
await install(earlierRun, RENDERER_V1);
await earlierRun.commit({fluxer_renderer: RENDERER_V1.sha256});
const orphan = await install(earlierRun, GRAMMARS_V1);
await earlierRun.recordShellRanges([
{module: 'fluxer_renderer', sha256: RENDERER_V1.sha256, minimum: SHELL_VERSION, maximum: null},
{module: 'fluxer_grammars', sha256: GRAMMARS_V1.sha256, minimum: null, maximum: '2026.900.1'},
{module: 'fluxer_renderer', sha256: 'd'.repeat(64), minimum: '2026.900.1', maximum: null},
]);
const store = await openStore(userDataPath);
assert.deepEqual(store.getShellRange('fluxer_renderer', RENDERER_V1.sha256), {
minimum_shell_version: SHELL_VERSION,
maximum_shell_version: null,
});
assert.deepEqual(store.getShellRange('fluxer_grammars', GRAMMARS_V1.sha256), {
minimum_shell_version: null,
maximum_shell_version: '2026.900.1',
});
await markLaunched(store);
await store.collectGarbage();
assert.equal(existsSync(orphan.directory), false);
assert.deepEqual(Object.keys(readState(userDataPath).shell_ranges), [`fluxer_renderer/${RENDERER_V1.sha256}`]);
assert.equal(store.getShellRange('fluxer_grammars', GRAMMARS_V1.sha256), null);
});
test('a recorded range that no longer parses is dropped instead of discarding the state', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const state = readState(userDataPath);
state.shell_ranges = {
[`fluxer_renderer/${'d'.repeat(64)}`]: {minimum_shell_version: 'not a version', maximum_shell_version: null},
[`fluxer_renderer/${'e'.repeat(64)}`]: {minimum_shell_version: SHELL_VERSION, maximum_shell_version: null},
};
writeFileSync(path.join(store.root, MODULE_STATE_FILE_NAME), JSON.stringify(state));
const reopened = await openStore(userDataPath);
assert.equal(reopened.recoveredUnreadableState, null);
assert.deepEqual(Object.keys(reopened.getState().shell_ranges), [`fluxer_renderer/${'e'.repeat(64)}`]);
});
test('retireIncompatible drops non-runnable pairs from committed, previous and floor', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await install(store, RENDERER_V2);
await install(store, GRAMMARS_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await store.commit({fluxer_renderer: RENDERER_V2.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
await store.recordManifestFetch({
etag: null,
fetchedAt: MANIFEST_FETCHED_AT,
manifest: manifestObservation(1, 'a'.repeat(64)),
floor: {fluxer_renderer: RENDERER_V2.sha256, fluxer_grammars: GRAMMARS_V1.sha256},
});
const retired = await store.retireIncompatible(
(moduleName, digest) => !(moduleName === 'fluxer_renderer' && digest === RENDERER_V2.sha256),
);
assert.deepEqual(retired, ['fluxer_renderer']);
assert.deepEqual(store.getCommitted(), {fluxer_grammars: GRAMMARS_V1.sha256});
assert.deepEqual(store.getState().previous, {fluxer_renderer: RENDERER_V1.sha256});
assert.deepEqual(store.getState().floor, {fluxer_grammars: GRAMMARS_V1.sha256});
assert.deepEqual(await store.retireIncompatible(() => true), []);
});
});
describe('ModuleStore on Windows sharing violations', () => {
test('a rename that an antivirus scanner briefly locks is retried instead of failing the install', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const platform = Object.getOwnPropertyDescriptor(process, 'platform');
const rename = fsPromises.rename;
let failures = 0;
Object.defineProperty(process, 'platform', {...platform, value: 'win32'});
fsPromises.rename = async (...args) => {
if (failures < 2) {
failures += 1;
throw Object.assign(new Error('operation not permitted'), {code: 'EPERM'});
}
return await rename(...args);
};
try {
await store.recordLaunchAttempt();
} finally {
fsPromises.rename = rename;
Object.defineProperty(process, 'platform', platform);
}
assert.equal(failures, 2);
assert.equal(readState(userDataPath).boot_attempt, 1);
});
test('the same lock on another platform is not retried', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const rename = fsPromises.rename;
let calls = 0;
fsPromises.rename = async () => {
calls += 1;
throw Object.assign(new Error('operation not permitted'), {code: 'EPERM'});
};
try {
await assert.rejects(store.recordLaunchAttempt(), /failed to publish/u);
} finally {
fsPromises.rename = rename;
}
assert.equal(calls, process.platform === 'win32' ? 7 : 1);
});
});
describe('ModuleStore boot checks without rehashing', () => {
async function countServedFileOpens(directory, operation) {
const open = fsPromises.open;
const opened = [];
fsPromises.open = async (target, ...rest) => {
if (
typeof target === 'string' &&
target.startsWith(`${directory}${path.sep}`) &&
path.basename(target) !== 'module.json' &&
!path.basename(target).startsWith(MODULE_FILE_STAMPS_NAME)
) {
opened.push(target);
}
return await open(target, ...rest);
};
try {
return {result: await operation(), opened};
} finally {
fsPromises.open = open;
}
}
test('an installed module is trusted from its persisted file stamps without reading a served file', async () => {
const userDataPath = createUserData();
const first = await openStore(userDataPath);
const installed = await install(first, RENDERER_V1);
await first.commit({fluxer_renderer: RENDERER_V1.sha256});
assert.equal(existsSync(path.join(installed.directory, MODULE_FILE_STAMPS_NAME)), true);
const store = await openStore(userDataPath);
const {result, opened} = await countServedFileOpens(installed.directory, async () => {
const isInstalled = await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256);
const index = await store.buildModuleIndex();
return {isInstalled, index};
});
assert.equal(result.isInstalled, true);
assert.deepEqual([...result.index.keys()].sort(), ['assets/index-v1.js', 'assets/style-v1.css']);
assert.deepEqual(opened, []);
});
test('a fresh install launches from its in-memory stamps and persists them only once the tree is durable', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await store.installModule({
module: RENDERER_V1.module,
sha256: RENDERER_V1.sha256,
download: () => streamOf(RENDERER_V1.packed),
});
const stampsPath = path.join(installed.directory, MODULE_FILE_STAMPS_NAME);
assert.equal(existsSync(stampsPath), false);
const {result, opened} = await countServedFileOpens(installed.directory, async () => {
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
return await store.buildModuleIndex();
});
assert.deepEqual([...result.keys()].sort(), ['assets/index-v1.js', 'assets/style-v1.css']);
assert.deepEqual(opened, []);
assert.equal(existsSync(stampsPath), false);
const attempt = await store.recordLaunchAttempt();
assert.equal(await store.markLaunchAttemptSucceeded(attempt), true);
await store.settleInstalledModules();
assert.equal(existsSync(stampsPath), true);
const reopened = await openStore(userDataPath);
const later = await countServedFileOpens(installed.directory, () =>
reopened.isInstalled('fluxer_renderer', RENDERER_V1.sha256),
);
assert.equal(later.result, true);
assert.deepEqual(later.opened, []);
});
test('a served file rewritten with the same length and its old mtime is still caught', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const served = path.join(installed.directory, 'assets', 'index-v1.js');
const before = await fsPromises.stat(served);
await new Promise((resolve) => setTimeout(resolve, 20));
writeFileSync(served, 'RENDERER ONE');
utimesSync(served, before.atime, before.mtime);
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), false);
await assert.rejects(store.buildModuleIndex(), ModuleStoreInstallationMissingError);
});
test('a deleted served file is caught without hashing the rest of the module', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
await rm(path.join(installed.directory, 'assets', 'style-v1.css'));
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), false);
});
test('an installation without stamps is hashed once and then trusted from the stamps it wrote', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const stampsPath = path.join(installed.directory, MODULE_FILE_STAMPS_NAME);
await rm(stampsPath);
const firstPass = await countServedFileOpens(installed.directory, () =>
store.isInstalled('fluxer_renderer', RENDERER_V1.sha256),
);
assert.equal(firstPass.result, true);
assert.equal(firstPass.opened.length, 2);
assert.equal(existsSync(stampsPath), true);
const secondPass = await countServedFileOpens(installed.directory, () =>
store.isInstalled('fluxer_renderer', RENDERER_V1.sha256),
);
assert.equal(secondPass.result, true);
assert.deepEqual(secondPass.opened, []);
});
test('unreadable or foreign stamps fall back to hashing instead of trusting the tree', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const installed = await install(store, RENDERER_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const stampsPath = path.join(installed.directory, MODULE_FILE_STAMPS_NAME);
const stamps = JSON.parse(readFileSync(stampsPath, 'utf8'));
const served = path.join(installed.directory, 'assets', 'index-v1.js');
writeFileSync(served, 'RENDERER ONE');
const tampered = await fsPromises.lstat(served);
const forged = {
...stamps,
manifest_sha256: 'f'.repeat(64),
files: {
...stamps.files,
'assets/index-v1.js': [tampered.size, tampered.mtimeMs, tampered.ctimeMs, tampered.ino],
},
};
writeFileSync(stampsPath, JSON.stringify(forged));
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), false);
writeFileSync(stampsPath, 'not json');
assert.equal(await store.isInstalled('fluxer_renderer', RENDERER_V1.sha256), false);
});
});
describe('ModuleStore beside a renderer bundled in the shell', () => {
test('an unreadable state file is set aside and the store starts clean', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const statePath = path.join(getModuleStoreRoot(userDataPath), MODULE_STATE_FILE_NAME);
for (const contents of ['{not json', JSON.stringify({state_version: 1})]) {
writeFileSync(statePath, contents);
const recovered = await openStore(userDataPath);
assert.equal(recovered.recoveredUnreadableState, `${statePath}.unreadable`);
assert.equal(readFileSync(`${statePath}.unreadable`, 'utf8'), contents);
assert.deepEqual(recovered.getCommitted(), {});
assert.equal(readState(userDataPath).state_version, MODULE_STATE_VERSION);
}
assert.equal(store.recoveredUnreadableState, null);
});
test('retiring a module drops it from the committed and previous sets so it can be collected', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
const first = await install(store, RENDERER_V1);
const second = await install(store, RENDERER_V2);
await install(store, GRAMMARS_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
await store.commit({fluxer_renderer: RENDERER_V2.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
assert.equal(await store.retireModule('fluxer_renderer'), true);
assert.equal(await store.retireModule('fluxer_renderer'), false);
assert.deepEqual(readState(userDataPath).committed, {fluxer_grammars: GRAMMARS_V1.sha256});
assert.deepEqual(readState(userDataPath).previous, {fluxer_grammars: GRAMMARS_V1.sha256});
await markLaunched(store);
await store.collectGarbage();
assert.equal(existsSync(first.directory), false);
assert.equal(existsSync(second.directory), false);
assert.equal(await store.isInstalled('fluxer_grammars', GRAMMARS_V1.sha256), true);
});
test('two failed boots of a first renderer module fall back to the bundle instead of looping', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await install(store, GRAMMARS_V1);
await store.commit({fluxer_grammars: GRAMMARS_V1.sha256});
await store.activateMergedForRendererReload({fluxer_renderer: RENDERER_V1.sha256});
await store.commit({fluxer_renderer: RENDERER_V1.sha256, fluxer_grammars: GRAMMARS_V1.sha256});
const state = readState(userDataPath);
writeFileSync(
path.join(getModuleStoreRoot(userDataPath), MODULE_STATE_FILE_NAME),
JSON.stringify({...state, previous: {}, boot_attempt: 2}),
);
const reopened = await openStore(userDataPath);
const withoutBundle = await reopened.beginBootAttempt();
assert.equal(withoutBundle.rolledBack, false);
writeFileSync(
path.join(getModuleStoreRoot(userDataPath), MODULE_STATE_FILE_NAME),
JSON.stringify({...readState(userDataPath), boot_attempt: 2}),
);
const bundled = await openStore(userDataPath);
const attempt = await bundled.beginBootAttempt({bundled: ['fluxer_renderer']});
assert.equal(attempt.rolledBack, true);
assert.deepEqual(attempt.committed, {fluxer_grammars: GRAMMARS_V1.sha256});
assert.equal(bundled.isRejected('fluxer_renderer', RENDERER_V1.sha256), true);
assert.equal(readState(userDataPath).boot_attempt, 0);
});
test('a renderer update that never confirms is reverted and rejected', async () => {
const userDataPath = createUserData();
const store = await openStore(userDataPath);
await install(store, RENDERER_V1);
await install(store, RENDERER_V2);
await install(store, GRAMMARS_V1);
await store.commit({fluxer_renderer: RENDERER_V1.sha256});
const attempt = await store.activateMergedForRendererReload({
fluxer_renderer: RENDERER_V2.sha256,
fluxer_grammars: GRAMMARS_V1.sha256,
});
assert.deepEqual(await store.revertLaunchAttempt(attempt, ['fluxer_renderer']), {
fluxer_renderer: RENDERER_V1.sha256,
fluxer_grammars: GRAMMARS_V1.sha256,
});
assert.equal(store.isRejected('fluxer_renderer', RENDERER_V2.sha256), true);
assert.equal(store.isRejected('fluxer_grammars', GRAMMARS_V1.sha256), false);
assert.equal(await store.revertLaunchAttempt(attempt, ['fluxer_renderer']), null);
const first = await store.activateMergedForRendererReload({fluxer_renderer: RENDERER_V2.sha256});
await store.revertLaunchAttempt(first, ['fluxer_renderer']);
await store.retireModule('fluxer_renderer');
const onBundle = await store.activateMergedForRendererReload({fluxer_renderer: RENDERER_V1.sha256});
assert.deepEqual(await store.revertLaunchAttempt(onBundle, ['fluxer_renderer']), {
fluxer_grammars: GRAMMARS_V1.sha256,
});
});
});