agent-ecosystem/agent-teams-controller/test/atomicFile.test.js

108 lines
2.9 KiB
JavaScript

const fs = require('fs');
const os = require('os');
const path = require('path');
const { writeJsonFileSync } = require('../src/internal/atomicFile.js');
function listTempFiles(dir) {
return fs.readdirSync(dir).filter((name) => name.includes('.tmp.'));
}
function withMockedRenameSync(mockRenameSync, callback) {
const originalRenameSync = fs.renameSync;
fs.renameSync = (from, to) => mockRenameSync(from, to, originalRenameSync);
try {
callback();
} finally {
fs.renameSync = originalRenameSync;
}
}
describe('atomic file writes', () => {
const tempDirs = [];
function makeTempDir() {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-teams-atomic-'));
tempDirs.push(dir);
return dir;
}
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
['EPERM', 'EACCES', 'EBUSY'].forEach((code) => {
it(`retries transient ${code} rename failures before publishing JSON`, () => {
const dir = makeTempDir();
const filePath = path.join(dir, 'state.json');
let attempts = 0;
withMockedRenameSync(
(from, to, originalRenameSync) => {
attempts += 1;
if (attempts < 3) {
const error = new Error(`simulated transient ${code}`);
error.code = code;
throw error;
}
return originalRenameSync.call(fs, from, to);
},
() => {
writeJsonFileSync(filePath, { ok: true });
}
);
expect(attempts).toBe(3);
expect(JSON.parse(fs.readFileSync(filePath, 'utf8'))).toEqual({ ok: true });
expect(listTempFiles(dir)).toEqual([]);
});
});
it('does not retry ENOENT rename failures and removes the temp file', () => {
const dir = makeTempDir();
const filePath = path.join(dir, 'state.json');
let attempts = 0;
withMockedRenameSync(
() => {
attempts += 1;
const error = new Error('missing target directory');
error.code = 'ENOENT';
throw error;
},
() => {
expect(() => writeJsonFileSync(filePath, { ok: true })).toThrow('missing target directory');
}
);
expect(attempts).toBe(1);
expect(fs.existsSync(filePath)).toBe(false);
expect(listTempFiles(dir)).toEqual([]);
});
it('removes the temp file after retryable rename failures are exhausted', () => {
const dir = makeTempDir();
const filePath = path.join(dir, 'state.json');
let attempts = 0;
withMockedRenameSync(
() => {
attempts += 1;
const error = new Error('transient lock stayed active');
error.code = 'EBUSY';
throw error;
},
() => {
expect(() => writeJsonFileSync(filePath, { ok: true })).toThrow(
'transient lock stayed active'
);
}
);
expect(attempts).toBe(8);
expect(fs.existsSync(filePath)).toBe(false);
expect(listTempFiles(dir)).toEqual([]);
});
});