Implement admin handlers integration test

This commit is contained in:
2024-11-27 21:28:59 +01:00
parent 4ddf1f570f
commit fbb8fa3a60
3 changed files with 268 additions and 14 deletions

View File

@@ -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

View 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)
})
}

View File

@@ -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)