/* * Copyright 2014 Sonoport (Asia) Pte Ltd * * 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.sonoport.freesound; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import mockit.Delegate; import mockit.Expectations; import mockit.Mocked; import mockit.Verifications; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.JsonNode; import com.mashape.unirest.http.Unirest; import com.mashape.unirest.request.GetRequest; import com.mashape.unirest.request.HttpRequestWithBody; import com.sonoport.freesound.query.BinaryResponseQuery; import com.sonoport.freesound.query.HTTPRequestMethod; import com.sonoport.freesound.query.JSONResponseQuery; import com.sonoport.freesound.query.oauth2.AccessTokenQuery; import com.sonoport.freesound.query.oauth2.OAuth2AccessTokenRequest; import com.sonoport.freesound.query.oauth2.RefreshOAuth2AccessTokenRequest; import com.sonoport.freesound.response.AccessTokenDetails; import com.sonoport.freesound.response.Response; import com.sonoport.freesound.response.Sound; import com.sonoport.freesound.response.mapping.SoundMapper; /** * Unit tests to ensure the correct operation of {@link FreesoundClient}. */ public class FreesoundClientTest { /** Client identifier to use when constructing the instance of {@link FreesoundClient}. */ private static final String CLIENT_ID = "client-id"; /** Client secret to use when constructing the instance of {@link FreesoundClient}. */ private static final String CLIENT_SECRET = "foobarbaz"; /** The name of a route parameter to use in tests. */ private static final String ROUTE_ELEMENT = "routeElement"; /** Value of the route parameter. */ private static final String ROUTE_ELEMENT_VALUE = "1234"; /** Name of a query parameter to use in tests. */ private static final String QUERY_PARAMETER = "foo"; /** Value of query parameter. */ private static final Object QUERY_PARAMETER_VALUE = "bar"; /** Path to use in queries for tests. */ private static final String TEST_PATH = String.format("/test/{%s}", ROUTE_ELEMENT); /** OAuth2 authorisation code. */ private static final String OAUTH_AUTHORISATION_CODE = "abc123"; /** OAuth2 bearer token. */ private static final String OAUTH_ACCESS_TOKEN = "def456"; /** OAuth2 refresh token. */ private static final String OAUTH_REFRESH_TOKEN = "ghi789"; /** OAuth2 access scope. */ private static final String OAUTH_SCOPE = "read write"; /** OAuth2 bearer token expiry. */ private static final int OAUTH_TOKEN_EXPIRES_IN = 84600; /** JSON representation of response from OAuth2 token endpoint. */ private static final JSONObject OAUTH_TOKEN_DETAILS_JSON = new JSONObject( String.format( "{ \"access_token\":\"%s\", \"scope\":\"%s\", \"expires_in\":%d, \"refresh_token\":\"%s\" }", OAUTH_ACCESS_TOKEN, OAUTH_SCOPE, OAUTH_TOKEN_EXPIRES_IN, OAUTH_REFRESH_TOKEN)); /** Custom User-Agent string to use in tests. */ private static final String USER_AGENT_STRING = "freesound-java/test"; /** Instance of {@link FreesoundClient} to use in unit tests. */ private FreesoundClient freesoundClient; /** * Configure the instance of {@link FreesoundClient} with its dependencies. */ @Before public void configureClient() { freesoundClient = new FreesoundClient(CLIENT_ID, CLIENT_SECRET); } /** * Ensure that instances of {@link FreesoundClient} are created correctly with default options set. * * @param mockUnirest Mock {@link Unirest} object */ @SuppressWarnings("static-access") @Test public void defaultHeadersSetCorrectly(@Mocked final Unirest mockUnirest) { new FreesoundClient(CLIENT_ID, CLIENT_SECRET); new Verifications() { { mockUnirest.setDefaultHeader( FreesoundClient.HTTP_ACCEPT_HEADER, FreesoundClient.CONTENT_TYPES_TO_ACCEPT); mockUnirest.setDefaultHeader( FreesoundClient.HTTP_USER_AGENT_HEADER, FreesoundClient.DEFAULT_USER_AGENT_STRING); } }; } /** * Ensure that instances of {@link FreesoundClient} are created correctly with a custom User-Agent string specified. * * @param mockUnirest Mock {@link Unirest} object */ @SuppressWarnings("static-access") @Test public void headersSetCorrectlyWithCustomUserAgent(@Mocked final Unirest mockUnirest) { new FreesoundClient(CLIENT_ID, CLIENT_SECRET, USER_AGENT_STRING); new Verifications() { { mockUnirest.setDefaultHeader( FreesoundClient.HTTP_ACCEPT_HEADER, FreesoundClient.CONTENT_TYPES_TO_ACCEPT); mockUnirest.setDefaultHeader(FreesoundClient.HTTP_USER_AGENT_HEADER, USER_AGENT_STRING); } }; } /** * Ensure calls to {@link FreesoundClient#shutdown()} correctly close down all background processes. This is * primarily aimed at ensuring that {@link Unirest#shutdown()} is called. * * @param mockUnirest Mocking of {@link Unirest#shutdown()} method * @throws Exception Any exceptions thrown during test */ @SuppressWarnings("static-access") @Test public void shutdownClient(@Mocked("shutdown") final Unirest mockUnirest) throws Exception { freesoundClient.shutdown(); new Verifications() { { mockUnirest.shutdown(); } }; } /** * Ensure that a {@link FreesoundClientException} is thrown should any errors be encountered when calling * {@link FreesoundClient#shutdown()} is called. * * @param mockUnirest Mocking of {@link Unirest#shutdown()} method * @throws Exception Any exceptions thrown during test */ @SuppressWarnings("static-access") @Test (expected = FreesoundClientException.class) public void unirestShutdownFails(@Mocked("shutdown") final Unirest mockUnirest) throws Exception { new Expectations() { { mockUnirest.shutdown(); result = new IOException(); } }; freesoundClient.shutdown(); } /** * Test the {@link FreesoundClient#executeQuery(Query)} method, to ensure it correctly constructs and submits an * HTTP GET request, and processes the results. * * @param mockUnirest Mock version of the {@link Unirest} library * @param mockGetRequest Mock {@link GetRequest} * @param mockHttpResponse Mock {@link HttpResponse} * @param mockResultsMapper Mock {@link SoundMapper} * * @throws Exception Any exceptions thrown in test */ @SuppressWarnings("static-access") @Test public void executeGetQuery( @Mocked final Unirest mockUnirest, @Mocked final GetRequest mockGetRequest, @Mocked final HttpResponse<JsonNode> mockHttpResponse, @Mocked final SoundMapper mockResultsMapper) throws Exception { final Sound sound = new Sound(); new Expectations() { { mockUnirest.get(FreesoundClient.API_ENDPOINT + TEST_PATH); result = mockGetRequest; mockGetRequest.header("Authorization", "Token " + CLIENT_SECRET); mockGetRequest.routeParam(ROUTE_ELEMENT, ROUTE_ELEMENT_VALUE); mockGetRequest.queryString(with(new Delegate<HashMap<String, Object>>() { @SuppressWarnings("unused") void checkRequestParameters(final Map<String, Object> queryParameters) { assertNotNull(queryParameters); assertTrue(queryParameters.size() == 1); assertEquals(QUERY_PARAMETER_VALUE, queryParameters.get(QUERY_PARAMETER)); } })); mockGetRequest.asJson(); result = mockHttpResponse; mockHttpResponse.getStatus(); result = 200; mockResultsMapper.map(mockHttpResponse.getBody().getObject()); result = sound; } }; final JSONResponseQuery<Sound> query = new TestJSONResponseQuery(HTTPRequestMethod.GET, mockResultsMapper); final Response<Sound> response = freesoundClient.executeQuery(query); assertSame(sound, response.getResults()); } /** * Test the {@link FreesoundClient#executeQuery(Query)} method, to ensure it correctly constructs and submits an * HTTP POST request, and processes the results. * * @param mockUnirest Mock version of the {@link Unirest} library * @param mockPostRequest Mock {@link HttpRequestWithBody} representing a POST call * @param mockHttpResponse Mock {@link HttpResponse} * @param mockResultsMapper Mock {@link SoundMapper} * * @throws Exception Any exceptions thrown in test */ @SuppressWarnings("static-access") @Test public void executePostQuery( @Mocked final Unirest mockUnirest, @Mocked final HttpRequestWithBody mockPostRequest, @Mocked final HttpResponse<JsonNode> mockHttpResponse, @Mocked final SoundMapper mockResultsMapper) throws Exception { final Sound sound = new Sound(); new Expectations() { { mockUnirest.post(FreesoundClient.API_ENDPOINT + TEST_PATH); result = mockPostRequest; mockPostRequest.header("Authorization", "Token " + CLIENT_SECRET); mockPostRequest.routeParam(ROUTE_ELEMENT, ROUTE_ELEMENT_VALUE); mockPostRequest.fields(with(new Delegate<HashMap<String, Object>>() { @SuppressWarnings("unused") void checkRequestParameters(final Map<String, Object> queryParameters) { assertNotNull(queryParameters); assertTrue(queryParameters.size() == 1); assertEquals(QUERY_PARAMETER_VALUE, queryParameters.get(QUERY_PARAMETER)); } })); mockPostRequest.asJson(); result = mockHttpResponse; mockHttpResponse.getStatus(); result = 200; mockResultsMapper.map(mockHttpResponse.getBody().getObject()); result = sound; } }; final JSONResponseQuery<Sound> query = new TestJSONResponseQuery(HTTPRequestMethod.POST, mockResultsMapper); final Response<Sound> response = freesoundClient.executeQuery(query); assertSame(sound, response.getResults()); } /** * Test the {@link FreesoundClient#executeQuery(Query)} method, to ensure it correctly constructs and submits an * HTTP GET request, and processes the results. * * @param mockUnirest Mock version of the {@link Unirest} library * @param mockGetRequest Mock {@link GetRequest} * @param mockHttpResponse Mock {@link HttpResponse} * @param mockInputStream Mock {@link InputStream} response * * @throws Exception Any exceptions thrown in test */ @SuppressWarnings("static-access") @Test public void executeBinaryResponseQuery( @Mocked final Unirest mockUnirest, @Mocked final GetRequest mockGetRequest, @Mocked final HttpResponse<InputStream> mockHttpResponse, @Mocked final InputStream mockInputStream) throws Exception { new Expectations() { { mockUnirest.get(FreesoundClient.API_ENDPOINT + TEST_PATH); result = mockGetRequest; mockGetRequest.header("Authorization", "Token " + CLIENT_SECRET); mockGetRequest.routeParam(ROUTE_ELEMENT, ROUTE_ELEMENT_VALUE); mockGetRequest.queryString(with(new Delegate<HashMap<String, Object>>() { @SuppressWarnings("unused") void checkRequestParameters(final Map<String, Object> queryParameters) { assertNotNull(queryParameters); assertTrue(queryParameters.size() == 1); assertEquals(QUERY_PARAMETER_VALUE, queryParameters.get(QUERY_PARAMETER)); } })); mockGetRequest.asBinary(); result = mockHttpResponse; mockHttpResponse.getStatus(); result = 200; mockHttpResponse.getBody(); result = mockInputStream; } }; final TestBinaryResponseQuery query = new TestBinaryResponseQuery(); final Response<InputStream> response = freesoundClient.executeQuery(query); assertSame(mockInputStream, response.getResults()); } /** * Test that requests to redeem an authorisation code for an OAuth2 bearer token are correctly constructed and * passed to the appropriate endpoint. * * @param mockUnirest Mock version of the {@link Unirest} library * @param mockTokenRequest Mock {@link HttpRequestWithBody} representing a POST call to token endpoint * @param mockHttpResponse Mock {@link HttpResponse} * * @throws Exception Any exceptions thrown in test */ @SuppressWarnings("static-access") @Test public void requestAccessToken( @Mocked final Unirest mockUnirest, @Mocked final HttpRequestWithBody mockTokenRequest, @Mocked final HttpResponse<JsonNode> mockHttpResponse) throws Exception { new Expectations() { { mockUnirest.post(FreesoundClient.API_ENDPOINT + AccessTokenQuery.OAUTH_TOKEN_ENDPOINT_PATH); result = mockTokenRequest; mockTokenRequest.fields(with(new Delegate<HashMap<String, Object>>() { @SuppressWarnings("unused") void checkRequestParameters(final Map<String, Object> queryParameters) { assertEquals(CLIENT_ID, queryParameters.get(AccessTokenQuery.CLIENT_ID_PARAMETER_NAME)); assertEquals(CLIENT_SECRET, queryParameters.get(AccessTokenQuery.CLIENT_SECRET_PARAMETER_NAME)); assertEquals( OAuth2AccessTokenRequest.GRANT_TYPE, queryParameters.get(AccessTokenQuery.GRANT_TYPE_PARAMETER_NAME)); assertEquals( OAUTH_AUTHORISATION_CODE, queryParameters.get(OAuth2AccessTokenRequest.CODE_PARAMETER_NAME)); } })); mockTokenRequest.asJson(); result = mockHttpResponse; mockHttpResponse.getStatus(); result = 200; mockHttpResponse.getBody().getObject(); result = OAUTH_TOKEN_DETAILS_JSON; } }; final Response<AccessTokenDetails> response = freesoundClient.redeemAuthorisationCodeForAccessToken(OAUTH_AUTHORISATION_CODE); final AccessTokenDetails accessTokenDetails = response.getResults(); assertEquals(OAUTH_ACCESS_TOKEN, accessTokenDetails.getAccessToken()); assertEquals(OAUTH_REFRESH_TOKEN, accessTokenDetails.getRefreshToken()); assertEquals(OAUTH_SCOPE, accessTokenDetails.getScope()); assertEquals(OAUTH_TOKEN_EXPIRES_IN, accessTokenDetails.getExpiresIn()); } /** * Test that requests to renew an OAuth2 bearer token are correctly constructed and passed to the appropriate * endpoint. * * @param mockUnirest Mock version of the {@link Unirest} library * @param mockTokenRequest Mock {@link HttpRequestWithBody} representing a POST call to token endpoint * @param mockHttpResponse Mock {@link HttpResponse} * * @throws Exception Any exceptions thrown in test */ @SuppressWarnings("static-access") @Test public void refreshAccessToken( @Mocked final Unirest mockUnirest, @Mocked final HttpRequestWithBody mockTokenRequest, @Mocked final HttpResponse<JsonNode> mockHttpResponse) throws Exception { new Expectations() { { mockUnirest.post(FreesoundClient.API_ENDPOINT + AccessTokenQuery.OAUTH_TOKEN_ENDPOINT_PATH); result = mockTokenRequest; mockTokenRequest.fields(with(new Delegate<HashMap<String, Object>>() { @SuppressWarnings("unused") void checkRequestParameters(final Map<String, Object> queryParameters) { assertEquals(CLIENT_ID, queryParameters.get(AccessTokenQuery.CLIENT_ID_PARAMETER_NAME)); assertEquals(CLIENT_SECRET, queryParameters.get(AccessTokenQuery.CLIENT_SECRET_PARAMETER_NAME)); assertEquals( RefreshOAuth2AccessTokenRequest.GRANT_TYPE, queryParameters.get(AccessTokenQuery.GRANT_TYPE_PARAMETER_NAME)); assertEquals( OAUTH_REFRESH_TOKEN, queryParameters.get(RefreshOAuth2AccessTokenRequest.CODE_PARAMETER_NAME)); } })); mockTokenRequest.asJson(); result = mockHttpResponse; mockHttpResponse.getStatus(); result = 200; mockHttpResponse.getBody().getObject(); result = OAUTH_TOKEN_DETAILS_JSON; } }; final Response<AccessTokenDetails> response = freesoundClient.refreshAccessToken(OAUTH_REFRESH_TOKEN); final AccessTokenDetails accessTokenDetails = response.getResults(); assertEquals(OAUTH_ACCESS_TOKEN, accessTokenDetails.getAccessToken()); assertEquals(OAUTH_REFRESH_TOKEN, accessTokenDetails.getRefreshToken()); assertEquals(OAUTH_SCOPE, accessTokenDetails.getScope()); assertEquals(OAUTH_TOKEN_EXPIRES_IN, accessTokenDetails.getExpiresIn()); } /** * Test situations where an unexpected error has been returned by the freesound API. * * @param mockUnirest Mock version of the {@link Unirest} library * @param mockGetRequest Mock {@link GetRequest} * @param mockHttpResponse Mock {@link HttpResponse} * @param mockResultsMapper Mock {@link SoundMapper} * * @throws Exception Any exceptions thrown in test */ @SuppressWarnings("static-access") @Test (expected = FreesoundClientException.class) public void unexpected500Response( @Mocked final Unirest mockUnirest, @Mocked final GetRequest mockGetRequest, @Mocked final HttpResponse<JsonNode> mockHttpResponse, @Mocked final SoundMapper mockResultsMapper) throws Exception { new Expectations() { { mockUnirest.get(FreesoundClient.API_ENDPOINT + TEST_PATH); result = mockGetRequest; mockGetRequest.header("Authorization", "Token " + CLIENT_SECRET); mockGetRequest.routeParam(ROUTE_ELEMENT, ROUTE_ELEMENT_VALUE); mockGetRequest.queryString(with(new Delegate<HashMap<String, Object>>() { @SuppressWarnings("unused") void checkRequestParameters(final Map<String, Object> queryParameters) { assertNotNull(queryParameters); assertTrue(queryParameters.size() == 1); assertEquals(QUERY_PARAMETER_VALUE, queryParameters.get(QUERY_PARAMETER)); } })); mockGetRequest.asJson(); result = mockHttpResponse; mockHttpResponse.getBody(); result = "<html><body><h1>500 Error</h1></body></html>"; } }; final JSONResponseQuery<Sound> query = new TestJSONResponseQuery(HTTPRequestMethod.GET, mockResultsMapper); freesoundClient.executeQuery(query); } /** * Simple {@link JSONResponseQuery} subclass for using in tests. */ private class TestJSONResponseQuery extends JSONResponseQuery<Sound> { /** * @param httpRequestMethod The HTTP Request method to use * @param resultsMapper Mapper to use */ protected TestJSONResponseQuery(final HTTPRequestMethod httpRequestMethod, final SoundMapper resultsMapper) { super(httpRequestMethod, TEST_PATH, resultsMapper); } @Override public Map<String, String> getRouteParameters() { final Map<String, String> routeParameters = new HashMap<>(); routeParameters.put(ROUTE_ELEMENT, ROUTE_ELEMENT_VALUE); return routeParameters; } @Override public Map<String, Object> getQueryParameters() { final Map<String, Object> queryParameters = new HashMap<>(); queryParameters.put(QUERY_PARAMETER, QUERY_PARAMETER_VALUE); return queryParameters; } } /** * Simple subclass of {@link BinaryResponseQuery} to use in tests. */ private class TestBinaryResponseQuery extends BinaryResponseQuery { /** * No-arg constructor. */ protected TestBinaryResponseQuery() { super(HTTPRequestMethod.GET, TEST_PATH); } @Override public Map<String, String> getRouteParameters() { final Map<String, String> routeParameters = new HashMap<>(); routeParameters.put(ROUTE_ELEMENT, ROUTE_ELEMENT_VALUE); return routeParameters; } @Override public Map<String, Object> getQueryParameters() { final Map<String, Object> queryParameters = new HashMap<>(); queryParameters.put(QUERY_PARAMETER, QUERY_PARAMETER_VALUE); return queryParameters; } } }