package rides import ( "database/sql" "os" "path/filepath" "testing" "time" "github.com/martinlehoux/biking_home/internal/dbtest" "github.com/martinlehoux/biking_home/ride" "github.com/martinlehoux/kagamigo/kcore" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func newTestDB(t *testing.T) *sql.DB { return dbtest.New(t) } func sampleRide(t *testing.T) Ride { t.Helper() gpxPath := filepath.Join(t.TempDir(), "ride.gpx") require.NoError(t, os.WriteFile(gpxPath, []byte(testGPX), 0o600)) return Ride{ ExternalID: "strava:1234", GPXPath: gpxPath, Name: "Morning Ride", Type: "Ride", StartDate: time.Date(2026, 8, 1, 7, 30, 0, 0, time.UTC), DistanceM: 42_195, MovingTimeS: 7_200, ElapsedTimeS: 7_800, TotalElevationGainM: 850, AverageSpeedMps: 5.86, } } const testGPX = `100200` func TestUpsertAndGet(t *testing.T) { db := newTestDB(t) sample := sampleRide(t) err := Save(db, sample) require.NoError(t, err) got, ok, err := GetByExternalID(db, "strava:1234") require.NoError(t, err) require.True(t, ok) assert.Equal(t, "Morning Ride", got.Name) assert.Equal(t, sample.StartDate, got.StartDate) assert.Equal(t, 42_195.0, got.DistanceM) assert.Equal(t, sample.GPXPath, got.GPXPath) score, found := got.CotacolScore() require.True(t, found) assert.Greater(t, score, 0.0) assert.Equal(t, ride.CotacolAlgorithmVersion, got.CotacolAlgorithmVersion()) _, ok, err = GetByExternalID(db, "strava:9999") require.NoError(t, err) assert.False(t, ok) } func TestGetByID(t *testing.T) { db := newTestDB(t) sample := sampleRide(t) require.NoError(t, Save(db, sample)) stored, found, err := GetByExternalID(db, sample.ExternalID) require.NoError(t, err) require.True(t, found) got, found, err := GetByID(db, stored.ID) require.NoError(t, err) require.True(t, found) assert.Equal(t, sample.ExternalID, got.ExternalID) assert.Equal(t, sample.Name, got.Name) _, found, err = GetByID(db, 999999) require.NoError(t, err) assert.False(t, found) } func TestSaveComputesCotacol(t *testing.T) { db := newTestDB(t) require.NoError(t, Save(db, sampleRide(t))) got, ok, err := GetByExternalID(db, "strava:1234") require.NoError(t, err) require.True(t, ok) score, found := got.CotacolScore() require.True(t, found) assert.Greater(t, score, 0.0) assert.Equal(t, ride.CotacolAlgorithmVersion, got.CotacolAlgorithmVersion()) } func TestSaveAllowsRideWithoutGPX(t *testing.T) { db := newTestDB(t) indoor := sampleRide(t) indoor.ExternalID = "strava:14701658670" indoor.GPXPath = "" require.NoError(t, Save(db, indoor)) got, found, err := GetByExternalID(db, indoor.ExternalID) require.NoError(t, err) require.True(t, found) assert.Empty(t, got.GPXPath) _, ready := got.CotacolScore() assert.False(t, ready) assert.Empty(t, got.CotacolAlgorithmVersion()) } func TestBackfill(t *testing.T) { db := newTestDB(t) require.NoError(t, Save(db, sampleRide(t))) count, err := Backfill(db) require.NoError(t, err) assert.Equal(t, 1, count) got, ok, err := GetByExternalID(db, "strava:1234") require.NoError(t, err) require.True(t, ok) _, found := got.CotacolScore() assert.True(t, found) assert.Equal(t, ride.CotacolAlgorithmVersion, got.CotacolAlgorithmVersion()) count, err = Backfill(db) require.NoError(t, err) assert.Equal(t, 1, count) } func TestBackfillSkipsInvalidRide(t *testing.T) { db := newTestDB(t) _, err := db.Exec(` INSERT INTO rides (external_id, gpx_path, name, type, start_date, distance_m, moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, "strava:550e8400-e29b-41d4-a716-446655440000", "missing.gpx", "Broken Ride", "Ride", "2026-08-01T07:00:00Z", 20_000, 0, 0, 0, 0) require.NoError(t, err) count, err := Backfill(db) require.NoError(t, err) assert.Zero(t, count) got, ok, err := GetByExternalID(db, "strava:550e8400-e29b-41d4-a716-446655440000") require.NoError(t, err) require.True(t, ok) _, found := got.CotacolScore() assert.False(t, found) } func TestUpsertUpdatesExisting(t *testing.T) { db := newTestDB(t) ride := sampleRide(t) require.NoError(t, Save(db, ride)) ride.Name = "Renamed Ride" ride.DistanceM = 50_000 require.NoError(t, Save(db, ride)) got, ok, err := GetByExternalID(db, "strava:1234") require.NoError(t, err) require.True(t, ok) assert.Equal(t, "Renamed Ride", got.Name) assert.Equal(t, 50_000.0, got.DistanceM) } func TestList(t *testing.T) { db := newTestDB(t) first := sampleRide(t) first.StartDate = time.Date(2026, 7, 1, 7, 0, 0, 0, time.UTC) second := sampleRide(t) second.ExternalID = "strava:5678" second.Name = "Evening Ride" require.NoError(t, Save(db, first)) require.NoError(t, Save(db, second)) rides, err := List(db) require.NoError(t, err) require.Len(t, rides, 2) assert.Equal(t, "strava:5678", rides[0].ExternalID) assert.Equal(t, "strava:1234", rides[1].ExternalID) kcore.Assert(len(rides) == 2, "two rides") } func TestListSortedByCotacolKeepsStaleLast(t *testing.T) { db := newTestDB(t) first := sampleRide(t) first.ExternalID = "strava:550e8400-e29b-41d4-a716-446655440000" first.DistanceM = 20_000 second := sampleRide(t) second.ExternalID = "strava:6ba7b810-9dad-41d1-80b4-00c04fd430c8" second.DistanceM = 40_000 require.NoError(t, Save(db, first)) require.NoError(t, Save(db, second)) _, err := db.Exec(` UPDATE rides SET cotacol_score = CASE external_id WHEN ? THEN 4 ELSE 6 END, cotacol_algo_version = ? `, first.ExternalID, ride.CotacolAlgorithmVersion) require.NoError(t, err) items, err := ListSorted(db, SortCotacol, false) require.NoError(t, err) assert.Equal(t, first.ExternalID, items[0].ExternalID) assert.Equal(t, second.ExternalID, items[1].ExternalID) _, err = db.Exec("UPDATE rides SET cotacol_algo_version = ? WHERE external_id = ?", "old", first.ExternalID) require.NoError(t, err) items, err = ListSorted(db, SortCotacol, false) require.NoError(t, err) assert.Equal(t, second.ExternalID, items[0].ExternalID) assert.Equal(t, first.ExternalID, items[1].ExternalID) } func TestListSortedByCotacolPer100Km(t *testing.T) { db := newTestDB(t) first := sampleRide(t) first.ExternalID = "strava:550e8400-e29b-41d4-a716-446655440000" first.DistanceM = 20_000 second := sampleRide(t) second.ExternalID = "strava:6ba7b810-9dad-41d1-80b4-00c04fd430c8" second.DistanceM = 40_000 require.NoError(t, Save(db, first)) require.NoError(t, Save(db, second)) _, err := db.Exec(` UPDATE rides SET cotacol_score = CASE external_id WHEN ? THEN 4 ELSE 6 END, cotacol_algo_version = ? `, first.ExternalID, ride.CotacolAlgorithmVersion) require.NoError(t, err) items, err := ListSorted(db, SortCotacolKm, false) require.NoError(t, err) assert.Equal(t, second.ExternalID, items[0].ExternalID) assert.Equal(t, first.ExternalID, items[1].ExternalID) }