package singlerequesttemplate_test import ( "strings" "testing" "iop/packages/go/singlerequesttemplate" ) // planTemplateOfSize pads the built-in Plan template with trailing static text // so the returned template is exactly size bytes long. func planTemplateOfSize(size int) string { return singlerequesttemplate.DefaultPlanTemplate + strings.Repeat(" ", size-len(singlerequesttemplate.DefaultPlanTemplate)) } // reviewTemplateOfSize pads the built-in Review handoff template with trailing // static text so the returned template is exactly size bytes long. func reviewTemplateOfSize(size int) string { return singlerequesttemplate.DefaultReviewTemplate + strings.Repeat(" ", size-len(singlerequesttemplate.DefaultReviewTemplate)) } func TestValidatePlanTemplate(t *testing.T) { tests := []struct { name string tmpl string wantErr bool }{ { name: "default plan template is valid", tmpl: singlerequesttemplate.DefaultPlanTemplate, wantErr: false, }, { name: "custom plan template with extra static text", tmpl: `# Plan Custom header notes. ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} Footer notes. `, wantErr: false, }, { name: "empty template", tmpl: "", wantErr: true, }, { name: "oversized template 8193 bytes", tmpl: singlerequesttemplate.DefaultPlanTemplate + strings.Repeat(" ", 8193-len(singlerequesttemplate.DefaultPlanTemplate)), wantErr: true, }, { name: "missing {{goal}}", tmpl: `# Plan ## Goal ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "duplicate {{goal}}", tmpl: `# Plan ## Goal {{goal}} {{goal}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "unknown token", tmpl: `# Plan ## Goal {{goal}} {{foo}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "wrong token order", tmpl: `# Plan ## Goal {{steps}} ## Steps {{goal}} ## Verification {{verification}} `, wantErr: true, }, { name: "missing required heading # Plan", tmpl: `## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "exact 8192 bytes accepted", tmpl: planTemplateOfSize(8192), wantErr: false, }, { name: "decorated heading ### Plan", tmpl: `### Plan ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "heading line carries trailing text", tmpl: `# Plan Mismatch ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "heading embedded inside a prose line", tmpl: `# Plan Documented as ## Goal below. {{goal}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "duplicate required heading", tmpl: `# Plan ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} ## Goal `, wantErr: true, }, { name: "unbalanced opening delimiter residue", tmpl: `# Plan ## Goal {{goal}} {{ ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "unbalanced closing delimiter residue", tmpl: `# Plan ## Goal {{goal}}}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, { name: "nested delimiter around a documented placeholder", tmpl: `# Plan ## Goal {{{{goal}}}} ## Steps {{steps}} ## Verification {{verification}} `, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := singlerequesttemplate.ValidatePlanTemplate(tt.tmpl) if (err != nil) != tt.wantErr { t.Errorf("ValidatePlanTemplate() err = %v, wantErr %v", err, tt.wantErr) } }) } } func TestParsePlan(t *testing.T) { validOutput := `# Plan ## Goal Fix single-request template handling bug. ## Steps - [P1] Inspect template file resolution. - [P2] Verify template validation logic. ## Verification - Run go test on singlerequesttemplate package. ` tests := []struct { name string tmpl string raw string maxOutputBytes int wantErr bool }{ { name: "valid plan output default template", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: validOutput, maxOutputBytes: 1024, wantErr: false, }, { name: "boundary steps = 2, verif = 1", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: validOutput, maxOutputBytes: 1024, wantErr: false, }, { name: "provider omits final line feed", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: strings.TrimSuffix(validOutput, "\n"), maxOutputBytes: 1024, wantErr: false, }, { name: "provider omits only final line feed after static suffix", tmpl: `# Plan ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} END `, raw: `# Plan ## Goal Fix suffix parsing. ## Steps - [P1] Keep the static suffix. - [P2] Allow the final line feed omission. ## Verification - Run the parser tests. END`, maxOutputBytes: 1024, wantErr: false, }, { name: "provider omits static suffix text", tmpl: `# Plan ## Goal {{goal}} ## Steps {{steps}} ## Verification {{verification}} END `, raw: strings.TrimSuffix(validOutput, "\n"), maxOutputBytes: 1024, wantErr: true, }, { name: "boundary steps = 6, verif = 3", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature end to end. ## Steps - [P1] Step one - [P2] Step two - [P3] Step three - [P4] Step four - [P5] Step five - [P6] Step six ## Verification - Verify 1 - Verify 2 - Verify 3 `, maxOutputBytes: 2048, wantErr: false, }, { name: "invalid step count = 1 (too few)", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - [P1] Step one ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "invalid step count = 7 (too many)", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - [P1] Step 1 - [P2] Step 2 - [P3] Step 3 - [P4] Step 4 - [P5] Step 5 - [P6] Step 6 - [P7] Step 7 ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "invalid verif count = 0 (too few)", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - [P1] Step 1 - [P2] Step 2 ## Verification `, maxOutputBytes: 1024, wantErr: true, }, { name: "invalid verif count = 4 (too many)", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - [P1] Step 1 - [P2] Step 2 ## Verification - Verify 1 - Verify 2 - Verify 3 - Verify 4 `, maxOutputBytes: 1024, wantErr: true, }, { name: "step missing deterministic P1 id", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - Inspect template file resolution. - Verify template validation logic. ## Verification - Run go test on singlerequesttemplate package. `, maxOutputBytes: 1024, wantErr: true, }, { name: "step id out of order P1, P3", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - [P1] Step one - [P3] Step three ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "step id duplicate P1, P1", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Implement feature. ## Steps - [P1] Step one - [P1] Step dup ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "multiline goal", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal First line of goal. Second line of goal. ## Steps - [P1] Step 1 - [P2] Step 2 ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "altered heading static text", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan Mismatch ## Goal Fix bug. ## Steps - [P1] Step 1 - [P2] Step 2 ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "unresolved token in output", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: `# Plan ## Goal Fix {{goal}} bug. ## Steps - [P1] Step 1 - [P2] Step 2 ## Verification - Verify 1 `, maxOutputBytes: 1024, wantErr: true, }, { name: "output exceeds maxOutputBytes", tmpl: singlerequesttemplate.DefaultPlanTemplate, raw: validOutput, maxOutputBytes: 10, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := singlerequesttemplate.ParsePlan(tt.tmpl, tt.raw, tt.maxOutputBytes) if (err != nil) != tt.wantErr { t.Errorf("ParsePlan() err = %v, wantErr %v", err, tt.wantErr) } if !tt.wantErr && string(got) != tt.raw { t.Errorf("ParsePlan() got = %q, want %q", string(got), tt.raw) } }) } } func TestRenderPlan(t *testing.T) { fields := singlerequesttemplate.PlanFields{ Goal: " Inspect the target. ", Steps: []string{" Step one. ", "\tStep two. "}, Verification: []string{" Run focused tests. "}, } want := "# Plan\n\n## Goal\nInspect the target.\n\n## Steps\n- [P1] Step one.\n- [P2] Step two.\n\n## Verification\n- Run focused tests.\n" got, err := singlerequesttemplate.RenderPlan(singlerequesttemplate.DefaultPlanTemplate, fields, 1024) if err != nil { t.Fatal(err) } if string(got) != want { t.Fatalf("RenderPlan() = %q, want %q", got, want) } custom := "# Plan\n\nOperator note.\n\n## Goal\n{{goal}}\n\n## Steps\n{{steps}}\n\n## Verification\n{{verification}}\n\nEND\n" wantCustom := "# Plan\n\nOperator note.\n\n## Goal\nInspect the target.\n\n## Steps\n- [P1] Step one.\n- [P2] Step two.\n\n## Verification\n- Run focused tests.\n\nEND\n" got, err = singlerequesttemplate.RenderPlan(custom, fields, 1024) if err != nil { t.Fatal(err) } if string(got) != wantCustom { t.Fatalf("custom RenderPlan() = %q, want %q", got, wantCustom) } } func TestRenderPlanRejectsMalformedFields(t *testing.T) { valid := singlerequesttemplate.PlanFields{Goal: "Fix bug.", Steps: []string{"Step 1", "Step 2"}, Verification: []string{"Verify 1"}} tests := []struct { name string fields singlerequesttemplate.PlanFields max int }{ {"empty-goal", singlerequesttemplate.PlanFields{Steps: valid.Steps, Verification: valid.Verification}, 1024}, {"multiline-goal", singlerequesttemplate.PlanFields{Goal: "Line 1\nLine 2", Steps: valid.Steps, Verification: valid.Verification}, 1024}, {"one-step", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: []string{"Step 1"}, Verification: valid.Verification}, 1024}, {"seven-steps", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: []string{"S1", "S2", "S3", "S4", "S5", "S6", "S7"}, Verification: valid.Verification}, 1024}, {"empty-step", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: []string{"Step 1", ""}, Verification: valid.Verification}, 1024}, {"multiline-step", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: []string{"Step 1\ncontinued", "Step 2"}, Verification: valid.Verification}, 1024}, {"four-verifications", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: valid.Steps, Verification: []string{"V1", "V2", "V3", "V4"}}, 1024}, {"empty-verification", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: valid.Steps, Verification: []string{""}}, 1024}, {"multiline-verification", singlerequesttemplate.PlanFields{Goal: valid.Goal, Steps: valid.Steps, Verification: []string{"Verify 1\ncontinued"}}, 1024}, {"unresolved-token", singlerequesttemplate.PlanFields{Goal: "Fix {{goal}}.", Steps: valid.Steps, Verification: valid.Verification}, 1024}, {"zero-max", valid, 0}, {"output-over-limit", valid, 10}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if _, err := singlerequesttemplate.RenderPlan(singlerequesttemplate.DefaultPlanTemplate, tt.fields, tt.max); err == nil { t.Fatal("RenderPlan() succeeded, want error") } }) } } func TestPlanItemIDs(t *testing.T) { rendered, err := singlerequesttemplate.RenderPlan(singlerequesttemplate.DefaultPlanTemplate, singlerequesttemplate.PlanFields{ Goal: "Goal line.", Steps: []string{"Step one.", "Step two.", "Step three."}, Verification: []string{"Verify."}, }, 4096) if err != nil { t.Fatal(err) } ids, err := singlerequesttemplate.PlanItemIDs(rendered) if err != nil || len(ids) != 3 || ids[0] != "P1" || ids[1] != "P2" || ids[2] != "P3" { t.Fatalf("ids=%v err=%v", ids, err) } tests := []struct { name string plan string wantErr bool }{ {"empty", "", true}, {"no step ids", "# Plan\n\n## Goal\nGoal.\n\n## Steps\n- Step one\n- Step two\n\n## Verification\n- V\n", true}, {"gap p1 then p3", "# Plan\n\n## Goal\nGoal.\n\n## Steps\n- [P1] Step one\n- [P3] Step three\n\n## Verification\n- V\n", true}, {"duplicate p1", "# Plan\n\n## Goal\nGoal.\n\n## Steps\n- [P1] Step one\n- [P1] Step dup\n\n## Verification\n- V\n", true}, {"starts at p0", "# Plan\n\n## Goal\nGoal.\n\n## Steps\n- [P0] Step zero\n- [P1] Step one\n\n## Verification\n- V\n", true}, {"descending order", "# Plan\n\n## Goal\nGoal.\n\n## Steps\n- [P2] Step two\n- [P1] Step one\n\n## Verification\n- V\n", true}, {"valid p1 p2", "# Plan\n\n## Goal\nGoal.\n\n## Steps\n- [P1] Step one\n- [P2] Step two\n\n## Verification\n- V\n", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := singlerequesttemplate.PlanItemIDs([]byte(tt.plan)) if (err != nil) != tt.wantErr { t.Fatalf("err=%v wantErr=%v", err, tt.wantErr) } }) } } func TestValidateReviewTemplate(t *testing.T) { tests := []struct { name string tmpl string wantErr bool }{ { name: "default review handoff template is valid", tmpl: singlerequesttemplate.DefaultReviewTemplate, wantErr: false, }, { name: "empty template", tmpl: "", wantErr: true, }, { name: "oversized template 8193 bytes", tmpl: reviewTemplateOfSize(8193), wantErr: true, }, { name: "exact 8192 bytes accepted", tmpl: reviewTemplateOfSize(8192), wantErr: false, }, { name: "missing {{deviations}}", tmpl: `# Review ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations `, wantErr: true, }, { name: "duplicate {{changes}}", tmpl: `# Review ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "unknown placeholder", tmpl: `# Review ## Worker Item Status {{item_status}} {{severity}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "wrong placeholder order", tmpl: `# Review ## Worker Item Status {{changes}} ## Worker Changes {{item_status}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "missing required heading # Review", tmpl: `## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "decorated heading ### Review", tmpl: `### Review ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "decorated worker heading", tmpl: `# Review ### Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "duplicate worker heading", tmpl: `# Review ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} ## Worker Item Status `, wantErr: true, }, { name: "unbalanced opening delimiter residue", tmpl: `# Review ## Worker Item Status {{item_status}} {{ ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "unbalanced closing delimiter residue", tmpl: `# Review ## Worker Item Status {{item_status}}}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "reviewer-only Result placeholder rejected", tmpl: `# Review ## Result PASS ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} `, wantErr: true, }, { name: "reviewer-only Summary placeholder rejected", tmpl: `# Review ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} ## Summary {{summary}} `, wantErr: true, }, { name: "legacy reviewer grammar fully rejected", tmpl: `# Review ## Result PASS ## Checks {{checks}} ## Verification {{verification}} ## Summary {{summary}} `, wantErr: true, }, { name: "custom handoff template with extra static text", tmpl: `# Review Operator preamble. ## Worker Item Status {{item_status}} ## Worker Changes {{changes}} ## Worker Verification {{verification}} ## Deviations {{deviations}} Operator footer. `, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := singlerequesttemplate.ValidateReviewTemplate(tt.tmpl) if (err != nil) != tt.wantErr { t.Errorf("ValidateReviewTemplate() err = %v, wantErr %v", err, tt.wantErr) } }) } } func TestRenderReview(t *testing.T) { fields := singlerequesttemplate.ReviewFields{ ItemStatus: "- P1: completed\n- P2: completed", Changes: "- Wrote result.txt with the requested value", Verification: "- Ran verify and observed success", Deviations: "None", } got, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatalf("RenderReview() unexpected err = %v", err) } want := `# Review ## Worker Item Status - P1: completed - P2: completed ## Worker Changes - Wrote result.txt with the requested value ## Worker Verification - Ran verify and observed success ## Deviations None ` if string(got) != want { t.Errorf("RenderReview() got:\n%s\nwant:\n%s", string(got), want) } custom := "# Review\n\nOperator preamble.\n\n## Worker Item Status\n{{item_status}}\n\n## Worker Changes\n{{changes}}\n\n## Worker Verification\n{{verification}}\n\n## Deviations\n{{deviations}}\n\nOperator footer.\n" wantCustom := `# Review Operator preamble. ## Worker Item Status - P1: completed - P2: completed ## Worker Changes - Wrote result.txt with the requested value ## Worker Verification - Ran verify and observed success ## Deviations None Operator footer. ` got, err = singlerequesttemplate.RenderReview(custom, fields, 4096) if err != nil { t.Fatalf("custom RenderReview() err=%v", err) } if string(got) != wantCustom { t.Errorf("custom RenderReview() got:\n%s\nwant:\n%s", string(got), wantCustom) } // Missing field test: every worker section must be non-empty. for name, mutated := range map[string]singlerequesttemplate.ReviewFields{ "empty-item-status": {ItemStatus: "", Changes: fields.Changes, Verification: fields.Verification, Deviations: fields.Deviations}, "empty-changes": {ItemStatus: fields.ItemStatus, Changes: "", Verification: fields.Verification, Deviations: fields.Deviations}, "empty-verification": {ItemStatus: fields.ItemStatus, Changes: fields.Changes, Verification: "", Deviations: fields.Deviations}, "empty-deviations": {ItemStatus: fields.ItemStatus, Changes: fields.Changes, Verification: fields.Verification, Deviations: ""}, "whitespace-deviations": {ItemStatus: fields.ItemStatus, Changes: fields.Changes, Verification: fields.Verification, Deviations: " "}, } { t.Run(name, func(t *testing.T) { if _, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, mutated, 4096); err == nil { t.Fatalf("RenderReview() expected error for %s", name) } }) } if _, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 0); err == nil { t.Fatalf("RenderReview() expected error for zero max") } if _, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 10); err == nil { t.Fatalf("RenderReview() expected error for output over limit") } } func TestValidateReviewHandoff(t *testing.T) { planIDs := []string{"P1", "P2"} baseFields := singlerequesttemplate.ReviewFields{ ItemStatus: "- P1: completed\n- P2: completed", Changes: "- Wrote result.txt", Verification: "- Ran verify", Deviations: "None", } valid, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, baseFields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(valid, planIDs); err != nil { t.Fatalf("valid handoff rejected: %v", err) } t.Run("missing or duplicate sections rejected", func(t *testing.T) { missingItemStatus := strings.Replace(string(valid), "## Worker Item Status", "## Renamed", 1) if err := singlerequesttemplate.ValidateReviewHandoff([]byte(missingItemStatus), planIDs); err == nil { t.Fatalf("expected error for missing Worker Item Status heading") } dup := string(valid) + "\n## Worker Item Status\n- P1: completed\n" if err := singlerequesttemplate.ValidateReviewHandoff([]byte(dup), planIDs); err == nil { t.Fatalf("expected error for duplicate Worker Item Status heading") } }) t.Run("empty content rejected", func(t *testing.T) { if err := singlerequesttemplate.ValidateReviewHandoff(nil, planIDs); err == nil { t.Fatalf("expected error for empty content") } }) t.Run("unknown plan id rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\n- P9: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for unknown plan id P9") } }) t.Run("missing plan id rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for missing plan id P2") } }) t.Run("duplicate plan id rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\n- P1: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for duplicate plan id P1") } }) t.Run("non-completed status rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\n- P2: skipped" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for non-completed P2") } }) t.Run("out-of-order plan ids rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P2: completed\n- P1: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for out-of-order plan ids") } }) t.Run("extra status line rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\n- P2: completed\n- P3: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for extra plan id P3") } }) t.Run("empty plan id list rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\n- P2: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, nil); err == nil { t.Fatalf("expected error for empty plan id list against non-empty status") } }) t.Run("prose injected between status lines rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\nThis is a note.\n- P2: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for prose injected between status lines") } }) t.Run("malformed bullet missing completed status rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: incomplete\n- P2: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for malformed bullet missing completed status") } }) t.Run("blank line in status section rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed\n\n- P2: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for blank line in status section") } }) t.Run("out-of-order status lines rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P2: completed\n- P1: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for out-of-order status lines") } }) t.Run("malformed bullet with extra text rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "- P1: completed extra text\n- P2: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for malformed bullet with extra text") } }) t.Run("decorated heading variant in status rejected", func(t *testing.T) { fields := baseFields fields.ItemStatus = "## P1: completed\n- P2: completed" rendered, err := singlerequesttemplate.RenderReview(singlerequesttemplate.DefaultReviewTemplate, fields, 4096) if err != nil { t.Fatal(err) } if err := singlerequesttemplate.ValidateReviewHandoff(rendered, planIDs); err == nil { t.Fatalf("expected error for decorated heading variant in status") } }) } func TestDigest(t *testing.T) { d1 := singlerequesttemplate.Digest(singlerequesttemplate.DefaultPlanTemplate) d2 := singlerequesttemplate.Digest(singlerequesttemplate.DefaultPlanTemplate) d3 := singlerequesttemplate.Digest("other content") if d1 == "" { t.Errorf("Digest() returned empty string") } if d1 != d2 { t.Errorf("Digest() not deterministic: %q != %q", d1, d2) } if d1 == d3 { t.Errorf("Digest() collision: %q == %q", d1, d3) } }