test(bd-0nd): add directory-source integration test with NEEDLE fixtures
Add e2e test proving DirectoryTailer works with real NEEDLE per-worker JSONL format: copies anonymized fixtures to temp dir, asserts events from multiple workers, hot-adds a gamma worker mid-test, all under 1.4s. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
632f35a4c3
commit
398f090a11
3 changed files with 93 additions and 1 deletions
|
|
@ -5,9 +5,14 @@
|
|||
* Uses real log samples from ~/.needle/logs/ to ensure compatibility.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { parseLogLine, parseLogLines } from './parser.js';
|
||||
import { LogEvent } from './types.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { DirectoryTailer } from './directoryTailer.js';
|
||||
|
||||
describe('NEEDLE-FABRIC Integration', () => {
|
||||
describe('worker.started events', () => {
|
||||
|
|
@ -600,3 +605,82 @@ describe('NEEDLE-FABRIC Integration', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('FABRIC ↔ NEEDLE directory source integration', () => {
|
||||
let tempDir: string;
|
||||
let tailer: DirectoryTailer;
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const fixturesDir = path.resolve(__dirname, '../tests/fixtures/needle-logs');
|
||||
|
||||
function makeNeedleEvent(worker_id: string, session_id: string, sequence: number, event_type: string, data: Record<string, unknown> = {}) {
|
||||
return JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
event_type,
|
||||
worker_id,
|
||||
session_id,
|
||||
sequence,
|
||||
...('bead_id' in data ? { bead_id: data.bead_id } : {}),
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'fabric-needle-integ-'));
|
||||
const entries = fs.readdirSync(fixturesDir);
|
||||
for (const entry of entries) {
|
||||
if (entry.endsWith('.jsonl')) {
|
||||
fs.copyFileSync(path.join(fixturesDir, entry), path.join(tempDir, entry));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tailer?.stop();
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('picks up events from multiple worker files and hot-adds a new file', async () => {
|
||||
const received: Array<{ worker: string; msg: string }> = [];
|
||||
|
||||
tailer = new DirectoryTailer({ directory: tempDir });
|
||||
tailer.on('event', (event: LogEvent) => {
|
||||
received.push({ worker: event.worker, msg: event.msg });
|
||||
});
|
||||
|
||||
tailer.start();
|
||||
// Let initial scan settle — tailers position at end of existing files
|
||||
await new Promise((r) => setTimeout(r, 150));
|
||||
|
||||
// Append new events to the pre-existing fixture files
|
||||
const alphaPath = path.join(tempDir, 'alpha-d6288428.jsonl');
|
||||
const bravoPath = path.join(tempDir, 'bravo-44c92b93.jsonl');
|
||||
|
||||
fs.appendFileSync(alphaPath, makeNeedleEvent('alpha-d6288428', 'session-alpha-001', 10, 'bead.claimed', { bead_id: 'bd-hot1', actor: 'fabric-test' }) + '\n');
|
||||
fs.appendFileSync(bravoPath, makeNeedleEvent('bravo-44c92b93', 'session-bravo-002', 10, 'bead.claimed', { bead_id: 'bd-hot2', actor: 'fabric-test' }) + '\n');
|
||||
|
||||
// Wait for watchers to fire and events to propagate
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
// Assert: at least 2 distinct worker_id values in received events
|
||||
const workers = new Set(received.map((e) => e.worker));
|
||||
expect(workers.size).toBeGreaterThanOrEqual(2);
|
||||
expect(workers).toContain('alpha-d6288428');
|
||||
expect(workers).toContain('bravo-44c92b93');
|
||||
|
||||
// Mid-test: hot-add a new gamma worker file
|
||||
const gammaPath = path.join(tempDir, 'gamma-aabb1122.jsonl');
|
||||
fs.writeFileSync(gammaPath, '');
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
|
||||
fs.appendFileSync(gammaPath, makeNeedleEvent('gamma-aabb1122', 'session-gamma-003', 1, 'worker.started', { pid: 30303, workspace: '/home/coder/NEEDLE', agent: 'claude-code-sonnet' }) + '\n');
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
// Assert: gamma event showed up
|
||||
const gammaEvents = received.filter((e) => e.worker === 'gamma-aabb1122');
|
||||
expect(gammaEvents.length).toBeGreaterThanOrEqual(1);
|
||||
expect(gammaEvents.some((e) => e.msg === 'worker.started')).toBe(true);
|
||||
|
||||
tailer.stop();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
4
tests/fixtures/needle-logs/alpha-d6288428.jsonl
vendored
Normal file
4
tests/fixtures/needle-logs/alpha-d6288428.jsonl
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{"timestamp":"2026-04-22T10:00:00.000Z","event_type":"worker.started","worker_id":"alpha-d6288428","session_id":"session-alpha-001","sequence":1,"data":{"pid":10101,"workspace":"/home/coder/NEEDLE","agent":"claude-anthropic-sonnet"}}
|
||||
{"timestamp":"2026-04-22T10:00:05.000Z","event_type":"bead.claimed","worker_id":"alpha-d6288428","session_id":"session-alpha-001","sequence":2,"bead_id":"bd-a1b2","data":{"actor":"fabric-test","attempt":1}}
|
||||
{"timestamp":"2026-04-22T10:00:10.000Z","event_type":"bead.completed","worker_id":"alpha-d6288428","session_id":"session-alpha-001","sequence":3,"bead_id":"bd-a1b2","data":{"duration_ms":5000,"output_file":"/tmp/needle-bd-a1b2.log"}}
|
||||
{"timestamp":"2026-04-22T10:00:11.000Z","event_type":"worker.idle","worker_id":"alpha-d6288428","session_id":"session-alpha-001","sequence":4,"data":{"consecutive_empty":1,"idle_seconds":0}}
|
||||
4
tests/fixtures/needle-logs/bravo-44c92b93.jsonl
vendored
Normal file
4
tests/fixtures/needle-logs/bravo-44c92b93.jsonl
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{"timestamp":"2026-04-22T10:00:01.000Z","event_type":"worker.started","worker_id":"bravo-44c92b93","session_id":"session-bravo-002","sequence":1,"data":{"pid":20202,"workspace":"/home/coder/NEEDLE","agent":"claude-anthropic-opus"}}
|
||||
{"timestamp":"2026-04-22T10:00:06.000Z","event_type":"bead.claimed","worker_id":"bravo-44c92b93","session_id":"session-bravo-002","sequence":2,"bead_id":"bd-c3d4","data":{"actor":"fabric-test","attempt":1}}
|
||||
{"timestamp":"2026-04-22T10:00:12.000Z","event_type":"bead.completed","worker_id":"bravo-44c92b93","session_id":"session-bravo-002","sequence":3,"bead_id":"bd-c3d4","data":{"duration_ms":6000,"output_file":"/tmp/needle-bd-c3d4.log"}}
|
||||
{"timestamp":"2026-04-22T10:00:13.000Z","event_type":"effort.recorded","worker_id":"bravo-44c92b93","session_id":"session-bravo-002","sequence":4,"bead_id":"bd-c3d4","data":{"duration_ms":6000}}
|
||||
Loading…
Add table
Reference in a new issue