mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Implement admin handlers integration test
This commit is contained in:
@@ -15,14 +15,16 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type createUserRequest struct {
|
||||
// CreateUserRequest holds the request fields for creating a new user
|
||||
type CreateUserRequest struct {
|
||||
Email string `json:"email"`
|
||||
DisplayName string `json:"displayName"`
|
||||
Password string `json:"password"`
|
||||
Role models.UserRole `json:"role"`
|
||||
}
|
||||
|
||||
type updateUserRequest struct {
|
||||
// UpdateUserRequest holds the request fields for updating a user
|
||||
type UpdateUserRequest struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
DisplayName string `json:"displayName,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
@@ -45,7 +47,7 @@ func (h *Handler) AdminListUsers() http.HandlerFunc {
|
||||
// AdminCreateUser creates a new user
|
||||
func (h *Handler) AdminCreateUser() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req createUserRequest
|
||||
var req CreateUserRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
@@ -136,7 +138,7 @@ func (h *Handler) AdminUpdateUser() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
var req updateUserRequest
|
||||
var req UpdateUserRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
|
||||
243
server/internal/handlers/admin_handlers_integration_test.go
Normal file
243
server/internal/handlers/admin_handlers_integration_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
//go:build integration
|
||||
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"novamd/internal/handlers"
|
||||
"novamd/internal/models"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Helper function to check if a user exists in a slice of users
|
||||
func containsUser(users []*models.User, searchUser *models.User) bool {
|
||||
for _, u := range users {
|
||||
if u.ID == searchUser.ID &&
|
||||
u.Email == searchUser.Email &&
|
||||
u.DisplayName == searchUser.DisplayName &&
|
||||
u.Role == searchUser.Role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func TestAdminHandlers_Integration(t *testing.T) {
|
||||
h := setupTestHarness(t)
|
||||
defer h.teardown(t)
|
||||
|
||||
t.Run("user management", func(t *testing.T) {
|
||||
t.Run("list users", func(t *testing.T) {
|
||||
// Test with admin token
|
||||
rr := h.makeRequest(t, http.MethodGet, "/api/v1/admin/users", nil, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var users []*models.User
|
||||
err := json.NewDecoder(rr.Body).Decode(&users)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have at least our admin and regular test users
|
||||
assert.GreaterOrEqual(t, len(users), 2)
|
||||
assert.True(t, containsUser(users, h.AdminUser), "Admin user not found in users list")
|
||||
assert.True(t, containsUser(users, h.RegularUser), "Regular user not found in users list")
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/users", nil, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
|
||||
// Test without token
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/users", nil, "", nil)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("create user", func(t *testing.T) {
|
||||
createReq := handlers.CreateUserRequest{
|
||||
Email: "newuser@test.com",
|
||||
DisplayName: "New User",
|
||||
Password: "password123",
|
||||
Role: models.RoleEditor,
|
||||
}
|
||||
|
||||
// Test with admin token
|
||||
rr := h.makeRequest(t, http.MethodPost, "/api/v1/admin/users", createReq, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var createdUser models.User
|
||||
err := json.NewDecoder(rr.Body).Decode(&createdUser)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, createReq.Email, createdUser.Email)
|
||||
assert.Equal(t, createReq.DisplayName, createdUser.DisplayName)
|
||||
assert.Equal(t, createReq.Role, createdUser.Role)
|
||||
assert.NotZero(t, createdUser.LastWorkspaceID)
|
||||
|
||||
// Test duplicate email
|
||||
rr = h.makeRequest(t, http.MethodPost, "/api/v1/admin/users", createReq, h.AdminToken, nil)
|
||||
assert.Equal(t, http.StatusConflict, rr.Code)
|
||||
|
||||
// Test invalid request (missing required fields)
|
||||
invalidReq := handlers.CreateUserRequest{
|
||||
Email: "invalid@test.com",
|
||||
// Missing password and role
|
||||
}
|
||||
rr = h.makeRequest(t, http.MethodPost, "/api/v1/admin/users", invalidReq, h.AdminToken, nil)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodPost, "/api/v1/admin/users", createReq, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("get user", func(t *testing.T) {
|
||||
path := fmt.Sprintf("/api/v1/admin/users/%d", h.RegularUser.ID)
|
||||
|
||||
// Test with admin token
|
||||
rr := h.makeRequest(t, http.MethodGet, path, nil, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var user models.User
|
||||
err := json.NewDecoder(rr.Body).Decode(&user)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, h.RegularUser.ID, user.ID)
|
||||
|
||||
// Test non-existent user
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/users/999999", nil, h.AdminToken, nil)
|
||||
assert.Equal(t, http.StatusNotFound, rr.Code)
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodGet, path, nil, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("update user", func(t *testing.T) {
|
||||
path := fmt.Sprintf("/api/v1/admin/users/%d", h.RegularUser.ID)
|
||||
updateReq := handlers.UpdateUserRequest{
|
||||
DisplayName: "Updated Name",
|
||||
Role: models.RoleViewer,
|
||||
}
|
||||
|
||||
// Test with admin token
|
||||
rr := h.makeRequest(t, http.MethodPut, path, updateReq, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var updatedUser models.User
|
||||
err := json.NewDecoder(rr.Body).Decode(&updatedUser)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, updateReq.DisplayName, updatedUser.DisplayName)
|
||||
assert.Equal(t, updateReq.Role, updatedUser.Role)
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodPut, path, updateReq, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
|
||||
t.Run("delete user", func(t *testing.T) {
|
||||
// Create a user to delete
|
||||
createReq := handlers.CreateUserRequest{
|
||||
Email: "todelete@test.com",
|
||||
DisplayName: "To Delete",
|
||||
Password: "password123",
|
||||
Role: models.RoleEditor,
|
||||
}
|
||||
|
||||
rr := h.makeRequest(t, http.MethodPost, "/api/v1/admin/users", createReq, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var createdUser models.User
|
||||
err := json.NewDecoder(rr.Body).Decode(&createdUser)
|
||||
require.NoError(t, err)
|
||||
|
||||
path := fmt.Sprintf("/api/v1/admin/users/%d", createdUser.ID)
|
||||
|
||||
// Test deleting own account (should fail)
|
||||
adminPath := fmt.Sprintf("/api/v1/admin/users/%d", h.AdminUser.ID)
|
||||
rr = h.makeRequest(t, http.MethodDelete, adminPath, nil, h.AdminToken, nil)
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
|
||||
// Test with admin token
|
||||
rr = h.makeRequest(t, http.MethodDelete, path, nil, h.AdminToken, nil)
|
||||
assert.Equal(t, http.StatusNoContent, rr.Code)
|
||||
|
||||
// Verify user is deleted
|
||||
rr = h.makeRequest(t, http.MethodGet, path, nil, h.AdminToken, nil)
|
||||
assert.Equal(t, http.StatusNotFound, rr.Code)
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodDelete, path, nil, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("workspace management", func(t *testing.T) {
|
||||
t.Run("list workspaces", func(t *testing.T) {
|
||||
// Create a test workspace first
|
||||
workspace := &models.Workspace{
|
||||
UserID: h.RegularUser.ID,
|
||||
Name: "Test Workspace",
|
||||
}
|
||||
|
||||
rr := h.makeRequest(t, http.MethodPost, "/api/v1/workspaces", workspace, h.RegularToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// Test with admin token
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/workspaces", nil, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var workspaces []*handlers.WorkspaceStats
|
||||
err := json.NewDecoder(rr.Body).Decode(&workspaces)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should have at least the default workspaces for admin and regular users
|
||||
assert.NotEmpty(t, workspaces)
|
||||
|
||||
// Verify workspace stats fields
|
||||
for _, ws := range workspaces {
|
||||
assert.NotZero(t, ws.UserID)
|
||||
assert.NotEmpty(t, ws.UserEmail)
|
||||
assert.NotZero(t, ws.WorkspaceID)
|
||||
assert.NotEmpty(t, ws.WorkspaceName)
|
||||
assert.NotZero(t, ws.WorkspaceCreatedAt)
|
||||
assert.GreaterOrEqual(t, ws.TotalFiles, 0)
|
||||
assert.GreaterOrEqual(t, ws.TotalSize, int64(0))
|
||||
}
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/workspaces", nil, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("system stats", func(t *testing.T) {
|
||||
// Create some test data
|
||||
workspace := &models.Workspace{
|
||||
UserID: h.RegularUser.ID,
|
||||
Name: "Stats Test Workspace",
|
||||
}
|
||||
rr := h.makeRequest(t, http.MethodPost, "/api/v1/workspaces", workspace, h.RegularToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
// Test with admin token
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/stats", nil, h.AdminToken, nil)
|
||||
require.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var stats handlers.SystemStats
|
||||
err := json.NewDecoder(rr.Body).Decode(&stats)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify stats fields
|
||||
assert.GreaterOrEqual(t, stats.TotalUsers, 2) // At least admin and regular user
|
||||
assert.GreaterOrEqual(t, stats.TotalWorkspaces, 2) // At least default workspaces
|
||||
assert.GreaterOrEqual(t, stats.ActiveUsers, 2) // Our test users should be active
|
||||
assert.GreaterOrEqual(t, stats.TotalFiles, 0)
|
||||
assert.GreaterOrEqual(t, stats.TotalSize, int64(0))
|
||||
|
||||
// Test with non-admin token
|
||||
rr = h.makeRequest(t, http.MethodGet, "/api/v1/admin/stats", nil, h.RegularToken, nil)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
})
|
||||
}
|
||||
@@ -91,23 +91,26 @@ func setupTestHarness(t *testing.T) *testHarness {
|
||||
api.SetupRoutes(r, database, storageSvc, authMiddleware, sessionSvc)
|
||||
})
|
||||
|
||||
// Create test users
|
||||
adminUser, adminToken := createTestUser(t, database, sessionSvc, "admin@test.com", "admin123", models.RoleAdmin)
|
||||
regularUser, regularToken := createTestUser(t, database, sessionSvc, "user@test.com", "user123", models.RoleEditor)
|
||||
|
||||
return &testHarness{
|
||||
h := &testHarness{
|
||||
DB: database,
|
||||
Storage: storageSvc,
|
||||
Router: router,
|
||||
Handler: handler,
|
||||
JWTManager: jwtSvc,
|
||||
SessionSvc: sessionSvc,
|
||||
AdminUser: adminUser,
|
||||
AdminToken: adminToken,
|
||||
RegularUser: regularUser,
|
||||
RegularToken: regularToken,
|
||||
TempDirectory: tempDir,
|
||||
}
|
||||
|
||||
// Create test users
|
||||
adminUser, adminToken := h.createTestUser(t, database, sessionSvc, "admin@test.com", "admin123", models.RoleAdmin)
|
||||
regularUser, regularToken := h.createTestUser(t, database, sessionSvc, "user@test.com", "user123", models.RoleEditor)
|
||||
|
||||
h.AdminUser = adminUser
|
||||
h.AdminToken = adminToken
|
||||
h.RegularUser = regularUser
|
||||
h.RegularToken = regularToken
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
// teardownTestHarness cleans up the test environment
|
||||
@@ -124,7 +127,7 @@ func (h *testHarness) teardown(t *testing.T) {
|
||||
}
|
||||
|
||||
// createTestUser creates a test user and returns the user and access token
|
||||
func createTestUser(t *testing.T, db db.Database, sessionSvc *auth.SessionService, email, password string, role models.UserRole) (*models.User, string) {
|
||||
func (h *testHarness) createTestUser(t *testing.T, db db.Database, sessionSvc *auth.SessionService, email, password string, role models.UserRole) (*models.User, string) {
|
||||
t.Helper()
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
@@ -144,6 +147,12 @@ func createTestUser(t *testing.T, db db.Database, sessionSvc *auth.SessionServic
|
||||
t.Fatalf("Failed to create user: %v", err)
|
||||
}
|
||||
|
||||
// Initialize the default workspace directory in storage
|
||||
err = h.Storage.InitializeUserWorkspace(user.ID, user.LastWorkspaceID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize user workspace: %v", err)
|
||||
}
|
||||
|
||||
session, accessToken, err := sessionSvc.CreateSession(user.ID, string(user.Role))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create session: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user