package handler import ( "baron-sso-backend/internal/domain" "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gofiber/fiber/v2" "github.com/stretchr/testify/assert" "gorm.io/gorm" ) // Mock DB for ApiKey tests using a real GORM instance but with a hijacked connection // or just a simple mock if we only check nil. // For ApiKeyHandler, it uses DB for Create/List/Delete. func TestApiKeyHandler_CreateApiKey(t *testing.T) { app := fiber.New() // ApiKeyHandler requires a valid DB connection to perform h.DB.Create // Since we don't have a real DB here, we'll check if it fails gracefully // or we can use sqlite in-memory for a more realistic test. h := &ApiKeyHandler{DB: nil} // Testing ServiceUnavailable app.Post("/api-keys", h.CreateApiKey) input := map[string]interface{}{ "name": "M2M Test", "scopes": []string{"read", "write"}, } body, _ := json.Marshal(input) req := httptest.NewRequest("POST", "/api-keys", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, _ := app.Test(req) assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode) } func TestApiKeyHandler_Validation(t *testing.T) { app := fiber.New() // Using a dummy DB pointer to pass the nil check h := &ApiKeyHandler{DB: &gorm.DB{}} app.Post("/api-keys", h.CreateApiKey) // Missing name input := map[string]interface{}{ "scopes": []string{"read"}, } body, _ := json.Marshal(input) req := httptest.NewRequest("POST", "/api-keys", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, _ := app.Test(req) assert.Equal(t, http.StatusBadRequest, resp.StatusCode) } func TestApiKeyHandler_UpdateApiKeyScopesRequiresDatabase(t *testing.T) { app := fiber.New() h := &ApiKeyHandler{DB: nil} app.Patch("/api-keys/:id", h.UpdateApiKey) body, _ := json.Marshal(map[string]interface{}{ "scopes": []string{"org-context:read"}, }) req := httptest.NewRequest("PATCH", "/api-keys/api-key-id", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, _ := app.Test(req) assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode) } func TestApiKeyHandler_RotateApiKeySecretRequiresDatabase(t *testing.T) { app := fiber.New() h := &ApiKeyHandler{DB: nil} app.Post("/api-keys/:id/secret/rotate", h.RotateApiKeySecret) req := httptest.NewRequest("POST", "/api-keys/api-key-id/secret/rotate", nil) resp, _ := app.Test(req) assert.Equal(t, http.StatusServiceUnavailable, resp.StatusCode) } func TestApiKeyWithUpdatedScopesPreservesClientID(t *testing.T) { key := domain.ApiKey{ ID: "api-key-id", Name: "M2M Test", ClientID: "client-id-stable", ClientSecretHash: "old-secret-hash", Scopes: "audit:read", Status: "active", } updated := apiKeyWithUpdatedScopes(key, []string{"audit:read", "org-context:read"}) assert.Equal(t, "client-id-stable", updated.ClientID) assert.Equal(t, "old-secret-hash", updated.ClientSecretHash) assert.Equal(t, "audit:read org-context:read", updated.Scopes) } func TestApiKeyWithRotatedSecretHashPreservesClientIDAndScopes(t *testing.T) { key := domain.ApiKey{ ID: "api-key-id", Name: "M2M Test", ClientID: "client-id-stable", ClientSecretHash: "old-secret-hash", Scopes: "audit:read org-context:read", Status: "active", } updated := apiKeyWithRotatedSecretHash(key, "new-secret-hash") assert.Equal(t, "client-id-stable", updated.ClientID) assert.Equal(t, "audit:read org-context:read", updated.Scopes) assert.Equal(t, "new-secret-hash", updated.ClientSecretHash) } func TestNormalizeApiKeyScopesTrimsAndDeduplicates(t *testing.T) { scopes := normalizeApiKeyScopes([]string{ " audit:read ", "", "org-context:read", "audit:read", }) assert.Equal(t, []string{"audit:read", "org-context:read"}, scopes) }