Skip to content

Commit c2cb3db

Browse files
fix(test): eliminate flaky test isolation issues
Two root causes fixed: 1. vitest.config.ts: exclude wt-*/** from test discovery - Worktree directories contain duplicate source/test files - Dashboard tests in worktrees ran in Node env (root config) instead of jsdom (dashboard config) → "document is not defined" - AuthManager tests in worktrees collided on Date.now() temp file names - CI doesn't have worktrees, but local runs do → flake was CI-silent 2. fix-2534-lastusedat-persist.test.ts: use crypto.randomUUID() + await - Replaced Date.now() with randomUUID() for temp file names (no collision under parallel execution across 17 worktree copies) - Replaced fire-and-forget sweepStaleRateLimits() + setTimeout(10) with proper await (eliminates race with afterEach cleanup) - Verified: 10/10 runs green, previously 4/5 had failures
1 parent 3f127f2 commit c2cb3db

2 files changed

Lines changed: 28 additions & 11 deletions

File tree

src/__tests__/fix-2534-lastusedat-persist.test.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@
33
*
44
* Bug: lastUsedAt was updated in memory but never persisted to disk.
55
* Fix: dirty flag + periodic save during sweepStaleRateLimits().
6+
*
7+
* Flakiness fix: Uses crypto.randomUUID() for temp file names (no
8+
* Date.now() collision under parallel execution) and properly awaits
9+
* async sweepStaleRateLimits() instead of fire-and-forget + setTimeout.
610
*/
711

812
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
913
import { AuthManager } from '../auth.js';
1014
import { join } from 'node:path';
1115
import { tmpdir } from 'node:os';
1216
import { readFileSync, rmSync } from 'node:fs';
17+
import { randomUUID } from 'node:crypto';
1318

1419
describe('Issue #2534: lastUsedAt persisted to disk', () => {
1520
let auth: AuthManager;
1621
let tmpFile: string;
1722

1823
beforeEach(async () => {
19-
tmpFile = join(tmpdir(), `aegis-test-2534-${Date.now()}.json`);
24+
tmpFile = join(tmpdir(), `aegis-test-2534-${randomUUID()}.json`);
2025
auth = new AuthManager(tmpFile, '');
2126
});
2227

@@ -44,12 +49,13 @@ describe('Issue #2534: lastUsedAt persisted to disk', () => {
4449

4550
it('should NOT persist when no key was used (dirty flag is false)', async () => {
4651
const { key } = await auth.createKey('no-use-test', 10);
52+
53+
// Read the on-disk state after createKey (which calls save)
4754
const before = JSON.parse(readFileSync(tmpFile, 'utf-8'));
4855
expect(before.keys[0].lastUsedAt).toBe(0);
4956

50-
// Sweep without any validate calls
51-
auth.sweepStaleRateLimits();
52-
await new Promise((r) => setTimeout(r, 10));
57+
// Sweep without any validate calls — properly await
58+
await auth.sweepStaleRateLimits();
5359

5460
const after = JSON.parse(readFileSync(tmpFile, 'utf-8'));
5561
// lastUsedAt should still be 0 — no unnecessary write with same value
@@ -68,9 +74,8 @@ describe('Issue #2534: lastUsedAt persisted to disk', () => {
6874
const result = auth.validate(oldKey);
6975
expect(result.valid).toBe(true);
7076

71-
// Sweep should persist the lastUsedAt update from the grace path
72-
auth.sweepStaleRateLimits();
73-
await new Promise((r) => setTimeout(r, 10));
77+
// Sweep should persist the lastUsedAt update from the grace path — properly await
78+
await auth.sweepStaleRateLimits();
7479

7580
const onDisk = JSON.parse(readFileSync(tmpFile, 'utf-8'));
7681
expect(onDisk.keys[0].lastUsedAt).toBeGreaterThan(0);
@@ -79,10 +84,9 @@ describe('Issue #2534: lastUsedAt persisted to disk', () => {
7984
it('should persist lastUsedAt across load/save cycle', async () => {
8085
const { key } = await auth.createKey('cycle-test', 10);
8186

82-
// Validate and sweep to persist
87+
// Validate and sweep to persist — properly await
8388
auth.validate(key);
84-
auth.sweepStaleRateLimits();
85-
await new Promise((r) => setTimeout(r, 10));
89+
await auth.sweepStaleRateLimits();
8690

8791
const onDisk = JSON.parse(readFileSync(tmpFile, 'utf-8'));
8892
const persistedTime = onDisk.keys[0].lastUsedAt;

vitest.config.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,20 @@ import { defineConfig } from 'vitest/config';
22

33
export default defineConfig({
44
test: {
5-
exclude: ['**/node_modules/**', 'dist', 'dashboard/**', '.worktrees/**', '.claude/worktrees/**', '.claude-internals/**'],
5+
exclude: [
6+
'**/node_modules/**',
7+
'dist',
8+
'dashboard/**',
9+
// Worktree directories contain duplicate source/test files. In CI only
10+
// the root source is tested, but local runs pick up worktree copies.
11+
// Exclude all wt-* directories to prevent:
12+
// 1. Dashboard tests running in Node env ("document is not defined")
13+
// 2. Temp file collisions in AuthManager tests under parallel execution
14+
'wt-*/**',
15+
'.worktrees/**',
16+
'.claude/worktrees/**',
17+
'.claude-internals/**',
18+
],
619
coverage: {
720
provider: 'v8',
821
reporter: ['text', 'lcov'],

0 commit comments

Comments
 (0)