This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Repères réels de Nantes (44000) utilisés pour vérifier le calcul de
|
||||
// distance/temps de trajet des commandes.
|
||||
var (
|
||||
placeRoyale = Coordinates{Latitude: 47.2148, Longitude: -1.5584}
|
||||
gareNantes = Coordinates{Latitude: 47.2173, Longitude: -1.5426}
|
||||
aeroportNantes = Coordinates{Latitude: 47.1532, Longitude: -1.6107}
|
||||
)
|
||||
|
||||
func almostEqual(a, b, tolerance float64) bool {
|
||||
return math.Abs(a-b) <= tolerance
|
||||
}
|
||||
|
||||
func TestCalculateDistance_SamePointIsZero(t *testing.T) {
|
||||
if got := CalculateDistance(placeRoyale, placeRoyale); got != 0 {
|
||||
t.Errorf("distance entre un point et lui-même: got=%.4f want=0", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Le long d'un même méridien (même longitude), la distance Haversine est
|
||||
// exacte : 1° de latitude = R * (π/180) ≈ 111.19 km.
|
||||
func TestCalculateDistance_OneDegreeLatitudeIsExact(t *testing.T) {
|
||||
from := Coordinates{Latitude: 47.0, Longitude: -1.5536}
|
||||
to := Coordinates{Latitude: 48.0, Longitude: -1.5536}
|
||||
want := EarthRadiusKm * (math.Pi / 180.0)
|
||||
|
||||
got := CalculateDistance(from, to)
|
||||
if !almostEqual(got, want, 0.01) {
|
||||
t.Errorf("distance 1° de latitude: got=%.4f want=%.4f", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDistance_IsSymmetric(t *testing.T) {
|
||||
d1 := CalculateDistance(placeRoyale, gareNantes)
|
||||
d2 := CalculateDistance(gareNantes, placeRoyale)
|
||||
if !almostEqual(d1, d2, 0.0001) {
|
||||
t.Errorf("la distance doit être symétrique: A->B=%.4f B->A=%.4f", d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
// Place Royale <-> Aéroport de Nantes : environ 8 km à vol d'oiseau.
|
||||
func TestCalculateDistance_RealNantesLandmarks(t *testing.T) {
|
||||
got := CalculateDistance(placeRoyale, aeroportNantes)
|
||||
if got < 6 || got > 10 {
|
||||
t.Errorf("distance Place Royale -> Aéroport Nantes hors plage réaliste: got=%.2f km, want=[6,10]", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateETA_VeryCloseReturnsMinETA(t *testing.T) {
|
||||
cases := []float64{0, 0.01, 0.05, 0.099}
|
||||
for _, d := range cases {
|
||||
if got := CalculateETA(d); got != MinETA {
|
||||
t.Errorf("CalculateETA(%.3f km): got=%d want=%d (MinETA)", d, got, MinETA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Formule : (distance/25 km/h)*60 min, +20% de marge trafic, arrondi par troncature.
|
||||
func TestCalculateETA_MatchesFormulaForNormalDistance(t *testing.T) {
|
||||
distanceKm := 10.0
|
||||
travelTime := (distanceKm / 25.0) * 60.0
|
||||
want := int(travelTime * 1.2)
|
||||
|
||||
got := CalculateETA(distanceKm)
|
||||
if got != want {
|
||||
t.Errorf("CalculateETA(%.1f km): got=%d want=%d", distanceKm, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateETA_VeryFarClampsToMaxETA(t *testing.T) {
|
||||
if got := CalculateETA(1000); got != MaxETA {
|
||||
t.Errorf("CalculateETA(1000 km): got=%d want=%d (MaxETA)", got, MaxETA)
|
||||
}
|
||||
}
|
||||
|
||||
// L'ETA ne doit jamais sortir de l'intervalle [MinETA, MaxETA], quelle que
|
||||
// soit la distance fournie (y compris des valeurs aberrantes).
|
||||
func TestCalculateETA_AlwaysWithinBounds(t *testing.T) {
|
||||
distances := []float64{-5, 0, 0.05, 1, 5, 10, 50, 100, 500, 10000}
|
||||
for _, d := range distances {
|
||||
got := CalculateETA(d)
|
||||
if got < MinETA || got > MaxETA {
|
||||
t.Errorf("CalculateETA(%.2f): got=%d, hors bornes [%d,%d]", d, got, MinETA, MaxETA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sans clé TomTom configurée (cas de cet environnement de test), le calcul
|
||||
// doit retomber sur Haversine + CalculateETA, sans appel réseau.
|
||||
func TestCalculateETAWithTomTom_FallsBackToHaversineWithoutAPIKey(t *testing.T) {
|
||||
if len(tomTomKeys.keys) != 0 {
|
||||
t.Skip("test valable uniquement sans clé TomTom configurée dans l'environnement")
|
||||
}
|
||||
|
||||
wantDistance := CalculateDistance(placeRoyale, aeroportNantes)
|
||||
wantETA := CalculateETA(wantDistance)
|
||||
|
||||
gotETA, gotDistance, err := CalculateETAWithTomTom(placeRoyale, aeroportNantes)
|
||||
if err != nil {
|
||||
t.Fatalf("CalculateETAWithTomTom (fallback): %v", err)
|
||||
}
|
||||
if gotDistance != wantDistance {
|
||||
t.Errorf("distance fallback: got=%.4f want=%.4f", gotDistance, wantDistance)
|
||||
}
|
||||
if gotETA != wantETA {
|
||||
t.Errorf("ETA fallback: got=%d want=%d", gotETA, wantETA)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCoordinates(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
coords Coordinates
|
||||
wantErr bool
|
||||
}{
|
||||
{"Nantes valide", placeRoyale, false},
|
||||
{"latitude limite haute valide", Coordinates{Latitude: 90, Longitude: 0}, false},
|
||||
{"latitude limite basse valide", Coordinates{Latitude: -90, Longitude: 0}, false},
|
||||
{"latitude trop haute", Coordinates{Latitude: 90.1, Longitude: 0}, true},
|
||||
{"latitude trop basse", Coordinates{Latitude: -90.1, Longitude: 0}, true},
|
||||
{"longitude limite haute valide", Coordinates{Latitude: 0, Longitude: 180}, false},
|
||||
{"longitude trop haute", Coordinates{Latitude: 0, Longitude: 180.1}, true},
|
||||
{"longitude trop basse", Coordinates{Latitude: 0, Longitude: -180.1}, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
err := ValidateCoordinates(c.coords)
|
||||
if c.wantErr && err == nil {
|
||||
t.Error("attendu une erreur, reçu nil")
|
||||
}
|
||||
if !c.wantErr && err != nil {
|
||||
t.Errorf("erreur inattendue: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user