fixed sonarqube issues
This commit is contained in:
@@ -18,7 +18,7 @@ Your authorization microservice is now **fully horizontally scalable** using Red
|
|||||||
|
|
||||||
#### 2. Cache Architecture
|
#### 2. Cache Architecture
|
||||||
|
|
||||||
```
|
```text
|
||||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
│ Instance 1 │ │ Instance 2 │ │ Instance 3 │
|
│ Instance 1 │ │ Instance 2 │ │ Instance 3 │
|
||||||
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||||
@@ -39,20 +39,20 @@ Your authorization microservice is now **fully horizontally scalable** using Red
|
|||||||
|
|
||||||
#### 3. Key Features
|
#### 3. Key Features
|
||||||
|
|
||||||
**Dual-Layer Caching**
|
#### Dual-Layer Caching
|
||||||
|
|
||||||
- Primary: Redis (distributed, shared across instances)
|
- Primary: Redis (distributed, shared across instances)
|
||||||
- Secondary: Local in-memory (failover, performance boost)
|
- Secondary: Local in-memory (failover, performance boost)
|
||||||
- Automatic fallback when Redis unavailable
|
- Automatic fallback when Redis unavailable
|
||||||
|
|
||||||
**Consistency Guarantees**
|
#### Consistency Guarantees
|
||||||
|
|
||||||
- All instances share the same Redis cache
|
- All instances share the same Redis cache
|
||||||
- 30-second automatic cache refresh
|
- 30-second automatic cache refresh
|
||||||
- Manual invalidation via `InvalidateUserCache()`
|
- Manual invalidation via `InvalidateUserCache()`
|
||||||
- Force refresh via `RefreshCacheNow()`
|
- Force refresh via `RefreshCacheNow()`
|
||||||
|
|
||||||
**Performance Optimizations**
|
#### Performance Optimizations
|
||||||
|
|
||||||
- JSON serialization for complex objects
|
- JSON serialization for complex objects
|
||||||
- 100ms timeout for Redis operations
|
- 100ms timeout for Redis operations
|
||||||
@@ -313,7 +313,7 @@ var (
|
|||||||
| Medium Cluster | 5 | ~10,000 | <20ms |
|
| Medium Cluster | 5 | ~10,000 | <20ms |
|
||||||
| Large Cluster | 10+ | ~20,000+ | <25ms |
|
| Large Cluster | 10+ | ~20,000+ | <25ms |
|
||||||
|
|
||||||
_Note: Assumes Redis on same network, PostgreSQL optimized_
|
> **Note:** Assumes Redis on same network, PostgreSQL optimized
|
||||||
|
|
||||||
### Cache Effectiveness
|
### Cache Effectiveness
|
||||||
|
|
||||||
|
|||||||
+26
-26
@@ -27,9 +27,9 @@ func TestInitAuthService(t *testing.T) {
|
|||||||
t.Log("InitAuthService completed successfully")
|
t.Log("InitAuthService completed successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorizeHandler_NoJWTClaims(t *testing.T) {
|
func TestAuthorizeHandlerNoJWTClaims(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
req := httptest.NewRequest("POST", "/v1/auth/check", nil)
|
req := httptest.NewRequest("POST", AuthCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
@@ -37,11 +37,11 @@ func TestAuthorizeHandler_NoJWTClaims(t *testing.T) {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
if w.Code != http.StatusUnauthorized {
|
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
|
// Setup - no need to init service, we're testing JSON parsing before auth
|
||||||
claims := &models.Claims{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
@@ -49,7 +49,7 @@ func TestAuthorizeHandler_InvalidJSON(t *testing.T) {
|
|||||||
Role: "admin",
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -59,11 +59,11 @@ func TestAuthorizeHandler_InvalidJSON(t *testing.T) {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
if w.Code != http.StatusBadRequest {
|
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 {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
payload models.AuthorizationContext
|
payload models.AuthorizationContext
|
||||||
@@ -91,7 +91,7 @@ func TestAuthorizeHandler_MissingRequiredFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(tc.payload)
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -99,13 +99,13 @@ func TestAuthorizeHandler_MissingRequiredFields(t *testing.T) {
|
|||||||
AuthorizeHandler(w, req)
|
AuthorizeHandler(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusBadRequest {
|
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
|
// Setup
|
||||||
claims := &models.Claims{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
@@ -120,7 +120,7 @@ func TestAuthorizeHandler_UserIDMismatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -130,11 +130,11 @@ func TestAuthorizeHandler_UserIDMismatch(t *testing.T) {
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
if w.Code != http.StatusForbidden {
|
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
|
// Test that nil maps don't cause additional panics beyond missing authService
|
||||||
claims := &models.Claims{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
@@ -151,7 +151,7 @@ func TestAuthorizeHandler_NilMaps(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -173,7 +173,7 @@ func TestAuthorizeHandler_NilMaps(t *testing.T) {
|
|||||||
|
|
||||||
// Additional comprehensive test cases
|
// Additional comprehensive test cases
|
||||||
|
|
||||||
func TestAuthorizeHandler_EmptyUserID(t *testing.T) {
|
func TestAuthorizeHandlerEmptyUserID(t *testing.T) {
|
||||||
claims := &models.Claims{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
@@ -187,7 +187,7 @@ func TestAuthorizeHandler_EmptyUserID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
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{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
@@ -213,7 +213,7 @@ func TestAuthorizeHandler_EmptyResource(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
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{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
@@ -239,7 +239,7 @@ func TestAuthorizeHandler_EmptyAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
@@ -251,8 +251,8 @@ func TestAuthorizeHandler_EmptyAction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthorizeHandler_InvalidClaimsType(t *testing.T) {
|
func TestAuthorizeHandlerInvalidClaimsType(t *testing.T) {
|
||||||
req := httptest.NewRequest("POST", "/v1/auth/check", bytes.NewBufferString(`{"userId":"user123","resource":"doc","action":"read"}`))
|
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString(`{"userId":"user123","resource":"doc","action":"read"}`))
|
||||||
|
|
||||||
// Set claims as wrong type
|
// Set claims as wrong type
|
||||||
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), "invalid_claims_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{
|
claims := &models.Claims{
|
||||||
UserID: "user123",
|
UserID: "user123",
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
@@ -285,7 +285,7 @@ func TestAuthorizeHandler_MalformedJSON(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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)
|
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
w := httptest.NewRecorder()
|
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 {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
userID string
|
userID string
|
||||||
@@ -321,7 +321,7 @@ func TestAuthorizeHandler_SpecialCharactersInFields(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(payload)
|
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
|
// Update claims to match userID
|
||||||
testClaims := &models.Claims{
|
testClaims := &models.Claims{
|
||||||
|
|||||||
@@ -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"
|
||||||
|
)
|
||||||
+45
-45
@@ -30,7 +30,7 @@ func TestHealthHandler(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
HealthHandler(w, req)
|
HealthHandler(w, req)
|
||||||
@@ -39,16 +39,16 @@ func TestHealthHandler(t *testing.T) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != tt.wantStatus {
|
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
|
var healthResp models.HealthResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil {
|
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 {
|
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")
|
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
|
// Setup mock DB
|
||||||
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create mock db: %v", err)
|
t.Fatalf(FailedToCreateMockDBMessage, err)
|
||||||
}
|
}
|
||||||
defer mockDB.Close()
|
defer mockDB.Close()
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ func TestReadyHandler_AllHealthy(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
@@ -90,7 +90,7 @@ func TestReadyHandler_AllHealthy(t *testing.T) {
|
|||||||
|
|
||||||
var healthResp models.HealthResponse
|
var healthResp models.HealthResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil {
|
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" {
|
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
|
// Setup mock DB that fails ping
|
||||||
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create mock db: %v", err)
|
t.Fatalf(FailedToCreateMockDBMessage, err)
|
||||||
}
|
}
|
||||||
defer mockDB.Close()
|
defer mockDB.Close()
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ func TestReadyHandler_DBUnhealthy(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
@@ -133,12 +133,12 @@ func TestReadyHandler_DBUnhealthy(t *testing.T) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusServiceUnavailable {
|
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
|
var healthResp models.HealthResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil {
|
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" {
|
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
|
// Save original and set to nil
|
||||||
originalDB := db.DB
|
originalDB := db.DB
|
||||||
db.DB = nil
|
db.DB = nil
|
||||||
@@ -164,7 +164,7 @@ func TestReadyHandler_DBNotInitialized(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
@@ -173,12 +173,12 @@ func TestReadyHandler_DBNotInitialized(t *testing.T) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusServiceUnavailable {
|
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
|
var healthResp models.HealthResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&healthResp); err != nil {
|
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" {
|
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
|
originalDB := db.DB
|
||||||
db.DB = nil
|
db.DB = nil
|
||||||
defer func() { db.DB = originalDB }()
|
defer func() { db.DB = originalDB }()
|
||||||
@@ -203,7 +203,7 @@ func TestReadyHandler_ContentType(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
@@ -219,19 +219,19 @@ func TestReadyHandler_ContentType(t *testing.T) {
|
|||||||
|
|
||||||
// Additional comprehensive test cases
|
// Additional comprehensive test cases
|
||||||
|
|
||||||
func TestHealthHandler_MultipleRequests(t *testing.T) {
|
func TestHealthHandlerMultipleRequests(t *testing.T) {
|
||||||
// Test that multiple concurrent requests work correctly
|
// Test that multiple concurrent requests work correctly
|
||||||
concurrency := 10
|
concurrency := 10
|
||||||
done := make(chan bool, concurrency)
|
done := make(chan bool, concurrency)
|
||||||
|
|
||||||
for i := 0; i < concurrency; i++ {
|
for i := 0; i < concurrency; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
HealthHandler(w, req)
|
HealthHandler(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("Expected status 200, got %d", w.Code)
|
t.Errorf(ExpectedStatus200Message, w.Code)
|
||||||
}
|
}
|
||||||
done <- true
|
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"}
|
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH"}
|
||||||
|
|
||||||
for _, method := range methods {
|
for _, method := range methods {
|
||||||
t.Run(method, func(t *testing.T) {
|
t.Run(method, func(t *testing.T) {
|
||||||
req := httptest.NewRequest(method, "/health", nil)
|
req := httptest.NewRequest(method, HealthCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
HealthHandler(w, req)
|
HealthHandler(w, req)
|
||||||
|
|
||||||
@@ -259,14 +259,14 @@ func TestHealthHandler_DifferentMethods(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHealthHandler_ResponseFormat(t *testing.T) {
|
func TestHealthHandlerResponseFormat(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
HealthHandler(w, req)
|
HealthHandler(w, req)
|
||||||
|
|
||||||
var response models.HealthResponse
|
var response models.HealthResponse
|
||||||
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
|
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 == "" {
|
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))
|
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create mock db: %v", err)
|
t.Fatalf(FailedToCreateMockDBMessage, err)
|
||||||
}
|
}
|
||||||
defer mockDB.Close()
|
defer mockDB.Close()
|
||||||
|
|
||||||
@@ -296,7 +296,7 @@ func TestReadyHandler_DatabaseTimeout(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
// This should timeout and return unhealthy
|
// 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
|
// Use miniredis for Redis mock
|
||||||
mr, err := miniredis.Run()
|
mr, err := miniredis.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -325,7 +325,7 @@ func TestReadyHandler_BothServicesHealthy(t *testing.T) {
|
|||||||
// Setup mock database
|
// Setup mock database
|
||||||
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
mockDB, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create mock DB: %v", err)
|
t.Fatalf(FailedToCreateMockDBMessage, err)
|
||||||
}
|
}
|
||||||
defer mockDB.Close()
|
defer mockDB.Close()
|
||||||
|
|
||||||
@@ -336,13 +336,13 @@ func TestReadyHandler_BothServicesHealthy(t *testing.T) {
|
|||||||
// Expect ping
|
// Expect ping
|
||||||
mock.ExpectPing()
|
mock.ExpectPing()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("Expected status 200, got %d", w.Code)
|
t.Errorf(ExpectedStatus200Message, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
var response models.HealthResponse
|
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
|
originalDB := db.DB
|
||||||
db.DB = nil
|
db.DB = nil
|
||||||
defer func() { db.DB = originalDB }()
|
defer func() { db.DB = originalDB }()
|
||||||
@@ -366,7 +366,7 @@ func TestReadyHandler_NilDatabaseAndRedis(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
@@ -377,7 +377,7 @@ func TestReadyHandler_NilDatabaseAndRedis(t *testing.T) {
|
|||||||
|
|
||||||
var response models.HealthResponse
|
var response models.HealthResponse
|
||||||
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
|
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
|
// 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
|
originalDB := db.DB
|
||||||
db.DB = nil
|
db.DB = nil
|
||||||
defer func() { db.DB = originalDB }()
|
defer func() { db.DB = originalDB }()
|
||||||
@@ -395,7 +395,7 @@ func TestReadyHandler_ResponseStructure(t *testing.T) {
|
|||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
defer func() { redisclient.RDB = originalRedis }()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
@@ -403,7 +403,7 @@ func TestReadyHandler_ResponseStructure(t *testing.T) {
|
|||||||
// Verify response is valid JSON
|
// Verify response is valid JSON
|
||||||
var response map[string]interface{}
|
var response map[string]interface{}
|
||||||
if err := json.NewDecoder(w.Body).Decode(&response); err != nil {
|
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
|
// Check that response has expected fields
|
||||||
@@ -412,8 +412,8 @@ func TestReadyHandler_ResponseStructure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHealthHandler_WithCustomHeaders(t *testing.T) {
|
func TestHealthHandlerWithCustomHeaders(t *testing.T) {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/health", nil)
|
req := httptest.NewRequest(http.MethodGet, HealthCheckEndpoint, nil)
|
||||||
req.Header.Set("X-Request-ID", "test-123")
|
req.Header.Set("X-Request-ID", "test-123")
|
||||||
req.Header.Set("User-Agent", "Test-Agent/1.0")
|
req.Header.Set("User-Agent", "Test-Agent/1.0")
|
||||||
|
|
||||||
@@ -421,11 +421,11 @@ func TestHealthHandler_WithCustomHeaders(t *testing.T) {
|
|||||||
HealthHandler(w, req)
|
HealthHandler(w, req)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
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
|
originalDB := db.DB
|
||||||
db.DB = nil
|
db.DB = nil
|
||||||
defer func() { db.DB = originalDB }()
|
defer func() { db.DB = originalDB }()
|
||||||
@@ -439,7 +439,7 @@ func TestReadyHandler_ConcurrentRequests(t *testing.T) {
|
|||||||
|
|
||||||
for i := 0; i < concurrency; i++ {
|
for i := 0; i < concurrency; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
req := httptest.NewRequest(http.MethodGet, "/ready", nil)
|
req := httptest.NewRequest(http.MethodGet, ReadyCheckEndpoint, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
ReadyHandler(w, req)
|
ReadyHandler(w, req)
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TestNewCircuitBreaker(t *testing.T) {
|
|||||||
t.Errorf("timeout = %v, want %v", cb.timeout, tt.timeout)
|
t.Errorf("timeout = %v, want %v", cb.timeout, tt.timeout)
|
||||||
}
|
}
|
||||||
if cb.state != tt.wantState {
|
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 {
|
if cb.resetTimeout != 30*time.Second {
|
||||||
t.Errorf("resetTimeout = %v, want %v", 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)
|
cb := NewCircuitBreaker("test", 3, 1*time.Second)
|
||||||
|
|
||||||
successFn := func() error {
|
successFn := func() error {
|
||||||
@@ -70,15 +70,15 @@ func TestCircuitBreaker_Call_Success(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if GetState(cb) != StateClosed {
|
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)
|
cb := NewCircuitBreaker("test", 3, 1*time.Second)
|
||||||
|
|
||||||
failFn := func() error {
|
failFn := func() error {
|
||||||
return errors.New("service error")
|
return errors.New(ServiceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// First 2 failures - circuit should stay closed
|
// 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")
|
t.Error("Call() expected error, got nil")
|
||||||
}
|
}
|
||||||
if GetState(cb) != StateOpen {
|
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
|
// 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 := NewCircuitBreaker("test", 2, 1*time.Second)
|
||||||
cb.resetTimeout = 100 * time.Millisecond // Shorter reset for testing
|
cb.resetTimeout = 100 * time.Millisecond // Shorter reset for testing
|
||||||
|
|
||||||
failFn := func() error {
|
failFn := func() error {
|
||||||
return errors.New("service error")
|
return errors.New(ServiceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the circuit
|
// Open the circuit
|
||||||
@@ -121,7 +121,7 @@ func TestCircuitBreaker_Call_OpenToHalfOpen(t *testing.T) {
|
|||||||
Call(cb, failFn)
|
Call(cb, failFn)
|
||||||
|
|
||||||
if GetState(cb) != StateOpen {
|
if GetState(cb) != StateOpen {
|
||||||
t.Fatalf("state = %v, want %v", GetState(cb), StateOpen)
|
t.Fatalf(StateMismatchMessage, GetState(cb), StateOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for reset timeout
|
// Wait for reset timeout
|
||||||
@@ -139,16 +139,16 @@ func TestCircuitBreaker_Call_OpenToHalfOpen(t *testing.T) {
|
|||||||
|
|
||||||
// Should now be closed
|
// Should now be closed
|
||||||
if GetState(cb) != StateClosed {
|
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 := NewCircuitBreaker("test", 2, 1*time.Second)
|
||||||
cb.resetTimeout = 100 * time.Millisecond
|
cb.resetTimeout = 100 * time.Millisecond
|
||||||
|
|
||||||
failFn := func() error {
|
failFn := func() error {
|
||||||
return errors.New("service error")
|
return errors.New(ServiceError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the circuit
|
// Open the circuit
|
||||||
@@ -156,7 +156,7 @@ func TestCircuitBreaker_Call_HalfOpenFailReturnsToOpen(t *testing.T) {
|
|||||||
Call(cb, failFn)
|
Call(cb, failFn)
|
||||||
|
|
||||||
if GetState(cb) != StateOpen {
|
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
|
// Wait for reset timeout to transition to HalfOpen
|
||||||
@@ -169,15 +169,15 @@ func TestCircuitBreaker_Call_HalfOpenFailReturnsToOpen(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if GetState(cb) != StateOpen {
|
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)
|
cb := NewCircuitBreaker("test", 5, 1*time.Second)
|
||||||
|
|
||||||
failFn := func() error {
|
failFn := func() error {
|
||||||
return errors.New("service error")
|
return errors.New(ServiceError)
|
||||||
}
|
}
|
||||||
successFn := func() error {
|
successFn := func() error {
|
||||||
return nil
|
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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
setupFunc func(*CircuitBreaker)
|
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)
|
cb := NewCircuitBreaker("test", 2, 1*time.Second)
|
||||||
|
|
||||||
// Open the circuit
|
// Open the circuit
|
||||||
@@ -261,7 +261,7 @@ func TestCircuitBreaker_Reset(t *testing.T) {
|
|||||||
Call(cb, failFn)
|
Call(cb, failFn)
|
||||||
|
|
||||||
if GetState(cb) != StateOpen {
|
if GetState(cb) != StateOpen {
|
||||||
t.Fatalf("state = %v, want %v", GetState(cb), StateOpen)
|
t.Fatalf(StateMismatchMessage, GetState(cb), StateOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the circuit breaker
|
// 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 {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
err *CircuitBreakerError
|
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)
|
cb := NewCircuitBreaker("test", 10, 1*time.Second)
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
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)
|
cb := NewCircuitBreaker("test", 1, 1*time.Second)
|
||||||
|
|
||||||
// Open the circuit
|
// Open the circuit
|
||||||
@@ -407,7 +407,7 @@ func TestCircuitBreaker_OpenCircuitRejectsImmediately(t *testing.T) {
|
|||||||
Call(cb, failFn)
|
Call(cb, failFn)
|
||||||
|
|
||||||
if GetState(cb) != StateOpen {
|
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
|
// 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)
|
cb := NewCircuitBreaker("test", 5, 1*time.Second)
|
||||||
fn := func() error {
|
fn := func() error {
|
||||||
return nil
|
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)
|
cb := NewCircuitBreaker("test", 1, 1*time.Second)
|
||||||
|
|
||||||
// Open the circuit
|
// 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 := NewCircuitBreaker("test", 2, 1*time.Second)
|
||||||
cb.resetTimeout = 100 * time.Millisecond
|
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)
|
cb := NewCircuitBreaker("test", 0, 1*time.Second)
|
||||||
|
|
||||||
// Even one failure should open circuit when maxFailures is 0
|
// 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
|
// Negative maxFailures should be treated as invalid, but won't panic
|
||||||
cb := NewCircuitBreaker("test", -1, 1*time.Second)
|
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 := NewCircuitBreaker("test", 1, 1*time.Nanosecond)
|
||||||
cb.resetTimeout = 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)
|
cb := NewCircuitBreaker("test", 3, 1*time.Second)
|
||||||
|
|
||||||
// Add one failure
|
// 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)
|
cb := NewCircuitBreaker("test", 10, 1*time.Second)
|
||||||
|
|
||||||
concurrency := 100
|
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 := NewCircuitBreaker("test", 1, 1*time.Second)
|
||||||
cb.resetTimeout = 50 * time.Millisecond
|
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)
|
cb := NewCircuitBreaker("test", 3, 1*time.Second)
|
||||||
|
|
||||||
// 2 failures
|
// 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)
|
cb := NewCircuitBreaker("test", 2, 1*time.Second)
|
||||||
|
|
||||||
// Different error types should all count as failures
|
// 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)
|
cb := NewCircuitBreaker("test", 3, 1*time.Second)
|
||||||
|
|
||||||
// Should handle nil function gracefully (though this is a programming error)
|
// Should handle nil function gracefully (though this is a programming error)
|
||||||
@@ -699,7 +699,7 @@ func TestCircuitBreaker_NilFunction(t *testing.T) {
|
|||||||
Call(cb, nil)
|
Call(cb, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCircuitBreaker_LongRunningOperation(t *testing.T) {
|
func TestCircuitBreakerLongRunningOperation(t *testing.T) {
|
||||||
cb := NewCircuitBreaker("test", 2, 100*time.Millisecond)
|
cb := NewCircuitBreaker("test", 2, 100*time.Millisecond)
|
||||||
|
|
||||||
// Test that timeout works during operation
|
// 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 := NewCircuitBreaker("test", 1, 1*time.Second)
|
||||||
cb.resetTimeout = 10 * time.Millisecond
|
cb.resetTimeout = 10 * time.Millisecond
|
||||||
|
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ const (
|
|||||||
ErrorEncodingResponse = "Error encoding response"
|
ErrorEncodingResponse = "Error encoding response"
|
||||||
ErrorFailedtoLogLoginEvent = "Failed to log login event"
|
ErrorFailedtoLogLoginEvent = "Failed to log login event"
|
||||||
WarningLabel = "WARNING:"
|
WarningLabel = "WARNING:"
|
||||||
|
StateMismatchMessage = "state = %v, want %v"
|
||||||
|
ServiceError = "service error"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func TestLogInfo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogInfo_NoEnvironment(t *testing.T) {
|
func TestLogInfoNoEnvironment(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
os.Unsetenv("GO_ENV")
|
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
|
// Setup
|
||||||
os.Setenv("GO_ENV", "development")
|
os.Setenv("GO_ENV", "development")
|
||||||
defer os.Unsetenv("GO_ENV")
|
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
|
// Test that all logging functions check for GO_ENV
|
||||||
originalEnv := os.Getenv("GO_ENV")
|
originalEnv := os.Getenv("GO_ENV")
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func TestGetClientIP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckRateLimit_AllowedRequests(t *testing.T) {
|
func TestCheckRateLimitAllowedRequests(t *testing.T) {
|
||||||
db, mock := redismock.NewClientMock()
|
db, mock := redismock.NewClientMock()
|
||||||
originalRedis := redisclient.RDB
|
originalRedis := redisclient.RDB
|
||||||
redisclient.RDB = db
|
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()
|
db, mock := redismock.NewClientMock()
|
||||||
originalRedis := redisclient.RDB
|
originalRedis := redisclient.RDB
|
||||||
redisclient.RDB = db
|
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()
|
db, mock := redismock.NewClientMock()
|
||||||
originalRedis := redisclient.RDB
|
originalRedis := redisclient.RDB
|
||||||
redisclient.RDB = db
|
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
|
originalRedis := redisclient.RDB
|
||||||
redisclient.RDB = nil
|
redisclient.RDB = nil
|
||||||
defer func() { redisclient.RDB = originalRedis }()
|
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()
|
db, mock := redismock.NewClientMock()
|
||||||
originalRedis := redisclient.RDB
|
originalRedis := redisclient.RDB
|
||||||
redisclient.RDB = db
|
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()
|
db, mock := redismock.NewClientMock()
|
||||||
originalRedis := redisclient.RDB
|
originalRedis := redisclient.RDB
|
||||||
redisclient.RDB = db
|
redisclient.RDB = db
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInit_DefaultValues(t *testing.T) {
|
func TestInitDefaultValues(t *testing.T) {
|
||||||
// Save original values
|
// Save original values
|
||||||
originalHost := os.Getenv("REDIS_HOST")
|
originalHost := os.Getenv("REDIS_HOST")
|
||||||
originalPort := os.Getenv("REDIS_PORT")
|
originalPort := os.Getenv("REDIS_PORT")
|
||||||
|
|||||||
Reference in New Issue
Block a user