Migrate TaskService + ResidenceService to ctx-aware repos
Every public method on TaskService and ResidenceService now takes ctx context.Context as the first arg and routes its repo calls through .WithContext(ctx). With otelgorm registered, this means every API endpoint backed by these two services produces a flame graph in Jaeger where the SQL spans nest under the parent HTTP request span — instead of appearing as orphaned queries. Endpoints now fully traced (HTTP → service → SQL): - GET /api/tasks/ (already shipped) - GET /api/tasks/by-residence/:id/ (already shipped) - GET /api/tasks/:id/ - POST /api/tasks/ - POST /api/tasks/bulk/ - PUT /api/tasks/:id/ - DELETE /api/tasks/:id/ - POST /api/tasks/:id/in-progress/ - POST /api/tasks/:id/cancel/ - POST /api/tasks/:id/uncancel/ - POST /api/tasks/:id/archive/ - POST /api/tasks/:id/unarchive/ - POST /api/tasks/:id/complete/ - POST /api/tasks/:id/quick-complete/ - GET /api/tasks/completions/* (CRUD) - GET /api/static_data/ (categories, priorities, frequencies) - GET /api/residences/ - GET /api/residences/my/ - GET /api/residences/summary/ - GET /api/residences/:id/ - POST /api/residences/ - PUT /api/residences/:id/ - DELETE /api/residences/:id/ - Share-code + member management endpoints - GET /api/residences/:id/report/ Mechanical work: ~50 method signatures, ~80 handler call sites, ~25 test call sites updated. Internal sendTaskCompletedNotification helper also takes ctx so background notification SQL nests correctly. The remaining services (ContractorService, DocumentService, AuthService, NotificationService, SubscriptionService) follow the same pattern; they continue to emit untraced SQL until migrated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -52,7 +53,7 @@ func TestTaskService_CompleteToCancel_OneTimeTask(t *testing.T) {
|
||||
TaskID: task.ID,
|
||||
Notes: "Completed",
|
||||
}
|
||||
_, err = service.CreateCompletion(completionReq, user.ID, now)
|
||||
_, err = service.CreateCompletion(context.Background(), completionReq, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify task is completed (NextDueDate=nil)
|
||||
@@ -66,7 +67,7 @@ func TestTaskService_CompleteToCancel_OneTimeTask(t *testing.T) {
|
||||
assert.Equal(t, "completed_tasks", column, "Completed one-time task should be in completed column")
|
||||
|
||||
// Step 2: Cancel the completed task
|
||||
cancelResp, err := service.CancelTask(task.ID, user.ID, now)
|
||||
cancelResp, err := service.CancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, cancelResp.Data.IsCancelled, "Task should be cancelled")
|
||||
|
||||
@@ -106,7 +107,7 @@ func TestTaskService_CancelToComplete(t *testing.T) {
|
||||
now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Step 1: Cancel the task
|
||||
cancelResp, err := service.CancelTask(task.ID, user.ID, now)
|
||||
cancelResp, err := service.CancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, cancelResp.Data.IsCancelled)
|
||||
|
||||
@@ -115,7 +116,7 @@ func TestTaskService_CancelToComplete(t *testing.T) {
|
||||
TaskID: task.ID,
|
||||
Notes: "Completed even though cancelled",
|
||||
}
|
||||
completionResp, err := service.CreateCompletion(completionReq, user.ID, now)
|
||||
completionResp, err := service.CreateCompletion(context.Background(), completionReq, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, completionResp.Data.ID, "Completion should be created")
|
||||
|
||||
@@ -160,7 +161,7 @@ func TestTaskService_ArchiveToComplete(t *testing.T) {
|
||||
now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Step 1: Archive the task
|
||||
archiveResp, err := service.ArchiveTask(task.ID, user.ID, now)
|
||||
archiveResp, err := service.ArchiveTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, archiveResp.Data.IsArchived)
|
||||
|
||||
@@ -169,7 +170,7 @@ func TestTaskService_ArchiveToComplete(t *testing.T) {
|
||||
TaskID: task.ID,
|
||||
Notes: "Completed even though archived",
|
||||
}
|
||||
completionResp, err := service.CreateCompletion(completionReq, user.ID, now)
|
||||
completionResp, err := service.CreateCompletion(context.Background(), completionReq, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, completionResp.Data.ID, "Completion should be created")
|
||||
|
||||
@@ -217,11 +218,11 @@ func TestTaskService_CompleteToArchive_OneTimeTask(t *testing.T) {
|
||||
TaskID: task.ID,
|
||||
Notes: "Done",
|
||||
}
|
||||
_, err = service.CreateCompletion(completionReq, user.ID, now)
|
||||
_, err = service.CreateCompletion(context.Background(), completionReq, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Step 2: Archive the completed task
|
||||
archiveResp, err := service.ArchiveTask(task.ID, user.ID, now)
|
||||
archiveResp, err := service.ArchiveTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, archiveResp.Data.IsArchived, "Task should be archived")
|
||||
|
||||
@@ -266,7 +267,7 @@ func TestTaskService_InProgressToCancelToUncancel(t *testing.T) {
|
||||
now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Step 1: Mark in progress
|
||||
inProgressResp, err := service.MarkInProgress(task.ID, user.ID, now)
|
||||
inProgressResp, err := service.MarkInProgress(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, inProgressResp.Data.InProgress)
|
||||
|
||||
@@ -277,7 +278,7 @@ func TestTaskService_InProgressToCancelToUncancel(t *testing.T) {
|
||||
assert.Equal(t, "in_progress_tasks", column, "Task should be in in_progress column")
|
||||
|
||||
// Step 2: Cancel the in-progress task
|
||||
cancelResp, err := service.CancelTask(task.ID, user.ID, now)
|
||||
cancelResp, err := service.CancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, cancelResp.Data.IsCancelled)
|
||||
|
||||
@@ -292,7 +293,7 @@ func TestTaskService_InProgressToCancelToUncancel(t *testing.T) {
|
||||
assert.Equal(t, "cancelled_tasks", column, "Cancelled task should be in cancelled column")
|
||||
|
||||
// Step 3: Uncancel the task
|
||||
uncancelResp, err := service.UncancelTask(task.ID, user.ID, now)
|
||||
uncancelResp, err := service.UncancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, uncancelResp.Data.IsCancelled)
|
||||
|
||||
@@ -336,15 +337,15 @@ func TestTaskService_MultipleCancelUncancelCycles(t *testing.T) {
|
||||
now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Cycle 1: Cancel -> Uncancel
|
||||
_, err = service.CancelTask(task.ID, user.ID, now)
|
||||
_, err = service.CancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
_, err = service.UncancelTask(task.ID, user.ID, now)
|
||||
_, err = service.UncancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Cycle 2: Cancel -> Uncancel
|
||||
_, err = service.CancelTask(task.ID, user.ID, now)
|
||||
_, err = service.CancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
uncancelResp, err := service.UncancelTask(task.ID, user.ID, now)
|
||||
uncancelResp, err := service.UncancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify final state
|
||||
@@ -393,7 +394,7 @@ func TestTaskService_CompleteToMarkInProgress_OneTimeTask(t *testing.T) {
|
||||
TaskID: task.ID,
|
||||
Notes: "Done",
|
||||
}
|
||||
_, err = service.CreateCompletion(completionReq, user.ID, now)
|
||||
_, err = service.CreateCompletion(context.Background(), completionReq, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify completed state
|
||||
@@ -402,7 +403,7 @@ func TestTaskService_CompleteToMarkInProgress_OneTimeTask(t *testing.T) {
|
||||
assert.Nil(t, taskAfterComplete.NextDueDate)
|
||||
|
||||
// Step 2: Mark in progress
|
||||
ipResp, err := service.MarkInProgress(task.ID, user.ID, now)
|
||||
ipResp, err := service.MarkInProgress(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ipResp.Data.InProgress, "InProgress should be true")
|
||||
|
||||
@@ -452,7 +453,7 @@ func TestTaskService_RecurringTaskStateCycle(t *testing.T) {
|
||||
now := time.Date(2026, 3, 26, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Step 1: Mark in progress
|
||||
ipResp, err := service.MarkInProgress(task.ID, user.ID, now)
|
||||
ipResp, err := service.MarkInProgress(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ipResp.Data.InProgress)
|
||||
|
||||
@@ -469,7 +470,7 @@ func TestTaskService_RecurringTaskStateCycle(t *testing.T) {
|
||||
Notes: "Week 1 done",
|
||||
CompletedAt: &firstCompletedAt,
|
||||
}
|
||||
_, err = service.CreateCompletion(completionReq1, user.ID, now)
|
||||
_, err = service.CreateCompletion(context.Background(), completionReq1, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify: InProgress reset to false, NextDueDate recalculated
|
||||
@@ -487,7 +488,7 @@ func TestTaskService_RecurringTaskStateCycle(t *testing.T) {
|
||||
assert.NotEqual(t, "completed_tasks", column, "Recurring task should not be in completed column")
|
||||
|
||||
// Step 3: Mark in progress again for next cycle
|
||||
ipResp2, err := service.MarkInProgress(task.ID, user.ID, now)
|
||||
ipResp2, err := service.MarkInProgress(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, ipResp2.Data.InProgress)
|
||||
|
||||
@@ -498,7 +499,7 @@ func TestTaskService_RecurringTaskStateCycle(t *testing.T) {
|
||||
Notes: "Week 2 done",
|
||||
CompletedAt: &secondCompletedAt,
|
||||
}
|
||||
_, err = service.CreateCompletion(completionReq2, user.ID, now)
|
||||
_, err = service.CreateCompletion(context.Background(), completionReq2, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify: InProgress reset again, NextDueDate recalculated from second completion
|
||||
@@ -546,7 +547,7 @@ func TestTaskService_ArchiveToUnarchive(t *testing.T) {
|
||||
assert.Equal(t, "due_soon_tasks", column, "Task due in 5 days should be due_soon")
|
||||
|
||||
// Step 1: Archive
|
||||
archiveResp, err := service.ArchiveTask(task.ID, user.ID, now)
|
||||
archiveResp, err := service.ArchiveTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, archiveResp.Data.IsArchived)
|
||||
|
||||
@@ -556,7 +557,7 @@ func TestTaskService_ArchiveToUnarchive(t *testing.T) {
|
||||
assert.Equal(t, "cancelled_tasks", column, "Archived task should be in cancelled column")
|
||||
|
||||
// Step 2: Unarchive
|
||||
unarchiveResp, err := service.UnarchiveTask(task.ID, user.ID, now)
|
||||
unarchiveResp, err := service.UnarchiveTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, unarchiveResp.Data.IsArchived)
|
||||
|
||||
@@ -738,7 +739,7 @@ func TestTaskService_OptimisticLocking_UpdateWithCorrectVersion(t *testing.T) {
|
||||
Title: &newTitle,
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
resp, err := service.UpdateTask(task.ID, user.ID, req, now)
|
||||
resp, err := service.UpdateTask(context.Background(), task.ID, user.ID, req, now)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated Title", resp.Data.Title)
|
||||
|
||||
@@ -946,7 +947,7 @@ func TestTaskService_OptimisticLocking_ServiceCancelConflictAndRecovery(t *testi
|
||||
|
||||
// Reset version and verify service cancel succeeds with correct version
|
||||
db.Model(&models.Task{}).Where("id = ?", task.ID).Update("version", 1)
|
||||
cancelResp, err := service.CancelTask(task.ID, user.ID, now)
|
||||
cancelResp, err := service.CancelTask(context.Background(), task.ID, user.ID, now)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, cancelResp.Data.IsCancelled, "Service cancel should succeed with correct version")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user