Files
descrybe/apps/api/internal/httpapi/admin_ai_calls_handlers_test.go
T

93 lines
3.0 KiB
Go
Raw Normal View History

2026-08-23 22:03:57 +02:00
package httpapi
import (
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
func readSourceFile(t *testing.T, name string) string {
t.Helper()
b, err := os.ReadFile(name)
if err != nil {
t.Fatalf("read %s: %v", name, err)
}
return string(b)
}
// Filter parsing must reject bad input before touching the database, so a typo in
// the admin UI cannot turn into a full-table scan or a 500.
func TestHandleAdminListAICalls_rejectsBadFilters(t *testing.T) {
t.Parallel()
s := &Server{}
cases := map[string]string{
"invalid company_id": "?company_id=not-a-uuid",
"invalid user_id": "?user_id=123",
"invalid job_id": "?job_id=abc",
"invalid raw_product_id": "?raw_product_id=xyz",
"invalid outcome": "?outcome=maybe",
"invalid before cursor": "?before=yesterday",
}
for name, query := range cases {
t.Run(name, func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/admin/ai-calls"+query, nil)
rec := httptest.NewRecorder()
s.handleAdminListAICalls(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d want 400 body=%s", rec.Code, rec.Body.String())
}
})
}
}
func TestHandleAdminGetAICall_rejectsBadID(t *testing.T) {
t.Parallel()
s := &Server{}
req := httptest.NewRequest(http.MethodGet, "/api/admin/ai-calls/nope", nil)
rec := httptest.NewRecorder()
s.handleAdminGetAICall(rec, req)
if rec.Code != http.StatusBadRequest {
t.Fatalf("status=%d want 400 body=%s", rec.Code, rec.Body.String())
}
}
// The list endpoint returns previews, never the full bodies — one unfiltered
// request must not stream a tenant's whole prompt corpus out of the admin API.
func TestAdminAICallsListSQL_selectsPreviewsNotFullBodies(t *testing.T) {
t.Parallel()
sql := adminAICallsListSQL("TRUE", 1)
selectList := sql[strings.Index(sql, "SELECT"):strings.Index(sql, "FROM ai_call_logs")]
// A bare column followed by a comma is the whole body; the length()/left()
// wrappers put a ")" in between.
for _, bare := range []string{" l.system_prompt,", " l.user_prompt,", " l.response_text,"} {
if strings.Contains(selectList, bare) {
t.Fatalf("list selects a full body (%q):\n%s", strings.TrimSpace(bare), selectList)
}
}
for _, want := range []string{
"left(l.user_prompt, 240)",
"left(l.response_text, 240)",
"length(l.system_prompt)",
} {
if !strings.Contains(selectList, want) {
t.Fatalf("list should select %s:\n%s", want, selectList)
}
}
if !strings.Contains(sql, "ORDER BY l.created_at DESC") || !strings.Contains(sql, "LIMIT $1") {
t.Fatalf("list must page newest-first with a bound limit:\n%s", sql)
}
}
// The detail endpoint is the only place full bodies come from.
func TestAdminAICallsDetail_returnsFullBodies(t *testing.T) {
t.Parallel()
src := readSourceFile(t, "admin_ai_calls_handlers.go")
detail := src[strings.Index(src, "func (s *Server) handleAdminGetAICall"):]
if !strings.Contains(detail, "l.system_prompt, l.user_prompt, l.response_text") {
t.Fatal("detail handler must select the full bodies")
}
}