store: scope kind assignments, laps, and samples via activity ownership
None of these three tables gained their own user_id column -- they're always accessed through a specific activity, so ownership is checked via a join/subquery against activities.user_id instead.
This commit is contained in:
@@ -143,7 +143,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) {
|
||||
}
|
||||
|
||||
// First: rule engine says needs_review (ambiguous).
|
||||
if _, err := db.InsertKindAssignment(ctx, KindAssignment{
|
||||
if _, err := db.InsertKindAssignment(ctx, userID, KindAssignment{
|
||||
ActivityID: activityID,
|
||||
AssignmentSource: AssignmentSourceRuleEngine,
|
||||
Status: AssignmentStatusNeedsReview,
|
||||
@@ -152,7 +152,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) {
|
||||
t.Fatalf("InsertKindAssignment (rule engine): %v", err)
|
||||
}
|
||||
|
||||
queue, err := db.ReviewQueue(ctx)
|
||||
queue, err := db.ReviewQueue(ctx, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("ReviewQueue: %v", err)
|
||||
}
|
||||
@@ -161,7 +161,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) {
|
||||
}
|
||||
|
||||
// Then: user manually resolves it.
|
||||
if _, err := db.InsertKindAssignment(ctx, KindAssignment{
|
||||
if _, err := db.InsertKindAssignment(ctx, userID, KindAssignment{
|
||||
ActivityID: activityID,
|
||||
WorkoutKindID: &kindID,
|
||||
AssignmentSource: AssignmentSourceManual,
|
||||
@@ -170,7 +170,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) {
|
||||
t.Fatalf("InsertKindAssignment (manual): %v", err)
|
||||
}
|
||||
|
||||
queue, err = db.ReviewQueue(ctx)
|
||||
queue, err = db.ReviewQueue(ctx, userID)
|
||||
if err != nil {
|
||||
t.Fatalf("ReviewQueue after resolve: %v", err)
|
||||
}
|
||||
@@ -178,7 +178,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) {
|
||||
t.Fatalf("expected 0 items in review queue after manual resolve, got %d", len(queue))
|
||||
}
|
||||
|
||||
current, ok, err := db.CurrentAssignment(ctx, activityID)
|
||||
current, ok, err := db.CurrentAssignment(ctx, userID, activityID)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("CurrentAssignment: ok=%v err=%v", ok, err)
|
||||
}
|
||||
@@ -195,7 +195,7 @@ func TestKindAssignment_AppendOnlyHistoryAndCurrentView(t *testing.T) {
|
||||
t.Errorf("expected 2 historical assignment rows (rule engine + manual), got %d", historyCount)
|
||||
}
|
||||
|
||||
forKind, err := db.AssignmentsForKind(ctx, kindID)
|
||||
forKind, err := db.AssignmentsForKind(ctx, userID, kindID)
|
||||
if err != nil {
|
||||
t.Fatalf("AssignmentsForKind: %v", err)
|
||||
}
|
||||
@@ -225,15 +225,15 @@ func TestReplaceLapsIsIdempotent(t *testing.T) {
|
||||
{LapIndex: 1, RawJSON: "{}"},
|
||||
{LapIndex: 2, RawJSON: "{}"},
|
||||
}
|
||||
if err := db.ReplaceLaps(ctx, activityID, laps); err != nil {
|
||||
if err := db.ReplaceLaps(ctx, userID, activityID, laps); err != nil {
|
||||
t.Fatalf("ReplaceLaps (first): %v", err)
|
||||
}
|
||||
// Re-sync with a different set (e.g. corrected data) should fully replace, not append.
|
||||
if err := db.ReplaceLaps(ctx, activityID, laps[:1]); err != nil {
|
||||
if err := db.ReplaceLaps(ctx, userID, activityID, laps[:1]); err != nil {
|
||||
t.Fatalf("ReplaceLaps (second): %v", err)
|
||||
}
|
||||
|
||||
got, err := db.LapsForActivity(ctx, activityID)
|
||||
got, err := db.LapsForActivity(ctx, userID, activityID)
|
||||
if err != nil {
|
||||
t.Fatalf("LapsForActivity: %v", err)
|
||||
}
|
||||
@@ -309,7 +309,7 @@ func TestForeignKeyEnforcementPostMigration(t *testing.T) {
|
||||
}
|
||||
|
||||
// Inserting a kind_assignment with a valid FK reference should succeed.
|
||||
assignmentID, err := db.InsertKindAssignment(ctx, KindAssignment{
|
||||
assignmentID, err := db.InsertKindAssignment(ctx, userID, KindAssignment{
|
||||
ActivityID: activityID,
|
||||
WorkoutKindID: &seedKindID,
|
||||
AssignmentSource: AssignmentSourceManual,
|
||||
@@ -325,7 +325,7 @@ func TestForeignKeyEnforcementPostMigration(t *testing.T) {
|
||||
// Inserting a kind_assignment with a nonexistent workout_kind_id should
|
||||
// be rejected by the FK constraint, proving FK enforcement is ON.
|
||||
nonexistentKindID := int64(999999)
|
||||
_, err = db.InsertKindAssignment(ctx, KindAssignment{
|
||||
_, err = db.InsertKindAssignment(ctx, userID, KindAssignment{
|
||||
ActivityID: activityID,
|
||||
WorkoutKindID: &nonexistentKindID,
|
||||
AssignmentSource: AssignmentSourceManual,
|
||||
@@ -568,6 +568,38 @@ func TestRebuildMigrationsPreserveForeignKeyReferences(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentAssignment_ScopedToOwningUser(t *testing.T) {
|
||||
db := openTestDB(t)
|
||||
ctx := context.Background()
|
||||
userA, err := db.ProvisionUser(ctx, "sub-a", "A")
|
||||
if err != nil {
|
||||
t.Fatalf("ProvisionUser(a): %v", err)
|
||||
}
|
||||
userB, err := db.ProvisionUser(ctx, "sub-b", "B")
|
||||
if err != nil {
|
||||
t.Fatalf("ProvisionUser(b): %v", err)
|
||||
}
|
||||
|
||||
activityID, err := db.UpsertActivity(ctx, userA, Activity{GarminActivityID: 1, StartTimeUTC: "2026-07-11 05:00:00", RawJSON: "{}"})
|
||||
if err != nil {
|
||||
t.Fatalf("UpsertActivity: %v", err)
|
||||
}
|
||||
if _, err := db.InsertKindAssignment(ctx, userA, KindAssignment{
|
||||
ActivityID: activityID, AssignmentSource: AssignmentSourceRuleEngine, Status: AssignmentStatusNeedsReview, CandidateKindsJSON: "[]",
|
||||
}); err != nil {
|
||||
t.Fatalf("InsertKindAssignment: %v", err)
|
||||
}
|
||||
|
||||
if _, found, err := db.CurrentAssignment(ctx, userB, activityID); err != nil || found {
|
||||
t.Fatalf("expected userB to not see userA's activity assignment, found=%v err=%v", found, err)
|
||||
}
|
||||
if _, err := db.InsertKindAssignment(ctx, userB, KindAssignment{
|
||||
ActivityID: activityID, AssignmentSource: AssignmentSourceManual, Status: AssignmentStatusAssigned, CandidateKindsJSON: "[]",
|
||||
}); err == nil {
|
||||
t.Fatal("expected InsertKindAssignment to reject an activity that doesn't belong to userB")
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s string, substrs ...string) bool {
|
||||
lower := strings.ToLower(s)
|
||||
for _, substr := range substrs {
|
||||
|
||||
Reference in New Issue
Block a user