Files
ECC/tests/lib/session-aliases-read.test.js
T

79 lines
3.6 KiB
JavaScript

'use strict';
const assert = require('assert');
const fs = require('fs');
const os = require('os');
const path = require('path');
const vm = require('vm');
// Use a module-local Windows atomic helper while retaining real filesystem and
// lease operations. This exercises the actual alias transaction entry points.
const helperPath = require.resolve('../../scripts/lib/atomic-write');
const record = { exports: {} };
vm.runInNewContext(fs.readFileSync(helperPath, 'utf8'), {
module: record, exports: record.exports, require,
process: { platform: 'win32', pid: process.pid },
performance, Atomics, SharedArrayBuffer, Int32Array,
}, { filename: helperPath });
require.cache[helperPath] = { id: helperPath, filename: helperPath, loaded: true, exports: record.exports };
const aliases = require('../../scripts/lib/session-aliases');
let passed = 0;
let failed = 0;
function test(name, run) {
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-alias-reader-')));
const keys = ['HOME', 'USERPROFILE', 'ECC_AGENT_DATA_HOME'];
const saved = Object.fromEntries(keys.map(key => [key, process.env[key]]));
Object.assign(process.env, { HOME: root, USERPROFILE: root, ECC_AGENT_DATA_HOME: path.join(root, '.claude') });
try { run(); passed++; console.log(`PASS ${name}`); }
catch (error) { failed++; console.error(`FAIL ${name}: ${error.stack}`); }
finally {
for (const [key, value] of Object.entries(saved)) {
if (value === undefined) delete process.env[key]; else process.env[key] = value;
}
fs.rmSync(root, { recursive: true, force: true });
}
}
for (const code of ['EPERM', 'EACCES', 'EBUSY']) {
test(`alias transaction preserves existing entries after a transient ${code} read`, () => {
aliases.setAlias('preserved', '/original');
const file = aliases.getAliasesPath();
const originalRead = fs.readFileSync;
let attempts = 0;
fs.readFileSync = function (input) {
if (input === file && ++attempts === 1) throw Object.assign(new Error('temporary sharing'), { code });
return originalRead.apply(this, arguments);
};
try { assert.strictEqual(aliases.setAlias('added', '/new').success, true); }
finally { fs.readFileSync = originalRead; }
assert.strictEqual(aliases.loadAliases().aliases.preserved.sessionPath, '/original');
assert.strictEqual(aliases.loadAliases().aliases.added.sessionPath, '/new');
assert.strictEqual(attempts, 2);
assert.deepStrictEqual(fs.readdirSync(path.dirname(file)), ['session-aliases.json']);
});
test(`alias transaction fails closed on a permanent ${code} read and releases its lock`, () => {
aliases.setAlias('preserved', '/original');
const file = aliases.getAliasesPath();
const originalRead = fs.readFileSync;
const before = originalRead(file, 'utf8');
const expected = Object.assign(new Error('permanent sharing failure'), { code });
let attempts = 0;
fs.readFileSync = function (input) {
if (input === file) { attempts++; throw expected; }
return originalRead.apply(this, arguments);
};
try { assert.throws(() => aliases.setAlias('added', '/new'), error => error === expected); }
finally { fs.readFileSync = originalRead; }
assert.ok(attempts >= 1 && attempts <= 21);
assert.strictEqual(originalRead(file, 'utf8'), before);
assert.deepStrictEqual(fs.readdirSync(path.dirname(file)), ['session-aliases.json']);
assert.strictEqual(aliases.setAlias('after-failure', '/next').success, true);
assert.strictEqual(aliases.loadAliases().aliases.preserved.sessionPath, '/original');
});
}
console.log(`Results: Passed: ${passed}, Failed: ${failed}`);
process.exitCode = failed ? 1 : 0;