mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
test: add 3 tests for Unicode alias rejection, newline-in-path heuristic, and read-only append (Round 112)
- resolveAlias rejects Unicode characters (accented, CJK, emoji, Cyrillic homoglyphs) - getSessionStats treats absolute .tmp paths with embedded newlines as content, not file paths - appendSessionContent returns false on EACCES for read-only files Total: 896 tests
This commit is contained in:
@@ -1418,6 +1418,40 @@ function runTests() {
|
||||
'Error message should mention 128-char limit');
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ── Round 112: resolveAlias rejects Unicode characters in alias name ──
|
||||
console.log('\nRound 112: resolveAlias (Unicode rejection):');
|
||||
if (test('resolveAlias returns null for alias names containing Unicode characters', () => {
|
||||
resetAliases();
|
||||
// First create a valid alias to ensure the store works
|
||||
aliases.setAlias('valid-alias', '/path/to/session');
|
||||
const validResult = aliases.resolveAlias('valid-alias');
|
||||
assert.notStrictEqual(validResult, null, 'Valid ASCII alias should resolve');
|
||||
|
||||
// Unicode accented characters — rejected by /^[a-zA-Z0-9_-]+$/
|
||||
const accentedResult = aliases.resolveAlias('café-session');
|
||||
assert.strictEqual(accentedResult, null,
|
||||
'Accented character "é" should be rejected by [a-zA-Z0-9_-]');
|
||||
|
||||
const umlautResult = aliases.resolveAlias('über-test');
|
||||
assert.strictEqual(umlautResult, null,
|
||||
'Umlaut "ü" should be rejected by [a-zA-Z0-9_-]');
|
||||
|
||||
// CJK characters
|
||||
const cjkResult = aliases.resolveAlias('会議-notes');
|
||||
assert.strictEqual(cjkResult, null,
|
||||
'CJK characters should be rejected');
|
||||
|
||||
// Emoji
|
||||
const emojiResult = aliases.resolveAlias('rocket-🚀');
|
||||
assert.strictEqual(emojiResult, null,
|
||||
'Emoji should be rejected by the ASCII-only regex');
|
||||
|
||||
// Cyrillic characters that look like Latin (homoglyphs)
|
||||
const cyrillicResult = aliases.resolveAlias('tеst'); // 'е' is Cyrillic U+0435
|
||||
assert.strictEqual(cyrillicResult, null,
|
||||
'Cyrillic homoglyph "е" (U+0435) should be rejected even though it looks like "e"');
|
||||
})) passed++; else failed++;
|
||||
|
||||
// Summary
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
||||
@@ -1930,6 +1930,66 @@ file.ts
|
||||
'Clean context should have second line');
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ── Round 112: getSessionStats with newline-containing absolute path — treated as content ──
|
||||
console.log('\nRound 112: getSessionStats (newline-in-path heuristic):');
|
||||
if (test('getSessionStats treats absolute .tmp path containing newline as content, not a file path', () => {
|
||||
// The looksLikePath heuristic at line 163-166 checks:
|
||||
// !sessionPathOrContent.includes('\n')
|
||||
// A string with embedded newline fails this check and is treated as content
|
||||
const pathWithNewline = '/tmp/sessions/2026-01-15\n-abcd1234-session.tmp';
|
||||
|
||||
// This should NOT throw (it's treated as content, not a path that doesn't exist)
|
||||
const stats = sessionManager.getSessionStats(pathWithNewline);
|
||||
assert.ok(stats, 'Should return stats object (treating input as content)');
|
||||
// The "content" has 2 lines (split by the embedded \n)
|
||||
assert.strictEqual(stats.lineCount, 2,
|
||||
'Should count 2 lines in the "content" (split at \\n)');
|
||||
// No markdown headings = no completed/in-progress items
|
||||
assert.strictEqual(stats.totalItems, 0,
|
||||
'Should find 0 items in non-markdown content');
|
||||
|
||||
// Contrast: a real absolute path without newlines IS treated as a path
|
||||
const realPath = '/tmp/nonexistent-session.tmp';
|
||||
const realStats = sessionManager.getSessionStats(realPath);
|
||||
// getSessionContent returns '' for non-existent files, so lineCount = 1 (empty string split)
|
||||
assert.ok(realStats, 'Should return stats even for nonexistent path');
|
||||
assert.strictEqual(realStats.lineCount, 0,
|
||||
'Non-existent file returns empty content with 0 lines');
|
||||
})) passed++; else failed++;
|
||||
|
||||
// ── Round 112: appendSessionContent with read-only file — returns false ──
|
||||
console.log('\nRound 112: appendSessionContent (read-only file):');
|
||||
if (test('appendSessionContent returns false when file is read-only (EACCES)', () => {
|
||||
if (process.platform === 'win32') {
|
||||
// chmod doesn't work reliably on Windows — skip
|
||||
assert.ok(true, 'Skipped on Windows');
|
||||
return;
|
||||
}
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'r112-readonly-'));
|
||||
const readOnlyFile = path.join(tmpDir, '2026-01-15-session.tmp');
|
||||
try {
|
||||
fs.writeFileSync(readOnlyFile, '# Session\n\nInitial content\n');
|
||||
// Make file read-only
|
||||
fs.chmodSync(readOnlyFile, 0o444);
|
||||
// Verify it exists and is readable
|
||||
const content = fs.readFileSync(readOnlyFile, 'utf8');
|
||||
assert.ok(content.includes('Initial content'), 'File should be readable');
|
||||
|
||||
// appendSessionContent should catch EACCES and return false
|
||||
const result = sessionManager.appendSessionContent(readOnlyFile, '\nAppended data');
|
||||
assert.strictEqual(result, false,
|
||||
'Should return false when file is read-only (fs.appendFileSync throws EACCES)');
|
||||
|
||||
// Verify original content unchanged
|
||||
const afterContent = fs.readFileSync(readOnlyFile, 'utf8');
|
||||
assert.ok(!afterContent.includes('Appended data'),
|
||||
'Original content should be unchanged');
|
||||
} finally {
|
||||
try { fs.chmodSync(readOnlyFile, 0o644); } catch {}
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// Summary
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
|
||||
Reference in New Issue
Block a user