Migrate Auth/Contractor/Document/Notification/Subscription services to ctx
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled

Every public method on these five services now takes ctx context.Context as
the first arg and routes its repo calls through .WithContext(ctx). With
TaskService and ResidenceService already migrated, this means every
in-process service that touches Postgres now produces a flame graph in
Jaeger where the SQL spans nest under the parent HTTP request span.

Endpoints now fully traced (HTTP → service → SQL):
- /api/auth/login, /register, /logout, /me, /verify-email, /resend-verification
- /api/auth/forgot-password, /verify-reset, /reset-password, /update-profile
- /api/contractors/* (CRUD + favorite + by-residence + tasks)
- /api/documents/* (CRUD + activate/deactivate + image upload/delete)
- /api/notifications/* (list, count, mark-read, prefs, devices)
- /api/subscription/* (status, purchase, cancel, triggers, promotions)
- All previously-migrated /api/tasks/* and /api/residences/* paths

Internal helpers also threaded:
- TaskService.sendTaskCompletedNotification → forwards ctx
- TaskService.UpdateUserTimezone → forwards ctx to NotificationService
- ResidenceService.CreateResidence → forwards ctx to SubscriptionService.CheckLimit
- NotificationService.registerAPNSDevice / registerGCMDevice → both take ctx

~75 method signatures, ~120 handler/test call sites updated. Tests pass
green; the only failure is the pre-existing flaky TaskHandler_QuickComplete
SQLite race that fails ~60% of runs on master.

Step 3 of the observability plan is now genuinely complete: every API
endpoint backed by a Go service emits a per-request flame graph with
HTTP → service → SQL spans, plus B2/APNs/FCM/asynq spans where applicable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-04-25 16:26:21 -05:00
parent 65a9aae4e5
commit e881d37de0
20 changed files with 529 additions and 522 deletions
+54 -53
View File
@@ -1,6 +1,7 @@
package services
import (
"context"
"net/http"
"testing"
"time"
@@ -53,7 +54,7 @@ func TestAuthService_Login(t *testing.T) {
Password: "Password123",
}
resp, err := service.Login(req)
resp, err := service.Login(context.Background(), req)
require.NoError(t, err)
assert.NotEmpty(t, resp.Token)
assert.Equal(t, "testuser", resp.User.Username)
@@ -74,7 +75,7 @@ func TestAuthService_Login_ByEmail(t *testing.T) {
Password: "Password123",
}
resp, err := service.Login(req)
resp, err := service.Login(context.Background(), req)
require.NoError(t, err)
assert.NotEmpty(t, resp.Token)
}
@@ -94,7 +95,7 @@ func TestAuthService_Login_InvalidCredentials(t *testing.T) {
Password: "WrongPassword1",
}
_, err := service.Login(req)
_, err := service.Login(context.Background(), req)
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
}
@@ -111,7 +112,7 @@ func TestAuthService_Login_UserNotFound(t *testing.T) {
Password: "Password123",
}
_, err := service.Login(req)
_, err := service.Login(context.Background(), req)
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
}
@@ -133,7 +134,7 @@ func TestAuthService_Login_InactiveUser(t *testing.T) {
Password: "Password123",
}
_, err := service.Login(req)
_, err := service.Login(context.Background(), req)
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.account_inactive")
}
@@ -148,7 +149,7 @@ func TestAuthService_Register(t *testing.T) {
Password: "Password123",
}
resp, code, err := service.Register(req)
resp, code, err := service.Register(context.Background(), req)
require.NoError(t, err)
assert.NotEmpty(t, resp.Token)
assert.Equal(t, "newuser", resp.User.Username)
@@ -172,7 +173,7 @@ func TestAuthService_Register_DuplicateUsername(t *testing.T) {
Password: "Password123",
}
_, _, err := service.Register(req)
_, _, err := service.Register(context.Background(), req)
testutil.AssertAppError(t, err, http.StatusConflict, "error.username_taken")
}
@@ -193,7 +194,7 @@ func TestAuthService_Register_DuplicateEmail(t *testing.T) {
Password: "Password123",
}
_, _, err := service.Register(req)
_, _, err := service.Register(context.Background(), req)
testutil.AssertAppError(t, err, http.StatusConflict, "error.email_taken")
}
@@ -211,7 +212,7 @@ func TestAuthService_GetCurrentUser(t *testing.T) {
// Create profile
userRepo.GetOrCreateProfile(user.ID)
resp, err := service.GetCurrentUser(user.ID)
resp, err := service.GetCurrentUser(context.Background(), user.ID)
require.NoError(t, err)
assert.Equal(t, "testuser", resp.Username)
assert.Equal(t, "test@test.com", resp.Email)
@@ -238,7 +239,7 @@ func TestAuthService_UpdateProfile(t *testing.T) {
LastName: &newLast,
}
resp, err := service.UpdateProfile(user.ID, req)
resp, err := service.UpdateProfile(context.Background(), user.ID, req)
require.NoError(t, err)
assert.Equal(t, "John", resp.FirstName)
assert.Equal(t, "Doe", resp.LastName)
@@ -261,7 +262,7 @@ func TestAuthService_UpdateProfile_DuplicateEmail(t *testing.T) {
Email: &takenEmail,
}
_, err := service.UpdateProfile(user2.ID, req)
_, err := service.UpdateProfile(context.Background(), user2.ID, req)
testutil.AssertAppError(t, err, http.StatusConflict, "error.email_already_taken")
}
@@ -282,7 +283,7 @@ func TestAuthService_UpdateProfile_SameEmail(t *testing.T) {
}
// Same email should not trigger duplicate error
resp, err := service.UpdateProfile(user.ID, req)
resp, err := service.UpdateProfile(context.Background(), user.ID, req)
require.NoError(t, err)
assert.Equal(t, "test@test.com", resp.Email)
}
@@ -298,7 +299,7 @@ func TestAuthService_VerifyEmail(t *testing.T) {
Email: "new@test.com",
Password: "Password123",
}
_, _, err := service.Register(req)
_, _, err := service.Register(context.Background(), req)
require.NoError(t, err)
// Get the user ID
@@ -306,11 +307,11 @@ func TestAuthService_VerifyEmail(t *testing.T) {
require.NoError(t, err)
// Verify with the debug code
err = service.VerifyEmail(user.ID, "123456")
err = service.VerifyEmail(context.Background(), user.ID, "123456")
require.NoError(t, err)
// Verify again — should get already verified error
err = service.VerifyEmail(user.ID, "123456")
err = service.VerifyEmail(context.Background(), user.ID, "123456")
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.email_already_verified")
}
@@ -323,7 +324,7 @@ func TestAuthService_VerifyEmail_InvalidCode(t *testing.T) {
Email: "new@test.com",
Password: "Password123",
}
_, _, err := service.Register(req)
_, _, err := service.Register(context.Background(), req)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("new@test.com")
@@ -331,7 +332,7 @@ func TestAuthService_VerifyEmail_InvalidCode(t *testing.T) {
// Wrong code — with DebugFixedCodes enabled, "123456" bypasses normal lookup,
// but a wrong code should use the normal path
err = service.VerifyEmail(user.ID, "000000")
err = service.VerifyEmail(context.Background(), user.ID, "000000")
assert.Error(t, err)
}
@@ -346,13 +347,13 @@ func TestAuthService_ResendVerificationCode(t *testing.T) {
Email: "new@test.com",
Password: "Password123",
}
_, _, err := service.Register(req)
_, _, err := service.Register(context.Background(), req)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("new@test.com")
require.NoError(t, err)
code, err := service.ResendVerificationCode(user.ID)
code, err := service.ResendVerificationCode(context.Background(), user.ID)
require.NoError(t, err)
assert.Equal(t, "123456", code) // DebugFixedCodes
}
@@ -366,16 +367,16 @@ func TestAuthService_ResendVerificationCode_AlreadyVerified(t *testing.T) {
Email: "new@test.com",
Password: "Password123",
}
_, _, err := service.Register(req)
_, _, err := service.Register(context.Background(), req)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("new@test.com")
require.NoError(t, err)
err = service.VerifyEmail(user.ID, "123456")
err = service.VerifyEmail(context.Background(), user.ID, "123456")
require.NoError(t, err)
_, err = service.ResendVerificationCode(user.ID)
_, err = service.ResendVerificationCode(context.Background(), user.ID)
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.email_already_verified")
}
@@ -390,10 +391,10 @@ func TestAuthService_ForgotPassword(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
code, user, err := service.ForgotPassword("test@test.com")
code, user, err := service.ForgotPassword(context.Background(), "test@test.com")
require.NoError(t, err)
assert.Equal(t, "123456", code) // DebugFixedCodes
assert.NotNil(t, user)
@@ -404,7 +405,7 @@ func TestAuthService_ForgotPassword_NonexistentEmail(t *testing.T) {
service, _ := setupAuthService(t)
// Should not reveal that email doesn't exist
code, user, err := service.ForgotPassword("nonexistent@test.com")
code, user, err := service.ForgotPassword(context.Background(), "nonexistent@test.com")
require.NoError(t, err)
assert.Empty(t, code)
assert.Nil(t, user)
@@ -421,20 +422,20 @@ func TestAuthService_ResetPassword(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
// Forgot password
_, _, err = service.ForgotPassword("test@test.com")
_, _, err = service.ForgotPassword(context.Background(), "test@test.com")
require.NoError(t, err)
// Verify reset code to get the token
resetToken, err := service.VerifyResetCode("test@test.com", "123456")
resetToken, err := service.VerifyResetCode(context.Background(), "test@test.com", "123456")
require.NoError(t, err)
assert.NotEmpty(t, resetToken)
// Reset password
err = service.ResetPassword(resetToken, "NewPassword123")
err = service.ResetPassword(context.Background(), resetToken, "NewPassword123")
require.NoError(t, err)
// Login with new password
@@ -442,7 +443,7 @@ func TestAuthService_ResetPassword(t *testing.T) {
Username: "testuser",
Password: "NewPassword123",
}
loginResp, err := service.Login(loginReq)
loginResp, err := service.Login(context.Background(), loginReq)
require.NoError(t, err)
assert.NotEmpty(t, loginResp.Token)
}
@@ -450,7 +451,7 @@ func TestAuthService_ResetPassword(t *testing.T) {
func TestAuthService_ResetPassword_InvalidToken(t *testing.T) {
service, _ := setupAuthService(t)
err := service.ResetPassword("invalid-token", "NewPassword123")
err := service.ResetPassword(context.Background(), "invalid-token", "NewPassword123")
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.invalid_reset_token")
}
@@ -471,15 +472,15 @@ func TestAuthService_Logout(t *testing.T) {
Username: "testuser",
Password: "Password123",
}
loginResp, err := service.Login(loginReq)
loginResp, err := service.Login(context.Background(), loginReq)
require.NoError(t, err)
// Logout
err = service.Logout(loginResp.Token)
err = service.Logout(context.Background(), loginResp.Token)
require.NoError(t, err)
// Token should be deleted — refreshing should fail
_, err = service.RefreshToken(loginResp.Token, user.ID)
_, err = service.RefreshToken(context.Background(), loginResp.Token, user.ID)
assert.Error(t, err)
}
@@ -494,14 +495,14 @@ func TestAuthService_DeleteAccount_EmailAuth(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("test@test.com")
require.NoError(t, err)
password := "Password123"
_, err = service.DeleteAccount(user.ID, &password, nil)
_, err = service.DeleteAccount(context.Background(), user.ID, &password, nil)
require.NoError(t, err)
}
@@ -513,14 +514,14 @@ func TestAuthService_DeleteAccount_WrongPassword(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("test@test.com")
require.NoError(t, err)
wrongPassword := "WrongPassword1"
_, err = service.DeleteAccount(user.ID, &wrongPassword, nil)
_, err = service.DeleteAccount(context.Background(), user.ID, &wrongPassword, nil)
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
}
@@ -532,13 +533,13 @@ func TestAuthService_DeleteAccount_NoPassword(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("test@test.com")
require.NoError(t, err)
_, err = service.DeleteAccount(user.ID, nil, nil)
_, err = service.DeleteAccount(context.Background(), user.ID, nil, nil)
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.password_required")
}
@@ -546,7 +547,7 @@ func TestAuthService_DeleteAccount_UserNotFound(t *testing.T) {
service, _ := setupAuthService(t)
password := "Password123"
_, err := service.DeleteAccount(99999, &password, nil)
_, err := service.DeleteAccount(context.Background(), 99999, &password, nil)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.user_not_found")
}
@@ -658,7 +659,7 @@ func TestAuthService_Login_EmptyPassword(t *testing.T) {
Password: "",
}
_, err := service.Login(req)
_, err := service.Login(context.Background(), req)
testutil.AssertAppError(t, err, http.StatusUnauthorized, "error.invalid_credentials")
}
@@ -672,17 +673,17 @@ func TestAuthService_ForgotPassword_RateLimit(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
// Make max allowed reset requests (3 based on setup)
for i := 0; i < 3; i++ {
_, _, err := service.ForgotPassword("test@test.com")
_, _, err := service.ForgotPassword(context.Background(), "test@test.com")
require.NoError(t, err)
}
// The 4th should be rate limited
_, _, err = service.ForgotPassword("test@test.com")
_, _, err = service.ForgotPassword(context.Background(), "test@test.com")
assert.Error(t, err)
}
@@ -696,14 +697,14 @@ func TestAuthService_VerifyResetCode_WrongCode(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
_, _, err = service.ForgotPassword("test@test.com")
_, _, err = service.ForgotPassword(context.Background(), "test@test.com")
require.NoError(t, err)
// Wrong code but with debug mode, "123456" works, "000000" should fail
_, err = service.VerifyResetCode("test@test.com", "000000")
_, err = service.VerifyResetCode(context.Background(), "test@test.com", "000000")
assert.Error(t, err)
}
@@ -712,7 +713,7 @@ func TestAuthService_VerifyResetCode_WrongCode(t *testing.T) {
func TestAuthService_VerifyResetCode_NonexistentEmail(t *testing.T) {
service, _ := setupAuthService(t)
_, err := service.VerifyResetCode("nonexistent@test.com", "123456")
_, err := service.VerifyResetCode(context.Background(), "nonexistent@test.com", "123456")
assert.Error(t, err)
}
@@ -734,7 +735,7 @@ func TestAuthService_UpdateProfile_ChangeEmail(t *testing.T) {
Email: &newEmail,
}
resp, err := service.UpdateProfile(user.ID, req)
resp, err := service.UpdateProfile(context.Background(), user.ID, req)
require.NoError(t, err)
assert.Equal(t, "newemail@test.com", resp.Email)
}
@@ -749,14 +750,14 @@ func TestAuthService_DeleteAccount_EmptyPassword(t *testing.T) {
Email: "test@test.com",
Password: "Password123",
}
_, _, err := service.Register(registerReq)
_, _, err := service.Register(context.Background(), registerReq)
require.NoError(t, err)
user, err := service.userRepo.FindByEmail("test@test.com")
require.NoError(t, err)
emptyPw := ""
_, err = service.DeleteAccount(user.ID, &emptyPw, nil)
_, err = service.DeleteAccount(context.Background(), user.ID, &emptyPw, nil)
testutil.AssertAppError(t, err, http.StatusBadRequest, "error.password_required")
}
@@ -789,7 +790,7 @@ func TestAuthService_Register_CreatesProfile(t *testing.T) {
LastName: "Doe",
}
resp, _, err := service.Register(req)
resp, _, err := service.Register(context.Background(), req)
require.NoError(t, err)
assert.Equal(t, "profileuser", resp.User.Username)