This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

547 lines
14 KiB
Go

package middleware
import (
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
func init() {
gin.SetMode(gin.TestMode)
}
// Helper to create a valid JWT token for testing
func createTestToken(secret string, userID, email, role string, exp time.Time) string {
claims := UserClaims{
UserID: userID,
Email: email,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(exp),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := token.SignedString([]byte(secret))
return tokenString
}
// TestCORS tests the CORS middleware
func TestCORS(t *testing.T) {
router := gin.New()
router.Use(CORS())
router.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
})
tests := []struct {
name string
origin string
method string
expectedStatus int
expectAllowedOrigin bool
}{
{"localhost:3000", "http://localhost:3000", "GET", http.StatusOK, true},
{"localhost:8000", "http://localhost:8000", "GET", http.StatusOK, true},
{"production", "https://breakpilot.app", "GET", http.StatusOK, true},
{"unknown origin", "https://unknown.com", "GET", http.StatusOK, false},
{"preflight", "http://localhost:3000", "OPTIONS", http.StatusNoContent, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest(tt.method, "/test", nil)
req.Header.Set("Origin", tt.origin)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != tt.expectedStatus {
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
}
allowedOrigin := w.Header().Get("Access-Control-Allow-Origin")
if tt.expectAllowedOrigin && allowedOrigin != tt.origin {
t.Errorf("Expected Access-Control-Allow-Origin to be %s, got %s", tt.origin, allowedOrigin)
}
if !tt.expectAllowedOrigin && allowedOrigin != "" {
t.Errorf("Expected no Access-Control-Allow-Origin header, got %s", allowedOrigin)
}
})
}
}
// TestCORSHeaders tests that CORS headers are set correctly
func TestCORSHeaders(t *testing.T) {
router := gin.New()
router.Use(CORS())
router.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/test", nil)
req.Header.Set("Origin", "http://localhost:3000")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
expectedHeaders := map[string]string{
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, Authorization, X-Requested-With",
"Access-Control-Allow-Credentials": "true",
"Access-Control-Max-Age": "86400",
}
for header, expected := range expectedHeaders {
actual := w.Header().Get(header)
if actual != expected {
t.Errorf("Expected %s to be %s, got %s", header, expected, actual)
}
}
}
// TestAuthMiddleware_ValidToken tests authentication with valid token
func TestAuthMiddleware_ValidToken(t *testing.T) {
secret := "test-secret-key"
userID := uuid.New().String()
email := "test@example.com"
role := "user"
router := gin.New()
router.Use(AuthMiddleware(secret))
router.GET("/protected", func(c *gin.Context) {
uid, _ := c.Get("user_id")
em, _ := c.Get("email")
r, _ := c.Get("role")
c.JSON(http.StatusOK, gin.H{
"user_id": uid,
"email": em,
"role": r,
})
})
token := createTestToken(secret, userID, email, role, time.Now().Add(time.Hour))
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status %d, got %d", http.StatusOK, w.Code)
}
}
// TestAuthMiddleware_MissingHeader tests authentication without header
func TestAuthMiddleware_MissingHeader(t *testing.T) {
router := gin.New()
router.Use(AuthMiddleware("test-secret"))
router.GET("/protected", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/protected", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
}
}
// TestAuthMiddleware_InvalidFormat tests authentication with invalid header format
func TestAuthMiddleware_InvalidFormat(t *testing.T) {
router := gin.New()
router.Use(AuthMiddleware("test-secret"))
router.GET("/protected", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
tests := []struct {
name string
header string
}{
{"no Bearer prefix", "some-token"},
{"Basic auth", "Basic dXNlcjpwYXNz"},
{"empty Bearer", "Bearer "},
{"multiple spaces", "Bearer token"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", tt.header)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
}
})
}
}
// TestAuthMiddleware_ExpiredToken tests authentication with expired token
func TestAuthMiddleware_ExpiredToken(t *testing.T) {
secret := "test-secret"
router := gin.New()
router.Use(AuthMiddleware(secret))
router.GET("/protected", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
// Create expired token
token := createTestToken(secret, "user-123", "test@example.com", "user", time.Now().Add(-time.Hour))
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
}
}
// TestAuthMiddleware_WrongSecret tests authentication with wrong secret
func TestAuthMiddleware_WrongSecret(t *testing.T) {
router := gin.New()
router.Use(AuthMiddleware("correct-secret"))
router.GET("/protected", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
// Create token with different secret
token := createTestToken("wrong-secret", "user-123", "test@example.com", "user", time.Now().Add(time.Hour))
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
}
}
// TestAdminOnly tests the AdminOnly middleware
func TestAdminOnly(t *testing.T) {
tests := []struct {
name string
role string
expectedStatus int
}{
{"admin allowed", "admin", http.StatusOK},
{"super_admin allowed", "super_admin", http.StatusOK},
{"dpo allowed", "data_protection_officer", http.StatusOK},
{"user forbidden", "user", http.StatusForbidden},
{"empty role forbidden", "", http.StatusForbidden},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("role", tt.role)
c.Next()
})
router.Use(AdminOnly())
router.GET("/admin", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/admin", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != tt.expectedStatus {
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
}
})
}
}
// TestAdminOnly_NoRole tests AdminOnly when role is not set
func TestAdminOnly_NoRole(t *testing.T) {
router := gin.New()
router.Use(AdminOnly())
router.GET("/admin", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/admin", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status %d, got %d", http.StatusUnauthorized, w.Code)
}
}
// TestDSBOnly tests the DSBOnly middleware
func TestDSBOnly(t *testing.T) {
tests := []struct {
name string
role string
expectedStatus int
}{
{"dpo allowed", "data_protection_officer", http.StatusOK},
{"super_admin allowed", "super_admin", http.StatusOK},
{"admin forbidden", "admin", http.StatusForbidden},
{"user forbidden", "user", http.StatusForbidden},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := gin.New()
router.Use(func(c *gin.Context) {
c.Set("role", tt.role)
c.Next()
})
router.Use(DSBOnly())
router.GET("/dsb", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/dsb", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
if w.Code != tt.expectedStatus {
t.Errorf("Expected status %d, got %d", tt.expectedStatus, w.Code)
}
})
}
}
// TestIsAdmin tests the IsAdmin helper function
func TestIsAdmin(t *testing.T) {
tests := []struct {
name string
role string
expected bool
}{
{"admin", "admin", true},
{"super_admin", "super_admin", true},
{"dpo", "data_protection_officer", true},
{"user", "user", false},
{"empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
if tt.role != "" {
c.Set("role", tt.role)
}
result := IsAdmin(c)
if result != tt.expected {
t.Errorf("Expected IsAdmin to be %v, got %v", tt.expected, result)
}
})
}
}
// TestIsDSB tests the IsDSB helper function
func TestIsDSB(t *testing.T) {
tests := []struct {
name string
role string
expected bool
}{
{"dpo", "data_protection_officer", true},
{"super_admin", "super_admin", true},
{"admin", "admin", false},
{"user", "user", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("role", tt.role)
result := IsDSB(c)
if result != tt.expected {
t.Errorf("Expected IsDSB to be %v, got %v", tt.expected, result)
}
})
}
}
// TestGetUserID tests the GetUserID helper function
func TestGetUserID(t *testing.T) {
validUUID := uuid.New()
tests := []struct {
name string
userID string
setUserID bool
expectError bool
expectedID uuid.UUID
}{
{"valid UUID", validUUID.String(), true, false, validUUID},
{"invalid UUID", "not-a-uuid", true, true, uuid.Nil},
{"missing user_id", "", false, false, uuid.Nil},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
if tt.setUserID {
c.Set("user_id", tt.userID)
}
result, err := GetUserID(c)
if tt.expectError && err == nil {
t.Error("Expected error but got none")
}
if !tt.expectError && result != tt.expectedID {
t.Errorf("Expected %v, got %v", tt.expectedID, result)
}
})
}
}
// TestGetClientIP tests the GetClientIP helper function
func TestGetClientIP(t *testing.T) {
tests := []struct {
name string
xff string
xri string
clientIP string
expectedIP string
}{
{"X-Forwarded-For", "10.0.0.1", "", "192.168.1.1", "10.0.0.1"},
{"X-Forwarded-For multiple", "10.0.0.1, 10.0.0.2", "", "192.168.1.1", "10.0.0.1"},
{"X-Real-IP", "", "10.0.0.1", "192.168.1.1", "10.0.0.1"},
{"direct", "", "", "192.168.1.1", "192.168.1.1"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
if tt.xff != "" {
c.Request.Header.Set("X-Forwarded-For", tt.xff)
}
if tt.xri != "" {
c.Request.Header.Set("X-Real-IP", tt.xri)
}
c.Request.RemoteAddr = tt.clientIP + ":12345"
result := GetClientIP(c)
// Note: gin.ClientIP() might return different values
// depending on trusted proxies config
if result != tt.expectedIP && result != tt.clientIP {
t.Logf("Note: GetClientIP returned %s (expected %s or %s)", result, tt.expectedIP, tt.clientIP)
}
})
}
}
// TestGetUserAgent tests the GetUserAgent helper function
func TestGetUserAgent(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request, _ = http.NewRequest("GET", "/", nil)
expectedUA := "Mozilla/5.0 (Test)"
c.Request.Header.Set("User-Agent", expectedUA)
result := GetUserAgent(c)
if result != expectedUA {
t.Errorf("Expected %s, got %s", expectedUA, result)
}
}
// TestIsSuspended tests the IsSuspended helper function
func TestIsSuspended(t *testing.T) {
tests := []struct {
name string
suspended interface{}
setSuspended bool
expected bool
}{
{"suspended true", true, true, true},
{"suspended false", false, true, false},
{"not set", nil, false, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
if tt.setSuspended {
c.Set("account_suspended", tt.suspended)
}
result := IsSuspended(c)
if result != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
}
})
}
}
// BenchmarkCORS benchmarks the CORS middleware
func BenchmarkCORS(b *testing.B) {
router := gin.New()
router.Use(CORS())
router.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/test", nil)
req.Header.Set("Origin", "http://localhost:3000")
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
}
}
// BenchmarkAuthMiddleware benchmarks the auth middleware
func BenchmarkAuthMiddleware(b *testing.B) {
secret := "test-secret-key"
token := createTestToken(secret, uuid.New().String(), "test@example.com", "user", time.Now().Add(time.Hour))
router := gin.New()
router.Use(AuthMiddleware(secret))
router.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
})
req, _ := http.NewRequest("GET", "/test", nil)
req.Header.Set("Authorization", "Bearer "+token)
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
}
}