diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/api/elaborate.go | 15 | ||||
| -rw-r--r-- | internal/api/elaborate_test.go | 44 |
2 files changed, 55 insertions, 4 deletions
diff --git a/internal/api/elaborate.go b/internal/api/elaborate.go index dd51c7d..37380c2 100644 --- a/internal/api/elaborate.go +++ b/internal/api/elaborate.go @@ -287,11 +287,18 @@ type elaboratedStoryTask struct { Subtasks []elaboratedStorySubtask `json:"subtasks"` } +// AcceptanceCriterion is a single verifiable condition in a story validation. +type AcceptanceCriterion struct { + Name string `json:"name"` + Verification string `json:"verification"` + TestRef string `json:"test_ref,omitempty"` +} + // elaboratedStoryValidation describes how to verify the story was successful. type elaboratedStoryValidation struct { - Type string `json:"type"` - Steps []string `json:"steps"` - SuccessCriteria string `json:"success_criteria"` + Type string `json:"type"` + AcceptanceCriteria []AcceptanceCriterion `json:"acceptance_criteria"` + SuccessCriteria string `json:"success_criteria"` } // elaboratedStory is the full implementation plan produced by story elaboration. @@ -320,7 +327,7 @@ Output ONLY valid JSON matching this schema: ], "validation": { "type": "build|test|smoke", - "steps": ["step1", "step2"], + "acceptance_criteria": [{"name": "...", "verification": "...", "test_ref": "optional"}], "success_criteria": "what success looks like" } } diff --git a/internal/api/elaborate_test.go b/internal/api/elaborate_test.go index 32cec3c..34269e9 100644 --- a/internal/api/elaborate_test.go +++ b/internal/api/elaborate_test.go @@ -477,6 +477,50 @@ func TestElaborateTask_NoRawNarrativeWithoutExplicitProjectDir(t *testing.T) { } } +func TestElaboratedStoryValidation_AcceptanceCriteriaSchema(t *testing.T) { + raw := `{ + "type": "test", + "acceptance_criteria": [ + {"name": "API returns 200", "verification": "curl /health returns 200", "test_ref": "TestHealthCheck"}, + {"name": "DB migrates cleanly", "verification": "migration runs without error"} + ], + "success_criteria": "all checks pass" + }` + + var v elaboratedStoryValidation + if err := json.Unmarshal([]byte(raw), &v); err != nil { + t.Fatalf("unmarshal failed: %v", err) + } + + if len(v.AcceptanceCriteria) != 2 { + t.Fatalf("expected 2 acceptance_criteria, got %d", len(v.AcceptanceCriteria)) + } + + ac0 := v.AcceptanceCriteria[0] + if ac0.Name != "API returns 200" { + t.Errorf("ac[0].name: want %q, got %q", "API returns 200", ac0.Name) + } + if ac0.Verification != "curl /health returns 200" { + t.Errorf("ac[0].verification: want %q, got %q", "curl /health returns 200", ac0.Verification) + } + if ac0.TestRef != "TestHealthCheck" { + t.Errorf("ac[0].test_ref: want %q, got %q", "TestHealthCheck", ac0.TestRef) + } + + ac1 := v.AcceptanceCriteria[1] + if ac1.Name != "DB migrates cleanly" { + t.Errorf("ac[1].name: want %q, got %q", "DB migrates cleanly", ac1.Name) + } + // test_ref omitted — backward compat: must be empty string, not an error + if ac1.TestRef != "" { + t.Errorf("ac[1].test_ref: want empty (omitempty), got %q", ac1.TestRef) + } + + if v.SuccessCriteria != "all checks pass" { + t.Errorf("success_criteria: want %q, got %q", "all checks pass", v.SuccessCriteria) + } +} + func TestElaborateTask_AppendsRawNarrative(t *testing.T) { srv, _ := testServer(t) |
