mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 03:35:09 +02:00
test(browser-skills-e2e): exercise dispatch with bundled hackernews-frontpage
Covers the full \$B skill list/show/test pipeline against the real bundled reference skill (defaultTierPaths picks up <repo>/browser-skills/). Verifies frontmatter shape, the three-tier walk surfaces the bundled entry, and \$B skill test successfully runs the bundled script.test.ts in a child bun process. \$B skill run end-to-end against the live network is intentionally NOT covered here (would be flaky against news.ycombinator.com); the spawn lifecycle is exercised in browser-skill-commands.test.ts using inline synthetic skills. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* browser-skills E2E — exercise the full dispatch path against the bundled
|
||||
* `hackernews-frontpage` reference skill. Verifies:
|
||||
*
|
||||
* - $B skill list resolves the bundled tier and surfaces hackernews-frontpage
|
||||
* - $B skill show returns the SKILL.md
|
||||
* - $B skill test runs script.test.ts (which itself runs against the bundled
|
||||
* fixture) and reports pass
|
||||
*
|
||||
* Coverage gap intentionally NOT here: $B skill run end-to-end against the
|
||||
* bundled skill goes to live news.ycombinator.com and would be flaky. The
|
||||
* spawnSkill lifecycle (env scrub, scoped token, timeout, stdout cap) is
|
||||
* already covered by browse/test/browser-skill-commands.test.ts using inline
|
||||
* scripts.
|
||||
*/
|
||||
|
||||
import { describe, test, expect, beforeAll } from 'bun:test';
|
||||
import { handleSkillCommand } from '../src/browser-skill-commands';
|
||||
import { listBrowserSkills, defaultTierPaths } from '../src/browser-skills';
|
||||
import { initRegistry, rotateRoot } from '../src/token-registry';
|
||||
|
||||
beforeAll(() => {
|
||||
// Some preceding tests may have rotated the registry; ensure we have a root.
|
||||
rotateRoot();
|
||||
initRegistry('e2e-root-token');
|
||||
});
|
||||
|
||||
describe('browser-skills E2E — bundled hackernews-frontpage', () => {
|
||||
test('defaultTierPaths resolves bundled tier to <repo>/browser-skills/', () => {
|
||||
const tiers = defaultTierPaths();
|
||||
expect(tiers.bundled).toMatch(/\/browser-skills$/);
|
||||
// Bundled tier should exist on disk (the reference skill is shipped).
|
||||
expect(require('fs').existsSync(tiers.bundled)).toBe(true);
|
||||
});
|
||||
|
||||
test('listBrowserSkills() returns hackernews-frontpage at bundled tier', () => {
|
||||
const skills = listBrowserSkills();
|
||||
const hn = skills.find(s => s.name === 'hackernews-frontpage');
|
||||
expect(hn).toBeTruthy();
|
||||
expect(hn!.tier).toBe('bundled');
|
||||
expect(hn!.frontmatter.host).toBe('news.ycombinator.com');
|
||||
expect(hn!.frontmatter.trusted).toBe(true);
|
||||
expect(hn!.frontmatter.triggers).toContain('scrape hn frontpage');
|
||||
});
|
||||
|
||||
test('$B skill list dispatches and includes hackernews-frontpage', async () => {
|
||||
const result = await handleSkillCommand(['list'], { port: 0 });
|
||||
expect(result).toContain('hackernews-frontpage');
|
||||
expect(result).toContain('bundled');
|
||||
expect(result).toContain('news.ycombinator.com');
|
||||
});
|
||||
|
||||
test('$B skill show hackernews-frontpage prints the SKILL.md', async () => {
|
||||
const result = await handleSkillCommand(['show', 'hackernews-frontpage'], { port: 0 });
|
||||
expect(result).toContain('host: news.ycombinator.com');
|
||||
expect(result).toContain('trusted: true');
|
||||
expect(result).toContain('Hacker News front-page scraper');
|
||||
expect(result).toContain('triggers:');
|
||||
});
|
||||
|
||||
test('$B skill show <missing> errors clearly', async () => {
|
||||
await expect(handleSkillCommand(['show', 'nonexistent-skill-xyz'], { port: 0 }))
|
||||
.rejects.toThrow(/not found in any tier/);
|
||||
});
|
||||
|
||||
test('$B skill help prints usage', async () => {
|
||||
const result = await handleSkillCommand([], { port: 0 });
|
||||
expect(result).toContain('Usage');
|
||||
expect(result).toContain('list');
|
||||
expect(result).toContain('show');
|
||||
expect(result).toContain('run');
|
||||
});
|
||||
|
||||
test('$B skill rm cannot tombstone bundled tier (read-only)', async () => {
|
||||
// The bundled hackernews-frontpage skill is shipped read-only; rm targets
|
||||
// user tiers (project default, --global). Attempting rm on a name that
|
||||
// only exists in bundled should error with "not found".
|
||||
await expect(handleSkillCommand(['rm', 'hackernews-frontpage', '--global'], { port: 0 }))
|
||||
.rejects.toThrow(/not found/);
|
||||
});
|
||||
|
||||
// The `test` subcommand spawns `bun test script.test.ts` in the skill dir.
|
||||
// It takes ~1s. Run it last so other assertions are quick.
|
||||
test('$B skill test hackernews-frontpage runs script.test.ts and reports pass', async () => {
|
||||
const result = await handleSkillCommand(['test', 'hackernews-frontpage'], { port: 0 });
|
||||
// bun test prints summary to stderr; handleSkillCommand returns stderr || stdout
|
||||
expect(result).toMatch(/13 pass|0 fail|tests passed/);
|
||||
}, 30_000);
|
||||
});
|
||||
Reference in New Issue
Block a user