mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 21:53:28 +08:00
fix: capture stderr in typecheck hook, add 13 tests for session-end and utils
- post-edit-typecheck.js: capture both stdout and stderr from tsc - hooks.test.js: 7 extractSessionSummary tests (JSONL parsing, array content, malformed lines, empty transcript, long message truncation, env var fallback) - utils.test.js: 6 tests (replaceInFile g-flag behavior, string replace, capture groups, writeFile overwrite, unicode content) Total test count: 294 → 307
This commit is contained in:
@@ -54,7 +54,7 @@ process.stdin.on('end', () => {
|
||||
});
|
||||
} catch (err) {
|
||||
// tsc exits non-zero when there are errors — filter to edited file
|
||||
const output = err.stdout || '';
|
||||
const output = (err.stdout || '') + (err.stderr || '');
|
||||
const relevantLines = output
|
||||
.split('\n')
|
||||
.filter(line => line.includes(filePath) || line.includes(path.basename(filePath)))
|
||||
|
||||
@@ -618,6 +618,137 @@ async function runTests() {
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
// session-end.js extractSessionSummary tests
|
||||
console.log('\nsession-end.js (extractSessionSummary):');
|
||||
|
||||
if (await asyncTest('extracts user messages from transcript', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
const lines = [
|
||||
'{"type":"user","content":"Fix the login bug"}',
|
||||
'{"type":"assistant","content":"I will fix it"}',
|
||||
'{"type":"user","content":"Also add tests"}',
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0);
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('handles transcript with array content fields', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
const lines = [
|
||||
'{"type":"user","content":[{"text":"Part 1"},{"text":"Part 2"}]}',
|
||||
'{"type":"user","content":"Simple message"}',
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0, 'Should handle array content without crash');
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('extracts tool names and file paths from transcript', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
const lines = [
|
||||
'{"type":"user","content":"Edit the file"}',
|
||||
'{"type":"tool_use","tool_name":"Edit","tool_input":{"file_path":"/src/main.ts"}}',
|
||||
'{"type":"tool_use","tool_name":"Read","tool_input":{"file_path":"/src/utils.ts"}}',
|
||||
'{"type":"tool_use","tool_name":"Write","tool_input":{"file_path":"/src/new.ts"}}',
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0);
|
||||
// Session file should contain summary with tools used
|
||||
assert.ok(
|
||||
result.stderr.includes('Created session file') || result.stderr.includes('Updated session file'),
|
||||
'Should create/update session file'
|
||||
);
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('handles transcript with malformed JSON lines', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
const lines = [
|
||||
'{"type":"user","content":"Valid message"}',
|
||||
'NOT VALID JSON',
|
||||
'{"broken json',
|
||||
'{"type":"user","content":"Another valid"}',
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0, 'Should skip malformed lines gracefully');
|
||||
assert.ok(
|
||||
result.stderr.includes('unparseable') || result.stderr.includes('Skipped'),
|
||||
`Should report parse errors, got: ${result.stderr.substring(0, 200)}`
|
||||
);
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('handles empty transcript (no user messages)', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
// Only tool_use entries, no user messages
|
||||
const lines = [
|
||||
'{"type":"tool_use","tool_name":"Read","tool_input":{}}',
|
||||
'{"type":"assistant","content":"done"}',
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0, 'Should handle transcript with no user messages');
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('truncates long user messages to 200 chars', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
const longMsg = 'x'.repeat(500);
|
||||
const lines = [
|
||||
`{"type":"user","content":"${longMsg}"}`,
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
const stdinJson = JSON.stringify({ transcript_path: transcriptPath });
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson);
|
||||
assert.strictEqual(result.code, 0, 'Should handle and truncate long messages');
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (await asyncTest('uses CLAUDE_TRANSCRIPT_PATH env var as fallback', async () => {
|
||||
const testDir = createTestDir();
|
||||
const transcriptPath = path.join(testDir, 'transcript.jsonl');
|
||||
|
||||
const lines = [
|
||||
'{"type":"user","content":"Fallback test message"}',
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, lines.join('\n'));
|
||||
|
||||
// Send invalid JSON to stdin so it falls back to env var
|
||||
const result = await runScript(path.join(scriptsDir, 'session-end.js'), 'not json', {
|
||||
CLAUDE_TRANSCRIPT_PATH: transcriptPath
|
||||
});
|
||||
assert.strictEqual(result.code, 0, 'Should use env var fallback');
|
||||
cleanupTestDir(testDir);
|
||||
})) passed++; else failed++;
|
||||
|
||||
// hooks.json validation
|
||||
console.log('\nhooks.json Validation:');
|
||||
|
||||
|
||||
@@ -505,6 +505,86 @@ function runTests() {
|
||||
assert.ok(dir.includes('learned'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
// replaceInFile behavior tests
|
||||
console.log('\nreplaceInFile (behavior):');
|
||||
|
||||
if (test('replaces first match when regex has no g flag', () => {
|
||||
const testFile = path.join(utils.getTempDir(), `utils-test-${Date.now()}.txt`);
|
||||
try {
|
||||
utils.writeFile(testFile, 'foo bar foo baz foo');
|
||||
utils.replaceInFile(testFile, /foo/, 'qux');
|
||||
const content = utils.readFile(testFile);
|
||||
// Without g flag, only first 'foo' should be replaced
|
||||
assert.strictEqual(content, 'qux bar foo baz foo');
|
||||
} finally {
|
||||
fs.unlinkSync(testFile);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('replaces all matches when regex has g flag', () => {
|
||||
const testFile = path.join(utils.getTempDir(), `utils-test-${Date.now()}.txt`);
|
||||
try {
|
||||
utils.writeFile(testFile, 'foo bar foo baz foo');
|
||||
utils.replaceInFile(testFile, /foo/g, 'qux');
|
||||
const content = utils.readFile(testFile);
|
||||
assert.strictEqual(content, 'qux bar qux baz qux');
|
||||
} finally {
|
||||
fs.unlinkSync(testFile);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('replaces with string search (first occurrence)', () => {
|
||||
const testFile = path.join(utils.getTempDir(), `utils-test-${Date.now()}.txt`);
|
||||
try {
|
||||
utils.writeFile(testFile, 'hello world hello');
|
||||
utils.replaceInFile(testFile, 'hello', 'goodbye');
|
||||
const content = utils.readFile(testFile);
|
||||
// String.replace with string search only replaces first
|
||||
assert.strictEqual(content, 'goodbye world hello');
|
||||
} finally {
|
||||
fs.unlinkSync(testFile);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('replaces with capture groups', () => {
|
||||
const testFile = path.join(utils.getTempDir(), `utils-test-${Date.now()}.txt`);
|
||||
try {
|
||||
utils.writeFile(testFile, '**Last Updated:** 10:30');
|
||||
utils.replaceInFile(testFile, /\*\*Last Updated:\*\*.*/, '**Last Updated:** 14:45');
|
||||
const content = utils.readFile(testFile);
|
||||
assert.strictEqual(content, '**Last Updated:** 14:45');
|
||||
} finally {
|
||||
fs.unlinkSync(testFile);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// writeFile edge cases
|
||||
console.log('\nwriteFile (edge cases):');
|
||||
|
||||
if (test('writeFile overwrites existing content', () => {
|
||||
const testFile = path.join(utils.getTempDir(), `utils-test-${Date.now()}.txt`);
|
||||
try {
|
||||
utils.writeFile(testFile, 'original');
|
||||
utils.writeFile(testFile, 'replaced');
|
||||
const content = utils.readFile(testFile);
|
||||
assert.strictEqual(content, 'replaced');
|
||||
} finally {
|
||||
fs.unlinkSync(testFile);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('writeFile handles unicode content', () => {
|
||||
const testFile = path.join(utils.getTempDir(), `utils-test-${Date.now()}.txt`);
|
||||
try {
|
||||
const unicode = '日本語テスト 🚀 émojis';
|
||||
utils.writeFile(testFile, unicode);
|
||||
const content = utils.readFile(testFile);
|
||||
assert.strictEqual(content, unicode);
|
||||
} finally {
|
||||
fs.unlinkSync(testFile);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
// findFiles with regex special characters in pattern
|
||||
console.log('\nfindFiles (regex chars):');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user