package api import ( "io" "log/slog" "net/http" "net/http/httptest" "testing" "github.com/iliaivanov/spec-kit-remote/cmd/dev-pod-api/internal/model" ) func newTestRouter() (*mockKeyValidator, http.Handler) { kv := newMockValidator() logger := slog.New(slog.NewTextHandler(io.Discard, nil)) srv := &Server{ Store: kv, K8s: newMockPodManager(), Users: newMockUserStore(), Usage: newMockUsageStore(), Logger: logger, GenerateKey: mockGenerateKey, } return kv, NewRouter(srv) } func TestHealthz(t *testing.T) { _, router := newTestRouter() req := httptest.NewRequest(http.MethodGet, "/healthz", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rr.Code) } if rr.Body.String() != "ok" { t.Fatalf("expected body 'ok', got %q", rr.Body.String()) } } func TestHealthz_NoAuthRequired(t *testing.T) { _, router := newTestRouter() // No Authorization header — should still work req := httptest.NewRequest(http.MethodGet, "/healthz", nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("expected 200 without auth, got %d", rr.Code) } } func TestAPIRoutes_RequireAuth(t *testing.T) { _, router := newTestRouter() routes := []struct { method string path string }{ {http.MethodGet, "/api/v1/pods"}, {http.MethodPost, "/api/v1/pods"}, {http.MethodGet, "/api/v1/pods/alice"}, {http.MethodDelete, "/api/v1/pods/alice"}, {http.MethodDelete, "/api/v1/pods/alice/main"}, {http.MethodPatch, "/api/v1/pods/alice/main"}, {http.MethodGet, "/api/v1/users"}, {http.MethodPost, "/api/v1/users"}, {http.MethodGet, "/api/v1/users/alice"}, {http.MethodDelete, "/api/v1/users/alice"}, {http.MethodPatch, "/api/v1/users/alice/quotas"}, {http.MethodGet, "/api/v1/users/alice/usage"}, {http.MethodGet, "/api/v1/billing/summary"}, {http.MethodGet, "/api/v1/billing/alice/history"}, {http.MethodGet, "/api/v1/cluster/status"}, {http.MethodGet, "/api/v1/cache/stats"}, } for _, rt := range routes { t.Run(rt.method+" "+rt.path, func(t *testing.T) { req := httptest.NewRequest(rt.method, rt.path, nil) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rr.Code) } }) } } func TestAPIRoutes_ClusterStatus_RequiresAdmin(t *testing.T) { kv, router := newTestRouter() user := &model.User{ID: "alice", Quota: model.DefaultQuota()} kv.addKey("dpk_test123", user, model.RoleUser) // Non-admin user should get 403 req := httptest.NewRequest(http.MethodGet, "/api/v1/cluster/status", nil) req.Header.Set("Authorization", "Bearer dpk_test123") rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusForbidden { t.Fatalf("expected 403, got %d", rr.Code) } } func TestAPIRoutes_NotFound(t *testing.T) { kv, router := newTestRouter() user := &model.User{ID: "alice", Quota: model.DefaultQuota()} kv.addKey("dpk_test123", user, model.RoleUser) req := httptest.NewRequest(http.MethodGet, "/api/v1/nonexistent", nil) req.Header.Set("Authorization", "Bearer dpk_test123") rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusNotFound { t.Fatalf("expected 404, got %d", rr.Code) } }