Files
msd-core/tests/atomic-write-rename-failure.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

85 lines
3.3 KiB
JavaScript

/**
* #1874 F5 hardening — atomicWriteFileSync (src/runtime-hooks-surface.cts)
* rename-failure fault injection.
*
* The temp-file + rename primitive documents a specific contract for a
* failure mid-swap (see the catch block wrapping shellCmdProjection.
* retryRenameSync in atomicWriteFileSync): the temp file is force-removed
* and the original error is rethrown, un-transformed. This exercises that
* contract directly rather than through an install() integration path, and
* asserts the pre-existing target is left byte-identical.
*/
'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { createTempDir, cleanup } = require('./helpers.cjs');
const { atomicWriteFileSync } = require('../msd-core/bin/lib/runtime-hooks-surface.cjs');
describe('#1874 F5: atomicWriteFileSync rename-failure fault injection', () => {
test('a renameSync failure mid-swap removes the temp file, leaves an existing target untouched, and rethrows', (t) => {
const dir = createTempDir('msd-1874-f5-rename-fail-');
t.after(() => cleanup(dir));
const target = path.join(dir, 'hooks.json');
const prior = '{"version":1,"hooks":{}}\n';
fs.writeFileSync(target, prior);
const origRenameSync = fs.renameSync;
fs.renameSync = (src, dst) => {
if (dst === target) {
// A non-retryable code (see RENAME_RETRY_ERRNOS in
// shell-command-projection.cts) so the failure is immediate, not
// masked behind retryRenameSync's bounded Windows-lock retry loop.
throw Object.assign(new Error('simulated rename failure mid-swap'), { code: 'ENOSPC' });
}
return origRenameSync(src, dst);
};
t.after(() => { fs.renameSync = origRenameSync; });
assert.throws(
() => atomicWriteFileSync(target, '{"version":1,"hooks":{"new":true}}\n', 'utf8'),
(e) => e.code === 'ENOSPC' && /simulated rename failure mid-swap/.test(e.message),
'the rename error must propagate un-transformed'
);
assert.strictEqual(
fs.readFileSync(target, 'utf8'),
prior,
'the pre-existing target must be left byte-identical when rename fails'
);
const residue = fs.readdirSync(dir).filter((n) => n !== 'hooks.json');
assert.deepStrictEqual(residue, [], 'no atomic temp file may survive a failed rename');
});
test('a renameSync failure when no target pre-exists leaves no target and no temp residue', (t) => {
const dir = createTempDir('msd-1874-f5-rename-fail-new-');
t.after(() => cleanup(dir));
const target = path.join(dir, 'hooks.json');
const origRenameSync = fs.renameSync;
fs.renameSync = (src, dst) => {
if (dst === target) {
throw Object.assign(new Error('simulated rename failure, no prior target'), { code: 'ENOSPC' });
}
return origRenameSync(src, dst);
};
t.after(() => { fs.renameSync = origRenameSync; });
assert.throws(
() => atomicWriteFileSync(target, '{"version":1,"hooks":{}}\n', 'utf8'),
(e) => e.code === 'ENOSPC',
'the rename error must propagate un-transformed'
);
assert.strictEqual(fs.existsSync(target), false, 'no target file may be created when rename fails');
assert.deepStrictEqual(fs.readdirSync(dir), [], 'no atomic temp file may survive a failed rename');
});
});