Initial commit: breakpilot-core - Shared Infrastructure
Docker Compose with 24+ services: - PostgreSQL (PostGIS), Valkey, MinIO, Qdrant - Vault (PKI/TLS), Nginx (Reverse Proxy) - Backend Core API, Consent Service, Billing Service - RAG Service, Embedding Service - Gitea, Woodpecker CI/CD - Night Scheduler, Health Aggregator - Jitsi (Web/XMPP/JVB/Jicofo), Mailpit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
805
consent-service/internal/handlers/handlers_test.go
Normal file
805
consent-service/internal/handlers/handlers_test.go
Normal file
@@ -0,0 +1,805 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gin.SetMode(gin.TestMode)
|
||||
}
|
||||
|
||||
// setupTestRouter creates a test router with handlers
|
||||
// Note: For full integration tests, use a test database
|
||||
func setupTestRouter() *gin.Engine {
|
||||
router := gin.New()
|
||||
return router
|
||||
}
|
||||
|
||||
// TestHealthEndpoint tests the health check endpoint
|
||||
func TestHealthEndpoint(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// Add health endpoint
|
||||
router.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "healthy",
|
||||
"service": "consent-service",
|
||||
"version": "1.0.0",
|
||||
})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "/health", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &response)
|
||||
|
||||
if response["status"] != "healthy" {
|
||||
t.Errorf("Expected status 'healthy', got %v", response["status"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnauthorizedAccess tests that protected endpoints require auth
|
||||
func TestUnauthorizedAccess(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// Add a protected endpoint
|
||||
router.GET("/api/v1/consent/my", func(c *gin.Context) {
|
||||
auth := c.GetHeader("Authorization")
|
||||
if auth == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization required"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"consents": []interface{}{}})
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
authorization string
|
||||
expectedStatus int
|
||||
}{
|
||||
{"no auth header", "", http.StatusUnauthorized},
|
||||
{"empty bearer", "Bearer ", http.StatusOK}, // Would be invalid in real middleware
|
||||
{"valid format", "Bearer test-token", http.StatusOK},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/api/v1/consent/my", nil)
|
||||
if tt.authorization != "" {
|
||||
req.Header.Set("Authorization", tt.authorization)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateConsentRequest tests consent creation request validation
|
||||
func TestCreateConsentRequest(t *testing.T) {
|
||||
type ConsentRequest struct {
|
||||
DocumentType string `json:"document_type"`
|
||||
VersionID string `json:"version_id"`
|
||||
Consented bool `json:"consented"`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
request ConsentRequest
|
||||
expectValid bool
|
||||
}{
|
||||
{
|
||||
name: "valid consent",
|
||||
request: ConsentRequest{
|
||||
DocumentType: "terms",
|
||||
VersionID: "123e4567-e89b-12d3-a456-426614174000",
|
||||
Consented: true,
|
||||
},
|
||||
expectValid: true,
|
||||
},
|
||||
{
|
||||
name: "missing document type",
|
||||
request: ConsentRequest{
|
||||
VersionID: "123e4567-e89b-12d3-a456-426614174000",
|
||||
Consented: true,
|
||||
},
|
||||
expectValid: false,
|
||||
},
|
||||
{
|
||||
name: "missing version ID",
|
||||
request: ConsentRequest{
|
||||
DocumentType: "terms",
|
||||
Consented: true,
|
||||
},
|
||||
expectValid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := tt.request.DocumentType != "" && tt.request.VersionID != ""
|
||||
if isValid != tt.expectValid {
|
||||
t.Errorf("Expected valid=%v, got %v", tt.expectValid, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDocumentTypeValidation tests valid document types
|
||||
func TestDocumentTypeValidation(t *testing.T) {
|
||||
validTypes := map[string]bool{
|
||||
"terms": true,
|
||||
"privacy": true,
|
||||
"cookies": true,
|
||||
"community_guidelines": true,
|
||||
"imprint": true,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
docType string
|
||||
expected bool
|
||||
}{
|
||||
{"terms", true},
|
||||
{"privacy", true},
|
||||
{"cookies", true},
|
||||
{"community_guidelines", true},
|
||||
{"imprint", true},
|
||||
{"invalid", false},
|
||||
{"", false},
|
||||
{"Terms", false}, // case sensitive
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.docType, func(t *testing.T) {
|
||||
_, isValid := validTypes[tt.docType]
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %s valid=%v, got %v", tt.docType, tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestVersionStatusTransitions tests valid status transitions
|
||||
func TestVersionStatusTransitions(t *testing.T) {
|
||||
validTransitions := map[string][]string{
|
||||
"draft": {"review"},
|
||||
"review": {"approved", "rejected"},
|
||||
"approved": {"scheduled", "published"},
|
||||
"scheduled": {"published"},
|
||||
"published": {"archived"},
|
||||
"rejected": {"draft"},
|
||||
"archived": {}, // terminal state
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
fromStatus string
|
||||
toStatus string
|
||||
expected bool
|
||||
}{
|
||||
{"draft", "review", true},
|
||||
{"draft", "published", false},
|
||||
{"review", "approved", true},
|
||||
{"review", "rejected", true},
|
||||
{"review", "published", false},
|
||||
{"approved", "published", true},
|
||||
{"approved", "scheduled", true},
|
||||
{"published", "archived", true},
|
||||
{"published", "draft", false},
|
||||
{"archived", "draft", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.fromStatus+"->"+tt.toStatus, func(t *testing.T) {
|
||||
allowed := false
|
||||
if transitions, ok := validTransitions[tt.fromStatus]; ok {
|
||||
for _, t := range transitions {
|
||||
if t == tt.toStatus {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if allowed != tt.expected {
|
||||
t.Errorf("Transition %s->%s: expected %v, got %v",
|
||||
tt.fromStatus, tt.toStatus, tt.expected, allowed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRolePermissions tests role-based access control
|
||||
func TestRolePermissions(t *testing.T) {
|
||||
permissions := map[string]map[string]bool{
|
||||
"user": {
|
||||
"view_documents": true,
|
||||
"give_consent": true,
|
||||
"view_own_data": true,
|
||||
"request_deletion": true,
|
||||
"create_document": false,
|
||||
"publish_version": false,
|
||||
"approve_version": false,
|
||||
},
|
||||
"admin": {
|
||||
"view_documents": true,
|
||||
"give_consent": true,
|
||||
"view_own_data": true,
|
||||
"create_document": true,
|
||||
"edit_version": true,
|
||||
"publish_version": true,
|
||||
"approve_version": false, // Only DSB
|
||||
},
|
||||
"data_protection_officer": {
|
||||
"view_documents": true,
|
||||
"create_document": true,
|
||||
"edit_version": true,
|
||||
"approve_version": true,
|
||||
"publish_version": true,
|
||||
"view_audit_log": true,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
role string
|
||||
action string
|
||||
shouldHave bool
|
||||
}{
|
||||
{"user", "view_documents", true},
|
||||
{"user", "create_document", false},
|
||||
{"admin", "create_document", true},
|
||||
{"admin", "approve_version", false},
|
||||
{"data_protection_officer", "approve_version", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.role+":"+tt.action, func(t *testing.T) {
|
||||
rolePerms, ok := permissions[tt.role]
|
||||
if !ok {
|
||||
t.Fatalf("Unknown role: %s", tt.role)
|
||||
}
|
||||
|
||||
hasPermission := rolePerms[tt.action]
|
||||
if hasPermission != tt.shouldHave {
|
||||
t.Errorf("Role %s action %s: expected %v, got %v",
|
||||
tt.role, tt.action, tt.shouldHave, hasPermission)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestJSONResponseFormat tests that responses have correct format
|
||||
func TestJSONResponseFormat(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
router.GET("/api/test", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": gin.H{
|
||||
"id": "123",
|
||||
"name": "Test",
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
contentType := w.Header().Get("Content-Type")
|
||||
if contentType != "application/json; charset=utf-8" {
|
||||
t.Errorf("Expected Content-Type 'application/json; charset=utf-8', got %s", contentType)
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
if err != nil {
|
||||
t.Fatalf("Response should be valid JSON: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestErrorResponseFormat tests error response format
|
||||
func TestErrorResponseFormat(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
router.GET("/api/error", func(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "Bad Request",
|
||||
"message": "Invalid input",
|
||||
})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/error", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
json.Unmarshal(w.Body.Bytes(), &response)
|
||||
|
||||
if response["error"] == nil {
|
||||
t.Error("Error response should contain 'error' field")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCookieCategoryValidation tests cookie category validation
|
||||
func TestCookieCategoryValidation(t *testing.T) {
|
||||
mandatoryCategories := []string{"necessary"}
|
||||
optionalCategories := []string{"functional", "analytics", "marketing"}
|
||||
|
||||
// Necessary should always be consented
|
||||
for _, cat := range mandatoryCategories {
|
||||
t.Run("mandatory_"+cat, func(t *testing.T) {
|
||||
// Business rule: mandatory categories cannot be declined
|
||||
isMandatory := true
|
||||
if !isMandatory {
|
||||
t.Errorf("Category %s should be mandatory", cat)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Optional categories can be toggled
|
||||
for _, cat := range optionalCategories {
|
||||
t.Run("optional_"+cat, func(t *testing.T) {
|
||||
isMandatory := false
|
||||
if isMandatory {
|
||||
t.Errorf("Category %s should not be mandatory", cat)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPaginationParams tests pagination parameter handling
|
||||
func TestPaginationParams(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
page int
|
||||
perPage int
|
||||
expPage int
|
||||
expLimit int
|
||||
}{
|
||||
{"defaults", 0, 0, 1, 50},
|
||||
{"page 1", 1, 10, 1, 10},
|
||||
{"page 5", 5, 20, 5, 20},
|
||||
{"negative page", -1, 10, 1, 10}, // should default
|
||||
{"too large per_page", 1, 500, 1, 100}, // should cap
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
page := tt.page
|
||||
perPage := tt.perPage
|
||||
|
||||
// Apply defaults and limits
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if perPage < 1 {
|
||||
perPage = 50
|
||||
}
|
||||
if perPage > 100 {
|
||||
perPage = 100
|
||||
}
|
||||
|
||||
if page != tt.expPage {
|
||||
t.Errorf("Expected page %d, got %d", tt.expPage, page)
|
||||
}
|
||||
if perPage != tt.expLimit {
|
||||
t.Errorf("Expected perPage %d, got %d", tt.expLimit, perPage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestIPAddressExtraction tests IP address extraction from requests
|
||||
func TestIPAddressExtraction(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
xForwarded string
|
||||
remoteAddr string
|
||||
expected string
|
||||
}{
|
||||
{"direct connection", "", "192.168.1.1:1234", "192.168.1.1"},
|
||||
{"behind proxy", "10.0.0.1", "192.168.1.1:1234", "10.0.0.1"},
|
||||
{"multiple proxies", "10.0.0.1, 10.0.0.2", "192.168.1.1:1234", "10.0.0.1"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
var extractedIP string
|
||||
|
||||
router.GET("/test", func(c *gin.Context) {
|
||||
if xf := c.GetHeader("X-Forwarded-For"); xf != "" {
|
||||
// Take first IP from list
|
||||
for i, ch := range xf {
|
||||
if ch == ',' {
|
||||
extractedIP = xf[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if extractedIP == "" {
|
||||
extractedIP = xf
|
||||
}
|
||||
} else {
|
||||
// Extract IP from RemoteAddr
|
||||
addr := c.Request.RemoteAddr
|
||||
for i := len(addr) - 1; i >= 0; i-- {
|
||||
if addr[i] == ':' {
|
||||
extractedIP = addr[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"ip": extractedIP})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "/test", nil)
|
||||
req.RemoteAddr = tt.remoteAddr
|
||||
if tt.xForwarded != "" {
|
||||
req.Header.Set("X-Forwarded-For", tt.xForwarded)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if extractedIP != tt.expected {
|
||||
t.Errorf("Expected IP %s, got %s", tt.expected, extractedIP)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequestBodySizeLimit tests that large requests are rejected
|
||||
func TestRequestBodySizeLimit(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// Simulate a body size limit check
|
||||
maxBodySize := int64(1024 * 1024) // 1MB
|
||||
|
||||
router.POST("/api/upload", func(c *gin.Context) {
|
||||
if c.Request.ContentLength > maxBodySize {
|
||||
c.JSON(http.StatusRequestEntityTooLarge, gin.H{
|
||||
"error": "Request body too large",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
contentLength int64
|
||||
expectedStatus int
|
||||
}{
|
||||
{"small body", 1000, http.StatusOK},
|
||||
{"medium body", 500000, http.StatusOK},
|
||||
{"exactly at limit", maxBodySize, http.StatusOK},
|
||||
{"over limit", maxBodySize + 1, http.StatusRequestEntityTooLarge},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
body := bytes.NewReader(make([]byte, 0))
|
||||
req, _ := http.NewRequest("POST", "/api/upload", body)
|
||||
req.ContentLength = tt.contentLength
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// EXTENDED HANDLER TESTS
|
||||
// ========================================
|
||||
|
||||
// TestAuthHandlers tests authentication endpoints
|
||||
func TestAuthHandlers(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// Register endpoint
|
||||
router.POST("/api/v1/auth/register", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "User registered"})
|
||||
})
|
||||
|
||||
// Login endpoint
|
||||
router.POST("/api/v1/auth/login", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"access_token": "token123"})
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
method string
|
||||
body interface{}
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "register - valid",
|
||||
endpoint: "/api/v1/auth/register",
|
||||
method: "POST",
|
||||
body: map[string]string{"email": "test@example.com", "password": "password123"},
|
||||
expectedStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "login - valid",
|
||||
endpoint: "/api/v1/auth/login",
|
||||
method: "POST",
|
||||
body: map[string]string{"email": "test@example.com", "password": "password123"},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
jsonBody, _ := json.Marshal(tt.body)
|
||||
req, _ := http.NewRequest(tt.method, tt.endpoint, bytes.NewBuffer(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDocumentHandlers tests document endpoints
|
||||
func TestDocumentHandlers(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// GET documents
|
||||
router.GET("/api/v1/documents", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"documents": []interface{}{}})
|
||||
})
|
||||
|
||||
// GET document by type
|
||||
router.GET("/api/v1/documents/:type", func(c *gin.Context) {
|
||||
docType := c.Param("type")
|
||||
if docType == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid type"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"id": "123", "type": docType})
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
expectedStatus int
|
||||
}{
|
||||
{"get all documents", "/api/v1/documents", http.StatusOK},
|
||||
{"get terms", "/api/v1/documents/terms", http.StatusOK},
|
||||
{"get privacy", "/api/v1/documents/privacy", http.StatusOK},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", tt.endpoint, nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestConsentHandlers tests consent endpoints
|
||||
func TestConsentHandlers(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// Create consent
|
||||
router.POST("/api/v1/consent", func(c *gin.Context) {
|
||||
var req struct {
|
||||
VersionID string `json:"version_id"`
|
||||
Consented bool `json:"consented"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "Consent saved"})
|
||||
})
|
||||
|
||||
// Check consent
|
||||
router.GET("/api/v1/consent/check/:type", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"has_consent": true, "needs_update": false})
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
endpoint string
|
||||
method string
|
||||
body interface{}
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "create consent",
|
||||
endpoint: "/api/v1/consent",
|
||||
method: "POST",
|
||||
body: map[string]interface{}{"version_id": "123", "consented": true},
|
||||
expectedStatus: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "check consent",
|
||||
endpoint: "/api/v1/consent/check/terms",
|
||||
method: "GET",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var req *http.Request
|
||||
if tt.body != nil {
|
||||
jsonBody, _ := json.Marshal(tt.body)
|
||||
req, _ = http.NewRequest(tt.method, tt.endpoint, bytes.NewBuffer(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else {
|
||||
req, _ = http.NewRequest(tt.method, tt.endpoint, nil)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdminHandlers tests admin endpoints
|
||||
func TestAdminHandlers(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
// Create document (admin only)
|
||||
router.POST("/api/v1/admin/documents", func(c *gin.Context) {
|
||||
auth := c.GetHeader("Authorization")
|
||||
if auth != "Bearer admin-token" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "Admin only"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "Document created"})
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
expectedStatus int
|
||||
}{
|
||||
{"admin token", "Bearer admin-token", http.StatusCreated},
|
||||
{"user token", "Bearer user-token", http.StatusForbidden},
|
||||
{"no token", "", http.StatusForbidden},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
body := map[string]string{"type": "terms", "name": "Test"}
|
||||
jsonBody, _ := json.Marshal(body)
|
||||
req, _ := http.NewRequest("POST", "/api/v1/admin/documents", bytes.NewBuffer(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if tt.token != "" {
|
||||
req.Header.Set("Authorization", tt.token)
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != tt.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCORSHeaders tests CORS headers
|
||||
func TestCORSHeaders(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Next()
|
||||
})
|
||||
|
||||
router.GET("/api/test", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "test"})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Header().Get("Access-Control-Allow-Origin") != "*" {
|
||||
t.Error("CORS headers not set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRateLimiting tests rate limiting logic
|
||||
func TestRateLimiting(t *testing.T) {
|
||||
requests := 0
|
||||
limit := 5
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
requests++
|
||||
if requests > limit {
|
||||
// Would return 429 Too Many Requests
|
||||
if requests <= limit {
|
||||
t.Error("Rate limit not enforced")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmailTemplateHandlers tests email template endpoints
|
||||
func TestEmailTemplateHandlers(t *testing.T) {
|
||||
router := setupTestRouter()
|
||||
|
||||
router.GET("/api/v1/admin/email-templates", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"templates": []interface{}{}})
|
||||
})
|
||||
|
||||
router.POST("/api/v1/admin/email-templates/test", func(c *gin.Context) {
|
||||
var req struct {
|
||||
Recipient string `json:"recipient"`
|
||||
VersionID string `json:"version_id"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Test email sent"})
|
||||
})
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/v1/admin/email-templates", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user