package api import ( "encoding/json" "io" "net/http" "net/http/httptest" "strings" "testing" "forge.secuaas.ovh/olivier/claude-failover/internal/delegation" "forge.secuaas.ovh/olivier/claude-failover/internal/state" ) // fakeDelegation implements DelegationProvider for tests. type fakeDelegation struct { active []delegation.ActiveJob snapshot delegation.Snapshot } func (f *fakeDelegation) Active() []delegation.ActiveJob { return f.active } func (f *fakeDelegation) CountersSnapshot() delegation.Snapshot { return f.snapshot } func newTestServer(t *testing.T, deleg DelegationProvider) *httptest.Server { t.Helper() s := New("ignored", state.New("")) if deleg != nil { s.WithDelegation(deleg) } mux := http.NewServeMux() mux.HandleFunc("/health", s.handleHealth) mux.HandleFunc("/status", s.handleStatus) mux.HandleFunc("/watchdog/status", s.handleWatchdogStatus) mux.HandleFunc("/api/delegated/status", s.handleDelegatedStatus) return httptest.NewServer(mux) } func TestHandleHealth(t *testing.T) { srv := newTestServer(t, nil) defer srv.Close() resp, err := http.Get(srv.URL + "/health") if err != nil { t.Fatal(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if !strings.Contains(string(body), `"status":"ok"`) { t.Errorf("unexpected /health body: %s", body) } } func TestHandleDelegatedStatus_Disabled(t *testing.T) { srv := newTestServer(t, nil) defer srv.Close() resp, err := http.Get(srv.URL + "/api/delegated/status") if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Errorf("expected 404 when delegation disabled, got %d", resp.StatusCode) } } func TestHandleDelegatedStatus_Enabled(t *testing.T) { deleg := &fakeDelegation{ active: []delegation.ActiveJob{ {JobID: "j1", Project: "/p/a", TaskFile: "task-1.md", Provider: "gpu", Duration: "12s"}, }, snapshot: delegation.Snapshot{Active: 1, CompletedTotal: 5, FailedTotal: 1}, } srv := newTestServer(t, deleg) defer srv.Close() resp, err := http.Get(srv.URL + "/api/delegated/status") if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected 200, got %d", resp.StatusCode) } var out struct { Active []delegation.ActiveJob `json:"active"` Counters delegation.Snapshot `json:"counters"` } if err := json.NewDecoder(resp.Body).Decode(&out); err != nil { t.Fatal(err) } if len(out.Active) != 1 || out.Active[0].JobID != "j1" { t.Errorf("active mismatch: %+v", out.Active) } if out.Counters.CompletedTotal != 5 || out.Counters.Active != 1 { t.Errorf("counters mismatch: %+v", out.Counters) } } func TestHandleWatchdogStatus_IncludesDelegationCounters(t *testing.T) { deleg := &fakeDelegation{snapshot: delegation.Snapshot{ Active: 2, CompletedTotal: 7, FailedTotal: 3, }} srv := newTestServer(t, deleg) defer srv.Close() resp, err := http.Get(srv.URL + "/watchdog/status") if err != nil { t.Fatal(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) for _, want := range []string{`"delegated_active":2`, `"delegated_completed_total":7`, `"delegated_failed_total":3`} { if !strings.Contains(string(body), want) { t.Errorf("missing %s in body: %s", want, body) } } }