From 241c35a589525f87eebb6e4bf9b6f29139cb7cc7 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 09:55:00 -0800 Subject: [PATCH] test: cover setGlobal/setProject catch blocks and session-start main().catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - setup-package-manager: setGlobal catch when HOME is non-directory (ENOTDIR) - setup-package-manager: setProject catch when CWD is read-only (EACCES) - session-start: main().catch handler when ensureDir throws (exit 0, don't block) Total tests: 825 → 828 --- tests/hooks/hooks.test.js | 20 +++++++++ tests/scripts/setup-package-manager.test.js | 45 +++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 8de08b8b..cc44d17d 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -3153,6 +3153,26 @@ async function runTests() { } })) passed++; else failed++; + // ── Round 74: session-start.js main().catch handler ── + console.log('\nRound 74: session-start.js (main catch — unrecoverable error):'); + + if (await asyncTest('session-start exits 0 with error message when HOME is non-directory', async () => { + if (process.platform === 'win32') { + console.log(' (skipped — /dev/null not available on Windows)'); + return; + } + // HOME=/dev/null makes ensureDir(sessionsDir) throw ENOTDIR, + // which propagates to main().catch — the top-level error boundary + const result = await runScript(path.join(scriptsDir, 'session-start.js'), '', { + HOME: '/dev/null', + USERPROFILE: '/dev/null' + }); + assert.strictEqual(result.code, 0, + `Should exit 0 (don't block on errors), got ${result.code}`); + assert.ok(result.stderr.includes('[SessionStart] Error:'), + `stderr should contain [SessionStart] Error:, got: ${result.stderr}`); + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); diff --git a/tests/scripts/setup-package-manager.test.js b/tests/scripts/setup-package-manager.test.js index ca0b6fa5..04cdb8c6 100644 --- a/tests/scripts/setup-package-manager.test.js +++ b/tests/scripts/setup-package-manager.test.js @@ -344,6 +344,51 @@ function runTests() { assert.strictEqual(currentCount, 1, `Expected exactly 1 "(current)" in --list, found ${currentCount}`); })) passed++; else failed++; + // ── Round 74: setGlobal catch — setPreferredPackageManager throws ── + console.log('\nRound 74: setGlobal catch (save failure):'); + + if (test('--global npm fails when HOME is not a directory', () => { + if (process.platform === 'win32') { + console.log(' (skipped — /dev/null not available on Windows)'); + return; + } + // HOME=/dev/null causes ensureDir to throw ENOTDIR when creating ~/.claude/ + const result = run(['--global', 'npm'], { HOME: '/dev/null', USERPROFILE: '/dev/null' }); + assert.strictEqual(result.code, 1, `Expected exit 1, got ${result.code}`); + assert.ok(result.stderr.includes('Error:'), + `stderr should contain Error:, got: ${result.stderr}`); + })) passed++; else failed++; + + // ── Round 74: setProject catch — setProjectPackageManager throws ── + console.log('\nRound 74: setProject catch (save failure):'); + + if (test('--project npm fails when CWD is read-only', () => { + if (process.platform === 'win32' || process.getuid?.() === 0) { + console.log(' (skipped — chmod ineffective on Windows/root)'); + return; + } + const tmpDir = path.join(os.tmpdir(), `spm-test-ro-${Date.now()}`); + fs.mkdirSync(tmpDir, { recursive: true }); + try { + // Make CWD read-only so .claude/ dir creation fails with EACCES + fs.chmodSync(tmpDir, 0o555); + const result = require('child_process').spawnSync('node', [SCRIPT, '--project', 'npm'], { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'pipe'], + env: { ...process.env }, + timeout: 10000, + cwd: tmpDir + }); + assert.strictEqual(result.status, 1, + `Expected exit 1, got ${result.status}. stderr: ${result.stderr}`); + assert.ok(result.stderr.includes('Error:'), + `stderr should contain Error:, got: ${result.stderr}`); + } finally { + try { fs.chmodSync(tmpDir, 0o755); } catch { /* best-effort */ } + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0);