- Add TestMain to set test env vars and testdb.TruncateTables for test isolation - Add chi routing context to test helpers for path parameter extraction - Fix SQL error handling to use errors.Is() instead of == - Add validators package with ID validation - Fix admin test middleware chain (RequireAdmin wrapper) - Update test user inserts to include phone and date_of_birth fields - Update service delete test to check soft-delete (is_active=false) - Update holiday hours test to use new schema (weekday, is_open) - Add phone number validation tests for UK mobile numbers
244 lines
6.1 KiB
Go
244 lines
6.1 KiB
Go
//go:build test
|
|
// +build test
|
|
|
|
package user
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"crussell/db"
|
|
"crussell/mw"
|
|
"crussell/testutils/fixtures"
|
|
"crussell/testutils/jwt"
|
|
"crussell/testutils/testdb"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func setupTest(t *testing.T) (func(), *pgxpool.Pool) {
|
|
pool := testdb.Pool(t)
|
|
testdb.Migrate(t, pool)
|
|
testdb.TruncateTables(t, pool)
|
|
|
|
// Set the global DB pool
|
|
db.DB = pool
|
|
|
|
// Initialize JWT
|
|
jwt.Init()
|
|
|
|
return func() {
|
|
pool.Close()
|
|
}, pool
|
|
}
|
|
|
|
func TestProfile_Get(t *testing.T) {
|
|
cleanup, pool := setupTest(t)
|
|
defer cleanup()
|
|
|
|
userID, err := fixtures.CreateTestUser(pool)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test user: %v", err)
|
|
}
|
|
|
|
token := jwt.GenerateUserToken(userID)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
|
|
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
rr := httptest.NewRecorder()
|
|
GetProfileHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", rr.Code)
|
|
t.Logf("response body: %s", rr.Body.String())
|
|
}
|
|
|
|
var profile UserProfile
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &profile); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if profile.ID != userID {
|
|
t.Errorf("expected user ID %s, got %s", userID, profile.ID)
|
|
}
|
|
}
|
|
|
|
func TestProfile_Get_NoAuth(t *testing.T) {
|
|
cleanup, _ := setupTest(t)
|
|
defer cleanup()
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
|
|
rr := httptest.NewRecorder()
|
|
GetProfileHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected status 401, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestProfile_Update(t *testing.T) {
|
|
cleanup, pool := setupTest(t)
|
|
defer cleanup()
|
|
|
|
userID, err := fixtures.CreateTestUser(pool)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test user: %v", err)
|
|
}
|
|
|
|
token := jwt.GenerateUserToken(userID)
|
|
|
|
updateReq := UpdateProfileRequest{
|
|
FirstName: "John",
|
|
LastName: "Doe",
|
|
Phone: "07123456789",
|
|
}
|
|
body, _ := json.Marshal(updateReq)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/user/profile", bytes.NewReader(body))
|
|
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rr := httptest.NewRecorder()
|
|
UpdateProfileHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", rr.Code)
|
|
t.Logf("response body: %s", rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPasswordChange_Success(t *testing.T) {
|
|
cleanup, pool := setupTest(t)
|
|
defer cleanup()
|
|
|
|
userID, err := fixtures.CreateTestUser(pool)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test user: %v", err)
|
|
}
|
|
|
|
token := jwt.GenerateUserToken(userID)
|
|
|
|
changeReq := ChangePasswordRequest{
|
|
CurrentPassword: "testpassword123",
|
|
NewPassword: "newpassword456",
|
|
}
|
|
body, _ := json.Marshal(changeReq)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/user/change-password", bytes.NewReader(body))
|
|
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rr := httptest.NewRecorder()
|
|
ChangePasswordHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", rr.Code)
|
|
t.Logf("response body: %s", rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestPasswordChange_WrongOld(t *testing.T) {
|
|
cleanup, pool := setupTest(t)
|
|
defer cleanup()
|
|
|
|
userID, err := fixtures.CreateTestUser(pool)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test user: %v", err)
|
|
}
|
|
|
|
token := jwt.GenerateUserToken(userID)
|
|
|
|
changeReq := ChangePasswordRequest{
|
|
CurrentPassword: "wrongpassword",
|
|
NewPassword: "newpassword456",
|
|
}
|
|
body, _ := json.Marshal(changeReq)
|
|
|
|
req := httptest.NewRequest(http.MethodPut, "/api/user/change-password", bytes.NewReader(body))
|
|
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
rr := httptest.NewRecorder()
|
|
ChangePasswordHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusUnauthorized {
|
|
t.Errorf("expected status 401, got %d", rr.Code)
|
|
}
|
|
}
|
|
|
|
func TestAccount_Delete(t *testing.T) {
|
|
cleanup, pool := setupTest(t)
|
|
defer cleanup()
|
|
|
|
userID, err := fixtures.CreateTestUser(pool)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test user: %v", err)
|
|
}
|
|
|
|
token := jwt.GenerateUserToken(userID)
|
|
|
|
req := httptest.NewRequest(http.MethodDelete, "/api/user/account", nil)
|
|
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
rr := httptest.NewRecorder()
|
|
DeleteAccountHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusNoContent {
|
|
t.Errorf("expected status 204, got %d", rr.Code)
|
|
t.Logf("response body: %s", rr.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestLoyalty_Get(t *testing.T) {
|
|
cleanup, pool := setupTest(t)
|
|
defer cleanup()
|
|
|
|
userID, err := fixtures.CreateTestUser(pool)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test user: %v", err)
|
|
}
|
|
|
|
// Add some loyalty stamps
|
|
_, err = pool.Exec(context.Background(), `UPDATE users SET loyalty_stamps = 10 WHERE id = $1`, userID)
|
|
if err != nil {
|
|
t.Fatalf("failed to update loyalty stamps: %v", err)
|
|
}
|
|
|
|
token := jwt.GenerateUserToken(userID)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/api/user/loyalty", nil)
|
|
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
|
|
rr := httptest.NewRecorder()
|
|
GetLoyaltyHandler(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Errorf("expected status 200, got %d", rr.Code)
|
|
t.Logf("response body: %s", rr.Body.String())
|
|
}
|
|
|
|
var loyalty LoyaltyResponse
|
|
if err := json.Unmarshal(rr.Body.Bytes(), &loyalty); err != nil {
|
|
t.Fatalf("failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if loyalty.Stamps != 10 {
|
|
t.Errorf("expected 10 stamps, got %d", loyalty.Stamps)
|
|
}
|
|
|
|
if loyalty.ReferralCode == "" {
|
|
t.Error("expected referral code to be set")
|
|
}
|
|
}
|