//go:build integration // Integration tests hit a live router (hosted Valhalla by default, or a // local OSRM via ROUTER_URL for the osrm backend). They verify the // client plumbing and the stop_cost end-to-end on the real road network. // // Run: go test -tags integration ./... package plan import ( "context" "os" "testing" "time" "maps/router/internal/geo" "maps/router/internal/route" ) func liveRouter(t *testing.T) route.Router { backend := os.Getenv("ROUTER_BACKEND") switch backend { case "osrm": return route.NewOSRM(os.Getenv("ROUTER_URL")) default: return route.NewValhalla("https://valhalla1.openstreetmap.de") } } var ( lincoln = geo.Point{Lat: 44.054, Lon: -71.650} sunnyside = geo.Point{Lat: 40.74707, Lon: -73.9181} // 39-84 46th St, Sunnyside 11104 ) func TestLiveDirectRoute(t *testing.T) { t.Parallel() r := liveRouter(t) ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() rt, err := r.Route(ctx, route.ProfileDrive, []geo.Point{lincoln, sunnyside}) if err != nil { t.Fatalf("route: %v", err) } // Lincoln NH -> Sunnyside is a 5.25-5.75h drive (I-93/90/495/290). // Anything outside this band means the backend or the dataset is wrong. mins := rt.Duration / 60 if mins < 290 || mins > 430 { t.Errorf("direct time = %.0f min, expected in [290, 430] (profile-dependent)", mins) } if rt.Distance < 480_000 || rt.Distance > 600_000 { t.Errorf("distance = %.0f m, expected in [480km, 600km]", rt.Distance) } t.Logf("%s: %d min, %.0f km", r.Name(), int(mins), rt.Distance/1000) } // TestLiveRouteAvoidsBoston is the regression test for the user's // "the router, not the model, knows the route" finding: the fastest // Lincoln->Queens drive goes via I-90/I-495/I-290 (Worcester corridor), // NOT through Boston. func TestLiveRouteAvoidsBoston(t *testing.T) { t.Parallel() r := liveRouter(t) ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() rt, err := r.Route(ctx, route.ProfileDrive, []geo.Point{lincoln, sunnyside}) if err != nil { t.Fatalf("route: %v", err) } boston := geo.Point{Lat: 42.36, Lon: -71.06} var dist float64 if len(rt.Geometry) >= 2 { dist = geo.DistToPolylineMeters(boston, rt.Geometry) } else if !rt.BBox.Empty() { dist = rt.BBox.DistMeters(boston) t.Logf("using bbox distance (no geometry from backend)") } else { t.Skip("no geometry or bbox available") } if dist < 15_000 { t.Errorf("route comes within %.1f km of Boston; expected the I-495/I-290 corridor (>= 15 km)", dist/1000) } t.Logf("Boston clearance: %.1f km", dist/1000) } // TestLiveStopCost exercises the full stop_cost pipeline on the real // network: Cascade Falls (Worcester) sits on the I-495 corridor and // must have a small detour; Mount Major (Alton NH) is far off it. func TestLiveStopCost(t *testing.T) { t.Parallel() r := liveRouter(t) ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) defer cancel() cascade := geo.Point{Lat: 42.2767, Lon: -71.806} mtmajor := geo.Point{Lat: 43.642, Lon: -71.156} pts := []geo.Point{cascade, mtmajor, lincoln, sunnyside} m, err := route.BuildMatrix(ctx, r, route.ProfileDrive, pts, 200*time.Millisecond) if err != nil { t.Fatalf("matrix: %v", err) } cascadeCost := StopCost(m, 0, Stop{ID: "cascade", DwellMin: 60}) majorCost := StopCost(m, 1, Stop{ID: "mtmajor", DwellMin: 120}) t.Logf("cascade detour = %d min; mtmajor detour = %d min", cascadeCost.DetourMin, majorCost.DetourMin) if cascadeCost.DetourMin > 20 { t.Errorf("Cascade Falls (on I-495 corridor) detour = %d min, want <= 20", cascadeCost.DetourMin) } if majorCost.DetourMin < 40 { t.Errorf("Mount Major (off corridor) detour = %d min, want >= 40", majorCost.DetourMin) } }