aboutsummaryrefslogtreecommitdiff
path: root/official_climb/official_climb_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'official_climb/official_climb_test.go')
-rw-r--r--official_climb/official_climb_test.go128
1 files changed, 118 insertions, 10 deletions
diff --git a/official_climb/official_climb_test.go b/official_climb/official_climb_test.go
index 6ddce5a..7fb2a32 100644
--- a/official_climb/official_climb_test.go
+++ b/official_climb/official_climb_test.go
@@ -5,6 +5,7 @@ import (
"time"
"github.com/jftuga/geodist"
+ "github.com/martinlehoux/biking_home/climb"
"github.com/martinlehoux/biking_home/internal/dbtest"
"github.com/martinlehoux/biking_home/internal/ridetest"
"github.com/martinlehoux/biking_home/official_climb"
@@ -19,6 +20,7 @@ func TestCreateListAndGetOfficialClimb(t *testing.T) {
Name: "Col de Test",
StartCoord: geodist.Coord{Lat: 43.1, Lon: 5.1},
EndCoord: geodist.Coord{Lat: 43.2, Lon: 5.2},
+ Sample: climb.SegmentSample{DistanceM: 1200, ElevationGainM: 90},
})
require.NoError(t, err)
assert.Positive(t, created.ID)
@@ -44,8 +46,16 @@ func TestCreateRejectsInvalidOfficialClimb(t *testing.T) {
Name: " ",
StartCoord: geodist.Coord{Lat: 43.1, Lon: 5.1},
EndCoord: geodist.Coord{Lat: 43.2, Lon: 5.2},
+ Sample: climb.SegmentSample{DistanceM: 1200, ElevationGainM: 90},
})
assert.EqualError(t, err, "official climb name is required")
+
+ _, err = official_climb.Create(db, official_climb.OfficialClimb{
+ Name: "Col Sans Sample",
+ StartCoord: geodist.Coord{Lat: 43.1, Lon: 5.1},
+ EndCoord: geodist.Coord{Lat: 43.2, Lon: 5.2},
+ })
+ assert.EqualError(t, err, "official climb sample must record a positive distance and elevation gain")
}
func TestMatchPolicyRejectsInvalidRadius(t *testing.T) {
@@ -58,8 +68,9 @@ func TestMatchClimbMatchesWithoutDetectedClimb(t *testing.T) {
Name: "On route",
StartCoord: parsed.Coords[1],
EndCoord: parsed.Coords[2],
+ Sample: parsed.Sample(1, 2),
}
- matched, found := official_climb.MatchClimb(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ matched, found := official_climb.MatchClimb(parsed.Route, official, official_climb.DefaultMatchRadiusM)
require.True(t, found)
assert.Equal(t, 1, matched.StartIndex)
assert.Equal(t, 2, matched.EndIndex)
@@ -71,8 +82,9 @@ func TestMatchClimbMatchesEndpointInsideRadius(t *testing.T) {
Name: "Offset from route",
StartCoord: geodist.Coord{Lat: 43.1101, Lon: 5.1101},
EndCoord: geodist.Coord{Lat: 43.1201, Lon: 5.1201},
+ Sample: parsed.Sample(1, 2),
}
- matched, found := official_climb.MatchClimb(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ matched, found := official_climb.MatchClimb(parsed.Route, official, official_climb.DefaultMatchRadiusM)
require.True(t, found)
assert.Equal(t, 1, matched.StartIndex)
assert.Equal(t, 2, matched.EndIndex)
@@ -84,8 +96,9 @@ func TestMatchClimbRejectsReversedEndpoints(t *testing.T) {
Name: "Reversed",
StartCoord: parsed.Coords[2],
EndCoord: parsed.Coords[1],
+ Sample: parsed.Sample(1, 2),
}
- _, found := official_climb.MatchClimb(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ _, found := official_climb.MatchClimb(parsed.Route, official, official_climb.DefaultMatchRadiusM)
assert.False(t, found)
}
@@ -95,8 +108,9 @@ func TestMatchClimbRejectsEndpointOutsideRadius(t *testing.T) {
Name: "Too far",
StartCoord: geodist.Coord{Lat: 43.09, Lon: 5.09},
EndCoord: parsed.Coords[2],
+ Sample: parsed.Sample(1, 2),
}
- _, found := official_climb.MatchClimb(parsed.Coords, official, 10)
+ _, found := official_climb.MatchClimb(parsed.Route, official, 10)
assert.False(t, found)
}
@@ -121,15 +135,16 @@ func TestMatchClimbsMatchesTwoLaps(t *testing.T) {
Name: "Loop",
StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
EndCoord: geodist.Coord{Lat: 43.12, Lon: 5.12},
+ Sample: parsed.Sample(0, 2),
}
- matches := official_climb.MatchClimbs(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ matches := official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM)
require.Len(t, matches, 2)
assert.Equal(t, 0, matches[0].StartIndex)
assert.Equal(t, 2, matches[0].EndIndex)
assert.Equal(t, 4, matches[1].StartIndex)
assert.Equal(t, 6, matches[1].EndIndex)
// Single-match helper still returns first occurrence for backward compatibility.
- single, found := official_climb.MatchClimb(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ single, found := official_climb.MatchClimb(parsed.Route, official, official_climb.DefaultMatchRadiusM)
require.True(t, found)
assert.Equal(t, 0, single.StartIndex)
}
@@ -153,8 +168,9 @@ func TestMatchClimbsMatchesOnlyCompleteLaps(t *testing.T) {
Name: "Partial",
StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
EndCoord: geodist.Coord{Lat: 43.12, Lon: 5.12},
+ Sample: parsed.Sample(3, 5),
}
- matches := official_climb.MatchClimbs(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ matches := official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM)
require.Len(t, matches, 1)
assert.Equal(t, 3, matches[0].StartIndex)
assert.Equal(t, 5, matches[0].EndIndex)
@@ -176,8 +192,9 @@ func TestMatchClimbsPreservesDirectionAcrossLaps(t *testing.T) {
Name: "Directional",
StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
EndCoord: geodist.Coord{Lat: 43.12, Lon: 5.12},
+ Sample: parsed.Sample(0, 1),
}
- matches := official_climb.MatchClimbs(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ matches := official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM)
assert.Empty(t, matches)
}
@@ -193,7 +210,7 @@ func TestMatchClimbsClustersContiguousPoints(t *testing.T) {
{Lat: 43.12, Lon: 5.12},
}
parsed := ride.FromColumns(
- []float64{0, 100, 200, 1000, 2000, 3000, 3100, 4000},
+ []float64{0, 100, 200, 1000, 2000, 3000, 3100, 5000},
[]float64{100, 101, 102, 150, 200, 100, 101, 200},
coords,
[]time.Time{time.Unix(0, 0), time.Unix(10, 0), time.Unix(20, 0), time.Unix(60, 0), time.Unix(120, 0), time.Unix(180, 0), time.Unix(190, 0), time.Unix(240, 0)},
@@ -202,11 +219,102 @@ func TestMatchClimbsClustersContiguousPoints(t *testing.T) {
Name: "Clustered",
StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
EndCoord: geodist.Coord{Lat: 43.12, Lon: 5.12},
+ Sample: parsed.Sample(0, 4),
}
- matches := official_climb.MatchClimbs(parsed.Coords, official, official_climb.DefaultMatchRadiusM)
+ matches := official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM)
require.Len(t, matches, 2)
assert.Equal(t, 0, matches[0].StartIndex)
assert.Equal(t, 4, matches[0].EndIndex)
assert.Equal(t, 5, matches[1].StartIndex)
assert.Equal(t, 7, matches[1].EndIndex)
}
+
+func TestCreateOfficialClimbStoresSample(t *testing.T) {
+ db := dbtest.New(t)
+ created, err := official_climb.Create(db, official_climb.OfficialClimb{
+ Name: "Col Sampled",
+ StartCoord: coord(43.6, 5.4),
+ EndCoord: coord(43.62, 5.42),
+ Sample: climb.SegmentSample{DistanceM: 1200, ElevationGainM: 90},
+ })
+ require.NoError(t, err)
+ require.NotNil(t, created.Sample)
+ assert.Equal(t, 1200.0, created.Sample.DistanceM)
+ assert.Equal(t, 90.0, created.Sample.ElevationGainM)
+
+ got, found, err := official_climb.GetByID(db, created.ID)
+ require.NoError(t, err)
+ require.True(t, found)
+ require.NotNil(t, got.Sample)
+ assert.Equal(t, 1200.0, got.Sample.DistanceM)
+ assert.Equal(t, 90.0, got.Sample.ElevationGainM)
+}
+
+func TestCreateRejectsSampleWithoutGain(t *testing.T) {
+ db := dbtest.New(t)
+ _, err := official_climb.Create(db, official_climb.OfficialClimb{
+ Name: "Col Plat",
+ StartCoord: coord(43.6, 5.4),
+ EndCoord: coord(43.62, 5.42),
+ Sample: climb.SegmentSample{DistanceM: 1200},
+ })
+ assert.Error(t, err)
+}
+
+func TestMatchClimbsAcceptsSegmentInsideSampleBand(t *testing.T) {
+ parsed := ride.FromColumns(
+ []float64{0, 850},
+ []float64{100, 170},
+ []geodist.Coord{{Lat: 43.1000, Lon: 5.1000}, {Lat: 43.1100, Lon: 5.1100}},
+ []time.Time{time.Unix(0, 0), time.Unix(120, 0)},
+ )
+ official := official_climb.OfficialClimb{
+ Name: "Sampled climb",
+ StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
+ EndCoord: geodist.Coord{Lat: 43.11, Lon: 5.11},
+ Sample: climb.SegmentSample{DistanceM: 850, ElevationGainM: 70},
+ }
+ matches := official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM)
+ require.Len(t, matches, 1)
+ assert.Equal(t, 0, matches[0].StartIndex)
+ assert.Equal(t, 1, matches[0].EndIndex)
+}
+
+func TestMatchClimbsRejectsSegmentOutsideSampleGainBand(t *testing.T) {
+ parsed := ride.FromColumns(
+ []float64{0, 850},
+ []float64{100, 105},
+ []geodist.Coord{{Lat: 43.1000, Lon: 5.1000}, {Lat: 43.1100, Lon: 5.1100}},
+ []time.Time{time.Unix(0, 0), time.Unix(120, 0)},
+ )
+ official := official_climb.OfficialClimb{
+ Name: "Sampled climb",
+ StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
+ EndCoord: geodist.Coord{Lat: 43.11, Lon: 5.11},
+ Sample: climb.SegmentSample{DistanceM: 850, ElevationGainM: 70},
+ }
+ assert.Empty(t, official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM))
+}
+
+func TestMatchClimbsRejectsSegmentOutsideSampleBand(t *testing.T) {
+ // Reverse loop: only a cross-lap passage pairs the endpoints, and the sample band rejects it.
+ parsed := ride.FromColumns(
+ []float64{0, 850, 6800, 7650, 13600},
+ []float64{170, 100, 235, 100, 235},
+ []geodist.Coord{
+ {Lat: 43.1100, Lon: 5.1100},
+ {Lat: 43.1000, Lon: 5.1000},
+ {Lat: 43.1100, Lon: 5.1100},
+ {Lat: 43.1000, Lon: 5.1000},
+ {Lat: 43.1100, Lon: 5.1100},
+ },
+ []time.Time{time.Unix(0, 0), time.Unix(120, 0), time.Unix(600, 0), time.Unix(720, 0), time.Unix(1200, 0)},
+ )
+ official := official_climb.OfficialClimb{
+ Name: "Reverse loop",
+ StartCoord: geodist.Coord{Lat: 43.10, Lon: 5.10},
+ EndCoord: geodist.Coord{Lat: 43.11, Lon: 5.11},
+ Sample: climb.SegmentSample{DistanceM: 850, ElevationGainM: 70},
+ }
+ assert.Empty(t, official_climb.MatchClimbs(parsed.Route, official, official_climb.DefaultMatchRadiusM))
+}