Files
msd-core/tests/capability-lock-mkdir-failure-3987.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

107 lines
4.7 KiB
JavaScript

'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { cleanup } = require('./helpers.cjs');
// Built lib is the test target (same surface the rest of the suite imports).
const capabilityLock = require('../msd-core/bin/lib/capability-lock.cjs');
const { acquireLock } = capabilityLock;
// ─────────────────────────────────────────────────────────────────────────────
// #3987 (same class as #1884/PR #3472): acquireLock swallows the mkdirSync
// failure creating the lock directory. The subsequent `fs.openSync(lockPath,
// 'wx')` then throws ENOENT (parent missing), and ENOENT is NOT 'EEXIST', so
// `if (code !== 'EEXIST') return null;` laundered a genuine EACCES/EROFS
// filesystem error into an ordinary "lock unavailable" (null) result —
// indistinguishable from another live process legitimately holding the lock.
//
// Fix (matching #1884's approach in 0c43d853e): stop swallowing — let the
// mkdir failure propagate immediately with its real errno. These tests pin
// the corrected contract: a permission/space failure creating the lock
// directory throws, it is never folded into a "null" (lock unavailable)
// result.
//
// IO failure is forced via t.mock.method(fs, 'mkdirSync', ...), which
// auto-restores per-test (never chmod 0o000 — root bypasses mode bits,
// leaking coverage).
// ─────────────────────────────────────────────────────────────────────────────
const realMkdirSync = fs.mkdirSync;
function failMkdirFor(t, targetDir, code) {
t.mock.method(fs, 'mkdirSync', (p, opts) => {
if (typeof p === 'string' && (p === targetDir || p.startsWith(targetDir + path.sep))) {
const err = new Error(`${code}: permission denied, mkdir '${p}'`);
err.code = code;
throw err;
}
return realMkdirSync.call(fs, p, opts);
});
}
describe('acquireLock surfaces mkdir failure fast (#3987, same class as #1884)', () => {
test('EACCES creating the lock directory is surfaced immediately, not laundered into a "lock unavailable" null', (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-caplock-mkdir-fail-'));
t.after(() => cleanup(tmpDir));
const lockDir = path.join(tmpDir, 'nested', 'sub');
const lockPath = path.join(lockDir, '.lock');
failMkdirFor(t, lockDir, 'EACCES');
assert.throws(
() => acquireLock(lockPath),
(err) => err && err.code === 'EACCES',
'a genuine EACCES creating the lock directory must surface as EACCES, not return null'
);
});
test('ENOSPC creating the lock directory is surfaced immediately', (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-caplock-mkdir-fail-'));
t.after(() => cleanup(tmpDir));
const lockDir = path.join(tmpDir, 'nested', 'sub');
const lockPath = path.join(lockDir, '.lock');
failMkdirFor(t, lockDir, 'ENOSPC');
assert.throws(
() => acquireLock(lockPath),
(err) => err && err.code === 'ENOSPC',
'a genuine ENOSPC creating the lock directory must surface immediately, not return null'
);
});
test('the thrown mkdir-failure error is never returned as a lock handle or null', (t) => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-caplock-mkdir-fail-'));
t.after(() => cleanup(tmpDir));
const lockDir = path.join(tmpDir, 'nested', 'sub');
const lockPath = path.join(lockDir, '.lock');
failMkdirFor(t, lockDir, 'EACCES');
let returned;
let threw = false;
try {
returned = acquireLock(lockPath);
} catch {
threw = true;
}
assert.strictEqual(threw, true, 'acquireLock must throw on a genuine mkdir failure, not return');
assert.strictEqual(returned, undefined, 'no value should be returned when the mkdir failure is surfaced');
});
test('normal path is unaffected: a creatable lock directory still acquires a lock', (t) => {
// No monkeypatch — the lock directory is creatable in the writable tmpDir.
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-caplock-mkdir-ok-'));
t.after(() => cleanup(tmpDir));
const lockPath = path.join(tmpDir, 'nested', 'sub', '.lock');
const handle = acquireLock(lockPath);
assert.ok(handle && typeof handle.token === 'string', 'a creatable lock directory must still yield a lock handle');
capabilityLock.releaseLock(handle);
});
});