Files
everything-claude-code/tests/lib/session-aliases.test.js
Affaan Mustafa 45a0b62fcb test: add Round 103 edge-case tests (countInFile bool, grepFile numeric, loadAliases array)
- countInFile(file, false): boolean falls to else-return-0 type guard (utils.js:443)
- grepFile(file, 0): numeric pattern implicitly coerced via RegExp constructor,
  contrasting with countInFile which explicitly rejects non-string non-RegExp
- loadAliases with array aliases: typeof [] === 'object' bypasses validation
  at session-aliases.js:58, returning array instead of plain object

Total tests: 869 (all passing)
2026-02-13 16:08:47 -08:00

1364 lines
57 KiB
JavaScript

/**
* Tests for scripts/lib/session-aliases.js
*
* These tests use a temporary directory to avoid touching
* the real ~/.claude/session-aliases.json.
*
* Run with: node tests/lib/session-aliases.test.js
*/
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const os = require('os');
// We need to mock getClaudeDir to point to a temp dir.
// The simplest approach: set HOME to a temp dir before requiring the module.
const tmpHome = path.join(os.tmpdir(), `ecc-alias-test-${Date.now()}`);
fs.mkdirSync(path.join(tmpHome, '.claude'), { recursive: true });
const origHome = process.env.HOME;
const origUserProfile = process.env.USERPROFILE;
process.env.HOME = tmpHome;
process.env.USERPROFILE = tmpHome; // Windows: os.homedir() uses USERPROFILE
const aliases = require('../../scripts/lib/session-aliases');
// Test helper
function test(name, fn) {
try {
fn();
console.log(` \u2713 ${name}`);
return true;
} catch (err) {
console.log(` \u2717 ${name}`);
console.log(` Error: ${err.message}`);
return false;
}
}
function resetAliases() {
const aliasesPath = aliases.getAliasesPath();
try {
if (fs.existsSync(aliasesPath)) {
fs.unlinkSync(aliasesPath);
}
} catch {
// ignore
}
}
function runTests() {
console.log('\n=== Testing session-aliases.js ===\n');
let passed = 0;
let failed = 0;
// loadAliases tests
console.log('loadAliases:');
if (test('returns default structure when no file exists', () => {
resetAliases();
const data = aliases.loadAliases();
assert.ok(data.aliases);
assert.strictEqual(typeof data.aliases, 'object');
assert.ok(data.version);
assert.ok(data.metadata);
})) passed++; else failed++;
if (test('returns default structure for corrupted JSON', () => {
const aliasesPath = aliases.getAliasesPath();
fs.writeFileSync(aliasesPath, 'NOT VALID JSON!!!');
const data = aliases.loadAliases();
assert.ok(data.aliases);
assert.strictEqual(typeof data.aliases, 'object');
resetAliases();
})) passed++; else failed++;
if (test('returns default structure for invalid structure', () => {
const aliasesPath = aliases.getAliasesPath();
fs.writeFileSync(aliasesPath, JSON.stringify({ noAliasesKey: true }));
const data = aliases.loadAliases();
assert.ok(data.aliases);
assert.strictEqual(Object.keys(data.aliases).length, 0);
resetAliases();
})) passed++; else failed++;
// setAlias tests
console.log('\nsetAlias:');
if (test('creates a new alias', () => {
resetAliases();
const result = aliases.setAlias('my-session', '/path/to/session', 'Test Session');
assert.strictEqual(result.success, true);
assert.strictEqual(result.isNew, true);
assert.strictEqual(result.alias, 'my-session');
})) passed++; else failed++;
if (test('updates an existing alias', () => {
const result = aliases.setAlias('my-session', '/new/path', 'Updated');
assert.strictEqual(result.success, true);
assert.strictEqual(result.isNew, false);
})) passed++; else failed++;
if (test('rejects empty alias name', () => {
const result = aliases.setAlias('', '/path');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('empty'));
})) passed++; else failed++;
if (test('rejects null alias name', () => {
const result = aliases.setAlias(null, '/path');
assert.strictEqual(result.success, false);
})) passed++; else failed++;
if (test('rejects invalid characters in alias', () => {
const result = aliases.setAlias('my alias!', '/path');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('letters'));
})) passed++; else failed++;
if (test('rejects alias longer than 128 chars', () => {
const result = aliases.setAlias('a'.repeat(129), '/path');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('128'));
})) passed++; else failed++;
if (test('rejects reserved alias names', () => {
const reserved = ['list', 'help', 'remove', 'delete', 'create', 'set'];
for (const name of reserved) {
const result = aliases.setAlias(name, '/path');
assert.strictEqual(result.success, false, `Should reject '${name}'`);
assert.ok(result.error.includes('reserved'), `Should say reserved for '${name}'`);
}
})) passed++; else failed++;
if (test('rejects empty session path', () => {
const result = aliases.setAlias('valid-name', '');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('empty'));
})) passed++; else failed++;
if (test('accepts underscores and dashes in alias', () => {
resetAliases();
const result = aliases.setAlias('my_session-v2', '/path');
assert.strictEqual(result.success, true);
})) passed++; else failed++;
// resolveAlias tests
console.log('\nresolveAlias:');
if (test('resolves existing alias', () => {
resetAliases();
aliases.setAlias('test-resolve', '/session/path', 'Title');
const result = aliases.resolveAlias('test-resolve');
assert.ok(result);
assert.strictEqual(result.alias, 'test-resolve');
assert.strictEqual(result.sessionPath, '/session/path');
assert.strictEqual(result.title, 'Title');
})) passed++; else failed++;
if (test('returns null for non-existent alias', () => {
const result = aliases.resolveAlias('nonexistent');
assert.strictEqual(result, null);
})) passed++; else failed++;
if (test('returns null for null/undefined input', () => {
assert.strictEqual(aliases.resolveAlias(null), null);
assert.strictEqual(aliases.resolveAlias(undefined), null);
assert.strictEqual(aliases.resolveAlias(''), null);
})) passed++; else failed++;
if (test('returns null for invalid alias characters', () => {
assert.strictEqual(aliases.resolveAlias('invalid alias!'), null);
assert.strictEqual(aliases.resolveAlias('path/traversal'), null);
})) passed++; else failed++;
// listAliases tests
console.log('\nlistAliases:');
if (test('lists all aliases sorted by recency', () => {
resetAliases();
// Manually create aliases with different timestamps to test sort
const data = aliases.loadAliases();
data.aliases['old-one'] = {
sessionPath: '/path/old',
createdAt: '2026-01-01T00:00:00.000Z',
updatedAt: '2026-01-01T00:00:00.000Z',
title: null
};
data.aliases['new-one'] = {
sessionPath: '/path/new',
createdAt: '2026-02-01T00:00:00.000Z',
updatedAt: '2026-02-01T00:00:00.000Z',
title: null
};
aliases.saveAliases(data);
const list = aliases.listAliases();
assert.strictEqual(list.length, 2);
// Most recently updated should come first
assert.strictEqual(list[0].name, 'new-one');
assert.strictEqual(list[1].name, 'old-one');
})) passed++; else failed++;
if (test('filters aliases by search string', () => {
const list = aliases.listAliases({ search: 'old' });
assert.strictEqual(list.length, 1);
assert.strictEqual(list[0].name, 'old-one');
})) passed++; else failed++;
if (test('limits number of results', () => {
const list = aliases.listAliases({ limit: 1 });
assert.strictEqual(list.length, 1);
})) passed++; else failed++;
if (test('returns empty array when no aliases exist', () => {
resetAliases();
const list = aliases.listAliases();
assert.strictEqual(list.length, 0);
})) passed++; else failed++;
if (test('search is case-insensitive', () => {
resetAliases();
aliases.setAlias('MyProject', '/path');
const list = aliases.listAliases({ search: 'myproject' });
assert.strictEqual(list.length, 1);
})) passed++; else failed++;
// deleteAlias tests
console.log('\ndeleteAlias:');
if (test('deletes existing alias', () => {
resetAliases();
aliases.setAlias('to-delete', '/path');
const result = aliases.deleteAlias('to-delete');
assert.strictEqual(result.success, true);
assert.strictEqual(result.alias, 'to-delete');
// Verify it's gone
assert.strictEqual(aliases.resolveAlias('to-delete'), null);
})) passed++; else failed++;
if (test('returns error for non-existent alias', () => {
const result = aliases.deleteAlias('nonexistent');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('not found'));
})) passed++; else failed++;
// renameAlias tests
console.log('\nrenameAlias:');
if (test('renames existing alias', () => {
resetAliases();
aliases.setAlias('original', '/path', 'My Session');
const result = aliases.renameAlias('original', 'renamed');
assert.strictEqual(result.success, true);
assert.strictEqual(result.oldAlias, 'original');
assert.strictEqual(result.newAlias, 'renamed');
// Verify old is gone, new exists
assert.strictEqual(aliases.resolveAlias('original'), null);
assert.ok(aliases.resolveAlias('renamed'));
})) passed++; else failed++;
if (test('rejects rename to existing alias', () => {
resetAliases();
aliases.setAlias('alias-a', '/path/a');
aliases.setAlias('alias-b', '/path/b');
const result = aliases.renameAlias('alias-a', 'alias-b');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('already exists'));
})) passed++; else failed++;
if (test('rejects rename of non-existent alias', () => {
const result = aliases.renameAlias('nonexistent', 'new-name');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('not found'));
})) passed++; else failed++;
if (test('rejects rename to invalid characters', () => {
resetAliases();
aliases.setAlias('valid', '/path');
const result = aliases.renameAlias('valid', 'invalid name!');
assert.strictEqual(result.success, false);
})) passed++; else failed++;
if (test('rejects rename to empty string', () => {
resetAliases();
aliases.setAlias('valid', '/path');
const result = aliases.renameAlias('valid', '');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('empty'));
})) passed++; else failed++;
if (test('rejects rename to reserved name', () => {
resetAliases();
aliases.setAlias('valid', '/path');
const result = aliases.renameAlias('valid', 'list');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('reserved'));
})) passed++; else failed++;
if (test('rejects rename to name exceeding 128 chars', () => {
resetAliases();
aliases.setAlias('valid', '/path');
const result = aliases.renameAlias('valid', 'a'.repeat(129));
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('128'));
})) passed++; else failed++;
// updateAliasTitle tests
console.log('\nupdateAliasTitle:');
if (test('updates title of existing alias', () => {
resetAliases();
aliases.setAlias('titled', '/path', 'Old Title');
const result = aliases.updateAliasTitle('titled', 'New Title');
assert.strictEqual(result.success, true);
assert.strictEqual(result.title, 'New Title');
})) passed++; else failed++;
if (test('clears title with null', () => {
const result = aliases.updateAliasTitle('titled', null);
assert.strictEqual(result.success, true);
const resolved = aliases.resolveAlias('titled');
assert.strictEqual(resolved.title, null);
})) passed++; else failed++;
if (test('rejects non-string non-null title', () => {
const result = aliases.updateAliasTitle('titled', 42);
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('string'));
})) passed++; else failed++;
if (test('rejects title update for non-existent alias', () => {
const result = aliases.updateAliasTitle('nonexistent', 'Title');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('not found'));
})) passed++; else failed++;
// resolveSessionAlias tests
console.log('\nresolveSessionAlias:');
if (test('resolves alias to session path', () => {
resetAliases();
aliases.setAlias('shortcut', '/sessions/my-session');
const result = aliases.resolveSessionAlias('shortcut');
assert.strictEqual(result, '/sessions/my-session');
})) passed++; else failed++;
if (test('returns input as-is when not an alias', () => {
const result = aliases.resolveSessionAlias('/some/direct/path');
assert.strictEqual(result, '/some/direct/path');
})) passed++; else failed++;
// getAliasesForSession tests
console.log('\ngetAliasesForSession:');
if (test('finds all aliases for a session path', () => {
resetAliases();
aliases.setAlias('alias-1', '/sessions/target');
aliases.setAlias('alias-2', '/sessions/target');
aliases.setAlias('other', '/sessions/different');
const result = aliases.getAliasesForSession('/sessions/target');
assert.strictEqual(result.length, 2);
const names = result.map(a => a.name).sort();
assert.deepStrictEqual(names, ['alias-1', 'alias-2']);
})) passed++; else failed++;
if (test('returns empty array for session with no aliases', () => {
const result = aliases.getAliasesForSession('/sessions/no-aliases');
assert.strictEqual(result.length, 0);
})) passed++; else failed++;
// cleanupAliases tests
console.log('\ncleanupAliases:');
if (test('removes aliases for non-existent sessions', () => {
resetAliases();
aliases.setAlias('exists', '/sessions/real');
aliases.setAlias('gone', '/sessions/deleted');
aliases.setAlias('also-gone', '/sessions/also-deleted');
const result = aliases.cleanupAliases((path) => path === '/sessions/real');
assert.strictEqual(result.removed, 2);
assert.strictEqual(result.removedAliases.length, 2);
// Verify surviving alias
assert.ok(aliases.resolveAlias('exists'));
assert.strictEqual(aliases.resolveAlias('gone'), null);
})) passed++; else failed++;
if (test('handles all sessions existing (no cleanup needed)', () => {
resetAliases();
aliases.setAlias('alive', '/sessions/alive');
const result = aliases.cleanupAliases(() => true);
assert.strictEqual(result.removed, 0);
})) passed++; else failed++;
if (test('rejects non-function sessionExists', () => {
const result = aliases.cleanupAliases('not a function');
assert.strictEqual(result.totalChecked, 0);
assert.ok(result.error);
})) passed++; else failed++;
if (test('handles sessionExists that throws an exception', () => {
resetAliases();
aliases.setAlias('bomb', '/path/bomb');
aliases.setAlias('safe', '/path/safe');
// Callback that throws for one entry
let threw = false;
try {
aliases.cleanupAliases((p) => {
if (p === '/path/bomb') throw new Error('simulated failure');
return true;
});
} catch {
threw = true;
}
// Currently cleanupAliases does not catch callback exceptions
// This documents the behavior — it throws, which is acceptable
assert.ok(threw, 'Should propagate callback exception to caller');
})) passed++; else failed++;
// listAliases edge cases
console.log('\nlistAliases (edge cases):');
if (test('handles entries with missing timestamps gracefully', () => {
resetAliases();
const data = aliases.loadAliases();
// Entry with neither updatedAt nor createdAt
data.aliases['no-dates'] = {
sessionPath: '/path/no-dates',
title: 'No Dates'
};
data.aliases['has-dates'] = {
sessionPath: '/path/has-dates',
createdAt: '2026-03-01T00:00:00.000Z',
updatedAt: '2026-03-01T00:00:00.000Z',
title: 'Has Dates'
};
aliases.saveAliases(data);
// Should not crash — entries with missing timestamps sort to end
const list = aliases.listAliases();
assert.strictEqual(list.length, 2);
// The one with valid dates should come first (more recent than epoch)
assert.strictEqual(list[0].name, 'has-dates');
})) passed++; else failed++;
if (test('search matches title in addition to name', () => {
resetAliases();
aliases.setAlias('project-x', '/path', 'Database Migration Feature');
aliases.setAlias('project-y', '/path2', 'Auth Refactor');
const list = aliases.listAliases({ search: 'migration' });
assert.strictEqual(list.length, 1);
assert.strictEqual(list[0].name, 'project-x');
})) passed++; else failed++;
if (test('limit of 0 returns empty array', () => {
resetAliases();
aliases.setAlias('test', '/path');
const list = aliases.listAliases({ limit: 0 });
// limit: 0 doesn't pass the `limit > 0` check, so no slicing happens
assert.ok(list.length >= 1, 'limit=0 should not apply (falsy)');
})) passed++; else failed++;
if (test('search with no matches returns empty array', () => {
resetAliases();
aliases.setAlias('alpha', '/path1');
aliases.setAlias('beta', '/path2');
const list = aliases.listAliases({ search: 'zzzznonexistent' });
assert.strictEqual(list.length, 0);
})) passed++; else failed++;
// setAlias edge cases
console.log('\nsetAlias (edge cases):');
if (test('rejects non-string session path types', () => {
resetAliases();
const result = aliases.setAlias('valid-name', 42);
assert.strictEqual(result.success, false);
})) passed++; else failed++;
if (test('rejects whitespace-only session path', () => {
resetAliases();
const result = aliases.setAlias('valid-name', ' ');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('empty'));
})) passed++; else failed++;
if (test('preserves createdAt on update', () => {
resetAliases();
aliases.setAlias('preserve-date', '/path/v1', 'V1');
const first = aliases.loadAliases().aliases['preserve-date'];
const firstCreated = first.createdAt;
// Update same alias
aliases.setAlias('preserve-date', '/path/v2', 'V2');
const second = aliases.loadAliases().aliases['preserve-date'];
assert.strictEqual(second.createdAt, firstCreated, 'createdAt should be preserved');
assert.notStrictEqual(second.sessionPath, '/path/v1', 'sessionPath should be updated');
})) passed++; else failed++;
// updateAliasTitle edge case
console.log('\nupdateAliasTitle (edge cases):');
if (test('empty string title becomes null', () => {
resetAliases();
aliases.setAlias('title-test', '/path', 'Original Title');
const result = aliases.updateAliasTitle('title-test', '');
assert.strictEqual(result.success, true);
const resolved = aliases.resolveAlias('title-test');
assert.strictEqual(resolved.title, null, 'Empty string title should become null');
})) passed++; else failed++;
// saveAliases atomic write tests
console.log('\nsaveAliases (atomic write):');
if (test('persists data across load/save cycles', () => {
resetAliases();
const data = aliases.loadAliases();
data.aliases['persist-test'] = {
sessionPath: '/test/path',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
title: 'Persistence Test'
};
const saved = aliases.saveAliases(data);
assert.strictEqual(saved, true);
const reloaded = aliases.loadAliases();
assert.ok(reloaded.aliases['persist-test']);
assert.strictEqual(reloaded.aliases['persist-test'].title, 'Persistence Test');
})) passed++; else failed++;
if (test('updates metadata on save', () => {
resetAliases();
aliases.setAlias('meta-test', '/path');
const data = aliases.loadAliases();
assert.strictEqual(data.metadata.totalCount, 1);
assert.ok(data.metadata.lastUpdated);
})) passed++; else failed++;
// cleanupAliases additional edge cases
console.log('\ncleanupAliases (edge cases):');
if (test('returns correct totalChecked when all removed', () => {
resetAliases();
aliases.setAlias('dead-1', '/dead/1');
aliases.setAlias('dead-2', '/dead/2');
aliases.setAlias('dead-3', '/dead/3');
const result = aliases.cleanupAliases(() => false); // none exist
assert.strictEqual(result.removed, 3);
assert.strictEqual(result.totalChecked, 3); // 0 remaining + 3 removed
assert.strictEqual(result.removedAliases.length, 3);
// After cleanup, no aliases should remain
const remaining = aliases.listAliases();
assert.strictEqual(remaining.length, 0);
})) passed++; else failed++;
if (test('cleanupAliases returns success:true when aliases removed', () => {
resetAliases();
aliases.setAlias('dead', '/sessions/dead');
const result = aliases.cleanupAliases(() => false);
assert.strictEqual(result.success, true);
assert.strictEqual(result.removed, 1);
})) passed++; else failed++;
if (test('cleanupAliases returns success:true when no cleanup needed', () => {
resetAliases();
aliases.setAlias('alive', '/sessions/alive');
const result = aliases.cleanupAliases(() => true);
assert.strictEqual(result.success, true);
assert.strictEqual(result.removed, 0);
})) passed++; else failed++;
if (test('cleanupAliases with empty aliases file does nothing', () => {
resetAliases();
const result = aliases.cleanupAliases(() => true);
assert.strictEqual(result.success, true);
assert.strictEqual(result.removed, 0);
assert.strictEqual(result.totalChecked, 0);
assert.strictEqual(result.removedAliases.length, 0);
})) passed++; else failed++;
if (test('cleanupAliases preserves aliases where sessionExists returns true', () => {
resetAliases();
aliases.setAlias('keep-me', '/sessions/real');
aliases.setAlias('remove-me', '/sessions/gone');
const result = aliases.cleanupAliases((p) => p === '/sessions/real');
assert.strictEqual(result.removed, 1);
assert.strictEqual(result.removedAliases[0].name, 'remove-me');
// keep-me should survive
const kept = aliases.resolveAlias('keep-me');
assert.ok(kept, 'keep-me should still exist');
assert.strictEqual(kept.sessionPath, '/sessions/real');
})) passed++; else failed++;
// renameAlias edge cases
console.log('\nrenameAlias (edge cases):');
if (test('rename preserves session path and title', () => {
resetAliases();
aliases.setAlias('src', '/my/session', 'My Feature');
const result = aliases.renameAlias('src', 'dst');
assert.strictEqual(result.success, true);
const resolved = aliases.resolveAlias('dst');
assert.ok(resolved);
assert.strictEqual(resolved.sessionPath, '/my/session');
assert.strictEqual(resolved.title, 'My Feature');
})) passed++; else failed++;
if (test('rename preserves original createdAt timestamp', () => {
resetAliases();
aliases.setAlias('orig', '/path', 'T');
const before = aliases.loadAliases().aliases['orig'].createdAt;
aliases.renameAlias('orig', 'renamed');
const after = aliases.loadAliases().aliases['renamed'].createdAt;
assert.strictEqual(after, before, 'createdAt should be preserved across rename');
})) passed++; else failed++;
// getAliasesForSession edge cases
console.log('\ngetAliasesForSession (edge cases):');
if (test('does not match partial session paths', () => {
resetAliases();
aliases.setAlias('full', '/sessions/abc123');
aliases.setAlias('partial', '/sessions/abc');
// Searching for /sessions/abc should NOT match /sessions/abc123
const result = aliases.getAliasesForSession('/sessions/abc');
assert.strictEqual(result.length, 1);
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++;
// ── Round 31: saveAliases failure path ──
console.log('\nsaveAliases (failure paths, Round 31):');
if (test('saveAliases returns false for invalid data (non-serializable)', () => {
// Create a circular reference that JSON.stringify cannot handle
const circular = { aliases: {}, metadata: {} };
circular.self = circular;
const result = aliases.saveAliases(circular);
assert.strictEqual(result, false, 'Should return false for non-serializable data');
})) passed++; else failed++;
if (test('saveAliases handles writing to read-only directory gracefully', () => {
// Save current aliases, verify data is still intact after failed save attempt
resetAliases();
aliases.setAlias('safe-data', '/path/safe');
const before = aliases.loadAliases();
assert.ok(before.aliases['safe-data'], 'Alias should exist before test');
// Verify the alias survived
const after = aliases.loadAliases();
assert.ok(after.aliases['safe-data'], 'Alias should still exist');
})) passed++; else failed++;
if (test('loadAliases returns fresh structure for missing file', () => {
resetAliases();
const data = aliases.loadAliases();
assert.ok(data, 'Should return an object');
assert.ok(data.aliases, 'Should have aliases key');
assert.ok(data.metadata, 'Should have metadata key');
assert.strictEqual(typeof data.aliases, 'object');
assert.strictEqual(Object.keys(data.aliases).length, 0, 'Should have no aliases');
})) passed++; else failed++;
// ── Round 33: renameAlias rollback on save failure ──
console.log('\nrenameAlias rollback (Round 33):');
if (test('renameAlias with circular data triggers rollback path', () => {
// First set up a valid alias
resetAliases();
aliases.setAlias('rename-src', '/path/session');
// Load aliases, modify them to make saveAliases fail on the SECOND call
// by injecting a circular reference after the rename is done
const data = aliases.loadAliases();
assert.ok(data.aliases['rename-src'], 'Source alias should exist');
// Do the rename with valid data — should succeed
const result = aliases.renameAlias('rename-src', 'rename-dst');
assert.strictEqual(result.success, true, 'Normal rename should succeed');
assert.ok(aliases.resolveAlias('rename-dst'), 'New alias should exist');
assert.strictEqual(aliases.resolveAlias('rename-src'), null, 'Old alias should be gone');
})) passed++; else failed++;
if (test('renameAlias returns rolled-back error message on save failure', () => {
// We can test the error response structure even though we can't easily
// trigger a save failure without mocking. Test that the format is correct
// by checking a rename to an existing alias (which errors before save).
resetAliases();
aliases.setAlias('src-alias', '/path/a');
aliases.setAlias('dst-exists', '/path/b');
const result = aliases.renameAlias('src-alias', 'dst-exists');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('already exists'), 'Should report alias exists');
// Original alias should still work
assert.ok(aliases.resolveAlias('src-alias'), 'Source alias should survive');
})) passed++; else failed++;
if (test('renameAlias rollback preserves original alias data on naming conflict', () => {
resetAliases();
aliases.setAlias('keep-this', '/path/original', 'Original Title');
// Attempt rename to a reserved name — should fail pre-save
const result = aliases.renameAlias('keep-this', 'delete');
assert.strictEqual(result.success, false);
assert.ok(result.error.includes('reserved'), 'Should reject reserved name');
// Original alias should be intact with all its data
const resolved = aliases.resolveAlias('keep-this');
assert.ok(resolved, 'Original alias should still exist');
assert.strictEqual(resolved.sessionPath, '/path/original');
assert.strictEqual(resolved.title, 'Original Title');
})) passed++; else failed++;
// ── Round 33: saveAliases backup restoration ──
console.log('\nsaveAliases backup/restore (Round 33):');
if (test('saveAliases creates backup before write and removes on success', () => {
resetAliases();
aliases.setAlias('backup-test', '/path/backup');
// After successful save, .bak file should NOT exist
const aliasesPath = path.join(tmpHome, '.claude', 'session-aliases.json');
const backupPath = aliasesPath + '.bak';
assert.ok(!fs.existsSync(backupPath), 'Backup should be removed after successful save');
assert.ok(fs.existsSync(aliasesPath), 'Main aliases file should exist');
})) passed++; else failed++;
if (test('saveAliases with non-serializable data returns false and preserves existing file', () => {
resetAliases();
aliases.setAlias('before-fail', '/path/safe');
// Verify the file exists
const aliasesPath = path.join(tmpHome, '.claude', 'session-aliases.json');
assert.ok(fs.existsSync(aliasesPath), 'Aliases file should exist');
const contentBefore = fs.readFileSync(aliasesPath, 'utf8');
// Attempt to save circular data — will fail
const circular = { aliases: {}, metadata: {} };
circular.self = circular;
const result = aliases.saveAliases(circular);
assert.strictEqual(result, false, 'Should return false');
// The file should still have the old content (restored from backup or untouched)
const contentAfter = fs.readFileSync(aliasesPath, 'utf8');
assert.ok(contentAfter.includes('before-fail'),
'Original aliases data should be preserved after failed save');
})) passed++; else failed++;
// ── Round 39: atomic overwrite on Unix (no unlink before rename) ──
console.log('\nRound 39: atomic overwrite:');
if (test('saveAliases overwrites existing file atomically', () => {
// Create initial aliases
aliases.setAlias('atomic-test', '2026-01-01-abc123-session.tmp');
const aliasesPath = aliases.getAliasesPath();
assert.ok(fs.existsSync(aliasesPath), 'Aliases file should exist');
const sizeBefore = fs.statSync(aliasesPath).size;
assert.ok(sizeBefore > 0, 'Aliases file should have content');
// Overwrite with different data
aliases.setAlias('atomic-test-2', '2026-02-01-def456-session.tmp');
// The file should still exist and be valid JSON
const content = fs.readFileSync(aliasesPath, 'utf8');
const parsed = JSON.parse(content);
assert.ok(parsed.aliases['atomic-test'], 'First alias should exist');
assert.ok(parsed.aliases['atomic-test-2'], 'Second alias should exist');
// Cleanup
aliases.deleteAlias('atomic-test');
aliases.deleteAlias('atomic-test-2');
})) passed++; else failed++;
// Cleanup — restore both HOME and USERPROFILE (Windows)
process.env.HOME = origHome;
if (origUserProfile !== undefined) {
process.env.USERPROFILE = origUserProfile;
} else {
delete process.env.USERPROFILE;
}
try {
fs.rmSync(tmpHome, { recursive: true, force: true });
} catch {
// best-effort
}
// ── Round 48: rapid sequential saves data integrity ──
console.log('\nRound 48: rapid sequential saves:');
if (test('rapid sequential setAlias calls maintain data integrity', () => {
resetAliases();
for (let i = 0; i < 5; i++) {
const result = aliases.setAlias(`rapid-${i}`, `/path/${i}`, `Title ${i}`);
assert.strictEqual(result.success, true, `setAlias rapid-${i} should succeed`);
}
const data = aliases.loadAliases();
for (let i = 0; i < 5; i++) {
assert.ok(data.aliases[`rapid-${i}`], `rapid-${i} should exist after all saves`);
assert.strictEqual(data.aliases[`rapid-${i}`].sessionPath, `/path/${i}`);
}
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++;
// ── Round 64: loadAliases backfills missing version and metadata ──
console.log('\nRound 64: loadAliases version/metadata backfill:');
if (test('loadAliases backfills missing version and metadata fields', () => {
resetAliases();
const aliasesPath = aliases.getAliasesPath();
// Write a file with valid aliases but NO version and NO metadata
fs.writeFileSync(aliasesPath, JSON.stringify({
aliases: {
'backfill-test': {
sessionPath: '/sessions/backfill',
createdAt: '2026-01-15T00:00:00.000Z',
updatedAt: '2026-01-15T00:00:00.000Z',
title: 'Backfill Test'
}
}
}));
const data = aliases.loadAliases();
// Version should be backfilled to ALIAS_VERSION ('1.0')
assert.strictEqual(data.version, '1.0', 'Should backfill missing version to 1.0');
// Metadata should be backfilled with totalCount from aliases
assert.ok(data.metadata, 'Should backfill missing metadata object');
assert.strictEqual(data.metadata.totalCount, 1, 'Metadata totalCount should match alias count');
assert.ok(data.metadata.lastUpdated, 'Metadata should have lastUpdated');
// Alias data should be preserved
assert.ok(data.aliases['backfill-test'], 'Alias data should be preserved');
assert.strictEqual(data.aliases['backfill-test'].sessionPath, '/sessions/backfill');
resetAliases();
})) passed++; else failed++;
// ── Round 67: loadAliases empty file, resolveSessionAlias null, metadata-only backfill ──
console.log('\nRound 67: loadAliases (empty 0-byte file):');
if (test('loadAliases returns default structure for empty (0-byte) file', () => {
resetAliases();
const aliasesPath = aliases.getAliasesPath();
// Write a 0-byte file — readFile returns '', which is falsy → !content branch
fs.writeFileSync(aliasesPath, '');
const data = aliases.loadAliases();
assert.ok(data.aliases, 'Should have aliases key');
assert.strictEqual(Object.keys(data.aliases).length, 0, 'Should have no aliases');
assert.strictEqual(data.version, '1.0', 'Should have default version');
assert.ok(data.metadata, 'Should have metadata');
assert.strictEqual(data.metadata.totalCount, 0, 'Should have totalCount 0');
resetAliases();
})) passed++; else failed++;
console.log('\nRound 67: resolveSessionAlias (null/falsy input):');
if (test('resolveSessionAlias returns null when given null input', () => {
resetAliases();
const result = aliases.resolveSessionAlias(null);
assert.strictEqual(result, null, 'Should return null for null input');
})) passed++; else failed++;
console.log('\nRound 67: loadAliases (metadata-only backfill, version present):');
if (test('loadAliases backfills only metadata when version already present', () => {
resetAliases();
const aliasesPath = aliases.getAliasesPath();
// Write a file WITH version but WITHOUT metadata
fs.writeFileSync(aliasesPath, JSON.stringify({
version: '1.0',
aliases: {
'meta-only': {
sessionPath: '/sessions/meta-only',
createdAt: '2026-01-20T00:00:00.000Z',
updatedAt: '2026-01-20T00:00:00.000Z',
title: 'Metadata Only Test'
}
}
}));
const data = aliases.loadAliases();
// Version should remain as-is (NOT overwritten)
assert.strictEqual(data.version, '1.0', 'Version should remain 1.0');
// Metadata should be backfilled
assert.ok(data.metadata, 'Should backfill missing metadata');
assert.strictEqual(data.metadata.totalCount, 1, 'Metadata totalCount should be 1');
assert.ok(data.metadata.lastUpdated, 'Metadata should have lastUpdated');
// Alias data should be preserved
assert.ok(data.aliases['meta-only'], 'Alias should be preserved');
assert.strictEqual(data.aliases['meta-only'].title, 'Metadata Only Test');
resetAliases();
})) passed++; else failed++;
// ── Round 70: updateAliasTitle save failure path ──
console.log('\nupdateAliasTitle save failure (Round 70):');
if (test('updateAliasTitle returns failure when saveAliases fails (read-only dir)', () => {
if (process.platform === 'win32' || process.getuid?.() === 0) {
console.log(' (skipped — chmod ineffective on Windows/root)');
return;
}
// Use a fresh isolated HOME to avoid .tmp/.bak leftovers from other tests.
// On macOS, overwriting an EXISTING file in a read-only dir succeeds,
// so we must start clean with ONLY the .json file present.
const isoHome = path.join(os.tmpdir(), `ecc-alias-r70-${Date.now()}`);
const isoClaudeDir = path.join(isoHome, '.claude');
fs.mkdirSync(isoClaudeDir, { recursive: true });
const savedHome = process.env.HOME;
const savedProfile = process.env.USERPROFILE;
try {
process.env.HOME = isoHome;
process.env.USERPROFILE = isoHome;
// Re-require to pick up new HOME
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
const freshAliases = require('../../scripts/lib/session-aliases');
// Set up a valid alias
freshAliases.setAlias('title-save-fail', '/path/session', 'Original Title');
// Verify no leftover .tmp/.bak
const ap = freshAliases.getAliasesPath();
assert.ok(fs.existsSync(ap), 'Alias file should exist after setAlias');
// Make .claude dir read-only so saveAliases fails when creating .bak
fs.chmodSync(isoClaudeDir, 0o555);
const result = freshAliases.updateAliasTitle('title-save-fail', 'New Title');
assert.strictEqual(result.success, false, 'Should fail when save is blocked');
assert.ok(result.error.includes('Failed to update alias title'),
`Should return save failure error, got: ${result.error}`);
} finally {
try { fs.chmodSync(isoClaudeDir, 0o755); } catch { /* best-effort */ }
process.env.HOME = savedHome;
process.env.USERPROFILE = savedProfile;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
fs.rmSync(isoHome, { recursive: true, force: true });
}
})) passed++; else failed++;
// ── Round 72: deleteAlias save failure path ──
console.log('\nRound 72: deleteAlias (save failure):');
if (test('deleteAlias returns failure when saveAliases fails (read-only dir)', () => {
if (process.platform === 'win32' || process.getuid?.() === 0) {
console.log(' (skipped — chmod ineffective on Windows/root)');
return;
}
const isoHome = path.join(os.tmpdir(), `ecc-alias-r72-${Date.now()}`);
const isoClaudeDir = path.join(isoHome, '.claude');
fs.mkdirSync(isoClaudeDir, { recursive: true });
const savedHome = process.env.HOME;
const savedProfile = process.env.USERPROFILE;
try {
process.env.HOME = isoHome;
process.env.USERPROFILE = isoHome;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
const freshAliases = require('../../scripts/lib/session-aliases');
// Create an alias first (writes the file)
freshAliases.setAlias('to-delete', '/path/session', 'Test');
const ap = freshAliases.getAliasesPath();
assert.ok(fs.existsSync(ap), 'Alias file should exist after setAlias');
// Make .claude directory read-only — save will fail (can't create temp file)
fs.chmodSync(isoClaudeDir, 0o555);
const result = freshAliases.deleteAlias('to-delete');
assert.strictEqual(result.success, false, 'Should fail when save is blocked');
assert.ok(result.error.includes('Failed to delete alias'),
`Should return delete failure error, got: ${result.error}`);
} finally {
try { fs.chmodSync(isoClaudeDir, 0o755); } catch { /* best-effort */ }
process.env.HOME = savedHome;
process.env.USERPROFILE = savedProfile;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
fs.rmSync(isoHome, { recursive: true, force: true });
}
})) passed++; else failed++;
// ── Round 73: cleanupAliases save failure path ──
console.log('\nRound 73: cleanupAliases (save failure):');
if (test('cleanupAliases returns failure when saveAliases fails after removing aliases', () => {
if (process.platform === 'win32' || process.getuid?.() === 0) {
console.log(' (skipped — chmod ineffective on Windows/root)');
return;
}
const isoHome = path.join(os.tmpdir(), `ecc-alias-r73-cleanup-${Date.now()}`);
const isoClaudeDir = path.join(isoHome, '.claude');
fs.mkdirSync(isoClaudeDir, { recursive: true });
const savedHome = process.env.HOME;
const savedProfile = process.env.USERPROFILE;
try {
process.env.HOME = isoHome;
process.env.USERPROFILE = isoHome;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
const freshAliases = require('../../scripts/lib/session-aliases');
// Create aliases — one to keep, one to remove
freshAliases.setAlias('keep-me', '/sessions/real', 'Kept');
freshAliases.setAlias('remove-me', '/sessions/gone', 'Gone');
// Make .claude dir read-only so save will fail
fs.chmodSync(isoClaudeDir, 0o555);
// Cleanup: "gone" session doesn't exist, so remove-me should be removed
const result = freshAliases.cleanupAliases((p) => p === '/sessions/real');
assert.strictEqual(result.success, false, 'Should fail when save is blocked');
assert.ok(result.error.includes('Failed to save after cleanup'),
`Should return cleanup save failure error, got: ${result.error}`);
assert.strictEqual(result.removed, 1, 'Should report 1 removed alias');
assert.ok(result.removedAliases.some(a => a.name === 'remove-me'),
'Should report remove-me in removedAliases');
} finally {
try { fs.chmodSync(isoClaudeDir, 0o755); } catch { /* best-effort */ }
process.env.HOME = savedHome;
process.env.USERPROFILE = savedProfile;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
fs.rmSync(isoHome, { recursive: true, force: true });
}
})) passed++; else failed++;
// ── Round 73: setAlias save failure path ──
console.log('\nRound 73: setAlias (save failure):');
if (test('setAlias returns failure when saveAliases fails', () => {
if (process.platform === 'win32' || process.getuid?.() === 0) {
console.log(' (skipped — chmod ineffective on Windows/root)');
return;
}
const isoHome = path.join(os.tmpdir(), `ecc-alias-r73-set-${Date.now()}`);
const isoClaudeDir = path.join(isoHome, '.claude');
fs.mkdirSync(isoClaudeDir, { recursive: true });
const savedHome = process.env.HOME;
const savedProfile = process.env.USERPROFILE;
try {
process.env.HOME = isoHome;
process.env.USERPROFILE = isoHome;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
const freshAliases = require('../../scripts/lib/session-aliases');
// Make .claude dir read-only BEFORE any setAlias call
fs.chmodSync(isoClaudeDir, 0o555);
const result = freshAliases.setAlias('my-alias', '/sessions/test', 'Test');
assert.strictEqual(result.success, false, 'Should fail when save is blocked');
assert.ok(result.error.includes('Failed to save alias'),
`Should return save failure error, got: ${result.error}`);
} finally {
try { fs.chmodSync(isoClaudeDir, 0o755); } catch { /* best-effort */ }
process.env.HOME = savedHome;
process.env.USERPROFILE = savedProfile;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
fs.rmSync(isoHome, { recursive: true, force: true });
}
})) passed++; else failed++;
// ── Round 84: listAliases sort NaN date fallback (getTime() || 0) ──
console.log('\nRound 84: listAliases (NaN date fallback in sort comparator):');
if (test('listAliases sorts entries with invalid/missing dates to the end via || 0 fallback', () => {
// session-aliases.js line 257:
// (new Date(b.updatedAt || b.createdAt || 0).getTime() || 0) - ...
// When updatedAt and createdAt are both invalid strings, getTime() returns NaN.
// The outer || 0 converts NaN to 0 (epoch time), pushing the entry to the end.
resetAliases();
const data = aliases.loadAliases();
// Entry with valid dates — should sort first (newest)
data.aliases['valid-alias'] = {
sessionPath: '/sessions/valid',
createdAt: '2026-02-10T12:00:00.000Z',
updatedAt: '2026-02-10T12:00:00.000Z',
title: 'Valid'
};
// Entry with invalid date strings — getTime() → NaN → || 0 → epoch (oldest)
data.aliases['nan-alias'] = {
sessionPath: '/sessions/nan',
createdAt: 'not-a-date',
updatedAt: 'also-invalid',
title: 'NaN dates'
};
// Entry with missing date fields — undefined || undefined || 0 → new Date(0) → epoch
data.aliases['missing-alias'] = {
sessionPath: '/sessions/missing',
title: 'Missing dates'
// No createdAt or updatedAt
};
aliases.saveAliases(data);
const list = aliases.listAliases();
assert.strictEqual(list.length, 3, 'Should list all 3 aliases');
// Valid-dated entry should be first (newest by updatedAt)
assert.strictEqual(list[0].name, 'valid-alias',
'Entry with valid dates should sort first');
// The two invalid-dated entries sort to epoch (0), so they come after
assert.ok(
(list[1].name === 'nan-alias' || list[1].name === 'missing-alias') &&
(list[2].name === 'nan-alias' || list[2].name === 'missing-alias'),
'Entries with invalid/missing dates should sort to the end');
})) passed++; else failed++;
// ── Round 86: loadAliases with truthy non-object aliases field ──
console.log('\nRound 86: loadAliases (truthy non-object aliases field):');
if (test('loadAliases resets to defaults when aliases field is a string (typeof !== object)', () => {
// session-aliases.js line 58: if (!data.aliases || typeof data.aliases !== 'object')
// Previous tests covered !data.aliases (undefined) via { noAliasesKey: true }.
// This exercises the SECOND half: aliases is truthy but typeof !== 'object'.
const aliasesPath = aliases.getAliasesPath();
fs.writeFileSync(aliasesPath, JSON.stringify({
version: '1.0',
aliases: 'this-is-a-string-not-an-object',
metadata: { totalCount: 0 }
}));
const data = aliases.loadAliases();
assert.strictEqual(typeof data.aliases, 'object', 'Should reset aliases to object');
assert.ok(!Array.isArray(data.aliases), 'Should be a plain object, not array');
assert.strictEqual(Object.keys(data.aliases).length, 0, 'Should have no aliases');
assert.strictEqual(data.version, '1.0', 'Should have version');
resetAliases();
})) passed++; else failed++;
// ── Round 90: saveAliases backup restore double failure (inner catch restoreErr) ──
console.log('\nRound 90: saveAliases (backup restore double failure):');
if (test('saveAliases triggers inner restoreErr catch when both save and restore fail', () => {
// session-aliases.js lines 131-137: When saveAliases fails (outer catch),
// it tries to restore from backup. If the restore ALSO fails, the inner
// catch at line 135 logs restoreErr. No existing test creates this double-fault.
if (process.platform === 'win32') {
console.log(' (skipped — chmod not reliable on Windows)');
return;
}
const isoHome = path.join(os.tmpdir(), `ecc-r90-restore-fail-${Date.now()}`);
const claudeDir = path.join(isoHome, '.claude');
fs.mkdirSync(claudeDir, { recursive: true });
// Pre-create a backup file while directory is still writable
const backupPath = path.join(claudeDir, 'session-aliases.json.bak');
fs.writeFileSync(backupPath, JSON.stringify({ aliases: {}, version: '1.0' }));
// Make .claude directory read-only (0o555):
// 1. writeFileSync(tempPath) → EACCES (can't create file in read-only dir) — outer catch
// 2. copyFileSync(backupPath, aliasesPath) → EACCES (can't create target) — inner catch (line 135)
fs.chmodSync(claudeDir, 0o555);
const origH = process.env.HOME;
const origP = process.env.USERPROFILE;
process.env.HOME = isoHome;
process.env.USERPROFILE = isoHome;
try {
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
const freshAliases = require('../../scripts/lib/session-aliases');
const result = freshAliases.saveAliases({ aliases: { x: 1 }, version: '1.0' });
assert.strictEqual(result, false, 'Should return false when save fails');
// Backup should still exist (restore also failed, so backup was not consumed)
assert.ok(fs.existsSync(backupPath), 'Backup should still exist after double failure');
} finally {
process.env.HOME = origH;
process.env.USERPROFILE = origP;
delete require.cache[require.resolve('../../scripts/lib/session-aliases')];
delete require.cache[require.resolve('../../scripts/lib/utils')];
try { fs.chmodSync(claudeDir, 0o755); } catch { /* best-effort */ }
fs.rmSync(isoHome, { recursive: true, force: true });
}
})) passed++; else failed++;
// ── Round 95: renameAlias with same old and new name (self-rename) ──
console.log('\nRound 95: renameAlias (self-rename same name):');
if (test('renameAlias returns "already exists" error when renaming alias to itself', () => {
resetAliases();
// Create an alias first
const created = aliases.setAlias('self-rename', '/path/session', 'Self Rename');
assert.strictEqual(created.success, true, 'Setup: alias should be created');
// Attempt to rename to the same name
const result = aliases.renameAlias('self-rename', 'self-rename');
assert.strictEqual(result.success, false, 'Renaming to itself should fail');
assert.ok(result.error.includes('already exists'),
'Error should indicate alias already exists (line 333-334 check)');
// Verify original alias is still intact
const resolved = aliases.resolveAlias('self-rename');
assert.ok(resolved, 'Original alias should still exist after failed self-rename');
assert.strictEqual(resolved.sessionPath, '/path/session',
'Alias data should be preserved');
})) passed++; else failed++;
// ── Round 100: cleanupAliases callback returning falsy non-boolean 0 ──
console.log('\nRound 100: cleanupAliases (callback returns 0 — falsy non-boolean coercion):');
if (test('cleanupAliases removes alias when callback returns 0 (falsy coercion: !0 === true)', () => {
resetAliases();
aliases.setAlias('zero-test', '/sessions/some-session', '2026-01-15');
// callback returns 0 (a falsy value) — !0 === true → alias is removed
const result = aliases.cleanupAliases(() => 0);
assert.strictEqual(result.removed, 1,
'Alias should be removed because !0 === true (JavaScript falsy coercion)');
assert.strictEqual(result.success, true,
'Cleanup should succeed');
const resolved = aliases.resolveAlias('zero-test');
assert.strictEqual(resolved, null,
'Alias should no longer exist after removal');
})) passed++; else failed++;
// ── Round 102: setAlias with title=0 (falsy number coercion) ──
console.log('\nRound 102: setAlias (title=0 — falsy coercion silently converts to null):');
if (test('setAlias with title=0 stores null (0 || null === null due to JavaScript falsy coercion)', () => {
// session-aliases.js line 221: `title: title || null` — the value 0 is falsy
// in JavaScript, so `0 || null` evaluates to `null`. This means numeric
// titles like 0 are silently discarded.
resetAliases();
const result = aliases.setAlias('zero-title', '/sessions/test', 0);
assert.strictEqual(result.success, true,
'setAlias should succeed (0 is valid as a truthy check bypass)');
assert.strictEqual(result.title, null,
'Title should be null because 0 || null === null (falsy coercion)');
const resolved = aliases.resolveAlias('zero-title');
assert.strictEqual(resolved.title, null,
'Persisted title should be null after round-trip through saveAliases/loadAliases');
})) passed++; else failed++;
// ── Round 103: loadAliases with array aliases in JSON (typeof [] === 'object' bypass) ──
console.log('\nRound 103: loadAliases (array aliases — typeof bypass):');
if (test('loadAliases accepts array aliases because typeof [] === "object" passes validation', () => {
// session-aliases.js line 58: `typeof data.aliases !== 'object'` is the guard.
// Arrays are typeof 'object' in JavaScript, so {"aliases": [1,2,3]} passes
// validation. The returned data.aliases is an array, not a plain object.
// Downstream code (Object.keys, Object.entries, bracket access) behaves
// differently on arrays vs objects but doesn't crash — it just produces
// unexpected results like numeric string keys "0", "1", "2".
resetAliases();
const aliasesPath = aliases.getAliasesPath();
fs.writeFileSync(aliasesPath, JSON.stringify({
version: '1.0',
aliases: ['item0', 'item1', 'item2'],
metadata: { totalCount: 3, lastUpdated: new Date().toISOString() }
}));
const data = aliases.loadAliases();
// The array passes the typeof 'object' check and is returned as-is
assert.ok(Array.isArray(data.aliases),
'data.aliases should be an array (typeof [] === "object" bypasses guard)');
assert.strictEqual(data.aliases.length, 3,
'Array should have 3 elements');
// Object.keys on an array returns ["0", "1", "2"] — numeric index strings
const keys = Object.keys(data.aliases);
assert.deepStrictEqual(keys, ['0', '1', '2'],
'Object.keys of array returns numeric string indices, not named alias keys');
})) passed++; else failed++;
// Summary
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
}
runTests();