/* * Copyright 2014 Google Inc. All rights reserved. * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this * file except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF * ANY KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.google.maps; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import com.google.maps.errors.OverQueryLimitException; import com.google.maps.internal.ApiConfig; import com.google.maps.internal.ApiResponse; import com.google.maps.model.GeocodingResult; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @Category(MediumTests.class) public class GeoApiContextTest { private MockWebServer server = new MockWebServer(); private GeoApiContext context = new GeoApiContext() .setApiKey("AIza...") .setQueryRateLimit(500, 0); private void setMockBaseUrl() { context.setBaseUrlForTesting("http://127.0.0.1:" + server.getPort()); } @Test public void testGetIncludesDefaultUserAgent() throws Exception { // Set up a mock request ApiResponse fakeResponse = mock(ApiResponse.class); String path = "/"; Map<String, String> params = new HashMap<String, String>(1); params.put("key", "value"); // Set up the fake web server server.enqueue(new MockResponse()); server.play(); setMockBaseUrl(); // Build & execute the request using our context context.get(new ApiConfig(path), fakeResponse.getClass(), params).awaitIgnoreError(); // Read the headers server.shutdown(); RecordedRequest request = server.takeRequest(); List<String> headers = request.getHeaders(); boolean headerFound = false; for (String header : headers) { if (header.startsWith("User-Agent: ")) { headerFound = true; assertTrue("User agent not in correct format", header.matches("User-Agent: GoogleGeoApiClientJava/[^\\s]+")); } } assertTrue("User agent header not present", headerFound); } @Test public void testErrorResponseRetries() throws Exception { // Set up mock responses MockResponse errorResponse = createMockBadResponse(); MockResponse goodResponse = createMockGoodResponse(); server.enqueue(errorResponse); server.enqueue(goodResponse); server.play(); // Build the context under test setMockBaseUrl(); // Execute GeocodingResult[] result = context.get(new ApiConfig("/"), GeocodingApi.Response.class, "k", "v").await(); assertEquals(1, result.length); assertEquals("1600 Amphitheatre Parkway, Mountain View, CA 94043, USA", result[0].formattedAddress); server.shutdown(); } @Test(expected = IOException.class) public void testSettingMaxRetries() throws Exception { MockResponse errorResponse = createMockBadResponse(); MockResponse goodResponse = createMockGoodResponse(); // Set up the fake web server server.enqueue(errorResponse); server.enqueue(errorResponse); server.enqueue(errorResponse); server.enqueue(goodResponse); server.play(); setMockBaseUrl(); // This should limit the number of retries, ensuring that the success response is NOT returned. context.setMaxRetries(2); context.get(new ApiConfig("/"), GeocodingApi.Response.class, "k", "v").await(); } private MockResponse createMockGoodResponse() { MockResponse response = new MockResponse(); response.setResponseCode(200); response.setBody("{\n" + " \"results\" : [\n" + " {\n" + " \"address_components\" : [\n" + " {\n" + " \"long_name\" : \"1600\",\n" + " \"short_name\" : \"1600\",\n" + " \"types\" : [ \"street_number\" ]\n" + " }\n" + " ],\n" + " \"formatted_address\" : \"1600 Amphitheatre Parkway, Mountain View, " + "CA 94043, USA\",\n" + " \"geometry\" : {\n" + " \"location\" : {\n" + " \"lat\" : 37.4220033,\n" + " \"lng\" : -122.0839778\n" + " },\n" + " \"location_type\" : \"ROOFTOP\",\n" + " \"viewport\" : {\n" + " \"northeast\" : {\n" + " \"lat\" : 37.4233522802915,\n" + " \"lng\" : -122.0826288197085\n" + " },\n" + " \"southwest\" : {\n" + " \"lat\" : 37.4206543197085,\n" + " \"lng\" : -122.0853267802915\n" + " }\n" + " }\n" + " },\n" + " \"types\" : [ \"street_address\" ]\n" + " }\n" + " ],\n" + " \"status\" : \"OK\"\n" + "}"); return response; } private MockResponse createMockBadResponse() { MockResponse response = new MockResponse(); response.setStatus("HTTP/1.1 500 Internal server error"); response.setBody("Uh-oh. Server Error."); return response; } @Test(expected = IOException.class) public void testRetryCanBeDisabled() throws Exception { // Set up 2 mock responses, an error that shouldn't be retried and a success MockResponse errorResponse = new MockResponse(); errorResponse.setStatus("HTTP/1.1 500 Internal server error"); errorResponse.setBody("Uh-oh. Server Error."); server.enqueue(errorResponse); MockResponse goodResponse = new MockResponse(); goodResponse.setResponseCode(200); goodResponse.setBody("{\n" + " \"results\" : [],\n" + " \"status\" : \"ZERO_RESULTS\"\n" + "}"); server.enqueue(goodResponse); server.play(); setMockBaseUrl(); // This should disable the retry, ensuring that the success response is NOT returned context.disableRetries(); // We should get the error response here, not the success response. context.get(new ApiConfig("/"), GeocodingApi.Response.class, "k", "v").await(); } @Test public void testRetryEventuallyReturnsTheRightException() throws Exception { MockResponse errorResponse = new MockResponse(); errorResponse.setStatus("HTTP/1.1 500 Internal server error"); errorResponse.setBody("Uh-oh. Server Error."); // Enqueue some error responses. for (int i = 0; i < 10; i++) { server.enqueue(errorResponse); } server.play(); // Wire the mock web server to the context setMockBaseUrl(); context.setRetryTimeout(5, TimeUnit.SECONDS); try { context.get(new ApiConfig("/"), GeocodingApi.Response.class, "k", "v").await(); } catch (IOException ioe) { // Ensure the message matches the status line in the mock responses. assertEquals("Server Error: 500 Internal server error", ioe.getMessage()); return; } fail("Internal server error was expected but not observed."); } @Test public void testQueryParamsHaveOrderPreserved() throws Exception { // This test is important for APIs (such as the speed limits API) where multiple parameters // must be provided with the same name with order preserved. MockResponse response = new MockResponse(); response.setResponseCode(200); response.setBody("{}"); server.enqueue(response); server.play(); setMockBaseUrl(); context.get(new ApiConfig("/"), GeocodingApi.Response.class, "a", "1", "a", "2", "a", "3").awaitIgnoreError(); server.shutdown(); RecordedRequest request = server.takeRequest(); String path = request.getPath(); assertTrue(path.contains("a=1&a=2&a=3")); } @Test public void testToggleIfExceptionIsAllowedToRetry() throws Exception { // Enqueue some error responses, although only the first should be used because the response's exception is not // allowed to be retried. MockResponse overQueryLimitResponse = new MockResponse(); overQueryLimitResponse.setStatus("HTTP/1.1 400 Internal server error"); overQueryLimitResponse.setBody(TestUtils.retrieveBody("OverQueryLimitResponse.json")); server.enqueue(overQueryLimitResponse); server.enqueue(overQueryLimitResponse); server.enqueue(overQueryLimitResponse); server.play(); context.setRetryTimeout(1, TimeUnit.MILLISECONDS); context.setMaxRetries(10); context.toggleifExceptionIsAllowedToRetry(OverQueryLimitException.class, false); setMockBaseUrl(); try { context.get(new ApiConfig("/"), GeocodingApi.Response.class, "any-key", "any-value").await(); } catch (OverQueryLimitException e) { assertEquals(1, server.getRequestCount()); return; } fail("OverQueryLimitException was expected but not observed."); } }