mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-28 07:32:15 +00:00
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:
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user