refactor(backend): update test files for PoolProxy and per-test transactions

Migrate all test files from SetupTestDB/db.DB pattern to per-test transactions:

- Replace SetupTestDB(t) with SetupTestTx(t) for context + transaction
- Replace db.DB.Query/QueryRow/Exec with tx.Query/QueryRow/Exec
- Replace context.Background() with context from SetupTestTx
- Replace defer rows.Close() pattern with explicit rows.Close()
- Add testdb.SeedBaseline(pool) to all TestMain functions
- Wire db.Conn = db.NewPoolProxy(pool) in all TestMain functions

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-06-21 19:29:24 +01:00
co-authored by Sisyphus
parent 3d0e2afc4c
commit 220a0ef6e8
57 changed files with 5911 additions and 6235 deletions
+140 -127
View File
@@ -25,19 +25,18 @@ import (
"net/http/httptest"
"testing"
"crussell/db"
"crussell/testutils"
"crussell/mw"
"crussell/testutils/fixtures"
"crussell/testutils/jwt"
"crussell/testutils/testdb"
)
// TestProfile_Get verifies that an authenticated user can retrieve their own profile data.
func TestProfile_Get(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -45,7 +44,7 @@ func TestProfile_Get(t *testing.T) {
token := jwt.GenerateUserToken(userID)
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
rr := httptest.NewRecorder()
@@ -68,7 +67,7 @@ func TestProfile_Get(t *testing.T) {
// TestProfile_Get_NoAuth verifies that an unauthenticated request to get profile returns 401 Unauthorized.
func TestProfile_Get_NoAuth(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
rr := httptest.NewRecorder()
@@ -81,9 +80,10 @@ func TestProfile_Get_NoAuth(t *testing.T) {
// TestProfile_Update verifies that a user can update their profile with valid first name, last name, and phone.
func TestProfile_Update(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -98,7 +98,7 @@ func TestProfile_Update(t *testing.T) {
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -113,9 +113,10 @@ func TestProfile_Update(t *testing.T) {
// TestPasswordChange_Success verifies that a user can successfully change their password with valid credentials.
func TestPasswordChange_Success(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -129,7 +130,7 @@ func TestPasswordChange_Success(t *testing.T) {
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -144,9 +145,10 @@ func TestPasswordChange_Success(t *testing.T) {
// TestPasswordChange_WrongOld verifies that providing an incorrect current password returns 401 Unauthorized.
func TestPasswordChange_WrongOld(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -160,7 +162,7 @@ func TestPasswordChange_WrongOld(t *testing.T) {
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -175,9 +177,10 @@ func TestPasswordChange_WrongOld(t *testing.T) {
// TestPasswordChange_InvalidNewPassword verifies that invalid new passwords (too short or too long for bcrypt)
// are rejected with 400 Bad Request.
func TestPasswordChange_InvalidNewPassword(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -201,7 +204,7 @@ func TestPasswordChange_InvalidNewPassword(t *testing.T) {
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -219,9 +222,10 @@ func TestPasswordChange_InvalidNewPassword(t *testing.T) {
// TestAccount_Delete verifies that a registered user can delete their own account,
// triggering anonymization and returning 204 No Content.
func TestAccount_Delete(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -229,7 +233,7 @@ func TestAccount_Delete(t *testing.T) {
token := jwt.GenerateUserToken(userID)
req := httptest.NewRequest(http.MethodDelete, "/api/user/account", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
rr := httptest.NewRecorder()
@@ -241,7 +245,7 @@ func TestAccount_Delete(t *testing.T) {
}
var firstName, accountRole string
err = db.DB.QueryRow(context.Background(), `SELECT n_first_name, account_role FROM users WHERE id = $1`, userID).Scan(&firstName, &accountRole)
err = tx.QueryRow(ctx, `SELECT n_first_name, account_role FROM users WHERE id = $1`, userID).Scan(&firstName, &accountRole)
if err != nil {
t.Fatalf("failed to query anonymized user: %v", err)
}
@@ -255,9 +259,10 @@ func TestAccount_Delete(t *testing.T) {
// TestAccount_DeleteGuest verifies that a guest user is fully deleted.
func TestAccount_DeleteGuest(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestGuestUser(db.DB)
userID, err := fixtures.CreateTestGuestUser(tx)
if err != nil {
t.Fatalf("failed to create test guest user: %v", err)
}
@@ -265,7 +270,7 @@ func TestAccount_DeleteGuest(t *testing.T) {
token := jwt.GenerateUserToken(userID)
req := httptest.NewRequest(http.MethodDelete, "/api/user/account", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
rr := httptest.NewRecorder()
@@ -277,7 +282,7 @@ func TestAccount_DeleteGuest(t *testing.T) {
}
var count int
err = db.DB.QueryRow(context.Background(), `SELECT COUNT(*) FROM users WHERE id = $1`, userID).Scan(&count)
err = tx.QueryRow(ctx, `SELECT COUNT(*) FROM users WHERE id = $1`, userID).Scan(&count)
if err != nil {
t.Fatalf("failed to query user count: %v", err)
}
@@ -288,15 +293,16 @@ func TestAccount_DeleteGuest(t *testing.T) {
// TestLoyalty_Get verifies that a user can retrieve their loyalty stamps count and referral code.
func TestLoyalty_Get(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
// Add some loyalty stamps
_, err = db.DB.Exec(context.Background(), `UPDATE users SET loyalty_stamps = 10 WHERE id = $1`, userID)
_, err = tx.Exec(ctx, `UPDATE users SET loyalty_stamps = 10 WHERE id = $1`, userID)
if err != nil {
t.Fatalf("failed to update loyalty stamps: %v", err)
}
@@ -304,7 +310,7 @@ func TestLoyalty_Get(t *testing.T) {
token := jwt.GenerateUserToken(userID)
req := httptest.NewRequest(http.MethodGet, "/api/user/loyalty", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
rr := httptest.NewRecorder()
@@ -332,9 +338,10 @@ func TestLoyalty_Get(t *testing.T) {
// TestProfile_Update_InvalidInput verifies that profile update validation rejects invalid inputs:
// missing first name, missing last name, missing phone, invalid phone format, invalid characters in name, name too long.
func TestProfile_Update_InvalidInput(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -383,7 +390,7 @@ func TestProfile_Update_InvalidInput(t *testing.T) {
body, _ := json.Marshal(tt.req)
req := httptest.NewRequest(http.MethodPut, "/api/user/profile", bytes.NewReader(body))
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -400,9 +407,10 @@ func TestProfile_Update_InvalidInput(t *testing.T) {
// TestProfile_Update_Success verifies that a valid profile update succeeds and the changes are persisted in the database.
func TestProfile_Update_Success(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -417,7 +425,7 @@ func TestProfile_Update_Success(t *testing.T) {
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -432,7 +440,7 @@ func TestProfile_Update_Success(t *testing.T) {
// Verify DB was updated
var firstName, lastName, phone string
err = db.DB.QueryRow(context.Background(),
err = tx.QueryRow(ctx,
"SELECT n_first_name, n_last_name, phone FROM users WHERE id = $1", userID).Scan(&firstName, &lastName, &phone)
if err != nil {
t.Fatalf("failed to query user: %v", err)
@@ -451,9 +459,10 @@ func TestProfile_Update_Success(t *testing.T) {
// TestPasswordChange_SameAsOld verifies that attempting to change password to the same value returns 400 Bad Request.
func TestPasswordChange_SameAsOld(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -468,7 +477,7 @@ func TestPasswordChange_SameAsOld(t *testing.T) {
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
@@ -484,9 +493,10 @@ func TestPasswordChange_SameAsOld(t *testing.T) {
// TestProfile_UploadPicture verifies that a user can upload a profile picture. May return 500 if S3 is not configured.
func TestProfile_UploadPicture(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -539,7 +549,7 @@ func TestProfile_UploadPicture(t *testing.T) {
writer.Close()
req := httptest.NewRequest(http.MethodPost, "/api/user/profile-picture", &b)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", writer.FormDataContentType())
@@ -569,16 +579,17 @@ func TestProfile_UploadPicture(t *testing.T) {
// TestContactInfo_ReturnsAdmin verifies that GetContactInfoHandler returns contact info for the first admin user.
func TestContactInfo_ReturnsAdmin(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
// Create admin user with profile data
adminID, err := fixtures.CreateTestAdminUser(db.DB)
adminID, err := fixtures.CreateTestAdminUser(tx)
if err != nil {
t.Fatalf("failed to create admin user: %v", err)
}
// Update admin with specific profile data
_, err = db.DB.Exec(context.Background(), `
_, err = tx.Exec(ctx, `
UPDATE users
SET n_first_name = 'Jane', n_last_name = 'Smith', phone = '+447700900000', email = 'jane@example.com'
WHERE id = $1
@@ -589,6 +600,7 @@ func TestContactInfo_ReturnsAdmin(t *testing.T) {
// Call handler directly (no auth needed - public endpoint)
req := httptest.NewRequest(http.MethodGet, "/api/contact", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetContactInfoHandler(rr, req)
@@ -626,16 +638,16 @@ func TestContactInfo_ReturnsAdmin(t *testing.T) {
// TestContactInfo_NoAdmin verifies that GetContactInfoHandler returns 404 when no admin exists.
func TestContactInfo_NoAdmin(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
testdb.TruncateTables(t, db.DB)
_, err := fixtures.CreateTestUser(db.DB)
_, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/contact", nil)
req = req.WithContext(ctx)
rr := httptest.NewRecorder()
GetContactInfoHandler(rr, req)
@@ -646,15 +658,16 @@ func TestContactInfo_NoAdmin(t *testing.T) {
}
func TestNotificationPreferences_Get_Defaults(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/user/notification-preferences", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
GetNotificationPreferencesHandler(rr, req)
@@ -680,9 +693,10 @@ func TestNotificationPreferences_Get_Defaults(t *testing.T) {
}
func TestNotificationPreferences_Update(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -698,7 +712,7 @@ func TestNotificationPreferences_Update(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/api/user/notification-preferences", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
UpdateNotificationPreferencesHandler(rr, req)
@@ -708,7 +722,7 @@ func TestNotificationPreferences_Update(t *testing.T) {
}
getReq := httptest.NewRequest(http.MethodGet, "/api/user/notification-preferences", nil)
getReq = getReq.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
getReq = getReq.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
getRR := httptest.NewRecorder()
GetNotificationPreferencesHandler(getRR, getReq)
@@ -729,9 +743,10 @@ func TestNotificationPreferences_Update(t *testing.T) {
}
func TestNotificationPreferences_Update_Partial(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
@@ -744,7 +759,7 @@ func TestNotificationPreferences_Update_Partial(t *testing.T) {
req := httptest.NewRequest(http.MethodPut, "/api/user/notification-preferences", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
UpdateNotificationPreferencesHandler(rr, req)
@@ -754,7 +769,7 @@ func TestNotificationPreferences_Update_Partial(t *testing.T) {
}
getReq := httptest.NewRequest(http.MethodGet, "/api/user/notification-preferences", nil)
getReq = getReq.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
getReq = getReq.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
getRR := httptest.NewRecorder()
GetNotificationPreferencesHandler(getRR, getReq)
@@ -779,17 +794,17 @@ func TestNotificationPreferences_Update_Partial(t *testing.T) {
// =============================================================================
func TestProfileUpdate_CreatesNameHistoryOnNameChange(t *testing.T) {
testutils.SetupTestDB(t)
ctx := context.Background()
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
// Fetch the user's current name from DB to verify against
var origFirstName, origLastName string
err = db.DB.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query original name: %v", err)
}
@@ -804,7 +819,7 @@ func TestProfileUpdate_CreatesNameHistoryOnNameChange(t *testing.T) {
}
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -814,7 +829,7 @@ func TestProfileUpdate_CreatesNameHistoryOnNameChange(t *testing.T) {
}
var count int
err = db.DB.QueryRow(ctx, `SELECT COUNT(*) FROM name_history WHERE user_id = $1`, userID).Scan(&count)
err = tx.QueryRow(ctx, `SELECT COUNT(*) FROM name_history WHERE user_id = $1`, userID).Scan(&count)
if err != nil {
t.Fatalf("failed to count name_history: %v", err)
}
@@ -823,7 +838,7 @@ func TestProfileUpdate_CreatesNameHistoryOnNameChange(t *testing.T) {
}
var prevFirstName, prevLastName string
err = db.DB.QueryRow(ctx, `SELECT previous_first_name, previous_last_name FROM name_history WHERE user_id = $1`, userID).Scan(&prevFirstName, &prevLastName)
err = tx.QueryRow(ctx, `SELECT previous_first_name, previous_last_name FROM name_history WHERE user_id = $1`, userID).Scan(&prevFirstName, &prevLastName)
if err != nil {
t.Fatalf("failed to query name_history: %v", err)
}
@@ -836,16 +851,16 @@ func TestProfileUpdate_CreatesNameHistoryOnNameChange(t *testing.T) {
}
func TestProfileUpdate_NoNameHistoryOnSameName(t *testing.T) {
testutils.SetupTestDB(t)
ctx := context.Background()
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
var origFirstName, origLastName string
err = db.DB.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query original name: %v", err)
}
@@ -859,7 +874,7 @@ func TestProfileUpdate_NoNameHistoryOnSameName(t *testing.T) {
}
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -869,7 +884,7 @@ func TestProfileUpdate_NoNameHistoryOnSameName(t *testing.T) {
}
var count int
err = db.DB.QueryRow(ctx, `SELECT COUNT(*) FROM name_history WHERE user_id = $1`, userID).Scan(&count)
err = tx.QueryRow(ctx, `SELECT COUNT(*) FROM name_history WHERE user_id = $1`, userID).Scan(&count)
if err != nil {
t.Fatalf("failed to count name_history: %v", err)
}
@@ -879,16 +894,16 @@ func TestProfileUpdate_NoNameHistoryOnSameName(t *testing.T) {
}
func TestProfileUpdate_CreatesNameHistoryOnLastNameChange(t *testing.T) {
testutils.SetupTestDB(t)
ctx := context.Background()
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
var origFirstName, origLastName string
err = db.DB.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query original name: %v", err)
}
@@ -902,7 +917,7 @@ func TestProfileUpdate_CreatesNameHistoryOnLastNameChange(t *testing.T) {
}
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -912,7 +927,7 @@ func TestProfileUpdate_CreatesNameHistoryOnLastNameChange(t *testing.T) {
}
var prevFirstName, prevLastName string
err = db.DB.QueryRow(ctx, `SELECT previous_first_name, previous_last_name FROM name_history WHERE user_id = $1`, userID).Scan(&prevFirstName, &prevLastName)
err = tx.QueryRow(ctx, `SELECT previous_first_name, previous_last_name FROM name_history WHERE user_id = $1`, userID).Scan(&prevFirstName, &prevLastName)
if err != nil {
t.Fatalf("failed to query name_history: %v", err)
}
@@ -925,14 +940,15 @@ func TestProfileUpdate_CreatesNameHistoryOnLastNameChange(t *testing.T) {
}
func TestProfileGet_ReturnsPreviousNameWhenChanged(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
_, err = db.DB.Exec(context.Background(), `
_, err = tx.Exec(ctx, `
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
VALUES ($1, 'OldFirst', 'OldLast')
`, userID)
@@ -941,7 +957,7 @@ func TestProfileGet_ReturnsPreviousNameWhenChanged(t *testing.T) {
}
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
GetProfileHandler(rr, req)
@@ -963,20 +979,21 @@ func TestProfileGet_ReturnsPreviousNameWhenChanged(t *testing.T) {
}
func TestProfileGet_OmitsPreviousNameWhenCurrentMatches(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
var origFirstName, origLastName string
err = db.DB.QueryRow(context.Background(), `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query original name: %v", err)
}
_, err = db.DB.Exec(context.Background(), `
_, err = tx.Exec(ctx, `
INSERT INTO name_history (user_id, previous_first_name, previous_last_name)
VALUES ($1, $2, $3)
`, userID, origFirstName, origLastName)
@@ -985,7 +1002,7 @@ func TestProfileGet_OmitsPreviousNameWhenCurrentMatches(t *testing.T) {
}
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
GetProfileHandler(rr, req)
@@ -1007,15 +1024,16 @@ func TestProfileGet_OmitsPreviousNameWhenCurrentMatches(t *testing.T) {
}
func TestProfileGet_OmitsPreviousNameWhenNoHistory(t *testing.T) {
testutils.SetupTestDB(t)
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
GetProfileHandler(rr, req)
@@ -1039,17 +1057,17 @@ func TestProfileGet_OmitsPreviousNameWhenNoHistory(t *testing.T) {
// TestProfileGet_ReturnsReferralSavings verifies that the user profile
// returns referralSavings reflecting applied referral discounts.
func TestProfileGet_ReturnsReferralSavings(t *testing.T) {
testutils.SetupTestDB(t)
ctx := context.Background()
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
// Create a referral discount that's been applied to a booking
var refID string
err = db.DB.QueryRow(ctx, `
err = tx.QueryRow(ctx, `
INSERT INTO user_referrals (referrer_id, referred_id)
VALUES ($1, $2)
RETURNING id
@@ -1059,7 +1077,7 @@ func TestProfileGet_ReturnsReferralSavings(t *testing.T) {
}
var rdID string
err = db.DB.QueryRow(ctx, `
err = tx.QueryRow(ctx, `
INSERT INTO referral_discounts (user_id, referral_id, discount_percent, used)
VALUES ($1, $2, 10.00, true)
RETURNING id
@@ -1068,33 +1086,28 @@ func TestProfileGet_ReturnsReferralSavings(t *testing.T) {
t.Fatalf("failed to create referral discount: %v", err)
}
// Record a booking_discount to simulate referral savings
_, err = db.DB.Exec(ctx, `
INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, discount_percent, original_total, discount_amount)
VALUES ((SELECT id FROM bookings LIMIT 1), $1, 'referral', $2, 10.00, 5000, 500)
`, userID, rdID)
// If no booking exists yet, create one
// Create a booking first, since booking_discounts.booking_id is NOT NULL
var bookingID string
err = tx.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW(), 'completed')
RETURNING id
`, userID).Scan(&bookingID)
if err != nil {
var bookingID string
err = db.DB.QueryRow(ctx, `
INSERT INTO bookings (user_id, start_time, status)
VALUES ($1, NOW(), 'completed')
RETURNING id
`, userID).Scan(&bookingID)
if err != nil {
t.Fatalf("failed to create booking: %v", err)
}
_, err = db.DB.Exec(ctx, `
INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, discount_percent, original_total, discount_amount)
VALUES ($1, $2, 'referral', $3, 10.00, 5000, 500)
`, bookingID, userID, rdID)
if err != nil {
t.Fatalf("failed to insert booking_discount: %v", err)
}
t.Fatalf("failed to create booking: %v", err)
}
// Record a booking_discount to simulate referral savings
_, err = tx.Exec(ctx, `
INSERT INTO booking_discounts (booking_id, user_id, discount_source, source_id, discount_percent, original_total, discount_amount)
VALUES ($1, $2, 'referral', $3, 10.00, 5000, 500)
`, bookingID, userID, rdID)
if err != nil {
t.Fatalf("failed to insert booking_discount: %v", err)
}
req := httptest.NewRequest(http.MethodGet, "/api/user/profile", nil)
req = req.WithContext(context.WithValue(context.Background(), mw.UserIDKey, userID))
req = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
rr := httptest.NewRecorder()
GetProfileHandler(rr, req)
@@ -1115,16 +1128,16 @@ func TestProfileGet_ReturnsReferralSavings(t *testing.T) {
// TestProfileUpdate_NameHistoryRollback verifies that if the user update
// fails after name_history is inserted, the name_history entry is rolled back.
func TestProfileUpdate_NameHistoryRollback(t *testing.T) {
testutils.SetupTestDB(t)
ctx := context.Background()
t.Parallel()
ctx, tx := testutils.SetupTestTx(t)
userID, err := fixtures.CreateTestUser(db.DB)
userID, err := fixtures.CreateTestUser(tx)
if err != nil {
t.Fatalf("failed to create test user: %v", err)
}
var origFirstName, origLastName string
err = db.DB.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
err = tx.QueryRow(ctx, `SELECT n_first_name, n_last_name FROM users WHERE id = $1`, userID).Scan(&origFirstName, &origLastName)
if err != nil {
t.Fatalf("failed to query original name: %v", err)
}
@@ -1144,7 +1157,7 @@ func TestProfileUpdate_NameHistoryRollback(t *testing.T) {
}
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 = req.WithContext(context.WithValue(ctx, mw.UserIDKey, userID))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
@@ -1155,7 +1168,7 @@ func TestProfileUpdate_NameHistoryRollback(t *testing.T) {
// Verify name was updated
var newFirstName string
err = db.DB.QueryRow(ctx, `SELECT n_first_name FROM users WHERE id = $1`, userID).Scan(&newFirstName)
err = tx.QueryRow(ctx, `SELECT n_first_name FROM users WHERE id = $1`, userID).Scan(&newFirstName)
if err != nil {
t.Fatalf("failed to query updated name: %v", err)
}
@@ -1165,7 +1178,7 @@ func TestProfileUpdate_NameHistoryRollback(t *testing.T) {
// Verify name_history has the original name recorded
var prevFirstName string
err = db.DB.QueryRow(ctx, `SELECT previous_first_name FROM name_history WHERE user_id = $1`, userID).Scan(&prevFirstName)
err = tx.QueryRow(ctx, `SELECT previous_first_name FROM name_history WHERE user_id = $1`, userID).Scan(&prevFirstName)
if err != nil {
t.Fatalf("failed to query name_history: %v", err)
}