Skip to content

Commit a0dcd89

Browse files
committed
test: add 34 tests for modular resolver pipeline (6 untested modules)
Covers the resolver modules split in v0.11.12.0 (garrytan#425): - preamble: update check, session tracking, AskUserQuestion format, contributor mode - testing: test bootstrap, coverage audit (plan/ship/review modes) - utility: slug eval/setup, base branch detect, QA methodology - codex-helpers: frontmatter parsing, description condensing, skill naming, host transform - constants: AI slop blacklist, OpenAI rejections, error handling - browse: setup instructions, binary detection 34 tests, 57 assertions. No source code modified.
1 parent 3501f5d commit a0dcd89

1 file changed

Lines changed: 249 additions & 0 deletions

File tree

test/resolvers.test.ts

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
/**
2+
* Tests for modular resolver pipeline (scripts/resolvers/).
3+
*
4+
* Covers the 6 untested resolver modules: preamble, testing, utility,
5+
* codex-helpers, constants, browse. Validates output structure, placeholder
6+
* resolution, and cross-module consistency.
7+
*/
8+
9+
import { describe, test, expect } from 'bun:test';
10+
import { generatePreamble, generateTestFailureTriage } from '../scripts/resolvers/preamble';
11+
import { generateTestBootstrap, generateTestCoverageAuditPlan, generateTestCoverageAuditShip, generateTestCoverageAuditReview } from '../scripts/resolvers/testing';
12+
import { generateSlugEval, generateSlugSetup, generateBaseBranchDetect, generateQAMethodology } from '../scripts/resolvers/utility';
13+
import { extractNameAndDescription, condenseOpenAIShortDescription, codexSkillName, transformFrontmatter, extractHookSafetyProse } from '../scripts/resolvers/codex-helpers';
14+
import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, codexErrorHandling } from '../scripts/resolvers/constants';
15+
import { generateBrowseSetup } from '../scripts/resolvers/browse';
16+
import type { TemplateContext, Host } from '../scripts/resolvers/types';
17+
import { HOST_PATHS } from '../scripts/resolvers/types';
18+
19+
// Shared test context
20+
const claudeCtx: TemplateContext = {
21+
host: 'claude' as Host,
22+
skillName: 'test-skill',
23+
tmplPath: 'test-skill/SKILL.md.tmpl',
24+
paths: HOST_PATHS.claude,
25+
};
26+
const codexCtx: TemplateContext = {
27+
host: 'codex' as Host,
28+
skillName: 'test-skill',
29+
tmplPath: 'test-skill/SKILL.md.tmpl',
30+
paths: HOST_PATHS.codex,
31+
};
32+
33+
// ─── Preamble ────────────────────────────────────────────────
34+
35+
describe('preamble resolver', () => {
36+
const output = generatePreamble(claudeCtx);
37+
38+
test('contains update check', () => {
39+
expect(output).toContain('gstack-update-check');
40+
});
41+
42+
test('contains session tracking', () => {
43+
expect(output).toContain('sessions');
44+
expect(output).toContain('PPID');
45+
});
46+
47+
test('contains AskUserQuestion format', () => {
48+
expect(output).toContain('AskUserQuestion');
49+
expect(output).toContain('RECOMMENDATION');
50+
});
51+
52+
test('contains session awareness', () => {
53+
expect(output).toContain('_SESSIONS');
54+
});
55+
56+
test('contains contributor mode', () => {
57+
expect(output).toContain('Contributor Mode');
58+
expect(output).toContain('gstack_contributor');
59+
});
60+
61+
test('bash blocks end with || true for safe eval', () => {
62+
expect(output).toContain('|| true');
63+
});
64+
65+
test('generateTestFailureTriage returns non-empty', () => {
66+
const triage = generateTestFailureTriage();
67+
expect(triage.length).toBeGreaterThan(50);
68+
expect(triage).toContain('fail');
69+
});
70+
});
71+
72+
// ─── Testing resolvers ──────────────────────────────────────
73+
74+
describe('testing resolvers', () => {
75+
test('generateTestBootstrap contains test framework detection', () => {
76+
const output = generateTestBootstrap(claudeCtx);
77+
expect(output.length).toBeGreaterThan(100);
78+
});
79+
80+
test('generateTestCoverageAuditPlan returns non-empty', () => {
81+
const output = generateTestCoverageAuditPlan(claudeCtx);
82+
expect(output.length).toBeGreaterThan(50);
83+
});
84+
85+
test('generateTestCoverageAuditShip returns non-empty', () => {
86+
const output = generateTestCoverageAuditShip(claudeCtx);
87+
expect(output.length).toBeGreaterThan(50);
88+
});
89+
90+
test('generateTestCoverageAuditReview returns non-empty', () => {
91+
const output = generateTestCoverageAuditReview(claudeCtx);
92+
expect(output.length).toBeGreaterThan(50);
93+
});
94+
95+
test('ship coverage audit mentions coverage thresholds', () => {
96+
const output = generateTestCoverageAuditShip(claudeCtx);
97+
expect(output).toMatch(/\d+%/); // should mention percentage thresholds
98+
});
99+
});
100+
101+
// ─── Utility resolvers ──────────────────────────────────────
102+
103+
describe('utility resolvers', () => {
104+
test('generateSlugEval outputs eval-safe slug command', () => {
105+
const output = generateSlugEval(claudeCtx);
106+
expect(output).toContain('gstack-slug');
107+
expect(output).toContain('eval');
108+
});
109+
110+
test('generateSlugSetup outputs slug setup with mkdir', () => {
111+
const output = generateSlugSetup(claudeCtx);
112+
expect(output).toContain('gstack-slug');
113+
expect(output).toContain('$SLUG');
114+
});
115+
116+
test('generateBaseBranchDetect uses gh pr view', () => {
117+
const output = generateBaseBranchDetect(claudeCtx);
118+
expect(output).toContain('gh pr view');
119+
expect(output).toContain('baseRefName');
120+
});
121+
122+
test('generateBaseBranchDetect has fallback to main', () => {
123+
const output = generateBaseBranchDetect(claudeCtx);
124+
expect(output).toMatch(/fall\s*back.*main/i);
125+
});
126+
127+
test('generateQAMethodology contains all 6 phases', () => {
128+
const output = generateQAMethodology(claudeCtx);
129+
for (let i = 1; i <= 6; i++) {
130+
expect(output).toContain(`Phase ${i}`);
131+
}
132+
});
133+
134+
test('generateQAMethodology contains health score rubric', () => {
135+
const output = generateQAMethodology(claudeCtx);
136+
expect(output).toContain('Health Score');
137+
});
138+
});
139+
140+
// ─── Codex helpers ──────────────────────────────────────────
141+
142+
describe('codex-helpers', () => {
143+
test('extractNameAndDescription parses frontmatter', () => {
144+
const content = '---\nname: test-skill\ndescription: |\n A test skill.\nallowed-tools:\n - Bash\n---\nBody content';
145+
const { name, description } = extractNameAndDescription(content);
146+
expect(name).toBe('test-skill');
147+
expect(description).toContain('test skill');
148+
});
149+
150+
test('extractNameAndDescription handles missing fields', () => {
151+
const { name, description } = extractNameAndDescription('no frontmatter here');
152+
expect(name).toBe('');
153+
expect(description).toBe('');
154+
});
155+
156+
test('condenseOpenAIShortDescription truncates to 1024 chars', () => {
157+
const long = 'A'.repeat(2000);
158+
const condensed = condenseOpenAIShortDescription(long);
159+
expect(condensed.length).toBeLessThanOrEqual(1024);
160+
});
161+
162+
test('condenseOpenAIShortDescription preserves short descriptions', () => {
163+
const short = 'A simple skill that does things.';
164+
expect(condenseOpenAIShortDescription(short)).toBe(short);
165+
});
166+
167+
test('codexSkillName prefixes with gstack-', () => {
168+
expect(codexSkillName('review')).toBe('gstack-review');
169+
expect(codexSkillName('plan-ceo-review')).toBe('gstack-plan-ceo-review');
170+
});
171+
172+
test('codexSkillName handles root skill', () => {
173+
const result = codexSkillName('.');
174+
expect(result).toContain('gstack');
175+
});
176+
177+
test('transformFrontmatter strips allowed-tools for codex', () => {
178+
const content = '---\nname: test\nversion: 1.0\ndescription: |\n Test.\nallowed-tools:\n - Bash\n - Read\n---\nBody';
179+
const transformed = transformFrontmatter(content, 'codex' as Host);
180+
expect(transformed).not.toContain('allowed-tools');
181+
expect(transformed).toContain('name:');
182+
expect(transformed).toContain('description:');
183+
});
184+
185+
test('transformFrontmatter preserves content for claude', () => {
186+
const content = '---\nname: test\nallowed-tools:\n - Bash\n---\nBody';
187+
const transformed = transformFrontmatter(content, 'claude' as Host);
188+
expect(transformed).toContain('allowed-tools');
189+
});
190+
191+
test('extractHookSafetyProse returns null for non-hook skills', () => {
192+
const content = '---\nname: review\n---\nRegular skill content';
193+
expect(extractHookSafetyProse(content)).toBeNull();
194+
});
195+
});
196+
197+
// ─── Constants ──────────────────────────────────────────────
198+
199+
describe('constants', () => {
200+
test('AI_SLOP_BLACKLIST is a non-empty array', () => {
201+
expect(Array.isArray(AI_SLOP_BLACKLIST)).toBe(true);
202+
expect(AI_SLOP_BLACKLIST.length).toBeGreaterThan(0);
203+
});
204+
205+
test('OPENAI_HARD_REJECTIONS is a non-empty array', () => {
206+
expect(Array.isArray(OPENAI_HARD_REJECTIONS)).toBe(true);
207+
expect(OPENAI_HARD_REJECTIONS.length).toBeGreaterThan(0);
208+
});
209+
210+
test('codexErrorHandling returns non-empty string', () => {
211+
const output = codexErrorHandling('test-feature');
212+
expect(output.length).toBeGreaterThan(20);
213+
expect(output).toContain('test-feature');
214+
});
215+
});
216+
217+
// ─── Browse resolver ────────────────────────────────────────
218+
219+
describe('browse resolver', () => {
220+
test('generateBrowseSetup contains binary detection', () => {
221+
const output = generateBrowseSetup(claudeCtx);
222+
expect(output).toContain('browse');
223+
expect(output).toContain('dist');
224+
});
225+
226+
test('generateBrowseSetup mentions setup instructions', () => {
227+
const output = generateBrowseSetup(claudeCtx);
228+
expect(output).toContain('NEEDS_SETUP');
229+
});
230+
});
231+
232+
// ─── Cross-module consistency ───────────────────────────────
233+
234+
describe('cross-module consistency', () => {
235+
test('utility slug functions reference gstack-slug', () => {
236+
const slugEval = generateSlugEval(claudeCtx);
237+
const slugSetup = generateSlugSetup(claudeCtx);
238+
expect(slugEval).toContain('gstack-slug');
239+
expect(slugSetup).toContain('gstack-slug');
240+
});
241+
242+
test('codex and claude preambles both contain core sections', () => {
243+
const claude = generatePreamble(claudeCtx);
244+
const codex = generatePreamble(codexCtx);
245+
// Both should have AskUserQuestion format
246+
expect(claude).toContain('AskUserQuestion');
247+
expect(codex).toContain('AskUserQuestion');
248+
});
249+
});

0 commit comments

Comments
 (0)