package store import ( "context" "testing" ) func TestProfile_DefaultsThenUpdate(t *testing.T) { db := openTestDB(t) ctx := context.Background() userID, err := db.ProvisionUser(ctx, "test-sub", "Default") if err != nil { t.Fatalf("ProvisionUser: %v", err) } p, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile: %v", err) } if p.RollingWindowDays != 90 { t.Errorf("RollingWindowDays = %d, want 90 (migration default)", p.RollingWindowDays) } if p.HRZone1MinPct != 50 || p.HRZone5MaxPct != 100 { t.Errorf("zone defaults = %+v, want Z1 min=50, Z5 max=100", p) } if p.WarmupMinutes != 10 || p.CooldownMinutes != 5 { t.Errorf("phase-minute defaults = %+v, want warmup=10, cooldown=5", p) } if p.BackfillHorizonDays != 90 { t.Errorf("BackfillHorizonDays = %d, want 90 (schema default)", p.BackfillHorizonDays) } if p.MinRepresentativePaceSecPerKm != 720 || p.MinRepresentativeTimeSeconds != 3 { t.Errorf("pace artifact filter defaults = %+v, want pace=720, time=3", p) } if p.PaceColor != "#3b82f6" || p.HeartRateColor != "#ef4444" { t.Errorf("main line color defaults = %+v, want pace=#3b82f6, heartRate=#ef4444", p) } if p.WarmupColor != "#c2410c" || p.EffortColor != "#7c3aed" || p.RecoveryColor != "#15803d" || p.CooldownColor != "#fb923c" { t.Errorf("effort color defaults = %+v", p) } if p.MainLineTintPct != 20 { t.Errorf("MainLineTintPct = %v, want 20 (migration default)", p.MainLineTintPct) } if p.BackgroundDarkenPct != 35 { t.Errorf("BackgroundDarkenPct = %v, want 35 (migration default)", p.BackgroundDarkenPct) } if p.TargetBrightenPct != 20 { t.Errorf("TargetBrightenPct = %v, want 20 (migration default)", p.TargetBrightenPct) } maxHR, restingHR := 190.0, 50.0 p.PaceColor = "#111111" p.EffortColor = "#222222" p.MainLineTintPct = 45 p.BackgroundDarkenPct = 60 p.TargetBrightenPct = 50 p.GarminEmail = "runner@example.com" p.GarminPassword = "hunter2" p.RollingWindowDays = 120 p.MaxHeartRate = &maxHR p.RestingHeartRate = &restingHR p.WarmupMinutes = 8 p.BackfillHorizonDays = 14 p.MinRepresentativePaceSecPerKm = 600 p.MinRepresentativeTimeSeconds = 5 if err := db.UpdateProfile(ctx, userID, p); err != nil { t.Fatalf("UpdateProfile: %v", err) } got, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile after update: %v", err) } if got.GarminEmail != "runner@example.com" || got.RollingWindowDays != 120 { t.Errorf("got = %+v, want updated email/window", got) } if got.MaxHeartRate == nil || *got.MaxHeartRate != 190 { t.Errorf("MaxHeartRate = %v, want 190", got.MaxHeartRate) } if got.WarmupMinutes != 8 { t.Errorf("WarmupMinutes = %v, want 8", got.WarmupMinutes) } if got.BackfillHorizonDays != 14 { t.Errorf("BackfillHorizonDays = %v, want 14", got.BackfillHorizonDays) } if got.MinRepresentativePaceSecPerKm != 600 || got.MinRepresentativeTimeSeconds != 5 { t.Errorf("pace artifact filter after update = %+v, want pace=600, time=5", got) } if got.PaceColor != "#111111" || got.EffortColor != "#222222" { t.Errorf("chart colors after update = %+v, want pace=#111111, effort=#222222", got) } if got.MainLineTintPct != 45 { t.Errorf("MainLineTintPct after update = %v, want 45", got.MainLineTintPct) } if got.BackgroundDarkenPct != 60 { t.Errorf("BackgroundDarkenPct after update = %v, want 60", got.BackgroundDarkenPct) } if got.TargetBrightenPct != 50 { t.Errorf("TargetBrightenPct after update = %v, want 50", got.TargetBrightenPct) } } func TestMarkGarminConnected_SetsTimestampOnceOnFirstCall(t *testing.T) { db := openTestDB(t) ctx := context.Background() userID, err := db.ProvisionUser(ctx, "test-sub", "Test") if err != nil { t.Fatalf("ProvisionUser: %v", err) } before, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile: %v", err) } if before.GarminConnectedAt != nil { t.Fatalf("expected a fresh profile to have nil GarminConnectedAt, got %v", *before.GarminConnectedAt) } if err := db.MarkGarminConnected(ctx, userID); err != nil { t.Fatalf("MarkGarminConnected: %v", err) } afterFirst, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile after first mark: %v", err) } if afterFirst.GarminConnectedAt == nil { t.Fatal("expected GarminConnectedAt to be set after MarkGarminConnected") } firstValue := *afterFirst.GarminConnectedAt // A second call must not change the recorded first-connection time. if err := db.MarkGarminConnected(ctx, userID); err != nil { t.Fatalf("MarkGarminConnected (second call): %v", err) } afterSecond, err := db.GetProfile(ctx, userID) if err != nil { t.Fatalf("GetProfile after second mark: %v", err) } if afterSecond.GarminConnectedAt == nil || *afterSecond.GarminConnectedAt != firstValue { t.Fatalf("GarminConnectedAt changed on second call: first=%q second=%v", firstValue, afterSecond.GarminConnectedAt) } }