package provider import ( "encoding/json" "orchestra/internal/domain" "strings" "testing" ) type sink struct{ events []domain.Event } func (s *sink) Append(e domain.Event) error { s.events = append(s.events, e); return nil } func TestJSONLIngest(t *testing.T) { s := &sink{} n, err := (JSONL{}).Ingest(strings.NewReader("{\"source\":\"local\",\"external_id\":\"1\",\"project\":\"demo\"}\n"), s) if err != nil || n != 1 || len(s.events) != 1 { t.Fatalf("n=%d events=%d err=%v", n, len(s.events), err) } } func TestJSONLRejectsMalformedLine(t *testing.T) { n, err := (JSONL{}).Ingest(strings.NewReader("not-json\n"), &sink{}) if err == nil || n != 0 { t.Fatalf("n=%d err=%v", n, err) } } // A Gitea issue states its own acceptance under one recognized heading. Before // this, every Gitea-sourced task rendered "Acceptance: Not stated." no matter // what the body said, because nothing on that path ever set the field. func TestGiteaIssueAcceptanceSection(t *testing.T) { body := "improve ambiguous attribution.\n\n## acceptance\n- labelled score does not regress\n- targeted cases improve\n" e := Gitea{}.event(giteaIssue{Number: 7, Title: "fix attribution", Body: body}, "gitea", "p") var p struct { Description string `json:"description"` Acceptance []string `json:"acceptance"` } if err := json.Unmarshal(e.Payload, &p); err != nil { t.Fatal(err) } if p.Description != "improve ambiguous attribution." { t.Fatalf("description=%q, want the prose without the acceptance section", p.Description) } want := []string{"labelled score does not regress", "targeted cases improve"} if len(p.Acceptance) != len(want) { t.Fatalf("acceptance=%q, want %q", p.Acceptance, want) } for i := range want { if p.Acceptance[i] != want[i] { t.Fatalf("acceptance[%d]=%q, want %q (order is preserved)", i, p.Acceptance[i], want[i]) } } } // A body with no recognized section yields no acceptance, and that is correct // rather than an ingestion failure: the task renders "Not stated." and the // frame phase resolves it through the decision-request path. Inferring // criteria from arbitrary prose would invent requirements that outrank every // human decision beneath them. func TestGiteaIssueWithoutAcceptanceSectionStaysEmpty(t *testing.T) { body := "just do the thing.\n\n## notes\n- not an acceptance criterion\n" e := Gitea{}.event(giteaIssue{Number: 8, Title: "t", Body: body}, "gitea", "p") var p map[string]any if err := json.Unmarshal(e.Payload, &p); err != nil { t.Fatal(err) } if _, ok := p["acceptance"]; ok { t.Fatalf("acceptance=%v, want none", p["acceptance"]) } if p["description"] != body { t.Fatalf("description=%q, want the body unchanged", p["description"]) } } func TestSplitAcceptanceConventions(t *testing.T) { for _, tc := range []struct { name string body string desc string want []string }{ {"checkboxes", "do it.\n\n## Acceptance\n- [ ] thing a works\n- [x] behaviour b unchanged\n", "do it.", []string{"thing a works", "behaviour b unchanged"}}, {"acceptance criteria spelling", "do it.\n\n### Acceptance Criteria\n* one\n", "do it.", []string{"one"}}, {"section ends at the next heading", "do it.\n\n## acceptance\n- one\n\n## notes\n- not acceptance\n", "do it.\n\n## notes\n- not acceptance", []string{"one"}}, {"empty items ignored", "do it.\n\n## acceptance\n-\n- [ ]\n- real\n", "do it.", []string{"real"}}, {"prose inside the section is not an item", "do it.\n\n## acceptance\nthese are the criteria:\n- one\n", "do it.", []string{"one"}}, {"heading only", "do it.\n\n## acceptance\n", "do it.", nil}, } { t.Run(tc.name, func(t *testing.T) { desc, got := splitAcceptance(tc.body) if desc != tc.desc { t.Fatalf("description=%q, want %q", desc, tc.desc) } if len(got) != len(tc.want) { t.Fatalf("acceptance=%q, want %q", got, tc.want) } for i := range tc.want { if got[i] != tc.want[i] { t.Fatalf("acceptance[%d]=%q, want %q", i, got[i], tc.want[i]) } } }) } }