mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
test: add Round 90 tests for readStdinJson timeout and saveAliases double failure
- Test readStdinJson timeout path when stdin never closes (resolves with {})
- Test readStdinJson timeout path with partial invalid JSON (catch resolves with {})
- Test saveAliases backup restore double failure (inner restoreErr catch at line 135)
Total tests: 830
This commit is contained in:
@@ -3562,6 +3562,65 @@ Some random content without the expected ### Context to Load section
|
|||||||
cleanupTestDir(testDir);
|
cleanupTestDir(testDir);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
// ── Round 90: readStdinJson timeout path (utils.js lines 215-229) ──
|
||||||
|
console.log('\nRound 90: readStdinJson (timeout fires when stdin stays open):');
|
||||||
|
|
||||||
|
if (await asyncTest('readStdinJson resolves with {} when stdin never closes (timeout fires, no data)', async () => {
|
||||||
|
// utils.js line 215: setTimeout fires because stdin 'end' never arrives.
|
||||||
|
// Line 225: data.trim() is empty → resolves with {}.
|
||||||
|
// Exercises: removeAllListeners, process.stdin.unref(), and the empty-data timeout resolution.
|
||||||
|
const script = 'const u=require("./scripts/lib/utils");u.readStdinJson({timeoutMs:100}).then(d=>{process.stdout.write(JSON.stringify(d));process.exit(0)})';
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn('node', ['-e', script], {
|
||||||
|
cwd: path.resolve(__dirname, '..', '..'),
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
// Don't write anything or close stdin — force the timeout to fire
|
||||||
|
let stdout = '';
|
||||||
|
child.stdout.on('data', d => stdout += d);
|
||||||
|
const timer = setTimeout(() => { child.kill(); reject(new Error('Test timed out')); }, 5000);
|
||||||
|
child.on('close', (code) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
try {
|
||||||
|
assert.strictEqual(code, 0, 'Should exit 0 via timeout resolution');
|
||||||
|
const parsed = JSON.parse(stdout);
|
||||||
|
assert.deepStrictEqual(parsed, {}, 'Should resolve with {} when no data received before timeout');
|
||||||
|
resolve();
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (await asyncTest('readStdinJson resolves with {} when timeout fires with invalid partial JSON', async () => {
|
||||||
|
// utils.js lines 224-228: setTimeout fires, data.trim() is non-empty,
|
||||||
|
// JSON.parse(data) throws → catch at line 226 resolves with {}.
|
||||||
|
const script = 'const u=require("./scripts/lib/utils");u.readStdinJson({timeoutMs:100}).then(d=>{process.stdout.write(JSON.stringify(d));process.exit(0)})';
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const child = spawn('node', ['-e', script], {
|
||||||
|
cwd: path.resolve(__dirname, '..', '..'),
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
});
|
||||||
|
// Write partial invalid JSON but don't close stdin — timeout fires with unparseable data
|
||||||
|
child.stdin.write('{"incomplete":');
|
||||||
|
let stdout = '';
|
||||||
|
child.stdout.on('data', d => stdout += d);
|
||||||
|
const timer = setTimeout(() => { child.kill(); reject(new Error('Test timed out')); }, 5000);
|
||||||
|
child.on('close', (code) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
try {
|
||||||
|
assert.strictEqual(code, 0, 'Should exit 0 via timeout resolution');
|
||||||
|
const parsed = JSON.parse(stdout);
|
||||||
|
assert.deepStrictEqual(parsed, {}, 'Should resolve with {} when partial JSON cannot be parsed');
|
||||||
|
resolve();
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
console.log('\n=== Test Results ===');
|
console.log('\n=== Test Results ===');
|
||||||
console.log(`Passed: ${passed}`);
|
console.log(`Passed: ${passed}`);
|
||||||
|
|||||||
@@ -1223,6 +1223,55 @@ function runTests() {
|
|||||||
resetAliases();
|
resetAliases();
|
||||||
})) passed++; else failed++;
|
})) 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++;
|
||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||||
process.exit(failed > 0 ? 1 : 0);
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user