593 lines
15 KiB
Go
593 lines
15 KiB
Go
package main
|
|
|
|
import (
|
|
"github.com/tkrajina/gpxgo/gpx"
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseMultiStops(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expected []MultiStop
|
|
}{
|
|
{
|
|
name: "Empty input",
|
|
input: "",
|
|
expected: []MultiStop{},
|
|
},
|
|
{
|
|
name: "Single stop",
|
|
input: "59.3293,18.0686:1",
|
|
expected: []MultiStop{
|
|
{Lat: 59.3293, Lon: 18.0686, Nights: 1},
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple stops",
|
|
input: "59.3293,18.0686:1;60.1282,18.6435:2",
|
|
expected: []MultiStop{
|
|
{Lat: 59.3293, Lon: 18.0686, Nights: 1},
|
|
{Lat: 60.1282, Lon: 18.6435, Nights: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "Invalid format",
|
|
input: "invalid",
|
|
expected: []MultiStop{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := parseMultiStops(tt.input)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d stops, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
for i, stop := range result {
|
|
if stop.Lat != tt.expected[i].Lat || stop.Lon != tt.expected[i].Lon || stop.Nights != tt.expected[i].Nights {
|
|
t.Errorf("Stop %d mismatch: expected %+v, got %+v", i, tt.expected[i], stop)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHaversine(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
lat1 float64
|
|
lon1 float64
|
|
lat2 float64
|
|
lon2 float64
|
|
expected float64
|
|
}{
|
|
{
|
|
name: "Same point",
|
|
lat1: 59.3293,
|
|
lon1: 18.0686,
|
|
lat2: 59.3293,
|
|
lon2: 18.0686,
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "Stockholm to Uppsala",
|
|
lat1: 59.3293,
|
|
lon1: 18.0686,
|
|
lat2: 59.8586,
|
|
lon2: 17.6389,
|
|
expected: 63.63, // Approximate distance in km
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := haversine(tt.lat1, tt.lon1, tt.lat2, tt.lon2)
|
|
// Allow for small floating point differences
|
|
if math.Abs(result-tt.expected) > 0.5 {
|
|
t.Errorf("Expected distance of %.2f km, got %.2f km", tt.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNormalizeCutIndexes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cutIndexes []int
|
|
days int
|
|
totalPoints int
|
|
expected []int
|
|
}{
|
|
{
|
|
name: "No cuts",
|
|
cutIndexes: []int{},
|
|
days: 5,
|
|
totalPoints: 100,
|
|
expected: []int{},
|
|
},
|
|
{
|
|
name: "Fewer cuts than days",
|
|
cutIndexes: []int{25, 50, 75},
|
|
days: 5,
|
|
totalPoints: 100,
|
|
expected: []int{25, 50, 75},
|
|
},
|
|
{
|
|
name: "More cuts than days",
|
|
cutIndexes: []int{20, 40, 60, 80, 99},
|
|
days: 3,
|
|
totalPoints: 100,
|
|
expected: []int{20, 40, 99},
|
|
},
|
|
{
|
|
name: "Duplicate cuts (rest days)",
|
|
cutIndexes: []int{25, 25, 50, 75},
|
|
days: 5,
|
|
totalPoints: 100,
|
|
expected: []int{25, 25, 50, 75},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := normalizeCutIndexes(tt.cutIndexes, tt.days, tt.totalPoints)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
for i, cut := range result {
|
|
if cut != tt.expected[i] {
|
|
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateSegment(t *testing.T) {
|
|
// Create test points
|
|
points := []TrackPoint{
|
|
{Index: 0, Distance: 0, Elevation: 0},
|
|
{Index: 1, Distance: 10, Elevation: 100},
|
|
{Index: 2, Distance: 20, Elevation: 200},
|
|
{Index: 3, Distance: 30, Elevation: 300},
|
|
{Index: 4, Distance: 40, Elevation: 400},
|
|
{Index: 5, Distance: 50, Elevation: 500},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
start int
|
|
end int
|
|
cutIndexes []int
|
|
segmentIndex int
|
|
expectedLen int
|
|
}{
|
|
{
|
|
name: "Normal segment",
|
|
start: 0,
|
|
end: 3,
|
|
cutIndexes: []int{3, 5},
|
|
segmentIndex: 0,
|
|
expectedLen: 3, // Points 0, 1, 2
|
|
},
|
|
{
|
|
name: "Forced stop (end <= start)",
|
|
start: 3,
|
|
end: 3,
|
|
cutIndexes: []int{3, 5},
|
|
segmentIndex: 0,
|
|
expectedLen: 2, // Points 3, 4 (halfway to the next cut)
|
|
},
|
|
{
|
|
name: "Last segment",
|
|
start: 3,
|
|
end: 5,
|
|
cutIndexes: []int{3, 5},
|
|
segmentIndex: 1,
|
|
expectedLen: 2, // Points 3, 4
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := createSegment(points, tt.start, tt.end, tt.cutIndexes, tt.segmentIndex)
|
|
if len(result) != tt.expectedLen {
|
|
t.Errorf("Expected segment length %d, got %d", tt.expectedLen, len(result))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateDaySegment(t *testing.T) {
|
|
// Create test points
|
|
points := []TrackPoint{
|
|
{Index: 0, Distance: 0, Elevation: 0},
|
|
{Index: 1, Distance: 10, Elevation: 100},
|
|
{Index: 2, Distance: 20, Elevation: 200},
|
|
}
|
|
|
|
dayNumber := 1
|
|
elevFactor := 4.0
|
|
forestRadius := 3
|
|
resupplyRadius := 500
|
|
|
|
// This test will not query for forest or resupply points
|
|
// since that would require network access. We're just testing the
|
|
// calculation of distance, elevation, and effort.
|
|
segment := createDaySegment(points, dayNumber, elevFactor, forestRadius, resupplyRadius, false)
|
|
|
|
if segment.DayNumber != dayNumber {
|
|
t.Errorf("Expected day number %d, got %d", dayNumber, segment.DayNumber)
|
|
}
|
|
|
|
expectedDist := 20.0 // Last point distance - first point distance
|
|
if math.Abs(segment.Distance-expectedDist) > 0.1 {
|
|
t.Errorf("Expected distance %.1f, got %.1f", expectedDist, segment.Distance)
|
|
}
|
|
|
|
expectedElev := 200.0 // Last point elevation - first point elevation
|
|
if math.Abs(segment.Elevation-expectedElev) > 0.1 {
|
|
t.Errorf("Expected elevation %.1f, got %.1f", expectedElev, segment.Elevation)
|
|
}
|
|
|
|
expectedEffort := expectedDist + (expectedElev / 100.0 * elevFactor)
|
|
if math.Abs(segment.Effort-expectedEffort) > 0.1 {
|
|
t.Errorf("Expected effort %.1f, got %.1f", expectedEffort, segment.Effort)
|
|
}
|
|
}
|
|
|
|
func TestCalculateCutIndexes(t *testing.T) {
|
|
// Create test route data
|
|
points := []TrackPoint{
|
|
{Index: 0, Distance: 0, Elevation: 0},
|
|
{Index: 1, Distance: 25, Elevation: 250}, // Effort: 25 + (250/100*4) = 35
|
|
{Index: 2, Distance: 50, Elevation: 500}, // Effort: 50 + (500/100*4) = 70
|
|
{Index: 3, Distance: 75, Elevation: 750}, // Effort: 75 + (750/100*4) = 105
|
|
{Index: 4, Distance: 100, Elevation: 1000}, // Effort: 100 + (1000/100*4) = 140
|
|
}
|
|
|
|
routeData := RouteData{
|
|
Points: points,
|
|
TotalDist: 100,
|
|
TotalElev: 1000,
|
|
TotalEffort: 140,
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
days int
|
|
elevFactor float64
|
|
multiStops []MultiStop
|
|
expected []int
|
|
}{
|
|
{
|
|
name: "2 days, no stops",
|
|
days: 2,
|
|
elevFactor: 4.0,
|
|
multiStops: []MultiStop{},
|
|
expected: []int{2, 4}, // Cut at index 2 (effort 70) and 4 (end)
|
|
},
|
|
{
|
|
name: "3 days, no stops",
|
|
days: 3,
|
|
elevFactor: 4.0,
|
|
multiStops: []MultiStop{},
|
|
expected: []int{1, 3, 4}, // Cut at index 1, 3, and 4 (end)
|
|
},
|
|
{
|
|
name: "3 days, with one multi-stop",
|
|
days: 3,
|
|
elevFactor: 4.0,
|
|
multiStops: []MultiStop{
|
|
{Lat: 0, Lon: 0, Nights: 1}, // This will match point 0
|
|
},
|
|
expected: []int{0, 1, 4}, // Cut at index 0 (forced stop), 1, and 4 (end)
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := calculateCutIndexes(routeData, tt.days, tt.elevFactor, tt.multiStops)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
for i, cut := range result {
|
|
if cut != tt.expected[i] {
|
|
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// New tests for refactored functions
|
|
|
|
func TestFindForcedStopIndexes(t *testing.T) {
|
|
// Create test points with latitude and longitude
|
|
points := []TrackPoint{
|
|
{Index: 0, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 59.3293, Longitude: 18.0686}}},
|
|
{Index: 1, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 59.8586, Longitude: 17.6389}}},
|
|
{Index: 2, Point: gpx.GPXPoint{Point: gpx.Point{Latitude: 60.1282, Longitude: 18.6435}}},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
points []TrackPoint
|
|
multiStops []MultiStop
|
|
expected []int
|
|
}{
|
|
{
|
|
name: "No stops",
|
|
points: points,
|
|
multiStops: []MultiStop{},
|
|
expected: []int{},
|
|
},
|
|
{
|
|
name: "One stop matching first point",
|
|
points: points,
|
|
multiStops: []MultiStop{
|
|
{Lat: 59.3293, Lon: 18.0686, Nights: 1}, // Matches point 0
|
|
},
|
|
expected: []int{0},
|
|
},
|
|
{
|
|
name: "One stop with multiple nights",
|
|
points: points,
|
|
multiStops: []MultiStop{
|
|
{Lat: 59.8586, Lon: 17.6389, Nights: 2}, // Matches point 1
|
|
},
|
|
expected: []int{1, 1}, // Duplicated for 2 nights
|
|
},
|
|
{
|
|
name: "Multiple stops",
|
|
points: points,
|
|
multiStops: []MultiStop{
|
|
{Lat: 59.3293, Lon: 18.0686, Nights: 1}, // Matches point 0
|
|
{Lat: 60.1282, Lon: 18.6435, Nights: 1}, // Matches point 2
|
|
},
|
|
expected: []int{0, 2},
|
|
},
|
|
{
|
|
name: "Stop too far from any point",
|
|
points: points,
|
|
multiStops: []MultiStop{
|
|
{Lat: 55.0, Lon: 15.0, Nights: 1}, // Far from all points
|
|
},
|
|
expected: []int{}, // No match within 2km
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := findForcedStopIndexes(tt.points, tt.multiStops)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d forced stops, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
for i, idx := range result {
|
|
if idx != tt.expected[i] {
|
|
t.Errorf("Forced stop %d mismatch: expected %d, got %d", i, tt.expected[i], idx)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleTooManyForcedStops(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
forcedStopIndexes []int
|
|
days int
|
|
lastPointIndex int
|
|
totalPoints int
|
|
expected []int
|
|
}{
|
|
{
|
|
name: "Fewer forced stops than days",
|
|
forcedStopIndexes: []int{25, 50},
|
|
days: 4,
|
|
lastPointIndex: 99,
|
|
totalPoints: 100,
|
|
expected: []int{25, 50, 99},
|
|
},
|
|
{
|
|
name: "More forced stops than days",
|
|
forcedStopIndexes: []int{20, 40, 60, 80},
|
|
days: 3,
|
|
lastPointIndex: 99,
|
|
totalPoints: 100,
|
|
expected: []int{20, 40, 99}, // Only first (days-1) forced stops are kept
|
|
},
|
|
{
|
|
name: "Equal forced stops and days",
|
|
forcedStopIndexes: []int{33, 66},
|
|
days: 3,
|
|
lastPointIndex: 99,
|
|
totalPoints: 100,
|
|
expected: []int{33, 66, 99},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := handleTooManyForcedStops(tt.forcedStopIndexes, tt.days, tt.lastPointIndex, tt.totalPoints)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d cuts, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
for i, cut := range result {
|
|
if cut != tt.expected[i] {
|
|
t.Errorf("Cut %d mismatch: expected %d, got %d", i, tt.expected[i], cut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateSegmentsBetweenStops(t *testing.T) {
|
|
// Create test points
|
|
points := []TrackPoint{
|
|
{Index: 0, Distance: 0, Elevation: 0},
|
|
{Index: 1, Distance: 10, Elevation: 100},
|
|
{Index: 2, Distance: 20, Elevation: 200},
|
|
{Index: 3, Distance: 30, Elevation: 300},
|
|
{Index: 4, Distance: 40, Elevation: 400},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
points []TrackPoint
|
|
allStops []int
|
|
elevFactor float64
|
|
expected []struct {
|
|
start, end int
|
|
effort float64
|
|
}
|
|
}{
|
|
{
|
|
name: "No stops",
|
|
points: points,
|
|
allStops: []int{},
|
|
elevFactor: 4.0,
|
|
expected: []struct {
|
|
start, end int
|
|
effort float64
|
|
}{},
|
|
},
|
|
{
|
|
name: "One segment",
|
|
points: points,
|
|
allStops: []int{0, 4},
|
|
elevFactor: 4.0,
|
|
expected: []struct {
|
|
start, end int
|
|
effort float64
|
|
}{
|
|
{0, 4, 56.0}, // Effort: 40 + (400/100*4) = 56
|
|
},
|
|
},
|
|
{
|
|
name: "Multiple segments",
|
|
points: points,
|
|
allStops: []int{0, 2, 4},
|
|
elevFactor: 4.0,
|
|
expected: []struct {
|
|
start, end int
|
|
effort float64
|
|
}{
|
|
{0, 2, 28.0}, // Effort: 20 + (200/100*4) = 28
|
|
{2, 4, 42.0}, // Effort: (30-20) + ((300-200)/100*4) + (40-30) + ((400-300)/100*4) = 42
|
|
},
|
|
},
|
|
{
|
|
name: "Duplicate stops (rest days)",
|
|
points: points,
|
|
allStops: []int{0, 2, 2, 4},
|
|
elevFactor: 4.0,
|
|
expected: []struct {
|
|
start, end int
|
|
effort float64
|
|
}{
|
|
{0, 2, 28.0}, // Effort: 20 + (200/100*4) = 28
|
|
{2, 4, 42.0}, // Effort: (30-20) + ((300-200)/100*4) + (40-30) + ((400-300)/100*4) = 42
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := createSegmentsBetweenStops(tt.points, tt.allStops, tt.elevFactor)
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("Expected %d segments, got %d", len(tt.expected), len(result))
|
|
return
|
|
}
|
|
for i, seg := range result {
|
|
if seg.start != tt.expected[i].start || seg.end != tt.expected[i].end {
|
|
t.Errorf("Segment %d bounds mismatch: expected (%d,%d), got (%d,%d)",
|
|
i, tt.expected[i].start, tt.expected[i].end, seg.start, seg.end)
|
|
}
|
|
if math.Abs(seg.effort-tt.expected[i].effort) > 0.1 {
|
|
t.Errorf("Segment %d effort mismatch: expected %.1f, got %.1f",
|
|
i, tt.expected[i].effort, seg.effort)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleTooManyCuts(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cutIndexes []int
|
|
forcedStopIndexes []int
|
|
days int
|
|
lastPointIndex int
|
|
expectedLen int
|
|
}{
|
|
{
|
|
name: "Fewer cuts than days",
|
|
cutIndexes: []int{25, 50},
|
|
forcedStopIndexes: []int{},
|
|
days: 4,
|
|
lastPointIndex: 99,
|
|
expectedLen: 4, // Will add cuts to reach days
|
|
},
|
|
{
|
|
name: "More cuts than days",
|
|
cutIndexes: []int{20, 40, 60, 80},
|
|
forcedStopIndexes: []int{},
|
|
days: 3,
|
|
lastPointIndex: 99,
|
|
expectedLen: 3, // Will reduce to days
|
|
},
|
|
{
|
|
name: "With forced stops",
|
|
cutIndexes: []int{20, 40, 60, 80},
|
|
forcedStopIndexes: []int{20, 60},
|
|
days: 3,
|
|
lastPointIndex: 99,
|
|
expectedLen: 3, // Will keep forced stops and last point
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := handleTooManyCuts(tt.cutIndexes, tt.forcedStopIndexes, tt.days, tt.lastPointIndex)
|
|
if len(result) != tt.expectedLen {
|
|
t.Errorf("Expected %d cuts, got %d", tt.expectedLen, len(result))
|
|
}
|
|
|
|
// Check that forced stops are included
|
|
for _, forcedIdx := range tt.forcedStopIndexes {
|
|
found := false
|
|
for _, resultIdx := range result {
|
|
if resultIdx == forcedIdx {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Forced stop %d not found in result", forcedIdx)
|
|
}
|
|
}
|
|
|
|
// Check that last point is included
|
|
lastPointFound := false
|
|
for _, resultIdx := range result {
|
|
if resultIdx == tt.lastPointIndex {
|
|
lastPointFound = true
|
|
break
|
|
}
|
|
}
|
|
if !lastPointFound {
|
|
t.Errorf("Last point %d not found in result", tt.lastPointIndex)
|
|
}
|
|
})
|
|
}
|
|
}
|