mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-07 17:53:32 +08:00
test: cover whitespace-only frontmatter field, empty SKILL.md, and getAllSessions TOCTOU symlink
This commit is contained in:
@@ -2105,6 +2105,40 @@ function runTests() {
|
|||||||
cleanupTestDir(testDir);
|
cleanupTestDir(testDir);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
// ── Round 83: validate-agents whitespace-only field, validate-skills empty SKILL.md ──
|
||||||
|
|
||||||
|
console.log('\nRound 83: validate-agents (whitespace-only frontmatter field value):');
|
||||||
|
|
||||||
|
if (test('rejects agent with whitespace-only model field (trim guard)', () => {
|
||||||
|
const testDir = createTestDir();
|
||||||
|
// model has only whitespace — extractFrontmatter produces { model: ' ', tools: 'Read' }
|
||||||
|
// The condition: typeof frontmatter[field] === 'string' && !frontmatter[field].trim()
|
||||||
|
// evaluates to true for model → "Missing required field: model"
|
||||||
|
fs.writeFileSync(path.join(testDir, 'ws.md'), '---\nmodel: \ntools: Read\n---\n# Whitespace model');
|
||||||
|
|
||||||
|
const result = runValidatorWithDir('validate-agents', 'AGENTS_DIR', testDir);
|
||||||
|
assert.strictEqual(result.code, 1, 'Should reject whitespace-only model');
|
||||||
|
assert.ok(result.stderr.includes('model'), 'Should report missing model field');
|
||||||
|
assert.ok(!result.stderr.includes('tools'), 'tools field is valid and should NOT be flagged');
|
||||||
|
cleanupTestDir(testDir);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
console.log('\nRound 83: validate-skills (empty SKILL.md file):');
|
||||||
|
|
||||||
|
if (test('rejects skill directory with empty SKILL.md file', () => {
|
||||||
|
const testDir = createTestDir();
|
||||||
|
const skillDir = path.join(testDir, 'empty-skill');
|
||||||
|
fs.mkdirSync(skillDir, { recursive: true });
|
||||||
|
// Create SKILL.md with only whitespace (trim to zero length)
|
||||||
|
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), ' \n \n');
|
||||||
|
|
||||||
|
const result = runValidatorWithDir('validate-skills', 'SKILLS_DIR', testDir);
|
||||||
|
assert.strictEqual(result.code, 1, 'Should reject empty SKILL.md');
|
||||||
|
assert.ok(result.stderr.includes('Empty file'),
|
||||||
|
`Should report "Empty file", got: ${result.stderr}`);
|
||||||
|
cleanupTestDir(testDir);
|
||||||
|
})) 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);
|
||||||
|
|||||||
@@ -1308,6 +1308,47 @@ src/main.ts
|
|||||||
assert.strictEqual(stats.hasContext, false, 'null input should yield hasContext false');
|
assert.strictEqual(stats.hasContext, false, 'null input should yield hasContext false');
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
// ── Round 83: getAllSessions TOCTOU statSync catch (broken symlink) ──
|
||||||
|
console.log('\nRound 83: getAllSessions (broken symlink — statSync catch):');
|
||||||
|
|
||||||
|
if (test('getAllSessions skips broken symlink .tmp files gracefully', () => {
|
||||||
|
// getAllSessions at line 241-246: statSync throws for broken symlinks,
|
||||||
|
// the catch causes `continue`, skipping that entry entirely.
|
||||||
|
const isoHome = path.join(os.tmpdir(), `ecc-r83-toctou-${Date.now()}`);
|
||||||
|
const sessionsDir = path.join(isoHome, '.claude', 'sessions');
|
||||||
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
||||||
|
|
||||||
|
// Create one real session file
|
||||||
|
const realFile = '2026-02-10-abcd1234-session.tmp';
|
||||||
|
fs.writeFileSync(path.join(sessionsDir, realFile), '# Real session\n');
|
||||||
|
|
||||||
|
// Create a broken symlink that matches the session filename pattern
|
||||||
|
const brokenSymlink = '2026-02-10-deadbeef-session.tmp';
|
||||||
|
fs.symlinkSync('/nonexistent/path/that/does/not/exist', path.join(sessionsDir, brokenSymlink));
|
||||||
|
|
||||||
|
const origHome = process.env.HOME;
|
||||||
|
const origUserProfile = process.env.USERPROFILE;
|
||||||
|
process.env.HOME = isoHome;
|
||||||
|
process.env.USERPROFILE = isoHome;
|
||||||
|
try {
|
||||||
|
delete require.cache[require.resolve('../../scripts/lib/session-manager')];
|
||||||
|
delete require.cache[require.resolve('../../scripts/lib/utils')];
|
||||||
|
const freshManager = require('../../scripts/lib/session-manager');
|
||||||
|
const result = freshManager.getAllSessions({ limit: 100 });
|
||||||
|
|
||||||
|
// Should have only the real session, not the broken symlink
|
||||||
|
assert.strictEqual(result.total, 1, 'Should find only the real session, not the broken symlink');
|
||||||
|
assert.ok(result.sessions[0].filename === realFile,
|
||||||
|
`Should return the real file, got: ${result.sessions[0].filename}`);
|
||||||
|
} 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
|
// 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