aboutsummaryrefslogtreecommitdiff
path: root/official_climb/attempt_test.go
blob: 891380749f0886c96705ea819f9a6eadc959b5b2 (plain) (blame)
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

package official_climb_test

import (
	"database/sql"
	"testing"
	"time"

	"github.com/jftuga/geodist"
	"github.com/martinlehoux/biking_home/climb"
	"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}
}

// sampleFixture is a plausible sample for tests that do not exercise matching.
func sampleFixture() climb.SegmentSample {
	return climb.SegmentSample{DistanceM: 3000, ElevationGainM: 200}
}

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), Sample: sampleFixture()})
	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 TestReplaceRideAttemptsKeepsRecordedAt(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), Sample: sampleFixture()})
	require.NoError(t, err)
	rideID := insertRide(t, db)
	attempts := []official_climb.Attempt{attemptFixture(rideID, created.ID, 0)}
	require.NoError(t, official_climb.ReplaceRideAttempts(db, rideID, attempts))

	recorded := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC)
	_, err = db.Exec("UPDATE ride_official_climb_attempts SET created_at = ? WHERE ride_id = ?", recorded.Format(time.RFC3339), rideID)
	require.NoError(t, err)

	// Re-analyzing the same ride must not restamp the attempt it already has.
	require.NoError(t, official_climb.ReplaceRideAttempts(db, rideID, attempts))
	stored, err := official_climb.AttemptsForRide(db, rideID)
	require.NoError(t, err)
	require.Len(t, stored, 1)
	assert.Equal(t, recorded, stored[0].CreatedAt.UTC())
}

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), Sample: sampleFixture()})
	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), Sample: sampleFixture()})
	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), Sample: sampleFixture()})
	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), Sample: sampleFixture()})
	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)
}