From 2d1e384eefac400ce972153de4779c28431ebe20 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Wed, 25 Mar 2026 03:51:15 -0400 Subject: [PATCH] test: isolate suggest-compact counter fixtures --- tests/hooks/suggest-compact.test.js | 169 ++++++++++++++++------------ 1 file changed, 97 insertions(+), 72 deletions(-) diff --git a/tests/hooks/suggest-compact.test.js b/tests/hooks/suggest-compact.test.js index 36dd8b17..217304b4 100644 --- a/tests/hooks/suggest-compact.test.js +++ b/tests/hooks/suggest-compact.test.js @@ -54,47 +54,56 @@ function getCounterFilePath(sessionId) { return path.join(os.tmpdir(), `claude-tool-count-${sessionId}`); } +let counterContextSeq = 0; + +function createCounterContext(prefix = 'test-compact') { + counterContextSeq += 1; + const sessionId = `${prefix}-${Date.now()}-${counterContextSeq}`; + const counterFile = getCounterFilePath(sessionId); + + return { + sessionId, + counterFile, + cleanup() { + try { + fs.unlinkSync(counterFile); + } catch (_err) { + // Ignore missing temp files between runs + } + } + }; +} + function runTests() { console.log('\n=== Testing suggest-compact.js ===\n'); let passed = 0; let failed = 0; - // Use a unique session ID per test run to avoid collisions - const testSession = `test-compact-${Date.now()}`; - const counterFile = getCounterFilePath(testSession); - - // Cleanup helper - function cleanupCounter() { - try { - fs.unlinkSync(counterFile); - } catch (_err) { - // Ignore error - } - } - // Basic functionality console.log('Basic counter functionality:'); if (test('creates counter file on first run', () => { - cleanupCounter(); - const result = runCompact({ CLAUDE_SESSION_ID: testSession }); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId }); assert.strictEqual(result.code, 0, 'Should exit 0'); assert.ok(fs.existsSync(counterFile), 'Counter file should be created'); const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 1, 'Counter should be 1 after first run'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('increments counter on subsequent runs', () => { - cleanupCounter(); - runCompact({ CLAUDE_SESSION_ID: testSession }); - runCompact({ CLAUDE_SESSION_ID: testSession }); - runCompact({ CLAUDE_SESSION_ID: testSession }); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); + runCompact({ CLAUDE_SESSION_ID: sessionId }); + runCompact({ CLAUDE_SESSION_ID: sessionId }); + runCompact({ CLAUDE_SESSION_ID: sessionId }); const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 3, 'Counter should be 3 after three runs'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; @@ -102,28 +111,30 @@ function runTests() { console.log('\nThreshold suggestion:'); if (test('suggests compact at threshold (COMPACT_THRESHOLD=3)', () => { - cleanupCounter(); + const { sessionId, cleanup } = createCounterContext(); + cleanup(); // Run 3 times with threshold=3 - runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '3' }); - runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '3' }); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '3' }); + runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '3' }); + runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '3' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '3' }); assert.ok( result.stderr.includes('3 tool calls reached') || result.stderr.includes('consider /compact'), `Should suggest compact at threshold. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('does NOT suggest compact before threshold', () => { - cleanupCounter(); - runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '5' }); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '5' }); + const { sessionId, cleanup } = createCounterContext(); + cleanup(); + runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '5' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '5' }); assert.ok( !result.stderr.includes('StrategicCompact'), 'Should NOT suggest compact before threshold' ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; @@ -131,18 +142,19 @@ function runTests() { console.log('\nInterval suggestion:'); if (test('suggests at threshold + 25 interval', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); // Set counter to threshold+24 (so next run = threshold+25) // threshold=3, so we need count=28 → 25 calls past threshold // Write 27 to the counter file, next run will be 28 = 3 + 25 fs.writeFileSync(counterFile, '27'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '3' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '3' }); // count=28, threshold=3, 28-3=25, 25 % 25 === 0 → should suggest assert.ok( result.stderr.includes('28 tool calls') || result.stderr.includes('checkpoint'), `Should suggest at threshold+25 interval. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; @@ -150,42 +162,45 @@ function runTests() { console.log('\nEnvironment variable handling:'); if (test('uses default threshold (50) when COMPACT_THRESHOLD is not set', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); // Write counter to 49, next run will be 50 = default threshold fs.writeFileSync(counterFile, '49'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId }); // Remove COMPACT_THRESHOLD from env assert.ok( result.stderr.includes('50 tool calls reached'), `Should use default threshold of 50. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('ignores invalid COMPACT_THRESHOLD (negative)', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '49'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '-5' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '-5' }); // Invalid threshold falls back to 50 assert.ok( result.stderr.includes('50 tool calls reached'), `Should fallback to 50 for negative threshold. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('ignores non-numeric COMPACT_THRESHOLD', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '49'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: 'abc' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: 'abc' }); // NaN falls back to 50 assert.ok( result.stderr.includes('50 tool calls reached'), `Should fallback to 50 for non-numeric threshold. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; @@ -193,38 +208,41 @@ function runTests() { console.log('\nCorrupted counter file:'); if (test('resets counter on corrupted file content', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, 'not-a-number'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId }); assert.strictEqual(result.code, 0); // Corrupted file → parsed is NaN → falls back to count=1 const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 1, 'Should reset to 1 on corrupted file'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('resets counter on extremely large value', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); // Value > 1000000 should be clamped fs.writeFileSync(counterFile, '9999999'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId }); assert.strictEqual(result.code, 0); const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 1, 'Should reset to 1 for value > 1000000'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('handles empty counter file', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, ''); - const result = runCompact({ CLAUDE_SESSION_ID: testSession }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId }); assert.strictEqual(result.code, 0); // Empty file → bytesRead=0 → count starts at 1 const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 1, 'Should start at 1 for empty file'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; @@ -255,10 +273,11 @@ function runTests() { console.log('\nExit code:'); if (test('always exits 0 (never blocks Claude)', () => { - cleanupCounter(); - const result = runCompact({ CLAUDE_SESSION_ID: testSession }); + const { sessionId, cleanup } = createCounterContext(); + cleanup(); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId }); assert.strictEqual(result.code, 0, 'Should always exit 0'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; @@ -266,48 +285,52 @@ function runTests() { console.log('\nThreshold boundary values:'); if (test('rejects COMPACT_THRESHOLD=0 (falls back to 50)', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '49'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '0' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '0' }); // 0 is invalid (must be > 0), falls back to 50, count becomes 50 → should suggest assert.ok( result.stderr.includes('50 tool calls reached'), `Should fallback to 50 for threshold=0. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('accepts COMPACT_THRESHOLD=10000 (boundary max)', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '9999'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '10000' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '10000' }); // count becomes 10000, threshold=10000 → should suggest assert.ok( result.stderr.includes('10000 tool calls reached'), `Should accept threshold=10000. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('rejects COMPACT_THRESHOLD=10001 (falls back to 50)', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '49'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '10001' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '10001' }); // 10001 > 10000, invalid, falls back to 50, count becomes 50 → should suggest assert.ok( result.stderr.includes('50 tool calls reached'), `Should fallback to 50 for threshold=10001. Got stderr: ${result.stderr}` ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('rejects float COMPACT_THRESHOLD (e.g. 3.5)', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '49'); - const result = runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '3.5' }); + const result = runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '3.5' }); // parseInt('3.5') = 3, which is valid (> 0 && <= 10000) // count becomes 50, threshold=3, 50-3=47, 47%25≠0 and 50≠3 → no suggestion assert.strictEqual(result.code, 0); @@ -316,28 +339,30 @@ function runTests() { !result.stderr.includes('StrategicCompact'), 'Float threshold should be parseInt-ed to 3, no suggestion at count=50' ); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('counter value at exact boundary 1000000 is valid', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '999999'); - runCompact({ CLAUDE_SESSION_ID: testSession, COMPACT_THRESHOLD: '3' }); + runCompact({ CLAUDE_SESSION_ID: sessionId, COMPACT_THRESHOLD: '3' }); // 999999 is valid (> 0, <= 1000000), count becomes 1000000 const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 1000000, 'Counter at 1000000 boundary should be valid'); - cleanupCounter(); + cleanup(); })) passed++; else failed++; if (test('counter value at 1000001 is clamped (reset to 1)', () => { - cleanupCounter(); + const { sessionId, counterFile, cleanup } = createCounterContext(); + cleanup(); fs.writeFileSync(counterFile, '1000001'); - runCompact({ CLAUDE_SESSION_ID: testSession }); + runCompact({ CLAUDE_SESSION_ID: sessionId }); const count = parseInt(fs.readFileSync(counterFile, 'utf8').trim(), 10); assert.strictEqual(count, 1, 'Counter > 1000000 should be reset to 1'); - cleanupCounter(); + cleanup(); })) passed++; else failed++;