fix(test): restructure DirectoryTailer re-activation test
Some checks are pending
CI / test (18.x) (push) Waiting to run
CI / test (20.x) (push) Waiting to run
CI / test (22.x) (push) Waiting to run

The test 'resumes from saved position when a file is re-activated after
eviction' was incorrectly creating both files before starting the tailer.
With maxActiveFiles: 1, only the newer file (fileB) was being activated
initially, so fileA never emitted its 'initial' event.

Restructured to:
1. Create fileA with content
2. Start tailer (fileA gets activated)
3. Wait for fileA to emit 'initial'
4. Create fileB (triggers eviction of fileA via dirWatcher)
5. Continue with re-activation test

This properly tests the LRU eviction and position checkpointing behavior.
This commit is contained in:
jedarden 2026-05-26 21:39:00 -04:00
parent 10533b0b4f
commit 7fa822d3ea

View file

@ -273,38 +273,59 @@ describe('DirectoryTailer', () => {
const fileA = path.join(tempDir, 'a.jsonl');
const fileB = path.join(tempDir, 'b.jsonl');
fs.writeFileSync(fileA, makeEvent('w-a', 'before-eviction', 1) + '\n');
fs.writeFileSync(fileB, '');
// Create fileA with initial content - this will be activated first.
fs.writeFileSync(fileA, makeEvent('w-a', 'initial', 1) + '\n');
// maxActiveFiles=1 so opening fileB will evict fileA.
const tailer = new DirectoryTailer({
directory: tempDir,
maxActiveFiles: 1,
recentMtimeMs: 86_400_000,
inactiveCheckIntervalMs: 200,
inactiveCheckIntervalMs: 100,
});
const received: string[] = [];
tailer.on('event', (event) => received.push(event.msg));
const received: Array<{ msg: string; filePath: string }> = [];
tailer.on('event', (event, filePath) => {
received.push({ msg: event.msg, filePath });
});
// Start with only fileA present - it will be activated.
tailer.start();
// Wait for fileA to be read and emit 'initial'.
await new Promise((r) => setTimeout(r, 100));
// Verify fileA is active and emitted 'initial'.
expect(tailer.activeFiles.length).toBe(1);
expect(tailer.activeFiles[0]).toBe(fileA);
expect(received.filter((r) => r.msg === 'initial').length).toBe(1);
// Create fileB empty - when detected, it will evict fileA via activateWithEviction.
fs.writeFileSync(fileB, '');
// Wait for dirWatcher to detect fileB and evict fileA.
await new Promise((r) => setTimeout(r, 200));
// Exactly one file should be active (fileB, the newer one).
expect(tailer.activeFiles.length).toBe(1);
expect(tailer.activeFiles[0]).toBe(fileB);
// fileA should have been read and emitted 'initial' before being evicted.
const initialCount = received.filter((r) => r.msg === 'initial').length;
expect(initialCount).toBe(1);
// Write to fileA (the inactive/evicted file) to trigger re-activation.
// This new content should be read from the checkpointed position.
fs.appendFileSync(fileA, makeEvent('w-a', 'after-eviction', 2) + '\n');
// Wait for the poll cycle to detect mtime change and re-activate fileA.
await new Promise((r) => setTimeout(r, 400));
// Exactly one file is active; the other is inactive.
expect(tailer.activeFiles.length).toBe(1);
// Write to the inactive file to trigger re-activation.
const inactive = tailer.activeFiles[0] === fileA ? fileB : fileA;
fs.appendFileSync(inactive, makeEvent('w-inactive', 'after-reactivation', 2) + '\n');
await new Promise((r) => setTimeout(r, 800));
tailer.stop();
// The event written after re-activation must have been received.
expect(received).toContain('after-reactivation');
// The event written before eviction (to fileA at start time) should NOT
// have been re-emitted when fileA was re-activated (position is checkpointed).
const beforeCount = received.filter((m) => m === 'before-eviction').length;
expect(beforeCount).toBe(0);
// The new event should be received.
expect(received.some((r) => r.msg === 'after-eviction')).toBe(true);
// 'initial' should only have been emitted once (during initial activation),
// not again when fileA was re-activated.
expect(received.filter((r) => r.msg === 'initial').length).toBe(1);
});
});