diff --git a/internal/adapters/llm/status_error_test.go b/internal/adapters/llm/status_error_test.go new file mode 100644 index 0000000..8e9b68c --- /dev/null +++ b/internal/adapters/llm/status_error_test.go @@ -0,0 +1,63 @@ +package llm + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "git.d-ma.be/mathias/tapir/internal/errkind" +) + +// A non-200 from the gateway is an HTTP status error: its kind can be logged and stored +// without its body, which can echo the prompt (adversarial review F3). + +func failingGateway(t *testing.T, status int, body string) *httptest.Server { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(status) + _, _ = w.Write([]byte(body)) + })) + t.Cleanup(srv.Close) + return srv +} + +func TestClient_ANon200IsAnHTTPStatusErrorWhoseKindCarriesNoBody(t *testing.T) { + srv := failingGateway(t, http.StatusBadRequest, `{"error":"prompt was: Anders ansvarar för virket"}`) + + _, err := New(srv.URL, "", "m", 5*time.Second).Complete(context.Background(), "s", "u") + + if err == nil { + t.Fatal("expected an error") + } + if got := errkind.Of(err); got != "http_400" { + t.Errorf("errkind.Of = %q, want http_400", got) + } + var he *HTTPError + if !errors.As(err, &he) || he.Code != http.StatusBadRequest { + t.Errorf("error is not an *HTTPError with the status code: %v", err) + } +} + +func TestClient_TheErrorTextIsUnchangedForCallersThatLogIt(t *testing.T) { + srv := failingGateway(t, http.StatusBadGateway, "upstream down") + + _, err := New(srv.URL, "", "m", 5*time.Second).Complete(context.Background(), "s", "u") + + if err == nil || err.Error() != "LLM returned 502: upstream down" { + t.Errorf("error text = %v: callers that log it already rely on this form", err) + } +} + +func TestClient_AnEnormousErrorBodyIsBoundedInTheErrorText(t *testing.T) { + srv := failingGateway(t, http.StatusInternalServerError, strings.Repeat("x", 100_000)) + + _, err := New(srv.URL, "", "m", 5*time.Second).Complete(context.Background(), "s", "u") + + if err == nil || len(err.Error()) > 1024 { + t.Errorf("error text is %d bytes: a gateway's whole body must not become an error message", len(err.Error())) + } +}