Files
ccs/tests/unit/profile-context-sync-lock.test.ts

95 lines
3.2 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import { createHash } from 'crypto';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import ProfileContextSyncLock from '../../src/management/profile-context-sync-lock';
describe('ProfileContextSyncLock', () => {
let tempRoot = '';
let instancesDir = '';
const getLockPath = (lockName: string): string => {
const safeName = lockName.replace(/[^a-zA-Z0-9_-]/g, '-').toLowerCase();
const profileHash = createHash('sha1').update(lockName).digest('hex').slice(0, 8);
return path.join(instancesDir, '.locks', `${safeName}-${profileHash}.lock`);
};
beforeEach(() => {
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-context-lock-test-'));
instancesDir = path.join(tempRoot, 'instances');
fs.mkdirSync(instancesDir, { recursive: true });
});
afterEach(() => {
if (tempRoot && fs.existsSync(tempRoot)) {
fs.rmSync(tempRoot, { recursive: true, force: true });
}
});
it('acquires and releases synchronous named locks', () => {
const lock = new ProfileContextSyncLock(instancesDir);
const lockPath = getLockPath('__plugin-layout__');
let sawLockInsideCallback = false;
const result = lock.withNamedLockSync('__plugin-layout__', () => {
sawLockInsideCallback = fs.existsSync(lockPath);
expect(fs.readFileSync(lockPath, 'utf8')).toContain(`"pid":${process.pid}`);
return 'ok';
});
expect(result).toBe('ok');
expect(sawLockInsideCallback).toBe(true);
expect(fs.existsSync(lockPath)).toBe(false);
});
it('releases synchronous named locks when the callback throws', () => {
const lock = new ProfileContextSyncLock(instancesDir);
const lockPath = getLockPath('__plugin-layout__');
expect(() =>
lock.withNamedLockSync('__plugin-layout__', () => {
throw new Error('boom');
})
).toThrow('boom');
expect(fs.existsSync(lockPath)).toBe(false);
});
it('reclaims dead-owner locks before entering the callback', () => {
const lock = new ProfileContextSyncLock(instancesDir);
const lockPath = getLockPath('__plugin-layout__');
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
fs.writeFileSync(
lockPath,
JSON.stringify({
version: 1,
pid: 999999,
nonce: 'dead-owner',
acquiredAtMs: Date.now() - 1000,
}),
'utf8'
);
const result = lock.withNamedLockSync('__plugin-layout__', () => 'reclaimed');
expect(result).toBe('reclaimed');
expect(fs.existsSync(lockPath)).toBe(false);
});
it('reclaims malformed stale locks before entering the callback', () => {
const lock = new ProfileContextSyncLock(instancesDir);
const lockPath = getLockPath('__plugin-layout__');
const staleDate = new Date(Date.now() - 60_000);
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
fs.writeFileSync(lockPath, 'not-json', 'utf8');
fs.utimesSync(lockPath, staleDate, staleDate);
const result = lock.withNamedLockSync('__plugin-layout__', () => 'stale-reclaimed');
expect(result).toBe('stale-reclaimed');
expect(fs.existsSync(lockPath)).toBe(false);
});
});