070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
// Phase 8 entry slice — diff-review GenUI bridge.
|
|
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {
|
|
applyDiffReviewDecisionToCwd,
|
|
isDiffReviewSurfaceId,
|
|
parseDiffReviewGenuiResponse,
|
|
} from '../src/plugins/atoms/diff-review-genui-bridge.js';
|
|
|
|
let cwd: string;
|
|
|
|
beforeEach(async () => {
|
|
cwd = await mkdtemp(path.join(os.tmpdir(), 'od-diff-review-bridge-'));
|
|
await mkdir(path.join(cwd, 'plan', 'receipts'), { recursive: true });
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(cwd, { recursive: true, force: true });
|
|
});
|
|
|
|
async function seedReceipts() {
|
|
await writeFile(
|
|
path.join(cwd, 'plan', 'steps.json'),
|
|
JSON.stringify([
|
|
{ id: 'rewrite-button', files: [], rationale: '', risk: 'low', status: 'completed' },
|
|
], null, 2),
|
|
);
|
|
await writeFile(
|
|
path.join(cwd, 'plan', 'receipts', 'step-rewrite-button.json'),
|
|
JSON.stringify({
|
|
step: 'rewrite-button',
|
|
files: ['Button.tsx', 'Button.css'],
|
|
added: 1,
|
|
removed: 1,
|
|
rationale: '',
|
|
completedAt: new Date().toISOString(),
|
|
}, null, 2),
|
|
);
|
|
}
|
|
|
|
describe('isDiffReviewSurfaceId', () => {
|
|
it('matches the auto-derived prefix', () => {
|
|
expect(isDiffReviewSurfaceId('__auto_diff_review_review')).toBe(true);
|
|
expect(isDiffReviewSurfaceId('__auto_diff_review_verify')).toBe(true);
|
|
});
|
|
it('rejects unrelated ids', () => {
|
|
expect(isDiffReviewSurfaceId('__auto_connector_slack')).toBe(false);
|
|
expect(isDiffReviewSurfaceId('plugin-declared-surface')).toBe(false);
|
|
expect(isDiffReviewSurfaceId('')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('parseDiffReviewGenuiResponse', () => {
|
|
it('accepts a well-shaped accept payload', () => {
|
|
const out = parseDiffReviewGenuiResponse({
|
|
decision: 'accept',
|
|
accepted_files: ['x.ts'],
|
|
rejected_files: [],
|
|
});
|
|
expect('error' in out).toBe(false);
|
|
if (!('error' in out)) {
|
|
expect(out.decision).toBe('accept');
|
|
expect(out.accepted_files).toEqual(['x.ts']);
|
|
}
|
|
});
|
|
|
|
it("forwards the optional 'reason' field when non-empty", () => {
|
|
const out = parseDiffReviewGenuiResponse({
|
|
decision: 'reject',
|
|
reason: 'looks wrong',
|
|
});
|
|
if ('error' in out) throw new Error(out.error);
|
|
expect(out.reason).toBe('looks wrong');
|
|
});
|
|
|
|
it('strips non-string entries from the file lists', () => {
|
|
const out = parseDiffReviewGenuiResponse({
|
|
decision: 'partial',
|
|
accepted_files: ['x.ts', 42, null],
|
|
rejected_files: ['y.ts'],
|
|
});
|
|
if ('error' in out) throw new Error(out.error);
|
|
expect(out.accepted_files).toEqual(['x.ts']);
|
|
});
|
|
|
|
it('rejects non-object payloads', () => {
|
|
const a = parseDiffReviewGenuiResponse(null);
|
|
expect('error' in a).toBe(true);
|
|
const b = parseDiffReviewGenuiResponse('accept');
|
|
expect('error' in b).toBe(true);
|
|
});
|
|
|
|
it('rejects unknown decision values', () => {
|
|
const out = parseDiffReviewGenuiResponse({ decision: 'maybe' });
|
|
expect('error' in out).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('applyDiffReviewDecisionToCwd', () => {
|
|
it('writes review/decision.json end-to-end on accept', async () => {
|
|
await seedReceipts();
|
|
const result = await applyDiffReviewDecisionToCwd({
|
|
cwd,
|
|
reviewer: 'user',
|
|
value: { decision: 'accept' },
|
|
});
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.report.decision?.decision).toBe('accept');
|
|
expect(result.report.decision?.reviewer).toBe('user');
|
|
expect(result.report.decision?.accepted_files).toEqual(['Button.css', 'Button.tsx']);
|
|
}
|
|
const decision = JSON.parse(await readFile(path.join(cwd, 'review', 'decision.json'), 'utf8'));
|
|
expect(decision.decision).toBe('accept');
|
|
expect(decision.accepted_files).toEqual(['Button.css', 'Button.tsx']);
|
|
});
|
|
|
|
it('forwards reason + reviewer=agent when respondedBy=agent', async () => {
|
|
await seedReceipts();
|
|
const result = await applyDiffReviewDecisionToCwd({
|
|
cwd,
|
|
reviewer: 'agent',
|
|
value: { decision: 'reject', reason: 'auto-revert' },
|
|
});
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) {
|
|
expect(result.report.decision?.decision).toBe('reject');
|
|
expect(result.report.decision?.reviewer).toBe('agent');
|
|
expect(result.report.decision?.reason).toBe('auto-revert');
|
|
}
|
|
});
|
|
|
|
it('returns ok=false on a partial decision missing some files', async () => {
|
|
await seedReceipts();
|
|
const result = await applyDiffReviewDecisionToCwd({
|
|
cwd,
|
|
reviewer: 'user',
|
|
value: {
|
|
decision: 'partial',
|
|
accepted_files: ['Button.tsx'],
|
|
rejected_files: [],
|
|
},
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(result.error).toMatch(/missing Button\.css/);
|
|
});
|
|
|
|
it('returns ok=false on malformed value WITHOUT touching disk', async () => {
|
|
await seedReceipts();
|
|
const result = await applyDiffReviewDecisionToCwd({
|
|
cwd,
|
|
reviewer: 'user',
|
|
value: { decision: 'maybe' },
|
|
});
|
|
expect(result.ok).toBe(false);
|
|
await expect(readFile(path.join(cwd, 'review', 'decision.json'), 'utf8'))
|
|
.rejects.toThrow();
|
|
});
|
|
});
|