mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
test: add parameterized host smoke tests for all hosts
35 new tests covering all 7 external hosts (Codex, Factory, Kiro, OpenCode, Slate, Cursor, OpenClaw). Each host gets 4-5 tests: - output exists on disk with SKILL.md files - no .claude/skills path leakage in non-root skills - frontmatter has name + description fields - --dry-run freshness check passes - /codex skill excluded (for hosts with skipSkills: ['codex']) Tests are parameterized over ALL_HOST_CONFIGS so adding a new host automatically gets smoke-tested with zero new test code. Also updates --host all test to verify all registered hosts generate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1886,19 +1886,95 @@ describe('Factory generation (--host factory)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Parameterized host smoke tests (config-driven) ─────────
|
||||
|
||||
import { ALL_HOST_CONFIGS, getExternalHosts } from '../hosts/index';
|
||||
|
||||
describe('Parameterized host smoke tests', () => {
|
||||
for (const hostConfig of getExternalHosts()) {
|
||||
describe(`${hostConfig.displayName} (--host ${hostConfig.name})`, () => {
|
||||
const hostDir = path.join(ROOT, hostConfig.hostSubdir, 'skills');
|
||||
|
||||
test('generates output that exists on disk', () => {
|
||||
// Generated dir should exist (created by earlier bun run gen:skill-docs --host all)
|
||||
if (!fs.existsSync(hostDir)) {
|
||||
// Generate if not already done
|
||||
Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', hostConfig.name], {
|
||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
||||
});
|
||||
}
|
||||
expect(fs.existsSync(hostDir)).toBe(true);
|
||||
const skills = fs.readdirSync(hostDir).filter(d =>
|
||||
fs.existsSync(path.join(hostDir, d, 'SKILL.md'))
|
||||
);
|
||||
expect(skills.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('no .claude/skills path leakage in non-root skills', () => {
|
||||
if (!fs.existsSync(hostDir)) return; // skip if not generated
|
||||
const skills = fs.readdirSync(hostDir);
|
||||
for (const skill of skills) {
|
||||
// Skip root gstack skill — it contains preamble with intentional .claude/skills
|
||||
// fallback paths for binary lookup and skill prefix instructions
|
||||
if (skill === 'gstack') continue;
|
||||
const skillMd = path.join(hostDir, skill, 'SKILL.md');
|
||||
if (!fs.existsSync(skillMd)) continue;
|
||||
const content = fs.readFileSync(skillMd, 'utf-8');
|
||||
// Strip bash blocks (which have legitimate fallback paths)
|
||||
const noBash = content.replace(/```bash\n[\s\S]*?```/g, '');
|
||||
const leaks = noBash.split('\n').filter(l => l.includes('.claude/skills'));
|
||||
if (leaks.length > 0) {
|
||||
throw new Error(`${skill}: .claude/skills leakage:\n${leaks.slice(0, 3).join('\n')}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('frontmatter has name and description', () => {
|
||||
if (!fs.existsSync(hostDir)) return;
|
||||
const skills = fs.readdirSync(hostDir);
|
||||
for (const skill of skills) {
|
||||
const skillMd = path.join(hostDir, skill, 'SKILL.md');
|
||||
if (!fs.existsSync(skillMd)) continue;
|
||||
const content = fs.readFileSync(skillMd, 'utf-8');
|
||||
expect(content).toMatch(/^---\n/);
|
||||
expect(content).toMatch(/^name:\s/m);
|
||||
expect(content).toMatch(/^description:\s/m);
|
||||
}
|
||||
});
|
||||
|
||||
test('--dry-run freshness check passes', () => {
|
||||
const result = Bun.spawnSync(
|
||||
['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', hostConfig.name, '--dry-run'],
|
||||
{ cwd: ROOT, stdout: 'pipe', stderr: 'pipe' }
|
||||
);
|
||||
expect(result.exitCode).toBe(0);
|
||||
const output = result.stdout.toString();
|
||||
expect(output).not.toContain('STALE');
|
||||
});
|
||||
|
||||
if (hostConfig.generation.skipSkills?.includes('codex')) {
|
||||
test('/codex skill excluded', () => {
|
||||
expect(fs.existsSync(path.join(hostDir, 'gstack-codex', 'SKILL.md'))).toBe(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ─── --host all tests ────────────────────────────────────────
|
||||
|
||||
describe('--host all', () => {
|
||||
test('--host all generates for claude, codex, and factory', () => {
|
||||
test('--host all generates for all registered hosts', () => {
|
||||
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--host', 'all', '--dry-run'], {
|
||||
cwd: ROOT, stdout: 'pipe', stderr: 'pipe',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
const output = result.stdout.toString();
|
||||
// All three hosts should appear in output
|
||||
// All hosts should appear in output
|
||||
expect(output).toContain('FRESH: SKILL.md'); // claude
|
||||
expect(output).toContain('FRESH: .agents/skills/'); // codex
|
||||
expect(output).toContain('FRESH: .factory/skills/'); // factory
|
||||
for (const hostConfig of getExternalHosts()) {
|
||||
expect(output).toContain(`FRESH: ${hostConfig.hostSubdir}/skills/`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user