From 25c5d58c440f97c0102035de9ef487e3d26c8be3 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 18:19:21 -0800 Subject: [PATCH] test: add Round 118 edge-case tests for writeFile type safety, renameAlias self, and reserved alias names - writeFile: null/undefined/number content throws TypeError (no try/catch unlike replaceInFile) - renameAlias: same-name rename returns "already exists" (no self-rename short-circuit) - setAlias: reserved names (list, help, remove, delete, create, set) rejected case-insensitively Total tests: 914 --- tests/lib/session-aliases.test.js | 54 +++++++++++++++++++++++++++++++ tests/lib/utils.test.js | 41 +++++++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/tests/lib/session-aliases.test.js b/tests/lib/session-aliases.test.js index e9b52bfc..97e6d5cd 100644 --- a/tests/lib/session-aliases.test.js +++ b/tests/lib/session-aliases.test.js @@ -1563,6 +1563,60 @@ function runTests() { 'Extra field should survive save/load round-trip'); })) passed++; else failed++; + // ── Round 118: renameAlias to the same name — "already exists" because self-check ── + console.log('\nRound 118: renameAlias (same name — "already exists" because data.aliases[newAlias] is truthy):'); + if (test('renameAlias to the same name returns "already exists" error (no self-rename short-circuit)', () => { + resetAliases(); + aliases.setAlias('same-name', '/path/to/session'); + + // Rename 'same-name' → 'same-name' + // Line 333: data.aliases[newAlias] → truthy (the alias exists under that name) + // Returns error before checking if oldAlias === newAlias + const result = aliases.renameAlias('same-name', 'same-name'); + assert.strictEqual(result.success, false, 'Should fail'); + assert.ok(result.error.includes('already exists'), + 'Error should say "already exists" (not "same name" or a no-op success)'); + + // Verify alias is unchanged + const resolved = aliases.resolveAlias('same-name'); + assert.ok(resolved, 'Original alias should still exist'); + assert.strictEqual(resolved.sessionPath, '/path/to/session'); + })) passed++; else failed++; + + // ── Round 118: setAlias reserved names — case-insensitive rejection ── + console.log('\nRound 118: setAlias (reserved names — case-insensitive rejection):'); + if (test('setAlias rejects all reserved names case-insensitively (list, help, remove, delete, create, set)', () => { + resetAliases(); + + // All reserved names in lowercase + const reserved = ['list', 'help', 'remove', 'delete', 'create', 'set']; + for (const name of reserved) { + const result = aliases.setAlias(name, '/path/to/session'); + assert.strictEqual(result.success, false, + `'${name}' should be rejected as reserved`); + assert.ok(result.error.includes('reserved'), + `Error for '${name}' should mention "reserved"`); + } + + // Case-insensitive: uppercase variants also rejected + const upperResult = aliases.setAlias('LIST', '/path/to/session'); + assert.strictEqual(upperResult.success, false, + '"LIST" (uppercase) should be rejected (toLowerCase check)'); + + const mixedResult = aliases.setAlias('Help', '/path/to/session'); + assert.strictEqual(mixedResult.success, false, + '"Help" (mixed case) should be rejected'); + + const allCapsResult = aliases.setAlias('DELETE', '/path/to/session'); + assert.strictEqual(allCapsResult.success, false, + '"DELETE" (all caps) should be rejected'); + + // Non-reserved names work fine + const validResult = aliases.setAlias('my-session', '/path/to/session'); + assert.strictEqual(validResult.success, true, + 'Non-reserved name should succeed'); + })) 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 d5fc69e8..d2c2db43 100644 --- a/tests/lib/utils.test.js +++ b/tests/lib/utils.test.js @@ -1933,6 +1933,47 @@ function runTests() { ); })) passed++; else failed++; + // ── Round 118: writeFile with non-string content — TypeError propagates (no try/catch) ── + console.log('\nRound 118: writeFile (non-string content — TypeError propagates uncaught):'); + if (test('writeFile with null/number content throws TypeError because fs.writeFileSync rejects non-string data', () => { + const tmpDir = fs.mkdtempSync(path.join(utils.getTempDir(), 'r118-writefile-type-')); + const testFile = path.join(tmpDir, 'test.txt'); + try { + // null content → TypeError from fs.writeFileSync (data must be string/Buffer/etc.) + assert.throws( + () => utils.writeFile(testFile, null), + (err) => err instanceof TypeError, + 'writeFile(path, null) should throw TypeError (no try/catch in writeFile)' + ); + + // undefined content → TypeError + assert.throws( + () => utils.writeFile(testFile, undefined), + (err) => err instanceof TypeError, + 'writeFile(path, undefined) should throw TypeError' + ); + + // number content → TypeError (numbers not valid for fs.writeFileSync) + assert.throws( + () => utils.writeFile(testFile, 42), + (err) => err instanceof TypeError, + 'writeFile(path, 42) should throw TypeError (number not a valid data type)' + ); + + // Contrast: string content works fine + utils.writeFile(testFile, 'valid string content'); + assert.strictEqual(utils.readFile(testFile), 'valid string content', + 'String content should write and read back correctly'); + + // Empty string is valid + utils.writeFile(testFile, ''); + assert.strictEqual(utils.readFile(testFile), '', + 'Empty string should write correctly'); + } finally { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`);