package ai import ( "context" "errors" "strings" "testing" ) func TestServiceNudgeSuccess(t *testing.T) { fb := &fakeBackend{out: `{"on_track": false, "message": "reading news, not the auth flow"}`} advisory, err := NewService(fb).Nudge(context.Background(), "fix auth", []string{"Firefox - Hacker News"}) if err != nil { t.Fatalf("nudge: %v", err) } if advisory == "" { t.Fatal("expected non-empty advisory") } if !strings.Contains(fb.gotPrompt, "fix auth") { t.Fatalf("prompt should embed the commitment, got: %s", fb.gotPrompt) } } func TestServiceNudgeBackendError(t *testing.T) { fb := &fakeBackend{err: errors.New("boom")} if _, err := NewService(fb).Nudge(context.Background(), "fix auth", []string{"Firefox - Hacker News"}); err == nil { t.Fatal("want backend error") } } func TestParseNudge(t *testing.T) { tests := []struct { name string in string want string wantErr error }{ {"on track yields empty", `{"on_track": true, "message": ""}`, "", nil}, {"concern returns message", `{"on_track": false, "message": "editing unrelated CSS, not the auth flow"}`, "editing unrelated CSS, not the auth flow", nil}, {"concern is trimmed", `{"on_track": false, "message": " drifted "}`, "drifted", nil}, {"concern without message degrades to silence", `{"on_track": false, "message": ""}`, "", nil}, {"json embedded in prose", `sure thing: {"on_track": false, "message": "wrong project"} done`, "wrong project", nil}, {"empty response", " ", "", ErrEmptyResponse}, {"no json", "no braces here", "", ErrNoJSON}, {"malformed json", `{"on_track": false, "message":`, "", ErrInvalidNudge}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseNudge(tt.in) if tt.wantErr != nil { if !errors.Is(err, tt.wantErr) { t.Fatalf("err = %v, want %v", err, tt.wantErr) } return } if err != nil { t.Fatalf("unexpected err: %v", err) } if got != tt.want { t.Fatalf("got %q, want %q", got, tt.want) } }) } }