feat: Add comprehensive API integration tests with authentication, RBAC, and endpoint coverage for all platform handlers

- Add TestSetup struct with DB/Router/AuthService/test users/JWT tokens for test environment initialization
- Add setupTest function with SQLite in-memory database, user/plugin/automation/config/auth session migrations, and test user creation with admin/operator/viewer/user roles
- Add makeRequest helper for authenticated HTTP requests with Bearer token and JSON body support
This commit is contained in:
nutcas3
2026-04-23 02:58:34 +03:00
parent 546c9cd69f
commit e2e39d8b37
6 changed files with 2621 additions and 0 deletions
@@ -0,0 +1,540 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/database"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/middleware"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/services"
)
// TestSetup creates a test environment with database and handlers
type TestSetup struct {
DB *database.Database
Router *gin.Engine
AuthService *services.AuthService
TestUser *models.User
AdminUser *models.User
OperatorUser *models.User
ViewerUser *models.User
JWTToken string
AdminToken string
OperatorToken string
ViewerToken string
}
func setupTest(t *testing.T) *TestSetup {
// Use in-memory SQLite for testing
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
// Migrate only the tables we need for testing
err = db.AutoMigrate(
&models.User{},
&models.Plugin{},
&models.Automation{},
&models.ConfigEntry{},
&models.AuthSession{},
&models.Role{},
&models.Permission{},
)
require.NoError(t, err)
// Create database wrapper
database := &database.Database{DB: db}
// Initialize services
authService := services.NewAuthService(db, "test-secret")
// Create test users with different roles
testUsers := []*models.User{
{Username: "admin", Email: "[email protected]", Password: "password123", Role: "admin", FirstName: "Admin", LastName: "User"},
{Username: "operator", Email: "[email protected]", Password: "password123", Role: "operator", FirstName: "Operator", LastName: "User"},
{Username: "viewer", Email: "[email protected]", Password: "password123", Role: "viewer", FirstName: "Viewer", LastName: "User"},
{Username: "user", Email: "[email protected]", Password: "password123", Role: "user", FirstName: "Regular", LastName: "User"},
}
for _, user := range testUsers {
// Hash the password properly for testing
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
require.NoError(t, err)
user.Password = string(hashedPassword)
err = db.Create(user).Error
require.NoError(t, err)
}
// Generate JWT tokens for each user
adminToken, _, err := authService.GetJWTService().GenerateToken(testUsers[0])
require.NoError(t, err)
operatorToken, _, err := authService.GetJWTService().GenerateToken(testUsers[1])
require.NoError(t, err)
viewerToken, _, err := authService.GetJWTService().GenerateToken(testUsers[2])
require.NoError(t, err)
userToken, _, err := authService.GetJWTService().GenerateToken(testUsers[3])
require.NoError(t, err)
// Setup Gin router
gin.SetMode(gin.TestMode)
router := gin.New()
// Create handlers
servicesHandler := NewServicesHandler(nil) // No K8s in tests
monitoringHandler := NewMonitoringHandler(nil) // No Prometheus in tests
pluginsHandler := NewPluginsHandler(services.NewPluginService(database))
automationHandler := NewAutomationHandler(services.NewAutomationService(database))
configHandler := NewConfigHandler(services.NewConfigStoreService(database))
chaosHandler := NewChaosHandler(services.NewChaosService(database))
authHandler := NewAuthHandler(authService)
// Setup routes with authentication and RBAC
v1 := router.Group("/v1")
{
// Authentication routes
auth := v1.Group("/auth")
{
auth.POST("/login", authHandler.Login)
auth.POST("/register", authHandler.Register)
auth.POST("/refresh", authHandler.RefreshToken)
}
// Protected routes
protected := v1.Group("/")
protected.Use(middleware.AuthMiddleware(authService))
{
// User management (admin only)
users := protected.Group("/users")
users.Use(middleware.RequireRole("admin"))
{
users.GET("", authHandler.GetUsers)
}
// Services Management
svcs := protected.Group("/services")
{
svcs.GET("", servicesHandler.List)
svcsWrite := svcs.Group("/")
svcsWrite.Use(middleware.RequireRole("admin", "operator"))
{
svcsWrite.POST("/:id/restart", servicesHandler.Restart)
}
}
// Monitoring
mon := protected.Group("/monitoring")
{
mon.GET("/metrics", monitoringHandler.Metrics)
}
// Plugin Management
plugs := protected.Group("/plugins")
{
plugs.GET("", pluginsHandler.List)
plugsWrite := plugs.Group("/")
plugsWrite.Use(middleware.RequireRole("admin"))
{
plugsWrite.POST("/install", pluginsHandler.Install)
plugsWrite.POST("/:id/enable", pluginsHandler.Enable)
plugsWrite.POST("/:id/disable", pluginsHandler.Disable)
}
plugsDelete := plugs.Group("/")
plugsDelete.Use(middleware.RequireRole("admin"))
plugsDelete.DELETE("/:id", pluginsHandler.Uninstall)
}
// Automation Management
auto := protected.Group("/automation")
{
auto.GET("", automationHandler.List)
autoWrite := auto.Group("/")
autoWrite.Use(middleware.RequireRole("admin", "operator"))
{
autoWrite.POST("", automationHandler.Create)
}
}
// Configuration Management
cfg := protected.Group("/config")
{
cfg.GET("", configHandler.Get)
cfgWrite := cfg.Group("/")
cfgWrite.Use(middleware.RequireRole("admin"))
{
cfgWrite.POST("", configHandler.Update)
}
}
// Chaos Engineering
chaosGroup := protected.Group("/chaos")
{
chaosGroup.GET("", chaosHandler.List)
chaosWrite := chaosGroup.Group("/")
chaosWrite.Use(middleware.RequireRole("admin"))
{
chaosWrite.POST("", chaosHandler.Run)
}
}
}
}
return &TestSetup{
DB: database,
Router: router,
AuthService: authService,
TestUser: testUsers[3],
AdminUser: testUsers[0],
OperatorUser: testUsers[1],
ViewerUser: testUsers[2],
JWTToken: userToken,
AdminToken: adminToken,
OperatorToken: operatorToken,
ViewerToken: viewerToken,
}
}
// Helper function to make authenticated requests
func (ts *TestSetup) makeRequest(method, path string, body interface{}, token string) *httptest.ResponseRecorder {
var reqBody *bytes.Buffer
if body != nil {
jsonBody, _ := json.Marshal(body)
reqBody = bytes.NewBuffer(jsonBody)
} else {
reqBody = bytes.NewBuffer(nil)
}
req, _ := http.NewRequest(method, path, reqBody)
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
w := httptest.NewRecorder()
ts.Router.ServeHTTP(w, req)
return w
}
// Test Authentication Endpoints
func TestAuthenticationEndpoints(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("Login with valid credentials", func(t *testing.T) {
loginData := map[string]string{
"username": "admin",
"password": "password123",
}
w := ts.makeRequest("POST", "/v1/auth/login", loginData, "")
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response, "access_token")
assert.Contains(t, response, "refresh_token")
})
t.Run("Login with invalid credentials", func(t *testing.T) {
loginData := map[string]string{
"username": "admin",
"password": "wrongpassword",
}
w := ts.makeRequest("POST", "/v1/auth/login", loginData, "")
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("Access protected endpoint without token", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/services", nil, "")
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("Access protected endpoint with valid token", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/services", nil, ts.AdminToken)
// Services endpoint returns 503 since Kubernetes is not configured in tests
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
})
}
// Test RBAC Permissions
func TestRBACPermissions(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("Admin can access user management", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/users", nil, ts.AdminToken)
assert.Equal(t, http.StatusOK, w.Code)
})
t.Run("Operator cannot access user management", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/users", nil, ts.OperatorToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Viewer can read monitoring", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/monitoring/metrics", nil, ts.ViewerToken)
assert.Equal(t, http.StatusServiceUnavailable, w.Code) // Expected due to no Prometheus
})
t.Run("Viewer cannot write to plugins", func(t *testing.T) {
pluginData := map[string]interface{}{
"name": "test-plugin",
"version": "1.0.0",
}
w := ts.makeRequest("POST", "/v1/plugins/install", pluginData, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Admin can write to plugins", func(t *testing.T) {
pluginData := map[string]interface{}{
"name": "test-plugin",
"version": "1.0.0",
}
w := ts.makeRequest("POST", "/v1/plugins/install", pluginData, ts.AdminToken)
// Should not be forbidden (may fail for other reasons like validation)
assert.NotEqual(t, http.StatusForbidden, w.Code)
})
}
// Test Service Endpoints
func TestServiceEndpoints(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("List services", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/services", nil, ts.AdminToken)
assert.Equal(t, http.StatusServiceUnavailable, w.Code) // Expected due to no K8s
})
t.Run("Restart service requires write permissions", func(t *testing.T) {
w := ts.makeRequest("POST", "/v1/services/123/restart", nil, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Restart service with admin permissions", func(t *testing.T) {
w := ts.makeRequest("POST", "/v1/services/123/restart", nil, ts.AdminToken)
assert.Equal(t, http.StatusServiceUnavailable, w.Code) // Expected due to no K8s
})
}
// Test Plugin Endpoints
func TestPluginEndpoints(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("List plugins", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/plugins", nil, ts.AdminToken)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response, "plugins")
})
t.Run("Install plugin without permissions", func(t *testing.T) {
pluginData := map[string]interface{}{
"name": "test-plugin",
"version": "1.0.0",
}
w := ts.makeRequest("POST", "/v1/plugins/install", pluginData, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Uninstall plugin without permissions", func(t *testing.T) {
w := ts.makeRequest("DELETE", "/v1/plugins/1", nil, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
}
// Test Automation Endpoints
func TestAutomationEndpoints(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("List automations", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/automation", nil, ts.AdminToken)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response, "automations")
})
t.Run("Create automation without permissions", func(t *testing.T) {
automationData := map[string]interface{}{
"name": "test-automation",
"description": "Test automation",
"type": "scheduled",
}
w := ts.makeRequest("POST", "/v1/automation/", automationData, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Create automation with operator permissions", func(t *testing.T) {
automationData := map[string]interface{}{
"name": "test-automation",
"description": "Test automation",
"type": "scheduled",
}
w := ts.makeRequest("POST", "/v1/automation/", automationData, ts.OperatorToken)
assert.Equal(t, http.StatusCreated, w.Code)
})
}
// Test Configuration Endpoints
func TestConfigurationEndpoints(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("List configuration", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/config", nil, ts.AdminToken)
assert.Equal(t, http.StatusOK, w.Code)
})
t.Run("Update configuration without permissions", func(t *testing.T) {
configData := map[string]interface{}{
"section": "test",
"key": "test.config",
"value": "test-value",
"type": "string",
}
w := ts.makeRequest("POST", "/v1/config/", configData, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Update configuration with admin permissions", func(t *testing.T) {
configData := map[string]interface{}{
"section": "test",
"key": "test.config",
"value": "test-value",
"type": "string",
}
w := ts.makeRequest("POST", "/v1/config/", configData, ts.AdminToken)
assert.Equal(t, http.StatusOK, w.Code)
})
}
// Test Chaos Engineering Endpoints
func TestChaosEndpoints(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("List chaos experiments", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/chaos", nil, ts.AdminToken)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Contains(t, response, "active")
assert.Contains(t, response, "history")
})
t.Run("Run chaos experiment without permissions", func(t *testing.T) {
chaosData := map[string]interface{}{
"name": "test-chaos",
"type": "pod-delete",
"target": "test-pod",
"duration": "30s",
}
w := ts.makeRequest("POST", "/v1/chaos/", chaosData, ts.ViewerToken)
assert.Equal(t, http.StatusForbidden, w.Code)
})
t.Run("Run chaos experiment with admin permissions", func(t *testing.T) {
chaosData := map[string]interface{}{
"name": "test-chaos",
"type": "pod-delete",
"target": "test-pod",
"duration": "30s",
}
w := ts.makeRequest("POST", "/v1/chaos/", chaosData, ts.AdminToken)
assert.Equal(t, http.StatusCreated, w.Code)
})
}
// Test Error Handling
func TestErrorHandling(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("Invalid JSON payload", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/v1/plugins/install", bytes.NewBufferString("invalid json"))
req.Header.Set("Authorization", "Bearer "+ts.AdminToken)
req.Header.Set("Content-Type", "application/json")
ts.Router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("Missing required fields", func(t *testing.T) {
pluginData := map[string]interface{}{
"version": "1.0.0",
// Missing name field
}
w := ts.makeRequest("POST", "/v1/plugins/install", pluginData, ts.AdminToken)
assert.Equal(t, http.StatusBadRequest, w.Code)
})
t.Run("Invalid token format", func(t *testing.T) {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/v1/services", nil)
req.Header.Set("Authorization", "InvalidFormat token")
ts.Router.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
}
// Test JWT Token Validation
func TestJWTTokenValidation(t *testing.T) {
ts := setupTest(t)
defer ts.DB.Close()
t.Run("Valid token should be accepted", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/services", nil, ts.AdminToken)
assert.NotEqual(t, http.StatusUnauthorized, w.Code)
})
t.Run("Invalid token should be rejected", func(t *testing.T) {
w := ts.makeRequest("GET", "/v1/services", nil, "invalid.token.here")
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
}
// Benchmark Tests
func BenchmarkAPIRequests(b *testing.B) {
ts := setupTest(&testing.T{})
defer ts.DB.Close()
b.Run("ListServices", func(b *testing.B) {
for i := 0; i < b.N; i++ {
w := ts.makeRequest("GET", "/v1/services", nil, ts.AdminToken)
_ = w.Code
}
})
b.Run("ListPlugins", func(b *testing.B) {
for i := 0; i < b.N; i++ {
w := ts.makeRequest("GET", "/v1/plugins", nil, ts.AdminToken)
_ = w.Code
}
})
}
@@ -0,0 +1,576 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/middleware"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/services"
)
func setupTestRouter() (*gin.Engine, *services.AuthService) {
gin.SetMode(gin.TestMode)
// Create test auth service
authService := setupAuthServiceForTests()
// Create auth handler
authHandler := NewAuthHandler(authService)
// Use authHandler to avoid unused variable warning
_ = authHandler
// Setup router
router := gin.New()
// Add auth routes
v1 := router.Group("/v1")
{
// Authentication routes (no auth required)
auth := v1.Group("/auth")
{
auth.POST("/login", authHandler.Login)
auth.POST("/register", authHandler.Register)
auth.POST("/refresh", authHandler.RefreshToken)
}
// Protected routes (authentication required)
protected := v1.Group("/")
protected.Use(middleware.AuthMiddleware(authService))
{
// User management
authProtected := protected.Group("/auth")
{
authProtected.POST("/logout", authHandler.Logout)
authProtected.GET("/profile", authHandler.GetProfile)
authProtected.POST("/change-password", authHandler.ChangePassword)
}
// Admin-only user management
users := protected.Group("/users")
users.Use(middleware.RequireRole("admin"))
{
users.GET("", authHandler.GetUsers)
users.POST("", authHandler.CreateUser)
users.PUT("/:id", authHandler.UpdateUser)
users.DELETE("/:id", authHandler.DeleteUser)
}
}
}
return router, authService
}
func setupAuthServiceForTests() *services.AuthService {
// Use in-memory SQLite for testing
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
panic("Failed to create test database")
}
// Migrate required tables
err = db.AutoMigrate(
&models.User{},
&models.AuthSession{},
&models.Role{},
&models.Permission{},
)
if err != nil {
panic("Failed to migrate test database")
}
// Create auth service with proper database
return services.NewAuthService(db, "test-secret")
}
func TestAuthHandler_Register(t *testing.T) {
router, _ := setupTestRouter()
// Test successful registration
reqBody := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "viewer",
}
body, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "User registered successfully", response["message"])
assert.NotNil(t, response["user"])
}
func TestAuthHandler_Login(t *testing.T) {
router, authService := setupTestRouter()
// Use authService to avoid unused variable warning
_ = authService
// First register a user
reqBody := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "admin",
}
body, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
// Test successful login
loginReq := map[string]any{
"username": "testuser",
"password": "password123",
}
loginBody, _ := json.Marshal(loginReq)
loginReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(loginBody))
loginReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, loginReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var loginResponse map[string]any
err := json.Unmarshal(w.Body.Bytes(), &loginResponse)
require.NoError(t, err)
assert.NotEmpty(t, loginResponse["access_token"])
assert.NotEmpty(t, loginResponse["refresh_token"])
assert.NotNil(t, loginResponse["user"])
}
func TestAuthHandler_LoginInvalidCredentials(t *testing.T) {
router, _ := setupTestRouter()
// Test invalid username
loginReq := map[string]any{
"username": "nonexistent",
"password": "password123",
}
loginBody, _ := json.Marshal(loginReq)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(loginBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "invalid credentials", response["error"])
}
func TestAuthHandler_RefreshToken(t *testing.T) {
router, authService := setupTestRouter()
// Use authService to avoid unused variable warning
_ = authService
// Register and login to get tokens
// First register a user
reqBody := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "admin",
}
body, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
// Login to get tokens
loginReq := map[string]any{
"username": "testuser",
"password": "password123",
}
loginBody, _ := json.Marshal(loginReq)
loginReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(loginBody))
loginReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, loginReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var loginResponse map[string]any
err := json.Unmarshal(w.Body.Bytes(), &loginResponse)
require.NoError(t, err)
refreshToken := loginResponse["refresh_token"].(string)
assert.NotEmpty(t, refreshToken)
// Test token refresh
refreshReq := map[string]any{
"refresh_token": refreshToken,
}
refreshBody, _ := json.Marshal(refreshReq)
refreshReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/refresh", bytes.NewBuffer(refreshBody))
refreshReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, refreshReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var refreshResponse map[string]any
err = json.Unmarshal(w.Body.Bytes(), &refreshResponse)
require.NoError(t, err)
assert.NotEmpty(t, refreshResponse["access_token"])
assert.NotEmpty(t, refreshResponse["refresh_token"])
}
func TestAuthHandler_GetProfile(t *testing.T) {
router, authService := setupTestRouter()
// Use authService to avoid unused variable warning
_ = authService
// Register and login to get token
reqBody := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "admin",
}
body, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
// Login
loginReq := map[string]any{
"username": "testuser",
"password": "password123",
}
loginBody, _ := json.Marshal(loginReq)
loginReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(loginBody))
loginReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, loginReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var loginResponse map[string]any
err := json.Unmarshal(w.Body.Bytes(), &loginResponse)
require.NoError(t, err)
accessToken := loginResponse["access_token"].(string)
// Test getting profile
profileReq := httptest.NewRequest(http.MethodGet, "/v1/auth/profile", nil)
profileReq.Header.Set("Authorization", "Bearer "+accessToken)
w = httptest.NewRecorder()
router.ServeHTTP(w, profileReq)
assert.Equal(t, http.StatusOK, w.Code)
var profileResponse map[string]any
err = json.Unmarshal(w.Body.Bytes(), &profileResponse)
require.NoError(t, err)
assert.Equal(t, "testuser", profileResponse["username"])
assert.Equal(t, "[email protected]", profileResponse["email"])
}
func TestAuthHandler_GetProfileUnauthorized(t *testing.T) {
router, _ := setupTestRouter()
// Test getting profile without token
profileReq := httptest.NewRequest(http.MethodGet, "/v1/auth/profile", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, profileReq)
assert.Equal(t, http.StatusUnauthorized, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "Authorization header required", response["error"])
}
func TestAuthHandler_ChangePassword(t *testing.T) {
router, authService := setupTestRouter()
// Use authService to avoid unused variable warning
_ = authService
// Register and login to get token
reqBody := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "admin",
}
body, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
// Login
loginReq := map[string]any{
"username": "testuser",
"password": "password123",
}
loginBody, _ := json.Marshal(loginReq)
loginReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(loginBody))
loginReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, loginReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var loginResponse map[string]any
err := json.Unmarshal(w.Body.Bytes(), &loginResponse)
require.NoError(t, err)
accessToken := loginResponse["access_token"].(string)
// Test changing password
changePasswordReq := map[string]any{
"old_password": "password123",
"new_password": "newpassword123",
}
changePasswordBody, _ := json.Marshal(changePasswordReq)
changePasswordReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/change-password", bytes.NewBuffer(changePasswordBody))
changePasswordReqHTTP.Header.Set("Authorization", "Bearer "+accessToken)
changePasswordReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, changePasswordReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err = json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "Password changed successfully", response["message"])
}
func TestAuthHandler_Logout(t *testing.T) {
router, authService := setupTestRouter()
// Use authService to avoid unused variable warning
_ = authService
// Register and login to get token
reqBody := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "admin",
}
body, _ := json.Marshal(reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
// Login
loginReq := map[string]any{
"username": "testuser",
"password": "password123",
}
loginBody, _ := json.Marshal(loginReq)
loginReqHTTP := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(loginBody))
loginReqHTTP.Header.Set("Content-Type", "application/json")
w = httptest.NewRecorder()
router.ServeHTTP(w, loginReqHTTP)
assert.Equal(t, http.StatusOK, w.Code)
var loginResponse map[string]any
err := json.Unmarshal(w.Body.Bytes(), &loginResponse)
require.NoError(t, err)
accessToken := loginResponse["access_token"].(string)
// Test logout
logoutReq := httptest.NewRequest(http.MethodPost, "/v1/auth/logout", nil)
logoutReq.Header.Set("Authorization", "Bearer "+accessToken)
w = httptest.NewRecorder()
router.ServeHTTP(w, logoutReq)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err = json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, "Logged out successfully", response["message"])
}
func TestAuthHandler_RegisterValidation(t *testing.T) {
router, _ := setupTestRouter()
testCases := []struct {
name string
reqBody map[string]any
expected int
}{
{
name: "Missing username",
reqBody: map[string]any{
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "viewer",
},
expected: http.StatusBadRequest,
},
{
name: "Invalid email",
reqBody: map[string]any{
"username": "testuser",
"email": "invalid-email",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "viewer",
},
expected: http.StatusBadRequest,
},
{
name: "Short password",
reqBody: map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "123",
"first_name": "Test",
"last_name": "User",
"role": "viewer",
},
expected: http.StatusBadRequest,
},
{
name: "Invalid role",
reqBody: map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "invalid",
},
expected: http.StatusBadRequest,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body, _ := json.Marshal(tc.reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/register", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, tc.expected, w.Code)
})
}
}
func TestAuthHandler_LoginValidation(t *testing.T) {
router, _ := setupTestRouter()
testCases := []struct {
name string
reqBody map[string]any
expected int
}{
{
name: "Missing username",
reqBody: map[string]any{
"password": "password123",
},
expected: http.StatusBadRequest,
},
{
name: "Missing password",
reqBody: map[string]any{
"username": "testuser",
},
expected: http.StatusBadRequest,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
body, _ := json.Marshal(tc.reqBody)
req := httptest.NewRequest(http.MethodPost, "/v1/auth/login", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, tc.expected, w.Code)
})
}
}
@@ -0,0 +1,516 @@
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/database"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/middleware"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
"github.com/nutcas3/telecom-platform/apps/api-server/internal/services"
)
// TestAPIIntegration tests the complete API integration
func TestAPIIntegration(t *testing.T) {
gin.SetMode(gin.TestMode)
// Use in-memory SQLite for testing
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
assert.NoError(t, err)
// Migrate all required tables
err = gormDB.AutoMigrate(
&models.User{},
&models.Plugin{},
&models.Automation{},
&models.ConfigEntry{},
&models.AuthSession{},
&models.Role{},
&models.Permission{},
&models.Transaction{},
)
assert.NoError(t, err)
// Create database wrapper
db := &database.Database{DB: gormDB}
defer db.Close()
// Initialize services
authSvc := services.NewAuthService(db.DB, "test-secret-key")
// Create router
router := gin.New()
// Add health endpoint
router.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"service": "api-server",
})
})
// Add Swagger documentation endpoints
router.GET("/swagger/*any", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "swagger endpoint"})
})
// Initialize other services
chaosSvc := services.NewChaosService(db)
invoiceSvc := services.NewInvoiceService(db)
pluginSvc := services.NewPluginService(db)
automationSvc := services.NewAutomationService(db)
configStoreSvc := services.NewConfigStoreService(db)
deploymentSvc := services.NewDeploymentService(db)
// Build handlers
authH := NewAuthHandler(authSvc)
servicesH := NewServicesHandler(nil) // Kubernetes service will be nil for testing
deploymentsH := NewDeploymentsHandler(deploymentSvc)
pluginsH := NewPluginsHandler(pluginSvc)
automationH := NewAutomationHandler(automationSvc)
configH := NewConfigHandler(configStoreSvc)
chaosH := NewChaosHandler(chaosSvc)
billingH := NewBillingHandler(invoiceSvc, db.DB)
// API v1 routes
v1 := router.Group("/v1")
{
// Authentication routes (no auth required)
auth := v1.Group("/auth")
{
auth.POST("/login", authH.Login)
auth.POST("/register", authH.Register)
auth.POST("/refresh", authH.RefreshToken)
}
// Protected routes (authentication required)
protected := v1.Group("/")
protected.Use(middleware.AuthMiddleware(authSvc))
{
// User management
authProtected := protected.Group("/auth")
{
authProtected.POST("/logout", authH.Logout)
authProtected.GET("/profile", authH.GetProfile)
authProtected.POST("/change-password", authH.ChangePassword)
}
// Admin-only user management
users := protected.Group("/users")
users.Use(middleware.RequireRole("admin"))
{
users.GET("", authH.GetUsers)
users.POST("", authH.CreateUser)
users.PUT("/:id", authH.UpdateUser)
users.DELETE("/:id", authH.DeleteUser)
}
}
// Protected API endpoints (authentication required)
apiProtected := v1.Group("/")
apiProtected.Use(middleware.AuthMiddleware(authSvc))
{
// Services Management
svcs := apiProtected.Group("/services")
{
svcs.GET("", servicesH.List)
svcs.GET("/:id", servicesH.Get)
svcs.POST("/:id/restart", servicesH.Restart)
svcs.GET("/:id/logs", servicesH.Logs)
svcs.POST("/:id/start", servicesH.Start)
svcs.POST("/:id/stop", servicesH.Stop)
svcs.POST("/:id/scale", servicesH.Scale)
svcs.GET("/:id/health", servicesH.Health)
svcs.GET("/:id/pods", servicesH.PodStatus)
svcs.GET("/:id/events", servicesH.Events)
}
// Deployment Management
deploy := apiProtected.Group("/deploy")
{
deploy.GET("/status", deploymentsH.Status)
deploy.POST("/start", deploymentsH.Start)
deploy.POST("/rollback", deploymentsH.Rollback)
deploy.GET("/history", deploymentsH.History)
}
// Plugin Management
plugs := apiProtected.Group("/plugins")
{
plugs.GET("", pluginsH.List)
plugs.POST("/install", pluginsH.Install)
plugs.DELETE("/:id", pluginsH.Uninstall)
plugs.POST("/:id/enable", pluginsH.Enable)
plugs.POST("/:id/disable", pluginsH.Disable)
}
// Automation Management
auto := apiProtected.Group("/automation")
{
auto.GET("", automationH.List)
auto.POST("", automationH.Create)
auto.POST("/:id/run", automationH.Run)
auto.POST("/:id/schedule", automationH.Schedule)
auto.GET("/logs", automationH.Logs)
}
// Billing & Invoicing
bill := apiProtected.Group("/billing")
{
bill.GET("/invoices", billingH.ListInvoices)
bill.POST("/invoices", billingH.GenerateInvoice)
bill.GET("/payments", billingH.ListPayments)
}
// Configuration Management
cfg := apiProtected.Group("/config")
{
cfg.GET("", configH.Get)
cfg.POST("", configH.Update)
cfg.GET("/validate", configH.Validate)
}
// Chaos Engineering
chaosGroup := apiProtected.Group("/chaos")
{
chaosGroup.GET("/experiments", chaosH.List)
chaosGroup.POST("/experiments", chaosH.Run)
chaosGroup.GET("/status", chaosH.Status)
}
}
}
// Test health endpoint
t.Run("Health Endpoint", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/health", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "healthy", response["status"])
assert.Equal(t, "api-server", response["service"])
})
// Test user registration
t.Run("User Registration", func(t *testing.T) {
registerData := map[string]any{
"username": "testuser",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "viewer",
}
requestBody, _ := json.Marshal(registerData)
req, _ := http.NewRequest("POST", "/v1/auth/register", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Debug: print the actual response
t.Logf("Registration response: %+v", response)
assert.Equal(t, "User registered successfully", response["message"])
})
// Test user login
t.Run("User Login", func(t *testing.T) {
loginData := map[string]any{
"username": "testuser",
"password": "password123",
}
requestBody, _ := json.Marshal(loginData)
req, _ := http.NewRequest("POST", "/v1/auth/login", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "access_token")
assert.Contains(t, response, "refresh_token")
// Store token for subsequent tests
token := response["access_token"].(string)
assert.NotEmpty(t, token)
// Test protected endpoint with authentication
t.Run("Protected Endpoint", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/services", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Should return 503 since Kubernetes service is not configured
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, "Kubernetes not configured", response["error"])
})
// Test plugin management endpoint
t.Run("Plugin Management", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/plugins", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "plugins")
assert.Contains(t, response, "total")
})
// Test automation management endpoint
t.Run("Automation Management", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/automation", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "automations")
})
// Test billing endpoint (payments).
// NOTE: /v1/billing/invoices preloads the LineItems relation of the Invoice model,
// which depends on the Subscriber model (embedded PLMN/SNSSAI types can't be migrated
// into SQLite). We exercise the payments endpoint instead, which uses the clean
// Transaction model.
t.Run("Billing Management", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/billing/payments", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "payments")
assert.Contains(t, response, "total")
assert.Contains(t, response, "page")
assert.Contains(t, response, "page_size")
})
// Test configuration endpoint
t.Run("Configuration Management", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/config", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "entries")
})
// Test chaos engineering endpoint
t.Run("Chaos Engineering", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/chaos/experiments", nil)
req.Header.Set("Authorization", "Bearer "+token)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "active")
assert.Contains(t, response, "history")
})
})
}
// TestAuthenticationFlow tests the complete authentication flow
func TestAuthenticationFlow(t *testing.T) {
gin.SetMode(gin.TestMode)
// Use in-memory SQLite for testing
gormDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
assert.NoError(t, err)
// Migrate all required tables
err = gormDB.AutoMigrate(
&models.User{},
&models.Plugin{},
&models.Automation{},
&models.ConfigEntry{},
&models.AuthSession{},
&models.Role{},
&models.Permission{},
&models.Transaction{},
)
assert.NoError(t, err)
// Create database wrapper
db := &database.Database{DB: gormDB}
defer db.Close()
// Initialize services
authSvc := services.NewAuthService(db.DB, "test-secret-key")
authH := NewAuthHandler(authSvc)
// Create router
router := gin.New()
// Authentication routes
auth := router.Group("/v1/auth")
{
auth.POST("/register", authH.Register)
auth.POST("/login", authH.Login)
auth.POST("/refresh", authH.RefreshToken)
}
// Protected routes
protected := router.Group("/v1")
protected.Use(middleware.AuthMiddleware(authSvc))
{
protected.GET("/profile", authH.GetProfile)
protected.POST("/logout", authH.Logout)
}
// Test registration
t.Run("Register User", func(t *testing.T) {
registerData := map[string]any{
"username": "testuser2",
"email": "[email protected]",
"password": "password123",
"first_name": "Test",
"last_name": "User",
"role": "viewer",
}
requestBody, _ := json.Marshal(registerData)
req, _ := http.NewRequest("POST", "/v1/auth/register", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
})
// Test login with invalid credentials
t.Run("Invalid Login", func(t *testing.T) {
loginData := map[string]any{
"username": "testuser2",
"password": "wrongpassword",
}
requestBody, _ := json.Marshal(loginData)
req, _ := http.NewRequest("POST", "/v1/auth/login", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
// Test successful login
t.Run("Successful Login", func(t *testing.T) {
loginData := map[string]any{
"username": "testuser2",
"password": "password123",
}
requestBody, _ := json.Marshal(loginData)
req, _ := http.NewRequest("POST", "/v1/auth/login", bytes.NewBuffer(requestBody))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]any
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Contains(t, response, "access_token")
token := response["access_token"].(string)
assert.NotEmpty(t, token)
// Test accessing protected endpoint with valid token
req, _ = http.NewRequest("GET", "/v1/profile", nil)
req.Header.Set("Authorization", "Bearer "+token)
w = httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var profileResponse map[string]any
err = json.Unmarshal(w.Body.Bytes(), &profileResponse)
assert.NoError(t, err)
assert.Equal(t, "testuser2", profileResponse["username"])
assert.Equal(t, "[email protected]", profileResponse["email"])
})
// Test accessing protected endpoint without token
t.Run("Unauthorized Access", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/profile", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
// Test accessing protected endpoint with invalid token
t.Run("Invalid Token", func(t *testing.T) {
req, _ := http.NewRequest("GET", "/v1/profile", nil)
req.Header.Set("Authorization", "Bearer invalid-token")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
})
}