2026-07-25 12:59:56 +02:00
|
|
|
package store
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestGetUserBySub_NotFoundReturnsFalseNotError(t *testing.T) {
|
|
|
|
|
db := openTestDB(t)
|
|
|
|
|
_, found, err := db.GetUserBySub(context.Background(), "no-such-sub")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetUserBySub: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if found {
|
|
|
|
|
t.Fatal("expected found=false for an unknown sub")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProvisionUser_SeedsProfileTaxonomyAndSyncState(t *testing.T) {
|
|
|
|
|
db := openTestDB(t)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
userID, err := db.ProvisionUser(ctx, "sub-123", "Lucie")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ProvisionUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u, found, err := db.GetUserBySub(ctx, "sub-123")
|
|
|
|
|
if err != nil || !found {
|
|
|
|
|
t.Fatalf("GetUserBySub: found=%v err=%v", found, err)
|
|
|
|
|
}
|
|
|
|
|
if u.ID != userID || u.DisplayName != "Lucie" {
|
|
|
|
|
t.Fatalf("got %+v, want ID=%d DisplayName=Lucie", u, userID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
profile, err := db.GetProfile(ctx, userID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetProfile: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if profile.Name != "Lucie" {
|
|
|
|
|
t.Errorf("profile.Name = %q, want %q", profile.Name, "Lucie")
|
|
|
|
|
}
|
|
|
|
|
if profile.RollingWindowDays != 90 {
|
|
|
|
|
t.Errorf("profile.RollingWindowDays = %d, want 90 (default)", profile.RollingWindowDays)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kinds, err := db.ListWorkoutKinds(ctx, userID, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListWorkoutKinds: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(kinds) != 8 {
|
|
|
|
|
t.Fatalf("expected 8 seeded workout kinds, got %d", len(kinds))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state, err := db.GetSyncState(ctx, userID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetSyncState: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if state.EarliestSyncedDate != nil || state.BackfillComplete {
|
|
|
|
|
t.Errorf("expected fresh sync state, got %+v", state)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProvisionUser_TwoUsersGetIndependentTaxonomies(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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kindsA, err := db.ListWorkoutKinds(ctx, userA, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListWorkoutKinds(a): %v", err)
|
|
|
|
|
}
|
|
|
|
|
kindsB, err := db.ListWorkoutKinds(ctx, userB, false)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListWorkoutKinds(b): %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(kindsA) != 8 || len(kindsB) != 8 {
|
|
|
|
|
t.Fatalf("expected 8 kinds each, got a=%d b=%d", len(kindsA), len(kindsB))
|
|
|
|
|
}
|
|
|
|
|
if kindsA[0].ID == kindsB[0].ID {
|
|
|
|
|
t.Fatal("expected each user's seeded kinds to be distinct rows")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-26 10:17:21 +02:00
|
|
|
|
|
|
|
|
func TestDeleteUser_RemovesUserAndCascadesEverything(t *testing.T) {
|
|
|
|
|
db := openTestDB(t)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
userID, err := db.ProvisionUser(ctx, "delete-me", "Delete Me")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ProvisionUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activityID, err := db.UpsertActivity(ctx, userID, Activity{GarminActivityID: 1, StartTimeUTC: "2026-07-11 06:00:00", RawJSON: "{}"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("UpsertActivity: %v", err)
|
|
|
|
|
}
|
|
|
|
|
kindID, err := db.CreateWorkoutKind(ctx, userID, WorkoutKind{Name: "Test Delete Kind", RuleJSON: `{"match":"all","conditions":[]}`, IsActive: true})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CreateWorkoutKind: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := db.ReplaceLaps(ctx, userID, activityID, []Lap{{LapIndex: 1}}); err != nil {
|
|
|
|
|
t.Fatalf("ReplaceLaps: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := db.ReplaceActivitySamples(ctx, userID, activityID, []Sample{{ElapsedSeconds: 0}}); err != nil {
|
|
|
|
|
t.Fatalf("ReplaceActivitySamples: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := db.InsertKindAssignment(ctx, userID, KindAssignment{
|
|
|
|
|
ActivityID: activityID, WorkoutKindID: &kindID, AssignmentSource: AssignmentSourceRuleEngine,
|
|
|
|
|
Status: AssignmentStatusAssigned, CandidateKindsJSON: "[]",
|
|
|
|
|
}); err != nil {
|
|
|
|
|
t.Fatalf("InsertKindAssignment: %v", err)
|
|
|
|
|
}
|
|
|
|
|
minPace := 300.0
|
|
|
|
|
if err := db.UpdateWorkoutTypePace(ctx, userID, WorkoutTypePace{WorkoutKindID: kindID, PaceMinSecPerKm: &minPace}); err != nil {
|
|
|
|
|
t.Fatalf("UpdateWorkoutTypePace: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if err := db.UpdateSyncState(ctx, userID, "2020-01-01", true); err != nil {
|
|
|
|
|
t.Fatalf("UpdateSyncState: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := db.StartSyncRun(ctx, userID, SyncKindBackfill); err != nil {
|
|
|
|
|
t.Fatalf("StartSyncRun: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := db.DeleteUser(ctx, userID); err != nil {
|
|
|
|
|
t.Fatalf("DeleteUser: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, found, err := db.GetUserBySub(ctx, "delete-me"); err != nil || found {
|
|
|
|
|
t.Fatalf("expected user gone after DeleteUser, found=%v err=%v", found, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checks := []struct {
|
|
|
|
|
query string
|
|
|
|
|
arg int64
|
|
|
|
|
}{
|
|
|
|
|
{`SELECT COUNT(*) FROM profile WHERE user_id = ?`, userID},
|
|
|
|
|
{`SELECT COUNT(*) FROM workout_kinds WHERE user_id = ?`, userID},
|
|
|
|
|
{`SELECT COUNT(*) FROM workout_type_paces WHERE workout_kind_id = ?`, kindID},
|
|
|
|
|
{`SELECT COUNT(*) FROM activities WHERE user_id = ?`, userID},
|
|
|
|
|
{`SELECT COUNT(*) FROM laps WHERE activity_id = ?`, activityID},
|
|
|
|
|
{`SELECT COUNT(*) FROM activity_samples WHERE activity_id = ?`, activityID},
|
|
|
|
|
{`SELECT COUNT(*) FROM kind_assignments WHERE activity_id = ?`, activityID},
|
|
|
|
|
{`SELECT COUNT(*) FROM sync_state WHERE user_id = ?`, userID},
|
|
|
|
|
{`SELECT COUNT(*) FROM sync_runs WHERE user_id = ?`, userID},
|
|
|
|
|
}
|
|
|
|
|
for _, c := range checks {
|
|
|
|
|
var count int
|
|
|
|
|
if err := db.QueryRowContext(ctx, c.query, c.arg).Scan(&count); err != nil {
|
|
|
|
|
t.Fatalf("count query %q: %v", c.query, err)
|
|
|
|
|
}
|
|
|
|
|
if count != 0 {
|
|
|
|
|
t.Errorf("query %q: got %d rows, want 0 after DeleteUser", c.query, count)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|