package auth import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "testing" authsvc "github.com/DeBrosOfficial/network/pkg/gateway/auth" "github.com/DeBrosOfficial/network/pkg/logging" "go.uber.org/zap" ) // --------------------------------------------------------------------------- // Mock implementations // --------------------------------------------------------------------------- // mockDatabaseClient implements DatabaseClient with configurable query results. type mockDatabaseClient struct { queryResult *QueryResult queryErr error } func (m *mockDatabaseClient) Query(_ context.Context, _ string, _ ...interface{}) (*QueryResult, error) { return m.queryResult, m.queryErr } // mockNetworkClient implements NetworkClient and returns a mockDatabaseClient. type mockNetworkClient struct { db *mockDatabaseClient } func (m *mockNetworkClient) Database() DatabaseClient { return m.db } // mockClusterProvisioner implements ClusterProvisioner as a no-op. type mockClusterProvisioner struct{} func (m *mockClusterProvisioner) CheckNamespaceCluster(_ context.Context, _ string) (string, string, bool, error) { return "", "", false, nil } func (m *mockClusterProvisioner) ProvisionNamespaceCluster(_ context.Context, _ int, _, _ string) (string, string, error) { return "", "", nil } func (m *mockClusterProvisioner) GetClusterStatusByID(_ context.Context, _ string) (interface{}, error) { return nil, nil } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- // testLogger returns a silent *logging.ColoredLogger suitable for tests. func testLogger() *logging.ColoredLogger { nop := zap.NewNop() return &logging.ColoredLogger{Logger: nop} } // noopInternalAuth is a no-op internal auth context function. func noopInternalAuth(ctx context.Context) context.Context { return ctx } // decodeBody is a test helper that decodes a JSON response body into a map. func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]interface{} { t.Helper() var m map[string]interface{} if err := json.NewDecoder(rec.Body).Decode(&m); err != nil { t.Fatalf("failed to decode response body: %v", err) } return m } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- func TestNewHandlers(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) if h == nil { t.Fatal("NewHandlers returned nil") } } func TestSetClusterProvisioner(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) // Should not panic. h.SetClusterProvisioner(&mockClusterProvisioner{}) } // --- ChallengeHandler tests ----------------------------------------------- func TestChallengeHandler_MissingWallet(t *testing.T) { // authService is nil, but the handler checks it first and returns 503. // To reach the wallet validation we need a non-nil authService. // Since authsvc.Service is a concrete struct, we create a zero-value one // (it will never be reached for this test path). // However, the handler checks `h.authService == nil` before everything else. // So we must supply a non-nil *authsvc.Service. We can create one with // an empty signing key (NewService returns error for empty PEM only if // the PEM is non-empty but unparseable). An empty PEM is fine. svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) body, _ := json.Marshal(ChallengeRequest{Wallet: ""}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/challenge", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.ChallengeHandler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "wallet is required" { t.Fatalf("expected error 'wallet is required', got %v", m["error"]) } } func TestChallengeHandler_InvalidMethod(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/challenge", nil) rec := httptest.NewRecorder() h.ChallengeHandler(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "method not allowed" { t.Fatalf("expected error 'method not allowed', got %v", m["error"]) } } func TestChallengeHandler_NilAuthService(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) body, _ := json.Marshal(ChallengeRequest{Wallet: "0xABC"}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/challenge", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.ChallengeHandler(rec, req) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("expected status %d, got %d", http.StatusServiceUnavailable, rec.Code) } } // --- WhoamiHandler tests -------------------------------------------------- func TestWhoamiHandler_NoAuth(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/whoami", nil) rec := httptest.NewRecorder() h.WhoamiHandler(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code) } m := decodeBody(t, rec) // When no auth context is set, "authenticated" should be false. if auth, ok := m["authenticated"].(bool); !ok || auth { t.Fatalf("expected authenticated=false, got %v", m["authenticated"]) } if method, ok := m["method"].(string); !ok || method != "api_key" { t.Fatalf("expected method='api_key', got %v", m["method"]) } if ns, ok := m["namespace"].(string); !ok || ns != "default" { t.Fatalf("expected namespace='default', got %v", m["namespace"]) } } func TestWhoamiHandler_WithAPIKey(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/whoami", nil) ctx := req.Context() ctx = context.WithValue(ctx, CtxKeyAPIKey, "ak_test123:default") ctx = context.WithValue(ctx, CtxKeyNamespaceOverride, "default") req = req.WithContext(ctx) rec := httptest.NewRecorder() h.WhoamiHandler(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code) } m := decodeBody(t, rec) if auth, ok := m["authenticated"].(bool); !ok || !auth { t.Fatalf("expected authenticated=true, got %v", m["authenticated"]) } if method, ok := m["method"].(string); !ok || method != "api_key" { t.Fatalf("expected method='api_key', got %v", m["method"]) } if key, ok := m["api_key"].(string); !ok || key != "ak_test123:default" { t.Fatalf("expected api_key='ak_test123:default', got %v", m["api_key"]) } if ns, ok := m["namespace"].(string); !ok || ns != "default" { t.Fatalf("expected namespace='default', got %v", m["namespace"]) } } func TestWhoamiHandler_WithJWT(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) claims := &authsvc.JWTClaims{ Iss: "orama-gateway", Sub: "0xWALLET", Aud: "gateway", Iat: 1000, Nbf: 1000, Exp: 9999, Namespace: "myns", } req := httptest.NewRequest(http.MethodGet, "/v1/auth/whoami", nil) ctx := context.WithValue(req.Context(), CtxKeyJWT, claims) ctx = context.WithValue(ctx, CtxKeyNamespaceOverride, "myns") req = req.WithContext(ctx) rec := httptest.NewRecorder() h.WhoamiHandler(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code) } m := decodeBody(t, rec) if auth, ok := m["authenticated"].(bool); !ok || !auth { t.Fatalf("expected authenticated=true, got %v", m["authenticated"]) } if method, ok := m["method"].(string); !ok || method != "jwt" { t.Fatalf("expected method='jwt', got %v", m["method"]) } if sub, ok := m["subject"].(string); !ok || sub != "0xWALLET" { t.Fatalf("expected subject='0xWALLET', got %v", m["subject"]) } if ns, ok := m["namespace"].(string); !ok || ns != "myns" { t.Fatalf("expected namespace='myns', got %v", m["namespace"]) } } // --- LogoutHandler tests -------------------------------------------------- func TestLogoutHandler_MissingRefreshToken(t *testing.T) { // The LogoutHandler does NOT validate refresh_token as required the same // way RefreshHandler does. Looking at the source, it checks: // if req.All && no JWT subject -> 401 // then passes req.RefreshToken to authService.RevokeToken // With All=false and empty RefreshToken, RevokeToken returns "nothing to revoke". // But before that, authService == nil returns 503. // // To test the validation path, we need authService != nil, and All=false // with empty RefreshToken. The handler will call authService.RevokeToken // which returns an error because we have a real service but no DB. // However, the key point is that the handler itself doesn't short-circuit // on empty token -- that's left to RevokeToken. So we must accept whatever // error code the handler returns via the authService error path. // // Since we can't easily mock authService (it's a concrete struct), // we test with nil authService to verify the 503 early return. h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) body, _ := json.Marshal(LogoutRequest{RefreshToken: ""}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/logout", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.LogoutHandler(rec, req) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("expected status %d, got %d", http.StatusServiceUnavailable, rec.Code) } } func TestLogoutHandler_InvalidMethod(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/logout", nil) rec := httptest.NewRecorder() h.LogoutHandler(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code) } } func TestLogoutHandler_AllTrueNoJWT(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) body, _ := json.Marshal(LogoutRequest{All: true}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/logout", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.LogoutHandler(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "jwt required for all=true" { t.Fatalf("expected error 'jwt required for all=true', got %v", m["error"]) } } // --- RefreshHandler tests ------------------------------------------------- func TestRefreshHandler_MissingRefreshToken(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) body, _ := json.Marshal(RefreshRequest{}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/refresh", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.RefreshHandler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "refresh_token is required" { t.Fatalf("expected error 'refresh_token is required', got %v", m["error"]) } } func TestRefreshHandler_InvalidMethod(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/refresh", nil) rec := httptest.NewRecorder() h.RefreshHandler(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code) } } func TestRefreshHandler_NilAuthService(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) body, _ := json.Marshal(RefreshRequest{RefreshToken: "some-token"}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/refresh", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.RefreshHandler(rec, req) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("expected status %d, got %d", http.StatusServiceUnavailable, rec.Code) } } // --- APIKeyToJWTHandler tests --------------------------------------------- func TestAPIKeyToJWTHandler_MissingKey(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodPost, "/v1/auth/token", nil) rec := httptest.NewRecorder() h.APIKeyToJWTHandler(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("expected status %d, got %d", http.StatusUnauthorized, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "missing API key" { t.Fatalf("expected error 'missing API key', got %v", m["error"]) } } func TestAPIKeyToJWTHandler_InvalidMethod(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/token", nil) rec := httptest.NewRecorder() h.APIKeyToJWTHandler(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code) } } func TestAPIKeyToJWTHandler_NilAuthService(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodPost, "/v1/auth/token", nil) req.Header.Set("X-API-Key", "ak_test:default") rec := httptest.NewRecorder() h.APIKeyToJWTHandler(rec, req) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("expected status %d, got %d", http.StatusServiceUnavailable, rec.Code) } } // --- RegisterHandler tests ------------------------------------------------ func TestRegisterHandler_MissingFields(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) tests := []struct { name string req RegisterRequest }{ {"missing wallet", RegisterRequest{Nonce: "n", Signature: "s"}}, {"missing nonce", RegisterRequest{Wallet: "0x123", Signature: "s"}}, {"missing signature", RegisterRequest{Wallet: "0x123", Nonce: "n"}}, {"all empty", RegisterRequest{}}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { body, _ := json.Marshal(tc.req) req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.RegisterHandler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "wallet, nonce and signature are required" { t.Fatalf("expected error 'wallet, nonce and signature are required', got %v", m["error"]) } }) } } func TestRegisterHandler_InvalidMethod(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/register", nil) rec := httptest.NewRecorder() h.RegisterHandler(rec, req) if rec.Code != http.StatusMethodNotAllowed { t.Fatalf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code) } } func TestRegisterHandler_NilAuthService(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) body, _ := json.Marshal(RegisterRequest{Wallet: "0x123", Nonce: "n", Signature: "s"}) req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.RegisterHandler(rec, req) if rec.Code != http.StatusServiceUnavailable { t.Fatalf("expected status %d, got %d", http.StatusServiceUnavailable, rec.Code) } } // --- markNonceUsed (tested indirectly via nil safety) ---------------------- func TestMarkNonceUsed_NilNetClient(t *testing.T) { // markNonceUsed is unexported but returns early when h.netClient == nil. // We verify it does not panic by constructing a Handlers with nil netClient // and invoking it through the struct directly (same-package test). h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) // This should not panic. h.markNonceUsed(context.Background(), 1, "0xwallet", "nonce123") } // --- resolveNamespace (tested indirectly via nil safety) -------------------- func TestResolveNamespace_NilAuthService(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) _, err := h.resolveNamespace(context.Background(), "default") if err == nil { t.Fatal("expected error when authService is nil, got nil") } } // --- extractAPIKey tests --------------------------------------------------- func TestExtractAPIKey_XAPIKeyHeader(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("X-API-Key", "ak_test123:ns") got := extractAPIKey(req) if got != "ak_test123:ns" { t.Fatalf("expected 'ak_test123:ns', got '%s'", got) } } func TestExtractAPIKey_BearerNonJWT(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Authorization", "Bearer ak_mykey") got := extractAPIKey(req) if got != "ak_mykey" { t.Fatalf("expected 'ak_mykey', got '%s'", got) } } func TestExtractAPIKey_BearerJWTSkipped(t *testing.T) { // A JWT-looking token (two dots) should be skipped by extractAPIKey. req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Authorization", "Bearer header.payload.signature") got := extractAPIKey(req) if got != "" { t.Fatalf("expected empty string for JWT bearer, got '%s'", got) } } func TestExtractAPIKey_ApiKeyScheme(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Authorization", "ApiKey ak_scheme_key") got := extractAPIKey(req) if got != "ak_scheme_key" { t.Fatalf("expected 'ak_scheme_key', got '%s'", got) } } func TestExtractAPIKey_QueryParam(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/?api_key=ak_query", nil) got := extractAPIKey(req) if got != "ak_query" { t.Fatalf("expected 'ak_query', got '%s'", got) } } func TestExtractAPIKey_TokenQueryParam(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/?token=ak_tokenval", nil) got := extractAPIKey(req) if got != "ak_tokenval" { t.Fatalf("expected 'ak_tokenval', got '%s'", got) } } func TestExtractAPIKey_NoKey(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) got := extractAPIKey(req) if got != "" { t.Fatalf("expected empty string, got '%s'", got) } } func TestExtractAPIKey_AuthorizationNoSchemeNonJWT(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Authorization", "ak_raw_token") got := extractAPIKey(req) if got != "ak_raw_token" { t.Fatalf("expected 'ak_raw_token', got '%s'", got) } } func TestExtractAPIKey_AuthorizationNoSchemeJWTSkipped(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/", nil) req.Header.Set("Authorization", "a.b.c") got := extractAPIKey(req) if got != "" { t.Fatalf("expected empty string for JWT-like auth, got '%s'", got) } } // --- ChallengeHandler invalid JSON ---------------------------------------- func TestChallengeHandler_InvalidJSON(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodPost, "/v1/auth/challenge", bytes.NewReader([]byte("not json"))) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.ChallengeHandler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code) } m := decodeBody(t, rec) if errMsg, ok := m["error"].(string); !ok || errMsg != "invalid json body" { t.Fatalf("expected error 'invalid json body', got %v", m["error"]) } } // --- WhoamiHandler with namespace override -------------------------------- func TestWhoamiHandler_NamespaceOverride(t *testing.T) { h := NewHandlers(testLogger(), nil, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodGet, "/v1/auth/whoami", nil) ctx := context.WithValue(req.Context(), CtxKeyNamespaceOverride, "custom-ns") req = req.WithContext(ctx) rec := httptest.NewRecorder() h.WhoamiHandler(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code) } m := decodeBody(t, rec) if ns, ok := m["namespace"].(string); !ok || ns != "custom-ns" { t.Fatalf("expected namespace='custom-ns', got %v", m["namespace"]) } } // --- LogoutHandler invalid JSON ------------------------------------------- func TestLogoutHandler_InvalidJSON(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodPost, "/v1/auth/logout", bytes.NewReader([]byte("bad json"))) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.LogoutHandler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code) } } // --- RefreshHandler invalid JSON ------------------------------------------ func TestRefreshHandler_InvalidJSON(t *testing.T) { svc, err := authsvc.NewService(testLogger(), nil, "", "default") if err != nil { t.Fatalf("failed to create auth service: %v", err) } h := NewHandlers(testLogger(), svc, nil, "default", noopInternalAuth) req := httptest.NewRequest(http.MethodPost, "/v1/auth/refresh", bytes.NewReader([]byte("bad json"))) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() h.RefreshHandler(rec, req) if rec.Code != http.StatusBadRequest { t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rec.Code) } }