mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 10:40:32 +01:00
102 lines
6.3 KiB
TypeScript
102 lines
6.3 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import captured from './fixtures/eng-binding-z-calls.json';
|
|
import { engFirstReviewAUQ, engSetupAUQ, engStep0Boundary, nativePlanCallFingerprint, planCountQuestionPhase } from './helpers/claude-pty-runner';
|
|
import type { NativePlanQuestionCall } from './helpers/plan-count-transcript';
|
|
|
|
const fresh = () => structuredClone(captured[1]!) as NativePlanQuestionCall;
|
|
const fp = (c: NativePlanQuestionCall) => nativePlanCallFingerprint(c, 0, true);
|
|
const first = (c: NativePlanQuestionCall) => engFirstReviewAUQ(fp(c));
|
|
function question(c: NativePlanQuestionCall, transform: (s: string) => string) {
|
|
const q = c.questions[0]!; const answer = c.answers![q.question]!;
|
|
q.question = transform(q.question); c.answers = {[q.question]: answer}; return c;
|
|
}
|
|
|
|
describe('Z Eng dependency binding starts substantive review', () => {
|
|
test('the actual cache dependency decision starts review without an issue label', () => {
|
|
expect(engSetupAUQ(fp(fresh()))).toBe(false);
|
|
expect(first(fresh())).toBe(true);
|
|
expect(planCountQuestionPhase(fp(fresh()), false, engStep0Boundary, engFirstReviewAUQ, engSetupAUQ))
|
|
.toEqual({preReview: false, reviewStarted: true});
|
|
});
|
|
|
|
test('the exact six native calls preserve one setup and all five review obligations', () => {
|
|
let started = false;
|
|
const phases = captured.map(c => {
|
|
const p = planCountQuestionPhase(fp(structuredClone(c) as NativePlanQuestionCall), started, engStep0Boundary, engFirstReviewAUQ, engSetupAUQ);
|
|
started = p.reviewStarted; return p.preReview;
|
|
});
|
|
expect(phases).toEqual([true, false, false, false, false, false]);
|
|
expect(first(structuredClone(captured[0]!) as NativePlanQuestionCall)).toBe(false);
|
|
expect(captured[5]!.questions[0]!.header).toBe('TODO: Timeout');
|
|
});
|
|
|
|
test('either offered choice, reordering and a different component retain issue identity', () => {
|
|
const c = fresh(); c.questions[0]!.options.reverse();
|
|
for (const option of c.questions[0]!.options) {
|
|
c.answers = {[c.questions[0]!.question]: option.label}; expect(first(c)).toBe(true);
|
|
}
|
|
const varied = question(fresh(), s => s.replace('AuthBroker', 'SessionGateway').replace('D2', 'D7'));
|
|
for (const option of varied.questions[0]!.options) option.description = option.description.replaceAll('AuthBroker', 'SessionGateway');
|
|
expect(first(varied)).toBe(true);
|
|
});
|
|
|
|
test('requires a complete native call and exact offered answer and fingerprint', () => {
|
|
for (const mutate of [
|
|
(c: NativePlanQuestionCall) => { c.answered = false; },
|
|
(c: NativePlanQuestionCall) => { c.failed = true; },
|
|
(c: NativePlanQuestionCall) => { delete c.failed; },
|
|
(c: NativePlanQuestionCall) => { delete c.unansweredQuestionIndices; },
|
|
(c: NativePlanQuestionCall) => { c.unansweredQuestionIndices = [0]; },
|
|
(c: NativePlanQuestionCall) => { c.questions[0]!.multiSelect = true; },
|
|
(c: NativePlanQuestionCall) => { c.questions.push(structuredClone(c.questions[0]!)); },
|
|
(c: NativePlanQuestionCall) => { c.questions[0]!.options.push(structuredClone(c.questions[0]!.options[0]!)); },
|
|
(c: NativePlanQuestionCall) => { c.questions[0]!.options[1]!.label = c.questions[0]!.options[0]!.label; },
|
|
(c: NativePlanQuestionCall) => { c.answers = {}; },
|
|
(c: NativePlanQuestionCall) => { c.answers = {[c.questions[0]!.question]: 'Foreign answer'}; },
|
|
]) { const c = fresh(); mutate(c); expect(first(c)).toBe(false); }
|
|
expect(engFirstReviewAUQ({...fp(fresh()), signature: 'foreign:call'})).toBe(false);
|
|
expect(engFirstReviewAUQ({...fp(fresh()), nativeCall: undefined})).toBe(false);
|
|
expect(engFirstReviewAUQ({...fp(fresh()), options: []})).toBe(false);
|
|
const mismatch = fp(fresh()); mismatch.options[0]!.label = 'foreign'; expect(engFirstReviewAUQ(mismatch)).toBe(false);
|
|
const wrongIndex = fp(fresh()); wrongIndex.options[0]!.index = 2; expect(engFirstReviewAUQ(wrongIndex)).toBe(false);
|
|
});
|
|
|
|
test('setup, foreign, hypothetical, quoted and mixed questions cannot open review', () => {
|
|
for (const header of ['Scope', 'Approach', 'Next review', 'Onboarding']) {
|
|
const c = fresh(); c.questions[0]!.header = header; expect(first(c)).toBe(false);
|
|
}
|
|
for (const transform of [
|
|
(s: string) => s.replace('Architecture:', 'Approach:'),
|
|
(s: string) => s.replace('How should AuthBroker', 'If needed, how should AuthBroker'),
|
|
(s: string) => s.replace('AuthBroker access', 'the whole plan access'),
|
|
(s: string) => s.replace('plan-eng-cache-binding', 'plan-eng-setup'),
|
|
(s: string) => s.replace('plan-eng-cache-binding', 'foreign-cache-binding'),
|
|
(s: string) => s.replace(/ <gstack-qid:[^>]+>/, ''),
|
|
(s: string) => s + ' <gstack-qid:plan-eng-cache-binding>',
|
|
(s: string) => s + ' Approve the release too.',
|
|
(s: string) => '> ' + s,
|
|
(s: string) => '```text\n' + s + '\n```',
|
|
]) expect(first(question(fresh(), transform))).toBe(false);
|
|
});
|
|
|
|
test('both descriptions must affirm the existing dependency and remedy without extra obligations', () => {
|
|
for (const [index, transform] of [
|
|
[0, (s: string) => s.replace('Eliminates module-level mutable state entirely.', 'Does not eliminate module-level mutable state.')],
|
|
[0, (s: string) => s.replace('Eliminates module-level mutable state entirely.', 'If shared state exists, eliminates it.')],
|
|
[0, (s: string) => s.replace('AuthBroker receives', 'DifferentComponent receives')],
|
|
[1, (s: string) => s.replace('same pattern as the current plan', 'unlike the current plan')],
|
|
[1, (s: string) => s.replace('makes tests require module-level mocking', 'does not make tests require module-level mocking')],
|
|
[1, (s: string) => s.replace('AuthBroker imports', 'DifferentComponent imports')],
|
|
[0, (s: string) => s + ' Also grant every tenant access.'],
|
|
[1, (s: string) => s + ' Also approve the missing timeout policy.'],
|
|
[0, (s: string) => '> ' + s],
|
|
[1, (s: string) => '```text\n' + s + '\n```'],
|
|
] as const) {
|
|
const c = fresh(); const option = c.questions[0]!.options[index]!;
|
|
option.description = transform(option.description ?? ''); expect(first(c)).toBe(false);
|
|
}
|
|
const c = fresh(); c.questions[0]!.options[0]!.label = 'Run /office-hours';
|
|
c.answers = {[c.questions[0]!.question]: 'Run /office-hours'}; expect(first(c)).toBe(false);
|
|
});
|
|
});
|