From 990c08159c04170e7bf5cbdd96f7e73508e2b058 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 05:59:07 -0800 Subject: [PATCH] test: add tsconfig walk-up, compact fallback, and Windows atomic write tests (Round 56) --- tests/hooks/hooks.test.js | 45 +++++++++++++++++++++++++++++++ tests/lib/session-aliases.test.js | 40 +++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 0393d58c..8c6692eb 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -2811,6 +2811,51 @@ async function runTests() { } })) passed++; else failed++; + // ── Round 56: typecheck tsconfig walk-up, suggest-compact fallback path ── + console.log('\nRound 56: post-edit-typecheck.js (tsconfig in parent directory):'); + + if (await asyncTest('walks up directory tree to find tsconfig.json in grandparent', async () => { + const testDir = createTestDir(); + // Place tsconfig at the TOP level, file is nested 2 levels deep + fs.writeFileSync(path.join(testDir, 'tsconfig.json'), JSON.stringify({ + compilerOptions: { strict: false, noEmit: true } + })); + const deepDir = path.join(testDir, 'src', 'components'); + fs.mkdirSync(deepDir, { recursive: true }); + const testFile = path.join(deepDir, 'widget.ts'); + fs.writeFileSync(testFile, 'export const value: number = 42;\n'); + + const stdinJson = JSON.stringify({ tool_input: { file_path: testFile } }); + const result = await runScript(path.join(scriptsDir, 'post-edit-typecheck.js'), stdinJson); + + assert.strictEqual(result.code, 0, 'Should exit 0 after walking up to find tsconfig'); + // Core assertion: stdin must pass through regardless of whether tsc ran + const parsed = JSON.parse(result.stdout); + assert.strictEqual(parsed.tool_input.file_path, testFile, + 'Should pass through original stdin data with file_path intact'); + cleanupTestDir(testDir); + })) passed++; else failed++; + + console.log('\nRound 56: suggest-compact.js (counter file as directory — fallback path):'); + + if (await asyncTest('exits 0 when counter file path is occupied by a directory', async () => { + const sessionId = `dirblock-${Date.now()}`; + const counterFile = path.join(os.tmpdir(), `claude-tool-count-${sessionId}`); + // Create a DIRECTORY at the counter file path — openSync('a+') will fail with EISDIR + fs.mkdirSync(counterFile); + + try { + const result = await runScript(path.join(scriptsDir, 'suggest-compact.js'), '', { + CLAUDE_SESSION_ID: sessionId + }); + assert.strictEqual(result.code, 0, + 'Should exit 0 even when counter file path is a directory (graceful fallback)'); + } finally { + // Cleanup: remove the blocking directory + try { fs.rmdirSync(counterFile); } catch { /* best-effort */ } + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); diff --git a/tests/lib/session-aliases.test.js b/tests/lib/session-aliases.test.js index e2b49f07..56e7c139 100644 --- a/tests/lib/session-aliases.test.js +++ b/tests/lib/session-aliases.test.js @@ -856,6 +856,46 @@ function runTests() { assert.strictEqual(data.metadata.totalCount, 5, 'Metadata count should match actual aliases'); })) passed++; else failed++; + // ── Round 56: Windows platform unlink-before-rename code path ── + console.log('\nRound 56: Windows platform atomic write path:'); + + if (test('Windows platform mock: unlinks existing file before rename', () => { + resetAliases(); + // First create an alias so the file exists + const r1 = aliases.setAlias('win-initial', '2026-01-01-abc123-session.tmp'); + assert.strictEqual(r1.success, true, 'Initial alias should succeed'); + const aliasesPath = aliases.getAliasesPath(); + assert.ok(fs.existsSync(aliasesPath), 'Aliases file should exist before win32 test'); + + // Mock process.platform to 'win32' to trigger the unlink-before-rename path + const origPlatform = Object.getOwnPropertyDescriptor(process, 'platform'); + Object.defineProperty(process, 'platform', { value: 'win32', configurable: true }); + + try { + // This save triggers the Windows code path: unlink existing → rename temp + const r2 = aliases.setAlias('win-updated', '2026-02-01-def456-session.tmp'); + assert.strictEqual(r2.success, true, 'setAlias should succeed under win32 mock'); + + // Verify data integrity after the Windows path + assert.ok(fs.existsSync(aliasesPath), 'Aliases file should exist after win32 save'); + const data = aliases.loadAliases(); + assert.ok(data.aliases['win-initial'], 'Original alias should still exist'); + assert.ok(data.aliases['win-updated'], 'New alias should exist'); + assert.strictEqual(data.aliases['win-updated'].sessionPath, + '2026-02-01-def456-session.tmp', 'Session path should match'); + + // No .tmp or .bak files left behind + assert.ok(!fs.existsSync(aliasesPath + '.tmp'), 'No temp file should remain'); + assert.ok(!fs.existsSync(aliasesPath + '.bak'), 'No backup file should remain'); + } finally { + // Restore original platform descriptor + if (origPlatform) { + Object.defineProperty(process, 'platform', origPlatform); + } + resetAliases(); + } + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0);