test/cred-store-lock-reclaim.test.js
Rama master · réplica de lectura del repositorio original.
// WHY THIS FILE EXISTS. The credential store serialises writers through a lock
// whose identity is an INSTANCE, never a path:
//
// <store>.lock permanent sentinel at the fixed pre-v2 path
// • a regular file holding {"v":2} — a fresh store
// • a pre-v2 directory that now holds format.json
// <store>.lock.claims/ retained coordination directory
// claim-<host>-<pid>-<token>/ one immutable claim per writer
//
// The first lock was a single owner.json at a fixed path, reclaimed by re-reading
// that path: two reclaimers could judge the same dead owner, and the slower one
// then deleted whatever occupied the path by then — including a live writer's
// brand-new lock (2026-09-17 review, P1). Its identity was also a second write, so
// a partial owner write (ENOSPC) left an unreadable stamp that fenced every later
// save forever (P2).
//
// Every interleaving below is driven by executing the real module text with only
// its boundary imports and the process liveness probe substituted (the same
// technique test/sec1-credential-persistence.test.js uses for fs). The claim name,
// the sentinel gate, the claims sweep and the publish fences are production code.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import * as fs from 'node:fs';
import { writeFileSync, mkdirSync, mkdtempSync, rmSync, existsSync, readFileSync, readdirSync, realpathSync, statSync, utimesSync } from 'node:fs';
import * as path from 'node:path';
import * as crypto from 'node:crypto';
import { tmpdir, hostname } from 'node:os';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
const cred = await import('../src/devin-connect-credentials.js');
const SOURCE = readFileSync(new URL('../src/devin-connect-credentials.js', import.meta.url), 'utf8');
const MODULE_URL = pathToFileURL(resolve('src/devin-connect-credentials.js')).href;
const KEY = 'fixture-master-key-for-lock-reclaim';
const EMAIL = 'lock-probe@example.test';
// Same encoding the module uses, so a name this file builds by hand is the one
// the module would have built.
const HOST_TAG = Buffer.from(hostname(), 'utf8').toString('hex');
function sandbox(t) {
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'cred-lock-')));
t.after(() => rmSync(dir, { recursive: true, force: true }));
return { dir, env: { DEVIN_CONNECT_CRED_KEY: KEY, DEVIN_CONNECT_CRED_FILE: join(dir, 'creds.json') } };
}
function deadPid() {
// A pid that is provably gone: run a child to completion and reuse its pid.
const child = spawnSync(process.execPath, ['-e', 'process.exit(0)']);
assert.equal(child.status, 0);
return child.pid;
}
const sentinel = env => `${env.DEVIN_CONNECT_CRED_FILE}.lock`;
const claimsDir = env => `${env.DEVIN_CONNECT_CRED_FILE}.lock.claims`;
const instances = env => (existsSync(claimsDir(env)) ? readdirSync(claimsDir(env)) : []);
const claimNamed = (pid, token = 'a'.repeat(32), host = HOST_TAG) => `claim-${host}-${pid}-${token}`;
// Execute the real module text with only its boundary imports substituted. Sync
// fs hooks must stay sync; returning promises would test a different API.
function loadModule({ fsOverrides = {}, kill } = {}) {
const deps = {
fs: { ...fs, ...fsOverrides },
path,
crypto,
os: { hostname },
'./config.js': { config: {}, log: { info() {}, warn() {}, error() {} } },
'./devin-connect-metrics.js': { bumpConnect() {}, __registerCredHealth() {} },
};
const executable = SOURCE
.replace(/^import \{([\s\S]*?)\} from '([^']+)';/gm,
(_all, bindings, name) => `const {${bindings}} = deps[${JSON.stringify(name)}];`)
.replace(/^export /gm, '');
const body = `const process = deps.kernelProcess;\n${executable}`
+ '\nreturn { storeCredential, getCredential, acquireClaim, __testing };';
return new Function('deps', body)({
...deps,
kernelProcess: { pid: process.pid, env: process.env, kill: kill || process.kill.bind(process) },
});
}
function legacyDir(env, stamp) {
const lock = sentinel(env);
mkdirSync(lock, { recursive: true });
if (stamp !== undefined) writeFileSync(join(lock, 'owner.json'), typeof stamp === 'string' ? stamp : JSON.stringify(stamp));
return lock;
}
// ─── The sentinel path ─────────────────────────────────────────────────────
test('a fresh store publishes a file sentinel a pre-v2 release cannot remove', (t) => {
const { env } = sandbox(t);
assert.equal(cred.storeCredential(EMAIL, 'pw-fresh', env), true);
assert.equal(statSync(sentinel(env)).isFile(), true, 'the sentinel must be a regular file, never a directory');
assert.deepEqual(JSON.parse(readFileSync(sentinel(env), 'utf8')), { v: 2 });
assert.deepEqual(instances(env), [], 'no claim instance may survive the write');
// A pre-v2 release released its lock with unlink(owner.json) + rmdir(lock): the
// sentinel is a file, so that rmdir can never take the path away.
assert.throws(() => fs.rmdirSync(sentinel(env)), 'rmdir on the sentinel must fail');
// Repeated writes reuse the same sentinel and leave the claims directory empty.
for (let i = 0; i < 4; i++) assert.equal(cred.storeCredential(`repeat-${i}@example.test`, `pw-${i}`, env), true);
assert.equal(statSync(sentinel(env)).isFile(), true);
assert.deepEqual(instances(env), []);
assert.equal(cred.listCredentialEmails(env).length, 5);
});
test('a sentinel file this code did not write is refused, never overwritten', (t) => {
const { env } = sandbox(t);
writeFileSync(sentinel(env), 'not a marker\n');
assert.throws(() => cred.storeCredential(EMAIL, 'pw-unknown-file', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.equal(readFileSync(sentinel(env), 'utf8'), 'not a marker\n');
writeFileSync(sentinel(env), JSON.stringify({ v: 99 }));
assert.throws(() => cred.storeCredential(EMAIL, 'pw-other-format', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
});
// ─── Pre-v2 directories: migrated in place, or refused ─────────────────────
test('a dead pre-v2 stamp is migrated in place and the write proceeds', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: deadPid(), host: hostname() });
assert.equal(cred.storeCredential(EMAIL, 'pw-dead-owner', env), true);
assert.equal(existsSync(join(lock, 'owner.json')), false, 'the dead stamp must be gone');
assert.deepEqual(JSON.parse(readFileSync(join(lock, 'format.json'), 'utf8')), { v: 2 });
assert.deepEqual(readdirSync(lock), ['format.json'], 'the directory keeps its permanent marker and nothing else');
assert.equal(cred.getCredential(EMAIL, env), 'pw-dead-owner');
// The migrated directory can never be removed by a pre-v2 release path.
assert.throws(() => fs.rmdirSync(lock), (e) => e.code === 'ENOTEMPTY');
});
test('a pre-v2 owner that is alive still refuses, and nothing is written into its directory', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: process.pid, host: hostname() });
assert.throws(() => cred.storeCredential(EMAIL, 'pw-live-owner', env),
(e) => e.code === 'ERR_CRED_STORE_BUSY', 'a live owner must still be respected');
assert.deepEqual(readdirSync(lock), ['owner.json'], 'a refusal must not add or remove anything');
assert.equal(existsSync(join(lock, 'format.json')), false);
});
test('a pre-v2 owner from another host is never migrated here', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: 999999, host: 'some-other-host' });
assert.throws(() => cred.storeCredential(EMAIL, 'pw-foreign', env),
(e) => e.code === 'ERR_CRED_STORE_BUSY', 'liveness of a foreign host cannot be checked from here');
assert.deepEqual(readdirSync(lock), ['owner.json']);
});
test('an unreadable pre-v2 stamp is refused, never stolen', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, '{"pid": 1234, "host": "fixture');
assert.throws(() => cred.storeCredential(EMAIL, 'pw-torn', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(readdirSync(lock), ['owner.json']);
assert.equal(readFileSync(join(lock, 'owner.json'), 'utf8'), '{"pid": 1234, "host": "fixture');
});
test('an unknown artifact in a pre-v2 directory refuses the write and is left alone', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: deadPid(), host: hostname() });
writeFileSync(join(lock, 'mystery'), 'x');
assert.throws(() => cred.storeCredential(EMAIL, 'pw-mystery', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(readdirSync(lock).sort(), ['mystery', 'owner.json']);
});
test('an empty pre-v2 directory is never adopted, however old it looks', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, undefined);
// A pre-v2 writer between its mkdir and its owner write is indistinguishable
// from a leftover, and how long it paused cannot be observed. Age is not proof.
utimesSync(lock, new Date(0), new Date(0));
assert.throws(() => cred.storeCredential(EMAIL, 'pw-aged', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(readdirSync(lock), [], 'the untouched directory must stay empty');
assert.throws(() => cred.storeCredential(EMAIL, 'pw-aged-again', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
});
test('a migrated directory cleans a crashed migrator but respects a live one', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: deadPid(), host: hostname() });
writeFileSync(join(lock, 'format.json'), JSON.stringify({ v: 2 }));
mkdirSync(join(lock, claimNamed(deadPid(), 'b'.repeat(32))));
assert.equal(cred.storeCredential(EMAIL, 'pw-after-crash', env), true, 'a dead guard must not fence the store');
assert.deepEqual(readdirSync(lock), ['format.json']);
mkdirSync(join(lock, claimNamed(process.pid, 'c'.repeat(32))));
assert.throws(() => cred.storeCredential(EMAIL, 'pw-live-guard', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(readdirSync(lock).sort(), [claimNamed(process.pid, 'c'.repeat(32)), 'format.json'].sort(),
'a live instance must never be removed');
});
test('a torn marker staged by a crashed migrator is reclaimed, not treated as an artifact', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: deadPid(), host: hostname() });
// The migrator died between staging the marker and renaming it into place.
writeFileSync(join(lock, claimNamed(deadPid(), 'd'.repeat(32))), '{"v":2');
assert.equal(cred.storeCredential(EMAIL, 'pw-torn-marker', env), true);
assert.deepEqual(readdirSync(lock), ['format.json']);
});
test('a pre-v2 directory replaced before the guard is refused, never adopted', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: deadPid(), host: hostname() });
// The audit's deterministic replacement case: the pre-guard listing still shows a
// dead D0, but an old reclaimer has already removed it and an old writer has
// created the replacement directory it is about to stamp. Nothing may be
// concluded from the stamp being gone after the guard — absence is not death.
let swapped = false;
const mod = loadModule({
fsOverrides: {
readdirSync(name, ...args) {
const entries = fs.readdirSync(name, ...args);
if (!swapped && String(name) === lock && entries.includes('owner.json')) {
swapped = true;
fs.unlinkSync(join(lock, 'owner.json'));
fs.rmdirSync(lock);
fs.mkdirSync(lock); // the old writer's mkdir, before its owner write
}
return entries; // the stale listing the migrator acted on
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-swapped', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.equal(swapped, true, 'the replacement must have happened before the guard');
assert.deepEqual(readdirSync(lock), [],
'the replacement directory belongs to the old writer: the guard must be given back');
assert.equal(existsSync(join(lock, 'format.json')), false, 'no marker may be installed over a directory this writer did not prove dead');
assert.equal(existsSync(env.DEVIN_CONNECT_CRED_FILE), false, 'and nothing may be stored');
});
test('a replacement directory holding a DIFFERENT stamp is refused, never adopted', (t) => {
const { env } = sandbox(t);
const stampA = { pid: deadPid(), host: hostname() };
const lock = legacyDir(env, stampA);
// Sharpening of the case above: the pre-guard read returns the stamp that was
// proved dead, and the directory is replaced BEFORE the guard, so the replacement
// already carries a stamp of its own by the time the migrator holds the barrier.
// That stamp parses and is even dead; only a re-read AFTER the guard can tell it
// is not the one this writer proved dead. A reader that trusted the pre-guard
// value would migrate a directory that belongs to another writer.
let replacement = null;
const mod = loadModule({
fsOverrides: {
readFileSync(name, ...args) {
const data = fs.readFileSync(name, ...args);
if (replacement === null && String(name) === join(lock, 'owner.json')) {
fs.unlinkSync(join(lock, 'owner.json'));
fs.rmdirSync(lock);
fs.mkdirSync(lock);
replacement = { pid: deadPid(), host: hostname() };
fs.writeFileSync(join(lock, 'owner.json'), JSON.stringify(replacement));
}
return data; // the stale bytes the migrator acted on
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-foreign-stamp', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(readdirSync(lock), ['owner.json'], 'the replacement keeps its stamp: this writer never proved THAT one dead');
assert.deepEqual(JSON.parse(readFileSync(join(lock, 'owner.json'), 'utf8')), replacement, 'and the stamp is untouched');
assert.equal(existsSync(join(lock, 'format.json')), false, 'no marker may be installed over a directory this writer did not prove dead');
assert.equal(existsSync(env.DEVIN_CONNECT_CRED_FILE), false, 'and nothing may be stored');
});
test('an unreadable stamp inside an already-migrated directory is refused, never deleted', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, 'not-json{{{');
writeFileSync(join(lock, 'format.json'), JSON.stringify({ v: 2 }));
// Marker present, stamp present but unparseable: absence of a READABLE stamp is
// not evidence of death, so the only safe move is to leave the evidence alone.
assert.throws(() => cred.storeCredential(EMAIL, 'pw-unreadable', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(readdirSync(lock).sort(), ['format.json', 'owner.json'], 'the unreadable stamp must survive');
assert.equal(existsSync(env.DEVIN_CONNECT_CRED_FILE), false, 'and nothing may be stored');
});
test('a failed marker install keeps the dead-owner evidence and the retry succeeds', (t) => {
const { env } = sandbox(t);
const lock = legacyDir(env, { pid: deadPid(), host: hostname() });
let inject = true;
const mod = loadModule({
fsOverrides: {
writeFileSync(name, ...args) {
// The permanent marker is staged under a claim-shaped name inside the
// directory; fail exactly there, once.
if (inject && typeof name === 'string' && name.startsWith(join(lock, 'claim-'))) {
inject = false;
throw Object.assign(new Error('synthetic marker ENOSPC'), { code: 'ENOSPC' });
}
return fs.writeFileSync(name, ...args);
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-marker-fault', env), (e) => e.code === 'ENOSPC');
assert.deepEqual(readdirSync(lock), ['owner.json'],
'the dead stamp must survive a failed install: never an empty directory, never lost evidence');
assert.equal(mod.storeCredential(EMAIL, 'pw-marker-fault', env), true, 'the retry must complete once the fault clears');
assert.deepEqual(readdirSync(lock), ['format.json']);
assert.equal(mod.getCredential(EMAIL, env), 'pw-marker-fault');
});
// ─── Claim instances ───────────────────────────────────────────────────────
test('a dead claim instance is reclaimed and a live one in the same directory is untouched', (t) => {
const { env } = sandbox(t);
cred.storeCredential(EMAIL, 'pw-seed', env);
mkdirSync(join(claimsDir(env), claimNamed(deadPid())));
const live = claimNamed(process.pid, 'b'.repeat(32));
mkdirSync(join(claimsDir(env), live));
assert.throws(() => cred.storeCredential(EMAIL, 'pw-both', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(instances(env), [live], 'the dead instance is gone, the live instance is exactly where it was');
});
test('a foreign-host claim and an unknown artifact in the claims directory both refuse', (t) => {
const { env } = sandbox(t);
cred.storeCredential(EMAIL, 'pw-seed', env);
const foreign = `claim-${Buffer.from('other-host', 'utf8').toString('hex')}-4242-${'c'.repeat(32)}`;
mkdirSync(join(claimsDir(env), foreign));
mkdirSync(join(claimsDir(env), 'mystery-artifact'));
assert.throws(() => cred.storeCredential(EMAIL, 'pw-unknown', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.deepEqual(instances(env).sort(), ['mystery-artifact', foreign].sort());
});
test('a dead claim whose initialization failed halfway is still reclaimed', (t) => {
const { env } = sandbox(t);
cred.storeCredential(EMAIL, 'pw-seed', env);
const dead = claimNamed(deadPid());
mkdirSync(join(claimsDir(env), dead));
writeFileSync(join(claimsDir(env), dead, 'leftover'), '{');
assert.equal(cred.storeCredential(EMAIL, 'pw-torn-instance', env), true);
assert.deepEqual(instances(env), []);
});
// ─── The F1 interleaving and the publish fences ────────────────────────────
test('a stale reclaimer must not delete the claim a new writer just took', (t) => {
const { env } = sandbox(t);
const gone = deadPid();
const dead = claimNamed(gone);
const stolen = claimNamed(process.pid, 'd'.repeat(32));
cred.storeCredential(EMAIL, 'pw-seed', env);
mkdirSync(join(claimsDir(env), dead));
// The interleaving the old lock lost to: during the liveness probe of the dead
// instance, that instance is already gone and a NEW writer's claim occupies the
// directory. Deleting by path takes the new claim; deleting by identity does not.
let switched = false;
const mod = loadModule({
kill(pid, signal) {
assert.equal(signal, 0);
if (pid === gone && !switched) {
switched = true;
fs.rmdirSync(join(claimsDir(env), dead));
fs.mkdirSync(join(claimsDir(env), stolen));
}
return process.kill(pid, signal);
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-stale-reclaimer', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.equal(switched, true, 'the probe must have been reached');
assert.equal(existsSync(join(claimsDir(env), stolen)), true, 'the new writer\'s claim must survive a stale reclaim');
assert.equal(cred.getCredential(EMAIL, env), 'pw-seed', 'and the stored record must be the one from before, not a stale snapshot');
});
test('a writer that lost its claim while reading refuses instead of publishing', (t) => {
const { env } = sandbox(t);
let dropped = false;
const mod = loadModule({
fsOverrides: {
readFileSync(name, ...args) {
// The snapshot read happens while the claim is held: drop the instance
// then, exactly as a foreign cleaner would.
if (!dropped && String(name) === env.DEVIN_CONNECT_CRED_FILE) {
for (const entry of readdirSync(claimsDir(env))) {
if (entry.startsWith('claim-')) { fs.rmdirSync(join(claimsDir(env), entry)); dropped = true; }
}
}
return fs.readFileSync(name, ...args);
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-lost-claim', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.equal(dropped, true, 'the claim must have been present while the lock was held');
assert.equal(existsSync(env.DEVIN_CONNECT_CRED_FILE), false, 'a lost claim must not publish a stale snapshot');
});
test('a writer that lost its claim during the temp write must not rename it into place', (t) => {
const { dir, env } = sandbox(t);
assert.equal(cred.storeCredential(EMAIL, 'pw-first', env), true);
const before = readFileSync(env.DEVIN_CONNECT_CRED_FILE);
let dropped = false;
const mod = loadModule({
fsOverrides: {
writeFileSync(target, ...args) {
// The store content is written through a file descriptor; the claim is
// still held at that moment.
if (!dropped && typeof target === 'number') {
for (const entry of readdirSync(claimsDir(env))) {
if (entry.startsWith('claim-')) { fs.rmdirSync(join(claimsDir(env), entry)); dropped = true; }
}
}
return fs.writeFileSync(target, ...args);
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-second', env), (e) => e.code === 'ERR_CRED_STORE_BUSY');
assert.equal(dropped, true);
assert.deepEqual(readFileSync(env.DEVIN_CONNECT_CRED_FILE), before, 'the published store must not change');
assert.equal(existsSync(env.DEVIN_CONNECT_CRED_FILE + '.tmp'), false);
assert.equal(readdirSync(dir).filter(name => name.includes('.tmp')).length, 0, 'no temp file may survive the refusal');
});
// ─── Failure injection: nothing fences, the original error survives ────────
test('a failed sentinel publication reports the original error and fences nothing', (t) => {
const { env } = sandbox(t);
const claims = claimsDir(env);
let inject = true;
const mod = loadModule({
fsOverrides: {
writeFileSync(name, ...args) {
if (inject && typeof name === 'string' && String(name).startsWith(claims)) {
throw Object.assign(new Error('no space left on device'), { code: 'ENOSPC' });
}
return fs.writeFileSync(name, ...args);
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-enospc', env), (e) => e.code === 'ENOSPC',
'the caller must see the real failure, not BUSY');
assert.equal(existsSync(sentinel(env)), false, 'the sentinel path must stay absent');
assert.deepEqual(instances(env), [], 'and no staged file may be left behind');
inject = false; // the fault clears
assert.equal(mod.storeCredential(EMAIL, 'pw-enospc', env), true, 'the next save must succeed');
assert.equal(mod.getCredential(EMAIL, env), 'pw-enospc');
});
test('a claim that could not be created reports the original error and fences nothing', (t) => {
const { env } = sandbox(t);
let inject = true;
const mod = loadModule({
fsOverrides: {
mkdirSync(name, ...args) {
if (inject && String(name).includes('claim-')) {
throw Object.assign(new Error('no space left on device'), { code: 'ENOSPC' });
}
return fs.mkdirSync(name, ...args);
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-enospc-claim', env), (e) => e.code === 'ENOSPC');
inject = false;
assert.equal(mod.storeCredential(EMAIL, 'pw-enospc-claim', env), true);
assert.deepEqual(instances(env), [], 'the retry must not leak its claim either');
});
test('a failed read after the claim is taken releases it and preserves the error', (t) => {
const { env } = sandbox(t);
const claims = claimsDir(env);
let inject = true;
const mod = loadModule({
fsOverrides: {
readdirSync(name, ...args) {
const entries = fs.readdirSync(name, ...args);
if (inject && String(name) === claims && entries.some(entry => String(entry).startsWith('claim-'))) {
inject = false;
throw Object.assign(new Error('injected post-claim scan failure'), { code: 'EIO' });
}
return entries;
},
},
});
assert.throws(() => mod.storeCredential(EMAIL, 'pw-eio', env), (e) => e.code === 'EIO',
'the real failure must reach the caller');
assert.deepEqual(instances(env), [], 'a failed read must not leak this writer\'s own claim');
assert.equal(mod.storeCredential(EMAIL, 'pw-eio', env), true, 'the next call must work');
assert.equal(mod.getCredential(EMAIL, env), 'pw-eio');
});
// ─── Real processes, real read-modify-write ────────────────────────────────
test('four processes serialise through the claim and lose no record', { timeout: 60000 }, async (t) => {
const { dir, env } = sandbox(t);
const childPath = join(dir, 'writer.mjs');
writeFileSync(childPath, [
`import { storeCredential } from ${JSON.stringify(MODULE_URL)};`,
'const [file, key, email, password] = process.argv.slice(2);',
'const env = { DEVIN_CONNECT_CRED_KEY: key, DEVIN_CONNECT_CRED_FILE: file };',
'for (let i = 0; i < 400; i++) {',
' try { storeCredential(email, password, env); process.exit(0); }',
' catch (error) {',
' if (error.code !== "ERR_CRED_STORE_BUSY") { console.error(error.code || error.message); process.exit(3); }',
' await new Promise(r => setTimeout(r, 5));',
' }',
'}',
'console.error("busy-timeout"); process.exit(4);',
].join('\n'));
// Every child this test spawns is tracked so an aborted run cannot leave a
// writer (or the lock it holds) behind: only our own children are terminated,
// and their close is awaited before the test is over.
const spawned = [];
t.after(async () => {
for (const entry of spawned) {
if (entry.child.exitCode === null && entry.child.signalCode === null) entry.child.kill();
}
await Promise.all(spawned.map(entry => entry.closed));
});
const runWriter = (name) => new Promise((resolve) => {
const child = spawn(process.execPath, [childPath, env.DEVIN_CONNECT_CRED_FILE, KEY, `${name}@example.test`, `pw-${name}`], { stdio: ['ignore', 'ignore', 'pipe'] });
let stderr = '';
const entry = { child, closed: null };
entry.closed = new Promise((closed) => {
child.stderr.on('data', (chunk) => { stderr += chunk; });
child.on('error', (error) => { closed(); resolve({ name, code: `spawn-error:${error.code || error.message}`, stderr }); });
child.on('close', (code) => { closed(); resolve({ name, code, stderr }); });
});
spawned.push(entry);
});
const results = await Promise.all(['one', 'two', 'three', 'four'].map(runWriter));
assert.deepEqual(results.map(r => r.code), [0, 0, 0, 0], JSON.stringify(results));
// Each writer read-modify-wrote the whole store under the lock: a lost update
// would leave a record missing or a password from another writer's snapshot.
const records = JSON.parse(readFileSync(env.DEVIN_CONNECT_CRED_FILE, 'utf8')).records;
assert.deepEqual(Object.keys(records).sort(), ['four@example.test', 'one@example.test', 'three@example.test', 'two@example.test']);
for (const name of ['one', 'two', 'three', 'four']) {
assert.equal(cred.getCredential(`${name}@example.test`, env), `pw-${name}`);
}
assert.deepEqual(instances(env), [], 'every writer must have given its claim instance back');
});