Add integration tests for use handlers

This commit is contained in:
2024-11-29 23:57:17 +01:00
parent d47b601447
commit af9ab42969
2 changed files with 212 additions and 35 deletions

View File

@@ -23,24 +23,6 @@ type DeleteAccountRequest struct {
Password string `json:"password"` Password string `json:"password"`
} }
// GetUser returns the current user's profile
func (h *Handler) GetUser() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, ok := context.GetRequestContext(w, r)
if !ok {
return
}
user, err := h.DB.GetUserByID(ctx.UserID)
if err != nil {
http.Error(w, "Failed to get user", http.StatusInternalServerError)
return
}
respondJSON(w, user)
}
}
// UpdateProfile updates the current user's profile // UpdateProfile updates the current user's profile
func (h *Handler) UpdateProfile() http.HandlerFunc { func (h *Handler) UpdateProfile() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
@@ -62,18 +44,6 @@ func (h *Handler) UpdateProfile() http.HandlerFunc {
return return
} }
// Start transaction for atomic updates
tx, err := h.DB.Begin()
if err != nil {
http.Error(w, "Failed to start transaction", http.StatusInternalServerError)
return
}
defer func() {
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
http.Error(w, "Failed to rollback transaction", http.StatusInternalServerError)
}
}()
// Handle password update if requested // Handle password update if requested
if req.NewPassword != "" { if req.NewPassword != "" {
// Current password must be provided to change password // Current password must be provided to change password
@@ -139,11 +109,6 @@ func (h *Handler) UpdateProfile() http.HandlerFunc {
return return
} }
if err := tx.Commit(); err != nil {
http.Error(w, "Failed to commit changes", http.StatusInternalServerError)
return
}
// Return updated user data // Return updated user data
respondJSON(w, user) respondJSON(w, user)
} }

View File

@@ -0,0 +1,212 @@
//go:build integration
package handlers_test
import (
"encoding/json"
"net/http"
"testing"
"novamd/internal/handlers"
"novamd/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserHandlers_Integration(t *testing.T) {
h := setupTestHarness(t)
defer h.teardown(t)
t.Run("get current user", func(t *testing.T) {
t.Run("successful get", func(t *testing.T) {
rr := h.makeRequest(t, http.MethodGet, "/api/v1/auth/me", nil, h.RegularToken, 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)
assert.Equal(t, h.RegularUser.Email, user.Email)
assert.Equal(t, h.RegularUser.DisplayName, user.DisplayName)
assert.Equal(t, h.RegularUser.Role, user.Role)
assert.Empty(t, user.PasswordHash, "Password hash should not be included in response")
})
t.Run("unauthorized", func(t *testing.T) {
rr := h.makeRequest(t, http.MethodGet, "/api/v1/auth/me", nil, "", nil)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
})
t.Run("update profile", func(t *testing.T) {
t.Run("update display name only", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
DisplayName: "Updated Name",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, 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, updateReq.DisplayName, user.DisplayName)
})
t.Run("update email", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
Email: "newemail@test.com",
CurrentPassword: "user123", // Regular user's password from test harness
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, 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, updateReq.Email, user.Email)
})
t.Run("update email without password", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
Email: "anotheremail@test.com",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("update email with wrong password", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
Email: "wrongpass@test.com",
CurrentPassword: "wrongpassword",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
t.Run("update password", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
CurrentPassword: "user123",
NewPassword: "newpassword123",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
require.Equal(t, http.StatusOK, rr.Code)
// Verify can login with new password
loginReq := handlers.LoginRequest{
Email: h.RegularUser.Email,
Password: "newpassword123",
}
rr = h.makeRequest(t, http.MethodPost, "/api/v1/auth/login", loginReq, "", nil)
assert.Equal(t, http.StatusOK, rr.Code)
})
t.Run("update password without current password", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
NewPassword: "newpass123",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("update password with wrong current password", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
CurrentPassword: "wrongpassword",
NewPassword: "newpass123",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
t.Run("update with short password", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
CurrentPassword: "user123",
NewPassword: "short",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
assert.Equal(t, http.StatusBadRequest, rr.Code)
})
t.Run("duplicate email", func(t *testing.T) {
updateReq := handlers.UpdateProfileRequest{
Email: h.AdminUser.Email,
CurrentPassword: "user123",
}
rr := h.makeRequest(t, http.MethodPut, "/api/v1/profile", updateReq, h.RegularToken, nil)
assert.Equal(t, http.StatusConflict, rr.Code)
})
})
t.Run("delete account", func(t *testing.T) {
// Create a new user that we can 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 newUser models.User
err := json.NewDecoder(rr.Body).Decode(&newUser)
require.NoError(t, err)
// Get token for new user
loginReq := handlers.LoginRequest{
Email: createReq.Email,
Password: createReq.Password,
}
rr = h.makeRequest(t, http.MethodPost, "/api/v1/auth/login", loginReq, "", nil)
require.Equal(t, http.StatusOK, rr.Code)
var loginResp handlers.LoginResponse
err = json.NewDecoder(rr.Body).Decode(&loginResp)
require.NoError(t, err)
userToken := loginResp.AccessToken
t.Run("successful delete", func(t *testing.T) {
deleteReq := handlers.DeleteAccountRequest{
Password: createReq.Password,
}
rr := h.makeRequest(t, http.MethodDelete, "/api/v1/profile", deleteReq, userToken, nil)
require.Equal(t, http.StatusOK, rr.Code)
// Verify user is deleted
rr = h.makeRequest(t, http.MethodPost, "/api/v1/auth/login", loginReq, "", nil)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
t.Run("delete with wrong password", func(t *testing.T) {
deleteReq := handlers.DeleteAccountRequest{
Password: "wrongpassword",
}
rr := h.makeRequest(t, http.MethodDelete, "/api/v1/profile", deleteReq, h.RegularToken, nil)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
t.Run("prevent last admin deletion", func(t *testing.T) {
deleteReq := handlers.DeleteAccountRequest{
Password: "admin123", // Admin password from test harness
}
rr := h.makeRequest(t, http.MethodDelete, "/api/v1/profile", deleteReq, h.AdminToken, nil)
assert.Equal(t, http.StatusForbidden, rr.Code)
})
})
}