M2: ai Proposal type, JSON extraction and validation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestExtractJSON(t *testing.T) {
|
||||
cases := []struct {
|
||||
name, in, want string
|
||||
wantErr bool
|
||||
}{
|
||||
{"bare", `{"a":1}`, `{"a":1}`, false},
|
||||
{"prose around", "sure, here:\n{\"a\":1}\nhope that helps", `{"a":1}`, false},
|
||||
{"code fence", "```json\n{\"a\":1}\n```", `{"a":1}`, false},
|
||||
{"nested", `{"a":{"b":2},"c":3}`, `{"a":{"b":2},"c":3}`, false},
|
||||
{"brace in string", `{"a":"}{"}`, `{"a":"}{"}`, false},
|
||||
{"first of two", `{"a":1} then {"b":2}`, `{"a":1}`, false},
|
||||
{"none", `no json here`, "", true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := extractJSON(tc.in)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("want error, got %q", got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Fatalf("got %q want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseProposal(t *testing.T) {
|
||||
good := `{"next_action":"Draft summary","success_condition":"3 paragraphs written","timebox_minutes":25}`
|
||||
p, err := parseProposal(good)
|
||||
if err != nil {
|
||||
t.Fatalf("good parse: %v", err)
|
||||
}
|
||||
if p.NextAction != "Draft summary" || p.SuccessCondition != "3 paragraphs written" {
|
||||
t.Fatalf("fields wrong: %+v", p)
|
||||
}
|
||||
if p.TimeboxSecs != 25*60 {
|
||||
t.Fatalf("timebox secs got %d want %d", p.TimeboxSecs, 25*60)
|
||||
}
|
||||
|
||||
if _, err := parseProposal(""); !errors.Is(err, ErrEmptyResponse) {
|
||||
t.Fatalf("empty: want ErrEmptyResponse, got %v", err)
|
||||
}
|
||||
if _, err := parseProposal("no json"); !errors.Is(err, ErrNoJSON) {
|
||||
t.Fatalf("no json: want ErrNoJSON, got %v", err)
|
||||
}
|
||||
bad := []string{
|
||||
`{"next_action":"","success_condition":"x","timebox_minutes":25}`,
|
||||
`{"next_action":"x","success_condition":" ","timebox_minutes":25}`,
|
||||
`{"next_action":"x","success_condition":"y","timebox_minutes":0}`,
|
||||
`{"next_action":"x","success_condition":"y","timebox_minutes":-5}`,
|
||||
`{"next_action":"x"`,
|
||||
}
|
||||
for _, in := range bad {
|
||||
if _, err := parseProposal(in); !errors.Is(err, ErrInvalidProposal) {
|
||||
t.Fatalf("bad %q: want ErrInvalidProposal, got %v", in, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user