From 1b273de13f3ad9c5f440ffdc32814ffa8e63d63e Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 17:18:06 -0800 Subject: [PATCH] test: add 3 tests for grepFile Unicode, SAFE_NAME_REGEX traversal, getSessionSize boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 108: - grepFile with Unicode/emoji content (UTF-16 string matching on split lines) - getRunCommand accepts ../ path traversal via SAFE_NAME_REGEX (allows / and . individually) - getSessionSize exact 1024-byte B→KB boundary and 1MB KB→MB boundary --- tests/lib/package-manager.test.js | 24 ++++++++++++++++++++++++ tests/lib/session-manager.test.js | 30 ++++++++++++++++++++++++++++++ tests/lib/utils.test.js | 23 +++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/tests/lib/package-manager.test.js b/tests/lib/package-manager.test.js index 307b126e..0eaed0b7 100644 --- a/tests/lib/package-manager.test.js +++ b/tests/lib/package-manager.test.js @@ -1489,6 +1489,30 @@ function runTests() { 'Same string as explicit string arg is correctly rejected by SAFE_ARGS_REGEX'); })) passed++; else failed++; + // ── Round 108: getRunCommand with path traversal — SAFE_NAME_REGEX allows ../ sequences ── + console.log('\nRound 108: getRunCommand (path traversal — SAFE_NAME_REGEX permits ../ via allowed / and . chars):'); + if (test('getRunCommand accepts @scope/../../evil because SAFE_NAME_REGEX allows ../', () => { + const originalEnv = process.env.CLAUDE_PACKAGE_MANAGER; + try { + process.env.CLAUDE_PACKAGE_MANAGER = 'npm'; + // SAFE_NAME_REGEX = /^[@a-zA-Z0-9_.\/-]+$/ allows each char individually, + // so '../' passes despite being a path traversal sequence + const cmd = pm.getRunCommand('@scope/../../evil'); + assert.strictEqual(cmd, 'npm run @scope/../../evil', + 'Path traversal passes SAFE_NAME_REGEX because / and . are individually allowed'); + // Also verify plain ../ passes + const cmd2 = pm.getRunCommand('../../../etc/passwd'); + assert.strictEqual(cmd2, 'npm run ../../../etc/passwd', + 'Bare ../ traversal also passes the regex'); + } finally { + if (originalEnv !== undefined) { + process.env.CLAUDE_PACKAGE_MANAGER = originalEnv; + } else { + delete process.env.CLAUDE_PACKAGE_MANAGER; + } + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`); diff --git a/tests/lib/session-manager.test.js b/tests/lib/session-manager.test.js index 56a99502..e33da514 100644 --- a/tests/lib/session-manager.test.js +++ b/tests/lib/session-manager.test.js @@ -1785,6 +1785,36 @@ file.ts } })) passed++; else failed++; + // ── Round 108: getSessionSize exact boundary at 1024 bytes — B→KB transition ── + console.log('\nRound 108: getSessionSize (exact 1024-byte boundary — < means 1024 is KB, 1023 is B):'); + if (test('getSessionSize returns KB at exactly 1024 bytes and B at 1023', () => { + const dir = createTempSessionDir(); + try { + // Exactly 1024 bytes → size < 1024 is FALSE → goes to KB branch + const atBoundary = path.join(dir, 'exact-1024.tmp'); + fs.writeFileSync(atBoundary, 'x'.repeat(1024)); + const sizeAt = sessionManager.getSessionSize(atBoundary); + assert.strictEqual(sizeAt, '1.0 KB', + 'Exactly 1024 bytes should return "1.0 KB" (not "1024 B")'); + + // 1023 bytes → size < 1024 is TRUE → stays in B branch + const belowBoundary = path.join(dir, 'below-1024.tmp'); + fs.writeFileSync(belowBoundary, 'x'.repeat(1023)); + const sizeBelow = sessionManager.getSessionSize(belowBoundary); + assert.strictEqual(sizeBelow, '1023 B', + '1023 bytes should return "1023 B" (still in bytes range)'); + + // Exactly 1MB boundary → 1048576 bytes + const atMB = path.join(dir, 'exact-1mb.tmp'); + fs.writeFileSync(atMB, 'x'.repeat(1024 * 1024)); + const sizeMB = sessionManager.getSessionSize(atMB); + assert.strictEqual(sizeMB, '1.0 MB', + 'Exactly 1MB should return "1.0 MB" (not "1024.0 KB")'); + } finally { + cleanup(dir); + } + })) passed++; else failed++; + // Summary console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); diff --git a/tests/lib/utils.test.js b/tests/lib/utils.test.js index a6b25ffd..69ab1b84 100644 --- a/tests/lib/utils.test.js +++ b/tests/lib/utils.test.js @@ -1629,6 +1629,29 @@ function runTests() { } })) passed++; else failed++; + // ── Round 108: grepFile with Unicode/emoji content — UTF-16 string matching on split lines ── + console.log('\nRound 108: grepFile (Unicode/emoji — regex matching on UTF-16 split lines):'); + if (test('grepFile finds Unicode emoji patterns across lines', () => { + const tmpDir = fs.mkdtempSync(path.join(utils.getTempDir(), 'r108-grep-unicode-')); + const testFile = path.join(tmpDir, 'test.txt'); + try { + fs.writeFileSync(testFile, '🎉 celebration\nnormal line\n🎉 party\n日本語テスト'); + const emojiResults = utils.grepFile(testFile, /🎉/); + assert.strictEqual(emojiResults.length, 2, + 'Should find emoji on 2 lines (lines 1 and 3)'); + assert.strictEqual(emojiResults[0].lineNumber, 1); + assert.strictEqual(emojiResults[1].lineNumber, 3); + const cjkResults = utils.grepFile(testFile, /日本語/); + assert.strictEqual(cjkResults.length, 1, + 'Should find CJK characters on line 4'); + assert.strictEqual(cjkResults[0].lineNumber, 4); + assert.ok(cjkResults[0].content.includes('日本語テスト'), + 'Matched line should contain full CJK text'); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`);