goplaces/route_test.go
Peter Steinberger 16c0093b92
Some checks failed
ci / test (push) Has been cancelled
build / goreleaser (push) Has been cancelled
test: expand route coverage
2026-01-02 23:01:29 +01:00

336 lines
9.2 KiB
Go

package goplaces
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestComputeRoutePolyline(t *testing.T) {
var gotBody map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != routesPath {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
if r.Header.Get("X-Goog-FieldMask") != routesFieldMask {
t.Fatalf("unexpected field mask: %s", r.Header.Get("X-Goog-FieldMask"))
}
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
t.Fatalf("decode body: %v", err)
}
_, _ = w.Write([]byte("{\"routes\": [{\"polyline\": {\"encodedPolyline\": \"_p~iF~ps|U_ulLnnqC_mqNvxq`@\"}}]}"))
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", RoutesBaseURL: server.URL})
polyline, err := client.computeRoutePolyline(context.Background(), RouteRequest{
From: "Seattle",
To: "Portland",
Mode: travelModeDrive,
})
if err != nil {
t.Fatalf("computeRoutePolyline error: %v", err)
}
if polyline == "" {
t.Fatalf("expected polyline")
}
if gotBody["travelMode"] != travelModeDrive {
t.Fatalf("unexpected travelMode: %#v", gotBody["travelMode"])
}
}
func TestDecodePolyline(t *testing.T) {
points, err := decodePolyline("_p~iF~ps|U_ulLnnqC_mqNvxq`@")
if err != nil {
t.Fatalf("decodePolyline error: %v", err)
}
if len(points) != 3 {
t.Fatalf("expected 3 points, got %d", len(points))
}
if points[0].Lat != 38.5 || points[0].Lng != -120.2 {
t.Fatalf("unexpected first point: %#v", points[0])
}
}
func TestDecodePolylineInvalid(t *testing.T) {
_, err := decodePolyline("")
if err == nil {
t.Fatalf("expected decode error")
}
}
func TestDecodePolylineMalformed(t *testing.T) {
_, err := decodePolyline("abc")
if err == nil {
t.Fatalf("expected malformed error")
}
}
func TestSampleWaypoints(t *testing.T) {
points := []LatLng{{Lat: 0, Lng: 0}, {Lat: 0, Lng: 1}, {Lat: 0, Lng: 2}}
waypoints := sampleWaypoints(points, 2)
if len(waypoints) != 2 {
t.Fatalf("expected 2 waypoints, got %d", len(waypoints))
}
if waypoints[0].Lng != 0 || waypoints[1].Lng != 2 {
t.Fatalf("unexpected waypoints: %#v", waypoints)
}
}
func TestSampleWaypointsSingle(t *testing.T) {
points := []LatLng{{Lat: 1, Lng: 1}, {Lat: 2, Lng: 2}}
waypoints := sampleWaypoints(points, 1)
if len(waypoints) != 1 {
t.Fatalf("expected 1 waypoint")
}
}
func TestSampleWaypointsSinglePoint(t *testing.T) {
points := []LatLng{{Lat: 1, Lng: 1}}
waypoints := sampleWaypoints(points, 5)
if len(waypoints) != 1 {
t.Fatalf("expected 1 waypoint")
}
}
func TestSampleWaypointsZeroTotal(t *testing.T) {
points := []LatLng{{Lat: 1, Lng: 1}, {Lat: 1, Lng: 1}}
waypoints := sampleWaypoints(points, 3)
if len(waypoints) != 1 {
t.Fatalf("expected 1 waypoint")
}
if waypoints[0] != points[0] {
t.Fatalf("unexpected waypoint: %#v", waypoints[0])
}
}
func TestSampleWaypointsMaxExceedsPoints(t *testing.T) {
points := []LatLng{{Lat: 0, Lng: 0}, {Lat: 0, Lng: 0}, {Lat: 0, Lng: 1}}
waypoints := sampleWaypoints(points, 5)
if len(waypoints) != 2 {
t.Fatalf("expected 2 waypoints, got %d", len(waypoints))
}
}
func TestPointAtDistanceBounds(t *testing.T) {
points := []LatLng{{Lat: 0, Lng: 0}, {Lat: 0, Lng: 2}}
cumulative := cumulativeDistances(points)
if got := pointAtCumulative(points, cumulative, -1); got != points[0] {
t.Fatalf("expected first point, got %#v", got)
}
if got := pointAtCumulative(points, cumulative, cumulative[len(cumulative)-1]+1); got != points[1] {
t.Fatalf("expected last point, got %#v", got)
}
}
func TestUniqueWaypoints(t *testing.T) {
points := []LatLng{{Lat: 1, Lng: 1}, {Lat: 1, Lng: 1}, {Lat: 2, Lng: 2}}
unique := uniqueWaypoints(points)
if len(unique) != 2 {
t.Fatalf("expected 2 unique points, got %d", len(unique))
}
}
func TestDistanceMeters(t *testing.T) {
distance := distanceMeters(LatLng{Lat: 0, Lng: 0}, LatLng{Lat: 0, Lng: 1})
if distance <= 0 {
t.Fatalf("expected positive distance")
}
}
func TestTotalDistanceEmpty(t *testing.T) {
if totalDistance([]LatLng{{Lat: 1, Lng: 1}}) != 0 {
t.Fatalf("expected zero distance")
}
}
func TestPointAtDistanceEmpty(t *testing.T) {
point := pointAtDistance(nil, 10)
if point != (LatLng{}) {
t.Fatalf("expected empty point")
}
}
func TestValidateRouteRequest(t *testing.T) {
err := validateRouteRequest(RouteRequest{})
if err == nil {
t.Fatalf("expected error")
}
err = validateRouteRequest(RouteRequest{
Query: "coffee",
From: "A",
To: "B",
Mode: "FLY",
Limit: 1,
RadiusM: 1,
MaxWaypoints: 1,
})
if err == nil {
t.Fatalf("expected mode error")
}
}
func TestValidateRouteRequestBounds(t *testing.T) {
err := validateRouteRequest(RouteRequest{
Query: "coffee",
From: "A",
To: "B",
Mode: travelModeDrive,
Limit: 0,
RadiusM: -1,
MaxWaypoints: 999,
})
if err == nil {
t.Fatalf("expected bounds error")
}
}
func TestApplyRouteDefaults(t *testing.T) {
req := applyRouteDefaults(RouteRequest{
Query: " coffee ",
From: " A ",
To: " B ",
Mode: "walk",
})
if req.Mode != travelModeWalk {
t.Fatalf("unexpected mode: %s", req.Mode)
}
if req.Limit != defaultRouteLimit {
t.Fatalf("unexpected limit: %d", req.Limit)
}
}
func TestApplyRouteDefaultsEmpty(t *testing.T) {
req := applyRouteDefaults(RouteRequest{})
if req.Mode != travelModeDrive {
t.Fatalf("expected default mode")
}
if req.Limit != defaultRouteLimit {
t.Fatalf("expected default limit")
}
if req.RadiusM != defaultRouteRadiusM {
t.Fatalf("expected default radius")
}
if req.MaxWaypoints != defaultRouteWaypoints {
t.Fatalf("expected default waypoints")
}
}
func TestComputeRoutePolylineErrors(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(`{"routes":[]}`))
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", RoutesBaseURL: server.URL})
_, err := client.computeRoutePolyline(context.Background(), RouteRequest{From: "A", To: "B"})
if err == nil {
t.Fatalf("expected route error")
}
}
func TestComputeRoutePolylineEmpty(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(`{"routes":[{"polyline":{"encodedPolyline":""}}]}`))
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", RoutesBaseURL: server.URL})
_, err := client.computeRoutePolyline(context.Background(), RouteRequest{From: "A", To: "B"})
if err == nil {
t.Fatalf("expected empty polyline error")
}
}
func TestComputeRoutePolylineInvalidJSON(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte("not-json"))
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", RoutesBaseURL: server.URL})
_, err := client.computeRoutePolyline(context.Background(), RouteRequest{From: "A", To: "B"})
if err == nil {
t.Fatalf("expected json error")
}
}
func TestRouteEndToEnd(t *testing.T) {
searchCalls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case routesPath:
_, _ = w.Write([]byte("{\"routes\": [{\"polyline\": {\"encodedPolyline\": \"_p~iF~ps|U_ulLnnqC_mqNvxq`@\"}}]}"))
case "/places:searchText":
searchCalls++
_, _ = w.Write([]byte(`{"places":[{"id":"abc","displayName":{"text":"Cafe"}}]}`))
default:
t.Fatalf("unexpected path: %s", r.URL.Path)
}
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", BaseURL: server.URL, RoutesBaseURL: server.URL})
response, err := client.Route(context.Background(), RouteRequest{
Query: "coffee",
From: "Seattle",
To: "Portland",
})
if err != nil {
t.Fatalf("route error: %v", err)
}
if len(response.Waypoints) == 0 {
t.Fatalf("expected waypoints")
}
if searchCalls == 0 {
t.Fatalf("expected search calls")
}
}
func TestRouteSearchError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case routesPath:
_, _ = w.Write([]byte("{\"routes\": [{\"polyline\": {\"encodedPolyline\": \"_p~iF~ps|U_ulLnnqC_mqNvxq`@\"}}]}"))
case "/places:searchText":
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("bad"))
default:
t.Fatalf("unexpected path: %s", r.URL.Path)
}
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", BaseURL: server.URL, RoutesBaseURL: server.URL})
_, err := client.Route(context.Background(), RouteRequest{
Query: "coffee",
From: "Seattle",
To: "Portland",
})
if err == nil {
t.Fatalf("expected route error")
}
}
func TestRouteComputeRouteError(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != routesPath {
t.Fatalf("unexpected path: %s", r.URL.Path)
}
_, _ = w.Write([]byte(`{"routes":[]}`))
}))
defer server.Close()
client := NewClient(Options{APIKey: "test-key", RoutesBaseURL: server.URL})
_, err := client.Route(context.Background(), RouteRequest{
Query: "coffee",
From: "A",
To: "B",
})
if err == nil {
t.Fatalf("expected route error")
}
}