From d90305383079d9f62619b9bfa785df62fb460e49 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 02:54:23 -0800 Subject: [PATCH] test: add 15 tests for session-manager and session-aliases edge cases Cover 30-day month validation (Sep/Nov 31 rejection), getSessionStats path heuristic with multiline content, combined date+search+pagination in getAllSessions, ambiguous prefix matching in getSessionById, unclosed code fence in parseSessionMetadata, empty checklist item behavior, reserved name case sensitivity (LIST/Help/Set), negative limit in listAliases, and undefined title in setAlias. --- tests/lib/session-aliases.test.js | 46 ++++++++++++++++ tests/lib/session-manager.test.js | 87 +++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/tests/lib/session-aliases.test.js b/tests/lib/session-aliases.test.js index 9debbc33..8e12ea8c 100644 --- a/tests/lib/session-aliases.test.js +++ b/tests/lib/session-aliases.test.js @@ -636,6 +636,52 @@ function runTests() { assert.strictEqual(result[0].name, 'partial'); })) passed++; else failed++; + // ── Round 26 tests ── + + console.log('\nsetAlias (reserved names case sensitivity):'); + + if (test('rejects uppercase reserved name LIST', () => { + resetAliases(); + const result = aliases.setAlias('LIST', '/path'); + assert.strictEqual(result.success, false); + assert.ok(result.error.includes('reserved')); + })) passed++; else failed++; + + if (test('rejects mixed-case reserved name Help', () => { + resetAliases(); + const result = aliases.setAlias('Help', '/path'); + assert.strictEqual(result.success, false); + assert.ok(result.error.includes('reserved')); + })) passed++; else failed++; + + if (test('rejects mixed-case reserved name Set', () => { + resetAliases(); + const result = aliases.setAlias('Set', '/path'); + assert.strictEqual(result.success, false); + assert.ok(result.error.includes('reserved')); + })) passed++; else failed++; + + console.log('\nlistAliases (negative limit):'); + + if (test('negative limit does not truncate results', () => { + resetAliases(); + aliases.setAlias('one', '/path1'); + aliases.setAlias('two', '/path2'); + const list = aliases.listAliases({ limit: -5 }); + // -5 fails the `limit > 0` check, so no slicing happens + assert.strictEqual(list.length, 2, 'Negative limit should not apply'); + })) passed++; else failed++; + + console.log('\nsetAlias (undefined title):'); + + if (test('undefined title becomes null (same as explicit null)', () => { + resetAliases(); + const result = aliases.setAlias('undef-title', '/path', undefined); + assert.strictEqual(result.success, true); + const resolved = aliases.resolveAlias('undef-title'); + assert.strictEqual(resolved.title, null, 'undefined title should become null'); + })) passed++; else failed++; + // Cleanup — restore both HOME and USERPROFILE (Windows) process.env.HOME = origHome; if (origUserProfile !== undefined) { diff --git a/tests/lib/session-manager.test.js b/tests/lib/session-manager.test.js index 0a2c7d28..57f15b59 100644 --- a/tests/lib/session-manager.test.js +++ b/tests/lib/session-manager.test.js @@ -851,6 +851,93 @@ src/main.ts assert.strictEqual(stats.hasContext, false); })) passed++; else failed++; + // ── Round 26 tests ── + + console.log('\nparseSessionFilename (30-day month validation):'); + + if (test('rejects Sep 31 (September has 30 days)', () => { + const result = sessionManager.parseSessionFilename('2026-09-31-abcd1234-session.tmp'); + assert.strictEqual(result, null, 'Sep 31 does not exist'); + })) passed++; else failed++; + + if (test('rejects Nov 31 (November has 30 days)', () => { + const result = sessionManager.parseSessionFilename('2026-11-31-abcd1234-session.tmp'); + assert.strictEqual(result, null, 'Nov 31 does not exist'); + })) passed++; else failed++; + + if (test('accepts Sep 30 (valid 30-day month boundary)', () => { + const result = sessionManager.parseSessionFilename('2026-09-30-abcd1234-session.tmp'); + assert.ok(result, 'Sep 30 is valid'); + assert.strictEqual(result.date, '2026-09-30'); + })) passed++; else failed++; + + console.log('\ngetSessionStats (path heuristic edge cases):'); + + if (test('multiline content ending with .tmp is treated as content', () => { + const content = 'Line 1\nLine 2\nDownload file.tmp'; + const stats = sessionManager.getSessionStats(content); + // Has newlines so looksLikePath is false → treated as content + assert.strictEqual(stats.lineCount, 3, 'Should count 3 lines'); + })) passed++; else failed++; + + if (test('single-line content not starting with / treated as content', () => { + const content = 'some random text.tmp'; + const stats = sessionManager.getSessionStats(content); + assert.strictEqual(stats.lineCount, 1, 'Should treat as content, not a path'); + })) passed++; else failed++; + + console.log('\ngetAllSessions (combined filters):'); + + if (test('combines date filter + search filter + pagination', () => { + // We have 2026-02-01-ijkl9012 and 2026-02-01-mnop3456 with date 2026-02-01 + const result = sessionManager.getAllSessions({ + date: '2026-02-01', + search: 'ijkl', + limit: 10 + }); + assert.strictEqual(result.total, 1, 'Only one session matches both date and search'); + assert.strictEqual(result.sessions[0].shortId, 'ijkl9012'); + })) passed++; else failed++; + + if (test('date filter + offset beyond matches returns empty', () => { + const result = sessionManager.getAllSessions({ + date: '2026-02-01', + offset: 100, + limit: 10 + }); + assert.strictEqual(result.sessions.length, 0); + assert.strictEqual(result.total, 2, 'Two sessions match the date'); + assert.strictEqual(result.hasMore, false); + })) passed++; else failed++; + + console.log('\ngetSessionById (ambiguous prefix):'); + + if (test('returns first match when multiple sessions share a prefix', () => { + // Sessions with IDs abcd1234 and efgh5678 exist + // 'e' should match efgh5678 (only match) + const result = sessionManager.getSessionById('efgh'); + assert.ok(result, 'Should find session by prefix'); + assert.strictEqual(result.shortId, 'efgh5678'); + })) passed++; else failed++; + + console.log('\nparseSessionMetadata (edge cases):'); + + if (test('handles unclosed code fence in Context section', () => { + const content = '# Session\n\n### Context to Load\n```\nsrc/index.ts\n'; + const meta = sessionManager.parseSessionMetadata(content); + // Regex requires closing ```, so no context should be extracted + assert.strictEqual(meta.context, '', 'Unclosed code fence should not extract context'); + })) passed++; else failed++; + + if (test('handles empty task text in checklist items', () => { + const content = '# Session\n\n### Completed\n- [x] \n- [x] Real task\n'; + const meta = sessionManager.parseSessionMetadata(content); + // \s* in the regex bridges across newlines, collapsing the empty + // task + next task into a single match. This is an edge case — + // real sessions don't have empty checklist items. + assert.strictEqual(meta.completed.length, 1); + })) passed++; else failed++; + // Cleanup — restore both HOME and USERPROFILE (Windows) process.env.HOME = origHome; if (origUserProfile !== undefined) {