1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
package official_climb_test
import (
"database/sql"
"testing"
"time"
"github.com/jftuga/geodist"
"github.com/martinlehoux/biking_home/internal/dbtest"
"github.com/martinlehoux/biking_home/official_climb"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func coord(lat, lon float64) geodist.Coord {
return geodist.Coord{Lat: lat, Lon: lon}
}
func insertRide(t *testing.T, db *sql.DB, opts ...func(*rideInsert)) int64 {
t.Helper()
insert := rideInsert{startDate: "2026-09-01T08:00:00Z"}
for _, opt := range opts {
opt(&insert)
}
result, err := db.Exec(`
INSERT INTO rides (external_id, gpx_path, name, external_sport_type, start_date, distance_m, external_moving_time_s, elapsed_time_s, total_elevation_gain_m, average_speed_mps)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, "strava:test-ride-"+insert.startDate, "missing.gpx", "Test Ride", "Ride", insert.startDate, 20_000, 0, 0, 0, 0)
require.NoError(t, err)
id, err := result.LastInsertId()
require.NoError(t, err)
return id
}
type rideInsert struct {
startDate string
}
func withStartDate(startDate string) func(*rideInsert) {
return func(r *rideInsert) { r.startDate = startDate }
}
func attemptFixture(rideID, climbID int64, occurrence int) official_climb.Attempt {
return official_climb.Attempt{
RideID: rideID,
ClimbID: climbID,
Occurrence: occurrence,
DistanceM: 3200,
ElevationGainM: 210,
SlopePercent: 6.5,
Cotacol: 84.2,
CotacolAlgoVersion: "v1",
StartedAt: time.Date(2026, 9, 1, 10, 0, 0, 0, time.UTC),
EndedAt: time.Date(2026, 9, 1, 10, 18, 30, 0, time.UTC),
}
}
func TestReplaceRideAttemptsRoundTrip(t *testing.T) {
db := dbtest.New(t)
created, err := official_climb.Create(db, official_climb.OfficialClimb{Name: "Col de Test", StartCoord: coord(43.6, 5.4), EndCoord: coord(43.62, 5.42)})
require.NoError(t, err)
rideID := insertRide(t, db)
require.NoError(t, official_climb.ReplaceRideAttempts(db, rideID, []official_climb.Attempt{
attemptFixture(rideID, created.ID, 0),
attemptFixture(rideID, created.ID, 1),
}))
attempts, err := official_climb.AttemptsForClimb(db, created.ID)
require.NoError(t, err)
require.Len(t, attempts, 2)
assert.Equal(t, 0, attempts[0].Occurrence)
assert.Equal(t, 1, attempts[1].Occurrence)
assert.Equal(t, 3200.0, attempts[0].DistanceM)
assert.Equal(t, "v1", attempts[0].CotacolAlgoVersion)
assert.Equal(t, time.Date(2026, 9, 1, 10, 18, 30, 0, time.UTC), attempts[0].EndedAt)
rideAttempts, err := official_climb.AttemptsForRide(db, rideID)
require.NoError(t, err)
require.Len(t, rideAttempts, 2)
}
func TestReplaceRideAttemptsDropsStaleRows(t *testing.T) {
db := dbtest.New(t)
first, err := official_climb.Create(db, official_climb.OfficialClimb{Name: "Col A", StartCoord: coord(43.6, 5.4), EndCoord: coord(43.62, 5.42)})
require.NoError(t, err)
second, err := official_climb.Create(db, official_climb.OfficialClimb{Name: "Col B", StartCoord: coord(43.7, 5.5), EndCoord: coord(43.72, 5.52)})
require.NoError(t, err)
rideID := insertRide(t, db)
require.NoError(t, official_climb.ReplaceRideAttempts(db, rideID, []official_climb.Attempt{attemptFixture(rideID, first.ID, 0)}))
require.NoError(t, official_climb.ReplaceRideAttempts(db, rideID, []official_climb.Attempt{attemptFixture(rideID, second.ID, 0)}))
attempts, err := official_climb.AttemptsForClimb(db, first.ID)
require.NoError(t, err)
assert.Empty(t, attempts)
attempts, err = official_climb.AttemptsForClimb(db, second.ID)
require.NoError(t, err)
require.Len(t, attempts, 1)
}
func TestReplaceRideAttemptsRejectsForeignRide(t *testing.T) {
db := dbtest.New(t)
created, err := official_climb.Create(db, official_climb.OfficialClimb{Name: "Col de Test", StartCoord: coord(43.6, 5.4), EndCoord: coord(43.62, 5.42)})
require.NoError(t, err)
rideID := insertRide(t, db)
attempt := attemptFixture(rideID+1, created.ID, 0)
err = official_climb.ReplaceRideAttempts(db, rideID, []official_climb.Attempt{attempt})
assert.Error(t, err)
}
func TestAttemptsForClimbOrdersByRideDate(t *testing.T) {
db := dbtest.New(t)
created, err := official_climb.Create(db, official_climb.OfficialClimb{Name: "Col de Test", StartCoord: coord(43.6, 5.4), EndCoord: coord(43.62, 5.42)})
require.NoError(t, err)
newer := insertRide(t, db, withStartDate("2026-09-10T08:00:00Z"))
older := insertRide(t, db, withStartDate("2026-09-02T08:00:00Z"))
require.NoError(t, official_climb.ReplaceRideAttempts(db, newer, []official_climb.Attempt{attemptFixture(newer, created.ID, 0)}))
require.NoError(t, official_climb.ReplaceRideAttempts(db, older, []official_climb.Attempt{attemptFixture(older, created.ID, 0)}))
attempts, err := official_climb.AttemptsForClimb(db, created.ID)
require.NoError(t, err)
require.Len(t, attempts, 2)
assert.Equal(t, older, attempts[0].RideID)
assert.Equal(t, newer, attempts[1].RideID)
}
|