agent-ecosystem/test/main/services/team/fileLock.test.ts
iliya c3eea4d6eb feat: add cross-team communication orchestrator
Autonomous message routing between Agent Teams via MCP with cascade
protection. Inbox-first canonical delivery with cross-process file
lock (O_CREAT|O_EXCL) and best-effort relay for online teams.

- CascadeGuard: rate limit (10/min), chain depth (max 5), pair cooldown (3s)
- FileLock: cross-process safe via kernel-level atomic lock files
- CrossTeamService: validate → cascade → file-lock → inbox write → relay → outbox
- Unified lead resolver with members.meta.json normalization (trim+dedup)
- 3 MCP tools: cross_team_send, cross_team_list_targets, cross_team_get_outbox
- Controller module with sync file lock, same protocol as main
- IPC adapter with 3 Electron handlers
- 64 new tests across 8 test files
2026-03-09 18:45:15 +02:00

77 lines
2.2 KiB
TypeScript

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { withFileLock } from '@main/services/team/fileLock';
describe('withFileLock', () => {
let tmpDir: string;
let testFile: string;
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'filelock-test-'));
testFile = path.join(tmpDir, 'test.json');
fs.writeFileSync(testFile, '[]', 'utf8');
});
afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('acquires and releases lock around fn()', async () => {
const lockPath = `${testFile}.lock`;
const result = await withFileLock(testFile, async () => {
expect(fs.existsSync(lockPath)).toBe(true);
return 42;
});
expect(result).toBe(42);
expect(fs.existsSync(lockPath)).toBe(false);
});
it('releases lock even on error', async () => {
const lockPath = `${testFile}.lock`;
await expect(
withFileLock(testFile, async () => {
throw new Error('boom');
})
).rejects.toThrow('boom');
expect(fs.existsSync(lockPath)).toBe(false);
});
it('serializes concurrent access', async () => {
const order: number[] = [];
const task = (id: number, delayMs: number) =>
withFileLock(testFile, async () => {
order.push(id);
await new Promise((resolve) => setTimeout(resolve, delayMs));
});
await Promise.all([task(1, 50), task(2, 10), task(3, 10)]);
expect(order).toHaveLength(3);
expect(new Set(order).size).toBe(3);
});
it('removes stale lock and acquires', async () => {
const lockPath = `${testFile}.lock`;
// Create a stale lock (timestamp 60s ago)
fs.writeFileSync(lockPath, `99999\n${Date.now() - 60_000}\n`, 'utf8');
const result = await withFileLock(testFile, async () => 'ok');
expect(result).toBe('ok');
});
it('creates parent directories for lock file', async () => {
const nested = path.join(tmpDir, 'a', 'b', 'deep.json');
const result = await withFileLock(nested, async () => 'created');
expect(result).toBe('created');
expect(fs.existsSync(`${nested}.lock`)).toBe(false);
});
});