mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Implement admin handlers integration test
This commit is contained in:
@@ -15,14 +15,16 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"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"`
|
Email string `json:"email"`
|
||||||
DisplayName string `json:"displayName"`
|
DisplayName string `json:"displayName"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Role models.UserRole `json:"role"`
|
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"`
|
Email string `json:"email,omitempty"`
|
||||||
DisplayName string `json:"displayName,omitempty"`
|
DisplayName string `json:"displayName,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
@@ -45,7 +47,7 @@ func (h *Handler) AdminListUsers() http.HandlerFunc {
|
|||||||
// AdminCreateUser creates a new user
|
// AdminCreateUser creates a new user
|
||||||
func (h *Handler) AdminCreateUser() http.HandlerFunc {
|
func (h *Handler) AdminCreateUser() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req createUserRequest
|
var req CreateUserRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -136,7 +138,7 @@ func (h *Handler) AdminUpdateUser() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var req updateUserRequest
|
var req UpdateUserRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
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)
|
api.SetupRoutes(r, database, storageSvc, authMiddleware, sessionSvc)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create test users
|
h := &testHarness{
|
||||||
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{
|
|
||||||
DB: database,
|
DB: database,
|
||||||
Storage: storageSvc,
|
Storage: storageSvc,
|
||||||
Router: router,
|
Router: router,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
JWTManager: jwtSvc,
|
JWTManager: jwtSvc,
|
||||||
SessionSvc: sessionSvc,
|
SessionSvc: sessionSvc,
|
||||||
AdminUser: adminUser,
|
|
||||||
AdminToken: adminToken,
|
|
||||||
RegularUser: regularUser,
|
|
||||||
RegularToken: regularToken,
|
|
||||||
TempDirectory: tempDir,
|
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
|
// 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
|
// 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()
|
t.Helper()
|
||||||
|
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
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)
|
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))
|
session, accessToken, err := sessionSvc.CreateSession(user.ID, string(user.Role))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create session: %v", err)
|
t.Fatalf("Failed to create session: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user