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
+42 -41
View File
@@ -1,6 +1,7 @@
package services
import (
"context"
"net/http"
"testing"
@@ -42,7 +43,7 @@ func TestDocumentService_CreateDocument(t *testing.T) {
FileName: "manual.pdf",
}
resp, err := service.CreateDocument(req, user.ID)
resp, err := service.CreateDocument(context.Background(), req, user.ID)
require.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "Furnace Manual", resp.Title)
@@ -64,7 +65,7 @@ func TestDocumentService_CreateDocument_DefaultType(t *testing.T) {
// DocumentType not set — should default to "general"
}
resp, err := service.CreateDocument(req, user.ID)
resp, err := service.CreateDocument(context.Background(), req, user.ID)
require.NoError(t, err)
assert.Equal(t, models.DocumentTypeGeneral, resp.DocumentType)
}
@@ -84,7 +85,7 @@ func TestDocumentService_CreateDocument_WithImages(t *testing.T) {
ImageURLs: []string{"https://example.com/img1.jpg", "https://example.com/img2.jpg"},
}
resp, err := service.CreateDocument(req, user.ID)
resp, err := service.CreateDocument(context.Background(), req, user.ID)
require.NoError(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "Receipt with photos", resp.Title)
@@ -105,7 +106,7 @@ func TestDocumentService_CreateDocument_AccessDenied(t *testing.T) {
Title: "Unauthorized Doc",
}
_, err := service.CreateDocument(req, other.ID)
_, err := service.CreateDocument(context.Background(), req, other.ID)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}
@@ -121,7 +122,7 @@ func TestDocumentService_GetDocument(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
resp, err := service.GetDocument(doc.ID, user.ID)
resp, err := service.GetDocument(context.Background(), doc.ID, user.ID)
require.NoError(t, err)
assert.Equal(t, doc.ID, resp.ID)
assert.Equal(t, "Test Doc", resp.Title)
@@ -135,7 +136,7 @@ func TestDocumentService_GetDocument_NotFound(t *testing.T) {
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
_, err := service.GetDocument(9999, user.ID)
_, err := service.GetDocument(context.Background(), 9999, user.ID)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_not_found")
}
@@ -150,7 +151,7 @@ func TestDocumentService_GetDocument_AccessDenied(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Private Doc")
_, err := service.GetDocument(doc.ID, other.ID)
_, err := service.GetDocument(context.Background(), doc.ID, other.ID)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -173,7 +174,7 @@ func TestDocumentService_UpdateDocument(t *testing.T) {
Description: &newDesc,
}
resp, err := service.UpdateDocument(doc.ID, user.ID, req)
resp, err := service.UpdateDocument(context.Background(), doc.ID, user.ID, req)
require.NoError(t, err)
assert.Equal(t, "Updated Title", resp.Title)
assert.Equal(t, "Updated description", resp.Description)
@@ -190,7 +191,7 @@ func TestDocumentService_UpdateDocument_NotFound(t *testing.T) {
newTitle := "Won't Work"
req := &requests.UpdateDocumentRequest{Title: &newTitle}
_, err := service.UpdateDocument(9999, user.ID, req)
_, err := service.UpdateDocument(context.Background(), 9999, user.ID, req)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_not_found")
}
@@ -208,7 +209,7 @@ func TestDocumentService_UpdateDocument_AccessDenied(t *testing.T) {
newTitle := "Hacked"
req := &requests.UpdateDocumentRequest{Title: &newTitle}
_, err := service.UpdateDocument(doc.ID, other.ID, req)
_, err := service.UpdateDocument(context.Background(), doc.ID, other.ID, req)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -225,7 +226,7 @@ func TestDocumentService_UpdateDocument_ChangeType(t *testing.T) {
newType := models.DocumentTypeWarranty
req := &requests.UpdateDocumentRequest{DocumentType: &newType}
resp, err := service.UpdateDocument(doc.ID, user.ID, req)
resp, err := service.UpdateDocument(context.Background(), doc.ID, user.ID, req)
require.NoError(t, err)
assert.Equal(t, models.DocumentTypeWarranty, resp.DocumentType)
}
@@ -242,11 +243,11 @@ func TestDocumentService_DeleteDocument(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "To Delete")
err := service.DeleteDocument(doc.ID, user.ID)
err := service.DeleteDocument(context.Background(), doc.ID, user.ID)
require.NoError(t, err)
// Should not be found after deletion
_, err = service.GetDocument(doc.ID, user.ID)
_, err = service.GetDocument(context.Background(), doc.ID, user.ID)
assert.Error(t, err)
}
@@ -258,7 +259,7 @@ func TestDocumentService_DeleteDocument_NotFound(t *testing.T) {
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
err := service.DeleteDocument(9999, user.ID)
err := service.DeleteDocument(context.Background(), 9999, user.ID)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_not_found")
}
@@ -273,7 +274,7 @@ func TestDocumentService_DeleteDocument_AccessDenied(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Private Doc")
err := service.DeleteDocument(doc.ID, other.ID)
err := service.DeleteDocument(context.Background(), doc.ID, other.ID)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -290,7 +291,7 @@ func TestDocumentService_ListDocuments(t *testing.T) {
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 1")
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Doc 2")
resp, err := service.ListDocuments(user.ID, nil)
resp, err := service.ListDocuments(context.Background(), user.ID, nil)
require.NoError(t, err)
assert.Len(t, resp, 2)
}
@@ -303,7 +304,7 @@ func TestDocumentService_ListDocuments_NoResidences(t *testing.T) {
user := testutil.CreateTestUser(t, db, "loner", "loner@test.com", "Password123")
resp, err := service.ListDocuments(user.ID, nil)
resp, err := service.ListDocuments(context.Background(), user.ID, nil)
require.NoError(t, err)
assert.Empty(t, resp)
}
@@ -321,7 +322,7 @@ func TestDocumentService_ListDocuments_FilterByResidence(t *testing.T) {
testutil.CreateTestDocument(t, db, residence2.ID, user.ID, "Doc B")
filter := &repositories.DocumentFilter{ResidenceID: &residence1.ID}
resp, err := service.ListDocuments(user.ID, filter)
resp, err := service.ListDocuments(context.Background(), user.ID, filter)
require.NoError(t, err)
assert.Len(t, resp, 1)
assert.Equal(t, "Doc A", resp[0].Title)
@@ -340,7 +341,7 @@ func TestDocumentService_ListDocuments_FilterByResidence_AccessDenied(t *testing
testutil.CreateTestResidence(t, db, other.ID, "Other House")
filter := &repositories.DocumentFilter{ResidenceID: &residence.ID}
_, err := service.ListDocuments(other.ID, filter)
_, err := service.ListDocuments(context.Background(), other.ID, filter)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.residence_access_denied")
}
@@ -369,7 +370,7 @@ func TestDocumentService_ListWarranties(t *testing.T) {
// Create a general doc
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "General Doc")
resp, err := service.ListWarranties(user.ID)
resp, err := service.ListWarranties(context.Background(), user.ID)
require.NoError(t, err)
assert.Len(t, resp, 1)
assert.Equal(t, "HVAC Warranty", resp[0].Title)
@@ -383,7 +384,7 @@ func TestDocumentService_ListWarranties_NoResidences(t *testing.T) {
user := testutil.CreateTestUser(t, db, "loner", "loner@test.com", "Password123")
resp, err := service.ListWarranties(user.ID)
resp, err := service.ListWarranties(context.Background(), user.ID)
require.NoError(t, err)
assert.Empty(t, resp)
}
@@ -400,7 +401,7 @@ func TestDocumentService_DeactivateDocument(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "To Deactivate")
resp, err := service.DeactivateDocument(doc.ID, user.ID)
resp, err := service.DeactivateDocument(context.Background(), doc.ID, user.ID)
require.NoError(t, err)
assert.False(t, resp.IsActive)
}
@@ -413,7 +414,7 @@ func TestDocumentService_DeactivateDocument_NotFound(t *testing.T) {
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
_, err := service.DeactivateDocument(9999, user.ID)
_, err := service.DeactivateDocument(context.Background(), 9999, user.ID)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_not_found")
}
@@ -428,7 +429,7 @@ func TestDocumentService_DeactivateDocument_AccessDenied(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Private Doc")
_, err := service.DeactivateDocument(doc.ID, other.ID)
_, err := service.DeactivateDocument(context.Background(), doc.ID, other.ID)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -444,7 +445,7 @@ func TestDocumentService_UploadDocumentImage(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
resp, err := service.UploadDocumentImage(doc.ID, user.ID, "https://example.com/photo.jpg", "Front view")
resp, err := service.UploadDocumentImage(context.Background(), doc.ID, user.ID, "https://example.com/photo.jpg", "Front view")
require.NoError(t, err)
assert.NotNil(t, resp)
}
@@ -457,7 +458,7 @@ func TestDocumentService_UploadDocumentImage_NotFound(t *testing.T) {
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
_, err := service.UploadDocumentImage(9999, user.ID, "https://example.com/photo.jpg", "")
_, err := service.UploadDocumentImage(context.Background(), 9999, user.ID, "https://example.com/photo.jpg", "")
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_not_found")
}
@@ -472,7 +473,7 @@ func TestDocumentService_UploadDocumentImage_AccessDenied(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Private Doc")
_, err := service.UploadDocumentImage(doc.ID, other.ID, "https://example.com/photo.jpg", "")
_, err := service.UploadDocumentImage(context.Background(), doc.ID, other.ID, "https://example.com/photo.jpg", "")
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -496,7 +497,7 @@ func TestDocumentService_DeleteDocumentImage(t *testing.T) {
err := db.Create(img).Error
require.NoError(t, err)
resp, err := service.DeleteDocumentImage(doc.ID, img.ID, user.ID)
resp, err := service.DeleteDocumentImage(context.Background(), doc.ID, img.ID, user.ID)
require.NoError(t, err)
assert.NotNil(t, resp)
}
@@ -511,7 +512,7 @@ func TestDocumentService_DeleteDocumentImage_NotFound(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, user.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "Test Doc")
_, err := service.DeleteDocumentImage(doc.ID, 9999, user.ID)
_, err := service.DeleteDocumentImage(context.Background(), doc.ID, 9999, user.ID)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_image_not_found")
}
@@ -535,7 +536,7 @@ func TestDocumentService_DeleteDocumentImage_WrongDocument(t *testing.T) {
require.NoError(t, err)
// Try to delete the image specifying doc2
_, err = service.DeleteDocumentImage(doc2.ID, img.ID, user.ID)
_, err = service.DeleteDocumentImage(context.Background(), doc2.ID, img.ID, user.ID)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_image_not_found")
}
@@ -557,7 +558,7 @@ func TestDocumentService_DeleteDocumentImage_AccessDenied(t *testing.T) {
err := db.Create(img).Error
require.NoError(t, err)
_, err = service.DeleteDocumentImage(doc.ID, img.ID, other.ID)
_, err = service.DeleteDocumentImage(context.Background(), doc.ID, img.ID, other.ID)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -575,7 +576,7 @@ func TestDocumentService_GetDocument_SharedUserHasAccess(t *testing.T) {
residenceRepo.AddUser(residence.ID, shared.ID)
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Shared Doc")
resp, err := service.GetDocument(doc.ID, shared.ID)
resp, err := service.GetDocument(context.Background(), doc.ID, shared.ID)
require.NoError(t, err)
assert.Equal(t, "Shared Doc", resp.Title)
}
@@ -593,11 +594,11 @@ func TestDocumentService_ActivateDocument(t *testing.T) {
doc := testutil.CreateTestDocument(t, db, residence.ID, user.ID, "To Activate")
// Deactivate first
_, err := service.DeactivateDocument(doc.ID, user.ID)
_, err := service.DeactivateDocument(context.Background(), doc.ID, user.ID)
require.NoError(t, err)
// Now activate
resp, err := service.ActivateDocument(doc.ID, user.ID)
resp, err := service.ActivateDocument(context.Background(), doc.ID, user.ID)
require.NoError(t, err)
assert.True(t, resp.IsActive)
}
@@ -610,7 +611,7 @@ func TestDocumentService_ActivateDocument_NotFound(t *testing.T) {
user := testutil.CreateTestUser(t, db, "owner", "owner@test.com", "Password123")
_, err := service.ActivateDocument(9999, user.ID)
_, err := service.ActivateDocument(context.Background(), 9999, user.ID)
testutil.AssertAppError(t, err, http.StatusNotFound, "error.document_not_found")
}
@@ -625,7 +626,7 @@ func TestDocumentService_ActivateDocument_AccessDenied(t *testing.T) {
residence := testutil.CreateTestResidence(t, db, owner.ID, "Test House")
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Private Doc")
_, err := service.ActivateDocument(doc.ID, other.ID)
_, err := service.ActivateDocument(context.Background(), doc.ID, other.ID)
testutil.AssertAppError(t, err, http.StatusForbidden, "error.document_access_denied")
}
@@ -646,7 +647,7 @@ func TestDocumentService_CreateDocument_WithEmptyImageURL(t *testing.T) {
ImageURLs: []string{"", "https://example.com/img.jpg", ""},
}
resp, err := service.CreateDocument(req, user.ID)
resp, err := service.CreateDocument(context.Background(), req, user.ID)
require.NoError(t, err)
assert.NotNil(t, resp)
}
@@ -687,7 +688,7 @@ func TestDocumentService_UpdateDocument_AllFields(t *testing.T) {
ModelNumber: &newModel,
}
resp, err := service.UpdateDocument(doc.ID, user.ID, req)
resp, err := service.UpdateDocument(context.Background(), doc.ID, user.ID, req)
require.NoError(t, err)
assert.Equal(t, "Updated", resp.Title)
assert.Equal(t, "New description", resp.Description)
@@ -720,7 +721,7 @@ func TestDocumentService_ListDocuments_FilterByType(t *testing.T) {
testutil.CreateTestDocument(t, db, residence.ID, user.ID, "General Doc")
filter := &repositories.DocumentFilter{DocumentType: string(models.DocumentTypeWarranty)}
resp, err := service.ListDocuments(user.ID, filter)
resp, err := service.ListDocuments(context.Background(), user.ID, filter)
require.NoError(t, err)
assert.Len(t, resp, 1)
assert.Equal(t, "Warranty Doc", resp[0].Title)
@@ -742,7 +743,7 @@ func TestDocumentService_SharedUser_CanUpdate(t *testing.T) {
newTitle := "Updated by shared user"
req := &requests.UpdateDocumentRequest{Title: &newTitle}
resp, err := service.UpdateDocument(doc.ID, shared.ID, req)
resp, err := service.UpdateDocument(context.Background(), doc.ID, shared.ID, req)
require.NoError(t, err)
assert.Equal(t, "Updated by shared user", resp.Title)
}
@@ -759,6 +760,6 @@ func TestDocumentService_SharedUser_CanDelete(t *testing.T) {
residenceRepo.AddUser(residence.ID, shared.ID)
doc := testutil.CreateTestDocument(t, db, residence.ID, owner.ID, "Shared Doc")
err := service.DeleteDocument(doc.ID, shared.ID)
err := service.DeleteDocument(context.Background(), doc.ID, shared.ID)
require.NoError(t, err)
}