package ai import ( "context" "errors" "strings" "testing" ) type fakeBackend struct { out string err error gotPrompt string } func (f *fakeBackend) Run(ctx context.Context, prompt string) (string, error) { f.gotPrompt = prompt return f.out, f.err } func (f *fakeBackend) Name() string { return "fake" } func TestServiceCoachSuccess(t *testing.T) { fb := &fakeBackend{out: `here you go {"next_action":"Write tests","success_condition":"green","timebox_minutes":30}`} svc := NewService(fb) p, err := svc.Coach(context.Background(), "write the tests", "") if err != nil { t.Fatalf("coach: %v", err) } if p.NextAction != "Write tests" || p.TimeboxSecs != 1800 { t.Fatalf("bad proposal: %+v", p) } if !strings.Contains(fb.gotPrompt, "write the tests") { t.Fatalf("prompt should embed the intent, got: %s", fb.gotPrompt) } } func TestServiceCoachBackendError(t *testing.T) { fb := &fakeBackend{err: errors.New("boom")} if _, err := NewService(fb).Coach(context.Background(), "x", ""); err == nil { t.Fatal("want backend error") } } func TestServiceCoachUnparseable(t *testing.T) { fb := &fakeBackend{out: "I cannot help with that."} if _, err := NewService(fb).Coach(context.Background(), "x", ""); !errors.Is(err, ErrNoJSON) { t.Fatalf("want ErrNoJSON, got %v", err) } } func TestCoachPromptIncludesGrounding(t *testing.T) { fb := &fakeBackend{out: `{"next_action":"a","success_condition":"b","timebox_minutes":20}`} if _, err := NewService(fb).Coach(context.Background(), "ship it", "I value small diffs."); err != nil { t.Fatalf("coach: %v", err) } if !strings.Contains(fb.gotPrompt, "About the user") || !strings.Contains(fb.gotPrompt, "small diffs") { t.Fatalf("prompt missing grounding block: %s", fb.gotPrompt) } } func TestCoachEmptyGroundingUnchanged(t *testing.T) { withFb := &fakeBackend{out: `{"next_action":"a","success_condition":"b","timebox_minutes":20}`} _, _ = NewService(withFb).Coach(context.Background(), "ship it", "") // The empty-grounding prompt must equal buildPrompt(intent) of the old shape: // it must NOT contain the grounding header. if strings.Contains(withFb.gotPrompt, "About the user") { t.Fatalf("empty grounding leaked a header: %s", withFb.gotPrompt) } }