diff --git a/docs/HORIZONTAL_SCALABILITY.md b/docs/HORIZONTAL_SCALABILITY.md index d22e6fb..3e9dd5e 100644 --- a/docs/HORIZONTAL_SCALABILITY.md +++ b/docs/HORIZONTAL_SCALABILITY.md @@ -18,7 +18,7 @@ Your authorization microservice is now **fully horizontally scalable** using Red #### 2. Cache Architecture -``` +```text ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ @@ -39,20 +39,20 @@ Your authorization microservice is now **fully horizontally scalable** using Red #### 3. Key Features -**Dual-Layer Caching** +#### Dual-Layer Caching - Primary: Redis (distributed, shared across instances) - Secondary: Local in-memory (failover, performance boost) - Automatic fallback when Redis unavailable -**Consistency Guarantees** +#### Consistency Guarantees - All instances share the same Redis cache - 30-second automatic cache refresh - Manual invalidation via `InvalidateUserCache()` - Force refresh via `RefreshCacheNow()` -**Performance Optimizations** +#### Performance Optimizations - JSON serialization for complex objects - 100ms timeout for Redis operations @@ -313,7 +313,7 @@ var ( | Medium Cluster | 5 | ~10,000 | <20ms | | Large Cluster | 10+ | ~20,000+ | <25ms | -_Note: Assumes Redis on same network, PostgreSQL optimized_ +> **Note:** Assumes Redis on same network, PostgreSQL optimized ### Cache Effectiveness diff --git a/handlers/authorize_test.go b/handlers/authorize_test.go index c9bdcd0..544f8de 100644 --- a/handlers/authorize_test.go +++ b/handlers/authorize_test.go @@ -27,9 +27,9 @@ func TestInitAuthService(t *testing.T) { t.Log("InitAuthService completed successfully") } -func TestAuthorizeHandler_NoJWTClaims(t *testing.T) { +func TestAuthorizeHandlerNoJWTClaims(t *testing.T) { // Setup - req := httptest.NewRequest("POST", "/v1/auth/check", nil) + req := httptest.NewRequest("POST", AuthCheckEndpoint, nil) w := httptest.NewRecorder() // Execute @@ -37,11 +37,11 @@ func TestAuthorizeHandler_NoJWTClaims(t *testing.T) { // Assert if w.Code != http.StatusUnauthorized { - t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code) + t.Errorf(ExpectedStatusMessage, http.StatusUnauthorized, w.Code) } } -func TestAuthorizeHandler_InvalidJSON(t *testing.T) { +func TestAuthorizeHandlerInvalidJSON(t *testing.T) { // Setup - no need to init service, we're testing JSON parsing before auth claims := &models.Claims{ UserID: "user123", @@ -49,7 +49,7 @@ func TestAuthorizeHandler_InvalidJSON(t *testing.T) { Role: "admin", } - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBufferString("invalid json")) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString("invalid json")) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -59,11 +59,11 @@ func TestAuthorizeHandler_InvalidJSON(t *testing.T) { // Assert if w.Code != http.StatusBadRequest { - t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code) + t.Errorf(ExpectedStatusMessage, http.StatusBadRequest, w.Code) } } -func TestAuthorizeHandler_MissingRequiredFields(t *testing.T) { +func TestAuthorizeHandlerMissingRequiredFields(t *testing.T) { testCases := []struct { name string payload models.AuthorizationContext @@ -91,7 +91,7 @@ func TestAuthorizeHandler_MissingRequiredFields(t *testing.T) { } body, _ := json.Marshal(tc.payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -99,13 +99,13 @@ func TestAuthorizeHandler_MissingRequiredFields(t *testing.T) { AuthorizeHandler(w, req) if w.Code != http.StatusBadRequest { - t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code) + t.Errorf(ExpectedStatusMessage, http.StatusBadRequest, w.Code) } }) } } -func TestAuthorizeHandler_UserIDMismatch(t *testing.T) { +func TestAuthorizeHandlerUserIDMismatch(t *testing.T) { // Setup claims := &models.Claims{ UserID: "user123", @@ -120,7 +120,7 @@ func TestAuthorizeHandler_UserIDMismatch(t *testing.T) { } body, _ := json.Marshal(payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -130,11 +130,11 @@ func TestAuthorizeHandler_UserIDMismatch(t *testing.T) { // Assert if w.Code != http.StatusForbidden { - t.Errorf("Expected status %d, got %d", http.StatusForbidden, w.Code) + t.Errorf(ExpectedStatusMessage, http.StatusForbidden, w.Code) } } -func TestAuthorizeHandler_NilMaps(t *testing.T) { +func TestAuthorizeHandlerNilMaps(t *testing.T) { // Test that nil maps don't cause additional panics beyond missing authService claims := &models.Claims{ UserID: "user123", @@ -151,7 +151,7 @@ func TestAuthorizeHandler_NilMaps(t *testing.T) { } body, _ := json.Marshal(payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -173,7 +173,7 @@ func TestAuthorizeHandler_NilMaps(t *testing.T) { // Additional comprehensive test cases -func TestAuthorizeHandler_EmptyUserID(t *testing.T) { +func TestAuthorizeHandlerEmptyUserID(t *testing.T) { claims := &models.Claims{ UserID: "user123", Username: "testuser", @@ -187,7 +187,7 @@ func TestAuthorizeHandler_EmptyUserID(t *testing.T) { } body, _ := json.Marshal(payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -199,7 +199,7 @@ func TestAuthorizeHandler_EmptyUserID(t *testing.T) { } } -func TestAuthorizeHandler_EmptyResource(t *testing.T) { +func TestAuthorizeHandlerEmptyResource(t *testing.T) { claims := &models.Claims{ UserID: "user123", Username: "testuser", @@ -213,7 +213,7 @@ func TestAuthorizeHandler_EmptyResource(t *testing.T) { } body, _ := json.Marshal(payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -225,7 +225,7 @@ func TestAuthorizeHandler_EmptyResource(t *testing.T) { } } -func TestAuthorizeHandler_EmptyAction(t *testing.T) { +func TestAuthorizeHandlerEmptyAction(t *testing.T) { claims := &models.Claims{ UserID: "user123", Username: "testuser", @@ -239,7 +239,7 @@ func TestAuthorizeHandler_EmptyAction(t *testing.T) { } body, _ := json.Marshal(payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -251,8 +251,8 @@ func TestAuthorizeHandler_EmptyAction(t *testing.T) { } } -func TestAuthorizeHandler_InvalidClaimsType(t *testing.T) { - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBufferString(`{"userId":"user123","resource":"doc","action":"read"}`)) +func TestAuthorizeHandlerInvalidClaimsType(t *testing.T) { + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString(`{"userId":"user123","resource":"doc","action":"read"}`)) // Set claims as wrong type ctx := context.WithValue(req.Context(), models.ContextKey("claims"), "invalid_claims_type") @@ -266,7 +266,7 @@ func TestAuthorizeHandler_InvalidClaimsType(t *testing.T) { } } -func TestAuthorizeHandler_MalformedJSON(t *testing.T) { +func TestAuthorizeHandlerMalformedJSON(t *testing.T) { claims := &models.Claims{ UserID: "user123", Username: "testuser", @@ -285,7 +285,7 @@ func TestAuthorizeHandler_MalformedJSON(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBufferString(tc.payload)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString(tc.payload)) ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims) req = req.WithContext(ctx) w := httptest.NewRecorder() @@ -299,7 +299,7 @@ func TestAuthorizeHandler_MalformedJSON(t *testing.T) { } } -func TestAuthorizeHandler_SpecialCharactersInFields(t *testing.T) { +func TestAuthorizeHandlerSpecialCharactersInFields(t *testing.T) { testCases := []struct { name string userID string @@ -321,7 +321,7 @@ func TestAuthorizeHandler_SpecialCharactersInFields(t *testing.T) { } body, _ := json.Marshal(payload) - req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBuffer(body)) + req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body)) // Update claims to match userID testClaims := &models.Claims{ diff --git a/handlers/constants.go b/handlers/constants.go new file mode 100644 index 0000000..36ff841 --- /dev/null +++ b/handlers/constants.go @@ -0,0 +1,12 @@ +package handlers + +const ( + ExpectedStatusMessage = "Expected status %d, got %d" + AuthCheckEndpoint = "/v1/auth/check" + HealthCheckEndpoint = "/health" + StatusMismatchMessage = "status = %v, want %v" + FailedToDecodeResponseMessage = "failed to decode response: %v" + FailedToCreateMockDBMessage = "failed to create mock db: %v" + ReadyCheckEndpoint = "/ready" + ExpectedStatus200Message = "Expected status 200, got %d" +) diff --git a/handlers/health_test.go b/handlers/health_test.go index 938444a..8450645 100644 --- a/handlers/health_test.go +++ b/handlers/health_test.go @@ -30,7 +30,7 @@ func TestHealthHandler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/health", nil) + req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil) w := httptest.NewRecorder() HealthHandler(w, req) @@ -39,16 +39,16 @@ func TestHealthHandler(t *testing.T) { defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { - t.Errorf("status = %v, want %v", resp.StatusCode, tt.wantStatus) + t.Errorf(StatusMismatchMessage, resp.StatusCode, tt.wantStatus) } var healthResp models.HealthResponse if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil { - t.Fatalf("failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } if healthResp.Status != tt.wantBodyStatus { - t.Errorf("status = %v, want %v", healthResp.Status, tt.wantBodyStatus) + t.Errorf(StatusMismatchMessage, healthResp.Status, tt.wantBodyStatus) } contentType := resp.Header.Get("Content-Type") @@ -59,11 +59,11 @@ func TestHealthHandler(t *testing.T) { } } -func TestReadyHandler_AllHealthy(t *testing.T) { +func TestReadyHandlerAllHealthy(t *testing.T) { // Setup mock DB mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) if err != nil { - t.Fatalf("failed to create mock db: %v", err) + t.Fatalf(FailedToCreateMockDBMessage, err) } defer mockDB.Close() @@ -80,7 +80,7 @@ func TestReadyHandler_AllHealthy(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) @@ -90,7 +90,7 @@ func TestReadyHandler_AllHealthy(t *testing.T) { var healthResp models.HealthResponse if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil { - t.Fatalf("failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } if healthResp.Services["database"] != "healthy" { @@ -103,11 +103,11 @@ func TestReadyHandler_AllHealthy(t *testing.T) { } } -func TestReadyHandler_DBUnhealthy(t *testing.T) { +func TestReadyHandlerDBUnhealthy(t *testing.T) { // Setup mock DB that fails ping mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) if err != nil { - t.Fatalf("failed to create mock db: %v", err) + t.Fatalf(FailedToCreateMockDBMessage, err) } defer mockDB.Close() @@ -124,7 +124,7 @@ func TestReadyHandler_DBUnhealthy(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) @@ -133,12 +133,12 @@ func TestReadyHandler_DBUnhealthy(t *testing.T) { defer resp.Body.Close() if resp.StatusCode != http.StatusServiceUnavailable { - t.Errorf("status = %v, want %v", resp.StatusCode, http.StatusServiceUnavailable) + t.Errorf(StatusMismatchMessage, resp.StatusCode, http.StatusServiceUnavailable) } var healthResp models.HealthResponse if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil { - t.Fatalf("failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } if healthResp.Status != "not_ready" { @@ -154,7 +154,7 @@ func TestReadyHandler_DBUnhealthy(t *testing.T) { } } -func TestReadyHandler_DBNotInitialized(t *testing.T) { +func TestReadyHandlerDBNotInitialized(t *testing.T) { // Save original and set to nil originalDB := db.DB db.DB = nil @@ -164,7 +164,7 @@ func TestReadyHandler_DBNotInitialized(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) @@ -173,12 +173,12 @@ func TestReadyHandler_DBNotInitialized(t *testing.T) { defer resp.Body.Close() if resp.StatusCode != http.StatusServiceUnavailable { - t.Errorf("status = %v, want %v", resp.StatusCode, http.StatusServiceUnavailable) + t.Errorf(StatusMismatchMessage, resp.StatusCode, http.StatusServiceUnavailable) } var healthResp models.HealthResponse if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil { - t.Fatalf("failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } if healthResp.Status != "not_ready" { @@ -194,7 +194,7 @@ func TestReadyHandler_DBNotInitialized(t *testing.T) { } } -func TestReadyHandler_ContentType(t *testing.T) { +func TestReadyHandlerContentType(t *testing.T) { originalDB := db.DB db.DB = nil defer func() { db.DB = originalDB }() @@ -203,7 +203,7 @@ func TestReadyHandler_ContentType(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) @@ -219,19 +219,19 @@ func TestReadyHandler_ContentType(t *testing.T) { // Additional comprehensive test cases -func TestHealthHandler_MultipleRequests(t *testing.T) { +func TestHealthHandlerMultipleRequests(t *testing.T) { // Test that multiple concurrent requests work correctly concurrency := 10 done := make(chan bool, concurrency) for i := 0; i < concurrency; i++ { go func() { - req := httptest.NewRequest(http.MethodGet, "/health", nil) + req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil) w := httptest.NewRecorder() HealthHandler(w, req) if w.Code != http.StatusOK { - t.Errorf("Expected status 200, got %d", w.Code) + t.Errorf(ExpectedStatus200Message, w.Code) } done <- true }() @@ -242,12 +242,12 @@ func TestHealthHandler_MultipleRequests(t *testing.T) { } } -func TestHealthHandler_DifferentMethods(t *testing.T) { +func TestHealthHandlerDifferentMethods(t *testing.T) { methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH"} for _, method := range methods { t.Run(method, func(t *testing.T) { - req := httptest.NewRequest(method, "/health", nil) + req := httptest.NewRequest(method, HealthCheckEndpoint, nil) w := httptest.NewRecorder() HealthHandler(w, req) @@ -259,14 +259,14 @@ func TestHealthHandler_DifferentMethods(t *testing.T) { } } -func TestHealthHandler_ResponseFormat(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/health", nil) +func TestHealthHandlerResponseFormat(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil) w := httptest.NewRecorder() HealthHandler(w, req) var response models.HealthResponse if err := json.NewDecoder(w.Body).Decode(&response); err != nil { - t.Fatalf("Failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } if response.Status == "" { @@ -278,10 +278,10 @@ func TestHealthHandler_ResponseFormat(t *testing.T) { } } -func TestReadyHandler_DatabaseTimeout(t *testing.T) { +func TestReadyHandlerDatabaseTimeout(t *testing.T) { mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) if err != nil { - t.Fatalf("failed to create mock db: %v", err) + t.Fatalf(FailedToCreateMockDBMessage, err) } defer mockDB.Close() @@ -296,7 +296,7 @@ func TestReadyHandler_DatabaseTimeout(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() // This should timeout and return unhealthy @@ -307,7 +307,7 @@ func TestReadyHandler_DatabaseTimeout(t *testing.T) { } } -func TestReadyHandler_BothServicesHealthy(t *testing.T) { +func TestReadyHandlerBothServicesHealthy(t *testing.T) { // Use miniredis for Redis mock mr, err := miniredis.Run() if err != nil { @@ -325,7 +325,7 @@ func TestReadyHandler_BothServicesHealthy(t *testing.T) { // Setup mock database mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) if err != nil { - t.Fatalf("Failed to create mock DB: %v", err) + t.Fatalf(FailedToCreateMockDBMessage, err) } defer mockDB.Close() @@ -336,13 +336,13 @@ func TestReadyHandler_BothServicesHealthy(t *testing.T) { // Expect ping mock.ExpectPing() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) if w.Code != http.StatusOK { - t.Errorf("Expected status 200, got %d", w.Code) + t.Errorf(ExpectedStatus200Message, w.Code) } var response models.HealthResponse @@ -357,7 +357,7 @@ func TestReadyHandler_BothServicesHealthy(t *testing.T) { } } -func TestReadyHandler_NilDatabaseAndRedis(t *testing.T) { +func TestReadyHandlerNilDatabaseAndRedis(t *testing.T) { originalDB := db.DB db.DB = nil defer func() { db.DB = originalDB }() @@ -366,7 +366,7 @@ func TestReadyHandler_NilDatabaseAndRedis(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) @@ -377,7 +377,7 @@ func TestReadyHandler_NilDatabaseAndRedis(t *testing.T) { var response models.HealthResponse if err := json.NewDecoder(w.Body).Decode(&response); err != nil { - t.Fatalf("Failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } // The handler returns "not_ready" when services are down @@ -386,7 +386,7 @@ func TestReadyHandler_NilDatabaseAndRedis(t *testing.T) { } } -func TestReadyHandler_ResponseStructure(t *testing.T) { +func TestReadyHandlerResponseStructure(t *testing.T) { originalDB := db.DB db.DB = nil defer func() { db.DB = originalDB }() @@ -395,7 +395,7 @@ func TestReadyHandler_ResponseStructure(t *testing.T) { redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) @@ -403,7 +403,7 @@ func TestReadyHandler_ResponseStructure(t *testing.T) { // Verify response is valid JSON var response map[string]interface{} if err := json.NewDecoder(w.Body).Decode(&response); err != nil { - t.Fatalf("Failed to decode response: %v", err) + t.Fatalf(FailedToDecodeResponseMessage, err) } // Check that response has expected fields @@ -412,8 +412,8 @@ func TestReadyHandler_ResponseStructure(t *testing.T) { } } -func TestHealthHandler_WithCustomHeaders(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "/health", nil) +func TestHealthHandlerWithCustomHeaders(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil) req.Header.Set("X-Request-ID", "test-123") req.Header.Set("User-Agent", "Test-Agent/1.0") @@ -421,11 +421,11 @@ func TestHealthHandler_WithCustomHeaders(t *testing.T) { HealthHandler(w, req) if w.Code != http.StatusOK { - t.Errorf("Expected status 200, got %d", w.Code) + t.Errorf(ExpectedStatus200Message, w.Code) } } -func TestReadyHandler_ConcurrentRequests(t *testing.T) { +func TestReadyHandlerConcurrentRequests(t *testing.T) { originalDB := db.DB db.DB = nil defer func() { db.DB = originalDB }() @@ -439,7 +439,7 @@ func TestReadyHandler_ConcurrentRequests(t *testing.T) { for i := 0; i < concurrency; i++ { go func() { - req := httptest.NewRequest(http.MethodGet, "/ready", nil) + req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil) w := httptest.NewRecorder() ReadyHandler(w, req) diff --git a/helper/circuit_breaker_test.go b/helper/circuit_breaker_test.go index 8938fcf..cc6d70f 100644 --- a/helper/circuit_breaker_test.go +++ b/helper/circuit_breaker_test.go @@ -45,7 +45,7 @@ func TestNewCircuitBreaker(t *testing.T) { t.Errorf("timeout = %v, want %v", cb.timeout, tt.timeout) } if cb.state != tt.wantState { - t.Errorf("state = %v, want %v", cb.state, tt.wantState) + t.Errorf(StateMismatchMessage, cb.state, tt.wantState) } if cb.resetTimeout != 30*time.Second { t.Errorf("resetTimeout = %v, want %v", cb.resetTimeout, 30*time.Second) @@ -57,7 +57,7 @@ func TestNewCircuitBreaker(t *testing.T) { } } -func TestCircuitBreaker_Call_Success(t *testing.T) { +func TestCircuitBreakerCallSuccess(t *testing.T) { cb := NewCircuitBreaker("test", 3, 1*time.Second) successFn := func() error { @@ -70,15 +70,15 @@ func TestCircuitBreaker_Call_Success(t *testing.T) { } if GetState(cb) != StateClosed { - t.Errorf("state = %v, want %v", GetState(cb), StateClosed) + t.Errorf(StateMismatchMessage, GetState(cb), StateClosed) } } -func TestCircuitBreaker_Call_FailuresOpenCircuit(t *testing.T) { +func TestCircuitBreakerCallFailuresOpenCircuit(t *testing.T) { cb := NewCircuitBreaker("test", 3, 1*time.Second) failFn := func() error { - return errors.New("service error") + return errors.New(ServiceError) } // First 2 failures - circuit should stay closed @@ -98,7 +98,7 @@ func TestCircuitBreaker_Call_FailuresOpenCircuit(t *testing.T) { t.Error("Call() expected error, got nil") } if GetState(cb) != StateOpen { - t.Errorf("state = %v, want %v", GetState(cb), StateOpen) + t.Errorf(StateMismatchMessage, GetState(cb), StateOpen) } // Next call should immediately return circuit breaker error @@ -108,12 +108,12 @@ func TestCircuitBreaker_Call_FailuresOpenCircuit(t *testing.T) { } } -func TestCircuitBreaker_Call_OpenToHalfOpen(t *testing.T) { +func TestCircuitBreakerCallOpenToHalfOpen(t *testing.T) { cb := NewCircuitBreaker("test", 2, 1*time.Second) cb.resetTimeout = 100 * time.Millisecond // Shorter reset for testing failFn := func() error { - return errors.New("service error") + return errors.New(ServiceError) } // Open the circuit @@ -121,7 +121,7 @@ func TestCircuitBreaker_Call_OpenToHalfOpen(t *testing.T) { Call(cb, failFn) if GetState(cb) != StateOpen { - t.Fatalf("state = %v, want %v", GetState(cb), StateOpen) + t.Fatalf(StateMismatchMessage, GetState(cb), StateOpen) } // Wait for reset timeout @@ -139,16 +139,16 @@ func TestCircuitBreaker_Call_OpenToHalfOpen(t *testing.T) { // Should now be closed if GetState(cb) != StateClosed { - t.Errorf("state = %v, want %v", GetState(cb), StateClosed) + t.Errorf(StateMismatchMessage, GetState(cb), StateClosed) } } -func TestCircuitBreaker_Call_HalfOpenFailReturnsToOpen(t *testing.T) { +func TestCircuitBreakerCallHalfOpenFailReturnsToOpen(t *testing.T) { cb := NewCircuitBreaker("test", 2, 1*time.Second) cb.resetTimeout = 100 * time.Millisecond failFn := func() error { - return errors.New("service error") + return errors.New(ServiceError) } // Open the circuit @@ -156,7 +156,7 @@ func TestCircuitBreaker_Call_HalfOpenFailReturnsToOpen(t *testing.T) { Call(cb, failFn) if GetState(cb) != StateOpen { - t.Fatalf("state = %v, want %v", GetState(cb), StateOpen) + t.Fatalf(StateMismatchMessage, GetState(cb), StateOpen) } // Wait for reset timeout to transition to HalfOpen @@ -169,15 +169,15 @@ func TestCircuitBreaker_Call_HalfOpenFailReturnsToOpen(t *testing.T) { } if GetState(cb) != StateOpen { - t.Errorf("state = %v, want %v", GetState(cb), StateOpen) + t.Errorf(StateMismatchMessage, GetState(cb), StateOpen) } } -func TestCircuitBreaker_Call_GradualFailureReduction(t *testing.T) { +func TestCircuitBreakerCallGradualFailureReduction(t *testing.T) { cb := NewCircuitBreaker("test", 5, 1*time.Second) failFn := func() error { - return errors.New("service error") + return errors.New(ServiceError) } successFn := func() error { return nil @@ -208,7 +208,7 @@ func TestCircuitBreaker_Call_GradualFailureReduction(t *testing.T) { } } -func TestCircuitBreaker_GetState(t *testing.T) { +func TestCircuitBreakerGetState(t *testing.T) { tests := []struct { name string setupFunc func(*CircuitBreaker) @@ -250,7 +250,7 @@ func TestCircuitBreaker_GetState(t *testing.T) { } } -func TestCircuitBreaker_Reset(t *testing.T) { +func TestCircuitBreakerReset(t *testing.T) { cb := NewCircuitBreaker("test", 2, 1*time.Second) // Open the circuit @@ -261,7 +261,7 @@ func TestCircuitBreaker_Reset(t *testing.T) { Call(cb, failFn) if GetState(cb) != StateOpen { - t.Fatalf("state = %v, want %v", GetState(cb), StateOpen) + t.Fatalf(StateMismatchMessage, GetState(cb), StateOpen) } // Reset the circuit breaker @@ -280,7 +280,7 @@ func TestCircuitBreaker_Reset(t *testing.T) { } } -func TestCircuitBreakerError_Error(t *testing.T) { +func TestCircuitBreakerErrorError(t *testing.T) { tests := []struct { name string err *CircuitBreakerError @@ -350,7 +350,7 @@ func TestIsCircuitBreakerError(t *testing.T) { } } -func TestCircuitBreaker_Concurrency(t *testing.T) { +func TestCircuitBreakerConcurrency(t *testing.T) { cb := NewCircuitBreaker("test", 10, 1*time.Second) var wg sync.WaitGroup @@ -397,7 +397,7 @@ func TestCircuitBreaker_Concurrency(t *testing.T) { } } -func TestCircuitBreaker_OpenCircuitRejectsImmediately(t *testing.T) { +func TestCircuitBreakerOpenCircuitRejectsImmediately(t *testing.T) { cb := NewCircuitBreaker("test", 1, 1*time.Second) // Open the circuit @@ -407,7 +407,7 @@ func TestCircuitBreaker_OpenCircuitRejectsImmediately(t *testing.T) { Call(cb, failFn) if GetState(cb) != StateOpen { - t.Fatalf("state = %v, want %v", GetState(cb), StateOpen) + t.Fatalf(StateMismatchMessage, GetState(cb), StateOpen) } // Try calling with a function that should not execute @@ -428,7 +428,7 @@ func TestCircuitBreaker_OpenCircuitRejectsImmediately(t *testing.T) { } } -func BenchmarkCircuitBreaker_Call_Success(b *testing.B) { +func BenchmarkCircuitBreakerCallSuccess(b *testing.B) { cb := NewCircuitBreaker("test", 5, 1*time.Second) fn := func() error { return nil @@ -440,7 +440,7 @@ func BenchmarkCircuitBreaker_Call_Success(b *testing.B) { } } -func BenchmarkCircuitBreaker_Call_Open(b *testing.B) { +func BenchmarkCircuitBreakerCallOpen(b *testing.B) { cb := NewCircuitBreaker("test", 1, 1*time.Second) // Open the circuit @@ -458,7 +458,7 @@ func BenchmarkCircuitBreaker_Call_Open(b *testing.B) { } } -func TestCircuitBreaker_StateTransitions(t *testing.T) { +func TestCircuitBreakerStateTransitions(t *testing.T) { cb := NewCircuitBreaker("test", 2, 1*time.Second) cb.resetTimeout = 100 * time.Millisecond @@ -497,7 +497,7 @@ func TestCircuitBreaker_StateTransitions(t *testing.T) { } } -func TestCircuitBreaker_ZeroMaxFailures(t *testing.T) { +func TestCircuitBreakerZeroMaxFailures(t *testing.T) { cb := NewCircuitBreaker("test", 0, 1*time.Second) // Even one failure should open circuit when maxFailures is 0 @@ -512,7 +512,7 @@ func TestCircuitBreaker_ZeroMaxFailures(t *testing.T) { } } -func TestCircuitBreaker_NegativeMaxFailures(t *testing.T) { +func TestCircuitBreakerNegativeMaxFailures(t *testing.T) { // Negative maxFailures should be treated as invalid, but won't panic cb := NewCircuitBreaker("test", -1, 1*time.Second) @@ -526,7 +526,7 @@ func TestCircuitBreaker_NegativeMaxFailures(t *testing.T) { } } -func TestCircuitBreaker_VeryShortTimeout(t *testing.T) { +func TestCircuitBreakerVeryShortTimeout(t *testing.T) { cb := NewCircuitBreaker("test", 1, 1*time.Nanosecond) cb.resetTimeout = 1 * time.Nanosecond @@ -542,7 +542,7 @@ func TestCircuitBreaker_VeryShortTimeout(t *testing.T) { } } -func TestCircuitBreaker_MultipleSuccessesAfterFailure(t *testing.T) { +func TestCircuitBreakerMultipleSuccessesAfterFailure(t *testing.T) { cb := NewCircuitBreaker("test", 3, 1*time.Second) // Add one failure @@ -570,7 +570,7 @@ func TestCircuitBreaker_MultipleSuccessesAfterFailure(t *testing.T) { } } -func TestCircuitBreaker_HighConcurrency(t *testing.T) { +func TestCircuitBreakerHighConcurrency(t *testing.T) { cb := NewCircuitBreaker("test", 10, 1*time.Second) concurrency := 100 @@ -608,7 +608,7 @@ func TestCircuitBreaker_HighConcurrency(t *testing.T) { } } -func TestCircuitBreaker_HalfOpenSingleRequest(t *testing.T) { +func TestCircuitBreakerHalfOpenSingleRequest(t *testing.T) { cb := NewCircuitBreaker("test", 1, 1*time.Second) cb.resetTimeout = 50 * time.Millisecond @@ -637,7 +637,7 @@ func TestCircuitBreaker_HalfOpenSingleRequest(t *testing.T) { } } -func TestCircuitBreaker_SuccessResetsFailureCount(t *testing.T) { +func TestCircuitBreakerSuccessResetsFailureCount(t *testing.T) { cb := NewCircuitBreaker("test", 3, 1*time.Second) // 2 failures @@ -674,7 +674,7 @@ func TestCircuitBreaker_SuccessResetsFailureCount(t *testing.T) { } } -func TestCircuitBreaker_DifferentErrorTypes(t *testing.T) { +func TestCircuitBreakerDifferentErrorTypes(t *testing.T) { cb := NewCircuitBreaker("test", 2, 1*time.Second) // Different error types should all count as failures @@ -686,7 +686,7 @@ func TestCircuitBreaker_DifferentErrorTypes(t *testing.T) { } } -func TestCircuitBreaker_NilFunction(t *testing.T) { +func TestCircuitBreakerNilFunction(t *testing.T) { cb := NewCircuitBreaker("test", 3, 1*time.Second) // Should handle nil function gracefully (though this is a programming error) @@ -699,7 +699,7 @@ func TestCircuitBreaker_NilFunction(t *testing.T) { Call(cb, nil) } -func TestCircuitBreaker_LongRunningOperation(t *testing.T) { +func TestCircuitBreakerLongRunningOperation(t *testing.T) { cb := NewCircuitBreaker("test", 2, 100*time.Millisecond) // Test that timeout works during operation @@ -715,7 +715,7 @@ func TestCircuitBreaker_LongRunningOperation(t *testing.T) { } } -func TestCircuitBreaker_RapidStateChanges(t *testing.T) { +func TestCircuitBreakerRapidStateChanges(t *testing.T) { cb := NewCircuitBreaker("test", 1, 1*time.Second) cb.resetTimeout = 10 * time.Millisecond diff --git a/helper/constants.go b/helper/constants.go index 4b7748d..b079e58 100644 --- a/helper/constants.go +++ b/helper/constants.go @@ -8,4 +8,6 @@ const ( ErrorEncodingResponse = "Error encoding response" ErrorFailedtoLogLoginEvent = "Failed to log login event" WarningLabel = "WARNING:" + StateMismatchMessage = "state = %v, want %v" + ServiceError = "service error" ) diff --git a/helper/error_logging_test.go b/helper/error_logging_test.go index 86dc73a..69b7231 100644 --- a/helper/error_logging_test.go +++ b/helper/error_logging_test.go @@ -68,7 +68,7 @@ func TestLogInfo(t *testing.T) { } } -func TestLogInfo_NoEnvironment(t *testing.T) { +func TestLogInfoNoEnvironment(t *testing.T) { // Setup os.Unsetenv("GO_ENV") @@ -188,7 +188,7 @@ func TestLogError(t *testing.T) { } } -func TestLogError_WithNilError(t *testing.T) { +func TestLogErrorWithNilError(t *testing.T) { // Setup os.Setenv("GO_ENV", "development") defer os.Unsetenv("GO_ENV") @@ -223,7 +223,7 @@ func TestLogFatal(t *testing.T) { }) } -func TestLogging_EnvironmentCheck(t *testing.T) { +func TestLoggingEnvironmentCheck(t *testing.T) { // Test that all logging functions check for GO_ENV originalEnv := os.Getenv("GO_ENV") defer func() { diff --git a/middleware/rate_limiter_test.go b/middleware/rate_limiter_test.go index 1becec8..cd5ae3c 100644 --- a/middleware/rate_limiter_test.go +++ b/middleware/rate_limiter_test.go @@ -74,7 +74,7 @@ func TestGetClientIP(t *testing.T) { } } -func TestCheckRateLimit_AllowedRequests(t *testing.T) { +func TestCheckRateLimitAllowedRequests(t *testing.T) { db, mock := redismock.NewClientMock() originalRedis := redisclient.RDB redisclient.RDB = db @@ -107,7 +107,7 @@ func TestCheckRateLimit_AllowedRequests(t *testing.T) { } } -func TestCheckRateLimit_ExceedsLimit(t *testing.T) { +func TestCheckRateLimitExceedsLimit(t *testing.T) { db, mock := redismock.NewClientMock() originalRedis := redisclient.RDB redisclient.RDB = db @@ -140,7 +140,7 @@ func TestCheckRateLimit_ExceedsLimit(t *testing.T) { } } -func TestCheckRateLimit_RedisError(t *testing.T) { +func TestCheckRateLimitRedisError(t *testing.T) { db, mock := redismock.NewClientMock() originalRedis := redisclient.RDB redisclient.RDB = db @@ -169,7 +169,7 @@ func TestCheckRateLimit_RedisError(t *testing.T) { } } -func TestRateLimiterMiddleware_RedisNotAvailable(t *testing.T) { +func TestRateLimiterMiddlewareRedisNotAvailable(t *testing.T) { originalRedis := redisclient.RDB redisclient.RDB = nil defer func() { redisclient.RDB = originalRedis }() @@ -243,7 +243,7 @@ func TestRateLimiterMiddleware_AllowsRequest(t *testing.T) { } } -func TestRateLimiterMiddleware_BlocksRequest(t *testing.T) { +func TestRateLimiterMiddlewareBlocksRequest(t *testing.T) { db, mock := redismock.NewClientMock() originalRedis := redisclient.RDB redisclient.RDB = db @@ -285,7 +285,7 @@ func TestRateLimiterMiddleware_BlocksRequest(t *testing.T) { } } -func TestRateLimiterMiddleware_FailsOpenOnError(t *testing.T) { +func TestRateLimiterMiddlewareFailsOpenOnError(t *testing.T) { db, mock := redismock.NewClientMock() originalRedis := redisclient.RDB redisclient.RDB = db diff --git a/redisclient/redis_test.go b/redisclient/redis_test.go index 91c2dd6..55cc908 100644 --- a/redisclient/redis_test.go +++ b/redisclient/redis_test.go @@ -10,7 +10,7 @@ import ( "github.com/redis/go-redis/v9" ) -func TestInit_DefaultValues(t *testing.T) { +func TestInitDefaultValues(t *testing.T) { // Save original values originalHost := os.Getenv("REDIS_HOST") originalPort := os.Getenv("REDIS_PORT")