package main import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "path/filepath" "testing" ) func testServer(t *testing.T, token string) *httptest.Server { t.Helper() store, err := Open(filepath.Join(t.TempDir(), "gadfly-reports.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { store.Close() }) srv := httptest.NewServer(newServer(store, token)) t.Cleanup(srv.Close) return srv } func post(t *testing.T, srv *httptest.Server, token, path string, body any) *http.Response { t.Helper() b, _ := json.Marshal(body) req, _ := http.NewRequest("POST", srv.URL+path, bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("POST %s: %v", path, err) } return resp } // TestServerEndToEnd: run -> reports -> grade -> scoreboard over HTTP. func TestServerEndToEnd(t *testing.T) { srv := testServer(t, "") if resp := post(t, srv, "", "/runs", Run{RunID: "r1", Repo: "r", PR: 1, Model: "m", Provider: "p", DurationSecs: 120}); resp.StatusCode != 200 { t.Fatalf("POST /runs = %d", resp.StatusCode) } resp := post(t, srv, "", "/reports", []ReportIn{ {Repo: "r", PR: 1, Lens: "security", File: "a.go", Line: 7, Title: "leak", Model: "m", Provider: "p", RunID: "r1"}, }) if resp.StatusCode != 200 { t.Fatalf("POST /reports = %d", resp.StatusCode) } var rep struct { FindingIDs []string `json:"finding_ids"` } json.NewDecoder(resp.Body).Decode(&rep) if len(rep.FindingIDs) != 1 { t.Fatalf("want 1 finding id, got %v", rep.FindingIDs) } id := rep.FindingIDs[0] if resp := post(t, srv, "", "/findings/"+id+"/grade", Grade{IsReal: true, Severity: "medium", Grader: "claude"}); resp.StatusCode != 200 { t.Fatalf("POST grade = %d", resp.StatusCode) } resp = mustGet(t, srv, "", "/scoreboard") var board []ModelStat json.NewDecoder(resp.Body).Decode(&board) if len(board) != 1 || board[0].Confirmed != 1 || board[0].BySeverity["medium"] != 1 || board[0].Minutes != 2 { t.Fatalf("unexpected scoreboard: %+v", board) } } // TestServerAuth: a set token gates writes but leaves /healthz open. func TestServerAuth(t *testing.T) { srv := testServer(t, "secret") if resp := post(t, srv, "", "/runs", Run{RunID: "r1", Model: "m"}); resp.StatusCode != http.StatusUnauthorized { t.Errorf("unauthenticated POST = %d, want 401", resp.StatusCode) } if resp := post(t, srv, "secret", "/runs", Run{RunID: "r1", Model: "m"}); resp.StatusCode != 200 { t.Errorf("authenticated POST = %d, want 200", resp.StatusCode) } if resp := mustGet(t, srv, "", "/healthz"); resp.StatusCode != 200 { t.Errorf("healthz should be open, got %d", resp.StatusCode) } } func mustGet(t *testing.T, srv *httptest.Server, token, path string) *http.Response { t.Helper() req, _ := http.NewRequest("GET", srv.URL+path, nil) if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("GET %s: %v", path, err) } return resp }