Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions GoogleMapsApi.Test/AssertInconclusiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,35 @@ public void HasTransitStep_OkResponseWithoutTransitStep_IsInconclusive()

Assert.Throws<InconclusiveException>(() => AssertInconclusive.HasTransitStep(response));
}

[Test]
public void EnforcedRouteLengthLimit_RouteRejected_Passes()
{
var response = new DirectionsResponse { Status = DirectionsStatusCodes.MAX_ROUTE_LENGTH_EXCEEDED };

Assert.DoesNotThrow(() => AssertInconclusive.EnforcedRouteLengthLimit(response));
}

[Test]
public void EnforcedRouteLengthLimit_GoogleAcceptedTheRoute_IsInconclusive()
{
var response = new DirectionsResponse { Status = DirectionsStatusCodes.OK };

Assert.Throws<InconclusiveException>(() => AssertInconclusive.EnforcedRouteLengthLimit(response));
}

[Test]
public void EnforcedRouteLengthLimit_UnrelatedFailure_Fails()
{
var response = new DirectionsResponse
{
Status = DirectionsStatusCodes.REQUEST_DENIED,
ErrorMessage = "API key rejected",
};

var ex = Assert.Throws<AssertionException>(() => AssertInconclusive.EnforcedRouteLengthLimit(response));

Assert.That(ex!.Message, Does.Contain("API key rejected"));
}
}
}
95 changes: 95 additions & 0 deletions GoogleMapsApi.Test/DirectionsUnitTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using GoogleMapsApi.Engine;
using GoogleMapsApi.Entities.Directions.Request;
using GoogleMapsApi.Entities.Directions.Response;
using NUnit.Framework;

namespace GoogleMapsApi.Test
{
/// <summary>
/// Offline coverage for the Directions status codes. The live
/// <c>DirectionsTests.Directions_ExceedingRouteLength</c> can only observe whatever Google's
/// current server-side route-length threshold happens to be, so the contract we actually own -
/// mapping the wire status onto <see cref="DirectionsStatusCodes"/> - is pinned here instead.
/// </summary>
[TestFixture]
public class DirectionsUnitTests
{
[Test]
public async Task MaxRouteLengthExceeded_IsDeserialized()
{
const string json = """
{
"geocoded_waypoints" : [],
"routes" : [],
"status" : "MAX_ROUTE_LENGTH_EXCEEDED"
}
""";

var response = await QueryAsync(json);

Assert.That(response.Status, Is.EqualTo(DirectionsStatusCodes.MAX_ROUTE_LENGTH_EXCEEDED));
Assert.That(response.Routes, Is.Empty);
}

[Test]
public async Task ErrorStatus_CarriesErrorMessage()
{
const string json = """
{
"error_message" : "The provided API key is invalid.",
"routes" : [],
"status" : "REQUEST_DENIED"
}
""";

var response = await QueryAsync(json);

Assert.That(response.Status, Is.EqualTo(DirectionsStatusCodes.REQUEST_DENIED));
Assert.That(response.ErrorMessage, Is.EqualTo("The provided API key is invalid."));
}

[TestCase("OK", DirectionsStatusCodes.OK)]
[TestCase("NOT_FOUND", DirectionsStatusCodes.NOT_FOUND)]
[TestCase("ZERO_RESULTS", DirectionsStatusCodes.ZERO_RESULTS)]
[TestCase("MAX_WAYPOINTS_EXCEEDED", DirectionsStatusCodes.MAX_WAYPOINTS_EXCEEDED)]
[TestCase("MAX_ROUTE_LENGTH_EXCEEDED", DirectionsStatusCodes.MAX_ROUTE_LENGTH_EXCEEDED)]
[TestCase("INVALID_REQUEST", DirectionsStatusCodes.INVALID_REQUEST)]
[TestCase("OVER_QUERY_LIMIT", DirectionsStatusCodes.OVER_QUERY_LIMIT)]
[TestCase("REQUEST_DENIED", DirectionsStatusCodes.REQUEST_DENIED)]
[TestCase("UNKNOWN_ERROR", DirectionsStatusCodes.UNKNOWN_ERROR)]
public async Task EveryDocumentedStatus_RoundTripsFromTheWire(string wireValue, DirectionsStatusCodes expected)
{
var response = await QueryAsync($$"""{ "routes" : [], "status" : "{{wireValue}}" }""");

Assert.That(response.Status, Is.EqualTo(expected));
}

private static Task<DirectionsResponse> QueryAsync(string responseJson)
{
var request = new DirectionsRequest { Origin = "NYC, USA", Destination = "Miami, USA", ApiKey = "KEY" };
var handler = new StubHandler(responseJson);
using var http = new HttpClient(handler);
return MapsAPIGenericEngine<DirectionsRequest, DirectionsResponse>.QueryGoogleAPIAsync(
http, request, TimeSpan.FromMilliseconds(-1), CancellationToken.None, null, null);
}

private sealed class StubHandler : HttpMessageHandler
{
private readonly string _responseJson;
public StubHandler(string responseJson) { _responseJson = responseJson; }

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
=> Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(_responseJson, Encoding.UTF8, "application/json")
});
}
}
}
2 changes: 1 addition & 1 deletion GoogleMapsApi.Test/IntegrationTests/DirectionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public async Task Directions_ExceedingRouteLength()
var result = await Maps.Directions.QueryAsync(request);

AssertInconclusive.NotExceedQuota(result);
Assert.That(result.Status, Is.EqualTo(DirectionsStatusCodes.MAX_ROUTE_LENGTH_EXCEEDED), result.ErrorMessage);
AssertInconclusive.EnforcedRouteLengthLimit(result);
}

[Test]
Expand Down
16 changes: 16 additions & 0 deletions GoogleMapsApi.Test/Utils/AssertInconclusive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ public static void HasTransitStep(DirectionsResponse response)
throw new InconclusiveException("Google returned no transit step for this route (likely a walking-only itinerary); cannot assert transit vehicle data.");
}

/// <summary>
/// Google's route-length ceiling is an undocumented server-side threshold that has moved
/// before: a route it once rejected with MAX_ROUTE_LENGTH_EXCEEDED now routes fine. Treat OK
/// as inconclusive so a shifted threshold surfaces as live drift instead of a red build; any
/// other status still fails. Deterministic coverage of the status itself lives in
/// DirectionsUnitTests.
/// </summary>
public static void EnforcedRouteLengthLimit(DirectionsResponse response)
{
if (response?.Status == DirectionsStatusCodes.OK)
throw new InconclusiveException("Google accepted a route that previously exceeded its length limit; the server-side threshold has moved.");

if (response?.Status != DirectionsStatusCodes.MAX_ROUTE_LENGTH_EXCEEDED)
Assert.Fail($"Directions API returned {response?.Status}. {response?.ErrorMessage}");
}

/// <summary>
/// If the response status indicates fail because of quota exceeded - mark test as inconclusive.
/// </summary>
Expand Down
Loading