mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
test: add 3 tests for TOCTOU catch paths and NaN date sort fallback (round 84)
- getSessionById returns null for broken symlink (session-manager.js:307-310) - findFiles skips broken symlinks matching the pattern (utils.js:170-173) - listAliases sorts entries with invalid/missing dates via getTime() || 0 fallback
This commit is contained in:
@@ -1154,6 +1154,54 @@ function runTests() {
|
||||
}
|
||||
})) 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++;
|
||||
|
||||
// Summary
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
||||
@@ -1349,6 +1349,42 @@ src/main.ts
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ── Round 84: getSessionById TOCTOU — statSync catch returns null for broken symlink ──
|
||||
console.log('\nRound 84: getSessionById (broken symlink — statSync catch):');
|
||||
|
||||
if (test('getSessionById returns null when matching session is a broken symlink', () => {
|
||||
// getSessionById at line 307-310: statSync throws for broken symlinks,
|
||||
// the catch returns null (file deleted between readdir and stat).
|
||||
const isoHome = path.join(os.tmpdir(), `ecc-r84-getbyid-toctou-${Date.now()}`);
|
||||
const sessionsDir = path.join(isoHome, '.claude', 'sessions');
|
||||
fs.mkdirSync(sessionsDir, { recursive: true });
|
||||
|
||||
// Create a broken symlink that matches a session ID pattern
|
||||
const brokenFile = '2026-02-11-deadbeef-session.tmp';
|
||||
fs.symlinkSync('/nonexistent/target/that/does/not/exist', path.join(sessionsDir, brokenFile));
|
||||
|
||||
const origHome = process.env.HOME;
|
||||
const origUserProfile = process.env.USERPROFILE;
|
||||
try {
|
||||
process.env.HOME = isoHome;
|
||||
process.env.USERPROFILE = isoHome;
|
||||
delete require.cache[require.resolve('../../scripts/lib/session-manager')];
|
||||
delete require.cache[require.resolve('../../scripts/lib/utils')];
|
||||
const freshSM = require('../../scripts/lib/session-manager');
|
||||
|
||||
// Search by the short ID "deadbeef" — should match the broken symlink
|
||||
const result = freshSM.getSessionById('deadbeef');
|
||||
assert.strictEqual(result, null,
|
||||
'Should return null when matching session file is a broken symlink');
|
||||
} finally {
|
||||
process.env.HOME = origHome;
|
||||
process.env.USERPROFILE = origUserProfile;
|
||||
delete require.cache[require.resolve('../../scripts/lib/session-manager')];
|
||||
delete require.cache[require.resolve('../../scripts/lib/utils')];
|
||||
fs.rmSync(isoHome, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// Summary
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
||||
@@ -1131,6 +1131,40 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ── Round 84: findFiles inner statSync catch (TOCTOU — broken symlink) ──
|
||||
console.log('\nRound 84: findFiles (inner statSync catch — broken symlink):');
|
||||
|
||||
if (test('findFiles skips broken symlinks that match the pattern', () => {
|
||||
// findFiles at utils.js:170-173: readdirSync returns entries including broken
|
||||
// symlinks (entry.isFile() returns false for broken symlinks, but the test also
|
||||
// verifies the overall robustness). On some systems, broken symlinks can be
|
||||
// returned by readdirSync and pass through isFile() depending on the driver.
|
||||
// More importantly: if statSync throws inside the inner loop, catch continues.
|
||||
//
|
||||
// To reliably trigger the statSync catch: create a real file, list it, then
|
||||
// simulate the race. Since we can't truly race, we use a broken symlink which
|
||||
// will at minimum verify the function doesn't crash on unusual dir entries.
|
||||
const tmpDir = path.join(utils.getTempDir(), `ecc-r84-findfiles-toctou-${Date.now()}`);
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
|
||||
// Create a real file and a broken symlink, both matching *.txt
|
||||
const realFile = path.join(tmpDir, 'real.txt');
|
||||
fs.writeFileSync(realFile, 'content');
|
||||
const brokenLink = path.join(tmpDir, 'broken.txt');
|
||||
fs.symlinkSync('/nonexistent/path/does/not/exist', brokenLink);
|
||||
|
||||
try {
|
||||
const results = utils.findFiles(tmpDir, '*.txt');
|
||||
// The real file should be found; the broken symlink should be skipped
|
||||
const paths = results.map(r => r.path);
|
||||
assert.ok(paths.some(p => p.includes('real.txt')), 'Should find the real file');
|
||||
assert.ok(!paths.some(p => p.includes('broken.txt')),
|
||||
'Should not include broken symlink in results');
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// Summary
|
||||
console.log('\n=== Test Results ===');
|
||||
console.log(`Passed: ${passed}`);
|
||||
|
||||
Reference in New Issue
Block a user