mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
test: integration tests for gstack-review-log and gstack-review-read
- Full temp-dir integration tests via GSTACK_HOME env var override - Tests: file creation, append behavior, slash sanitization, NO_REVIEWS fallback, review content read, CONFIG separator - Regression tests: generated SKILL.md files use new helpers Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -329,14 +329,14 @@ describe('REVIEW_DASHBOARD resolver', () => {
|
||||
for (const skill of REVIEW_SKILLS) {
|
||||
test(`review dashboard appears in ${skill} generated file`, () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('reviews.jsonl');
|
||||
expect(content).toContain('gstack-review-read');
|
||||
expect(content).toContain('REVIEW READINESS DASHBOARD');
|
||||
});
|
||||
}
|
||||
|
||||
test('review dashboard appears in ship generated file', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('reviews.jsonl');
|
||||
expect(content).toContain('gstack-review-read');
|
||||
expect(content).toContain('REVIEW READINESS DASHBOARD');
|
||||
});
|
||||
|
||||
@@ -349,4 +349,18 @@ describe('REVIEW_DASHBOARD resolver', () => {
|
||||
expect(content).toContain('Design Review');
|
||||
expect(content).toContain('skip_eng_review');
|
||||
});
|
||||
|
||||
test('review dashboard uses gstack-review-read (not multi-line eval+cat pattern)', () => {
|
||||
for (const skill of [...REVIEW_SKILLS, 'ship']) {
|
||||
const content = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('gstack-review-read');
|
||||
}
|
||||
});
|
||||
|
||||
test('review log uses gstack-review-log (not multi-line eval+mkdir+echo pattern)', () => {
|
||||
for (const skill of REVIEW_SKILLS) {
|
||||
const content = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('gstack-review-log');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -775,6 +775,132 @@ describe('gstack-slug', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// --- gstack-review-log helper ---
|
||||
|
||||
describe('gstack-review-log', () => {
|
||||
const REVIEW_LOG_BIN = path.join(ROOT, 'bin', 'gstack-review-log');
|
||||
|
||||
test('binary exists and is executable', () => {
|
||||
expect(fs.existsSync(REVIEW_LOG_BIN)).toBe(true);
|
||||
const stat = fs.statSync(REVIEW_LOG_BIN);
|
||||
expect(stat.mode & 0o111).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('creates review log file and appends JSON payload', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gstack-review-log-'));
|
||||
const payload = '{"skill":"test","timestamp":"2026-03-17T00:00:00Z","status":"clean"}';
|
||||
const result = Bun.spawnSync([REVIEW_LOG_BIN, payload], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_HOME: tmpDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
|
||||
// Determine the written file path via gstack-slug
|
||||
const slugResult = Bun.spawnSync([path.join(ROOT, 'bin', 'gstack-slug')], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' });
|
||||
const slug = slugResult.stdout.toString().match(/SLUG=(.*)/)?.[1] ?? '';
|
||||
const branch = slugResult.stdout.toString().match(/BRANCH=(.*)/)?.[1] ?? '';
|
||||
const filePath = path.join(tmpDir, 'projects', slug, `${branch}-reviews.jsonl`);
|
||||
|
||||
expect(fs.existsSync(filePath)).toBe(true);
|
||||
expect(fs.readFileSync(filePath, 'utf-8').trim()).toBe(payload);
|
||||
|
||||
// File path should not contain forward slashes in slug or branch segments
|
||||
expect(slug).not.toContain('/');
|
||||
expect(branch).not.toContain('/');
|
||||
|
||||
fs.rmSync(tmpDir, { recursive: true });
|
||||
});
|
||||
|
||||
test('appending a second entry adds to same file (not overwrites)', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gstack-review-log-'));
|
||||
const payload1 = '{"skill":"test","entry":1}';
|
||||
const payload2 = '{"skill":"test","entry":2}';
|
||||
|
||||
Bun.spawnSync([REVIEW_LOG_BIN, payload1], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_HOME: tmpDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
Bun.spawnSync([REVIEW_LOG_BIN, payload2], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_HOME: tmpDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
|
||||
const slugResult = Bun.spawnSync([path.join(ROOT, 'bin', 'gstack-slug')], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' });
|
||||
const slug = slugResult.stdout.toString().match(/SLUG=(.*)/)?.[1] ?? '';
|
||||
const branch = slugResult.stdout.toString().match(/BRANCH=(.*)/)?.[1] ?? '';
|
||||
const filePath = path.join(tmpDir, 'projects', slug, `${branch}-reviews.jsonl`);
|
||||
|
||||
const lines = fs.readFileSync(filePath, 'utf-8').trim().split('\n');
|
||||
expect(lines.length).toBe(2);
|
||||
expect(lines[0]).toBe(payload1);
|
||||
expect(lines[1]).toBe(payload2);
|
||||
|
||||
fs.rmSync(tmpDir, { recursive: true });
|
||||
});
|
||||
});
|
||||
|
||||
// --- gstack-review-read helper ---
|
||||
|
||||
describe('gstack-review-read', () => {
|
||||
const REVIEW_READ_BIN = path.join(ROOT, 'bin', 'gstack-review-read');
|
||||
|
||||
test('binary exists and is executable', () => {
|
||||
expect(fs.existsSync(REVIEW_READ_BIN)).toBe(true);
|
||||
const stat = fs.statSync(REVIEW_READ_BIN);
|
||||
expect(stat.mode & 0o111).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('outputs NO_REVIEWS when no log file exists', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gstack-review-read-'));
|
||||
const result = Bun.spawnSync([REVIEW_READ_BIN], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_HOME: tmpDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
const output = result.stdout.toString();
|
||||
expect(output).toContain('NO_REVIEWS');
|
||||
expect(output).toContain('---CONFIG---');
|
||||
|
||||
fs.rmSync(tmpDir, { recursive: true });
|
||||
});
|
||||
|
||||
test('outputs review content when log file exists', () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gstack-review-read-'));
|
||||
const logBin = path.join(ROOT, 'bin', 'gstack-review-log');
|
||||
const payload = '{"skill":"plan-eng-review","status":"clean"}';
|
||||
|
||||
// Write a review first
|
||||
Bun.spawnSync([logBin, payload], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_HOME: tmpDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
|
||||
// Now read
|
||||
const result = Bun.spawnSync([REVIEW_READ_BIN], {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_HOME: tmpDir },
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
});
|
||||
expect(result.exitCode).toBe(0);
|
||||
const output = result.stdout.toString();
|
||||
expect(output).toContain(payload);
|
||||
expect(output).toContain('---CONFIG---');
|
||||
|
||||
fs.rmSync(tmpDir, { recursive: true });
|
||||
});
|
||||
});
|
||||
|
||||
// --- Test Bootstrap validation ---
|
||||
|
||||
describe('Test Bootstrap ({{TEST_BOOTSTRAP}}) integration', () => {
|
||||
|
||||
Reference in New Issue
Block a user