package jp.webpay.android.token; import org.apache.http.HttpEntity; import org.apache.http.HttpRequest; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.InputStreamEntity; import org.apache.http.message.BasicHeader; import org.apache.maven.artifact.ant.shaded.IOUtil; import org.apache.tools.ant.filters.StringInputStream; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.annotation.Config; import org.robolectric.tester.org.apache.http.HttpResponseStub; import org.robolectric.tester.org.apache.http.TestHttpResponse; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import jp.webpay.android.token.model.AccountAvailability; import jp.webpay.android.token.model.CardType; import jp.webpay.android.token.model.ErrorResponse; import jp.webpay.android.token.model.RawCard; import jp.webpay.android.token.model.StoredCard; import jp.webpay.android.token.model.Token; import jp.webpay.android.token.ui.RobolectricTestRunnerWithDummyResources; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @Config(manifest = "./src/main/AndroidManifestTest.xml", emulateSdk = 18) @RunWith(RobolectricTestRunnerWithDummyResources.class) public class WebPayTest { private WebPay webpay; @Before public void prepareWebPay() { webpay = new WebPay("test_public_dummykey"); } @Rule public final ExpectedException thrown = ExpectedException.none(); @Test public void createTokenReturnsTokenObject() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.tokenResponse); Token token = createToken(ApiSample.testCard); assertEquals("tok_3ybc93ckR01qeKx", token.id); assertEquals("token", token.object); assertEquals(false, token.livemode); assertEquals(1396007350L, (long)token.created); assertEquals(false, token.used); StoredCard card = token.card; assertEquals("card", card.object); assertEquals(2020, (int)card.expYear); assertEquals(8, (int)card.expMonth); assertEquals("0000000000000000000000000000000000000000", card.fingerprint); assertEquals("TEST USER", card.name); assertEquals("JP", card.country); assertEquals(CardType.VISA, card.type); assertEquals("pass", card.cvcCheck); assertEquals("0123", card.last4); } @Test public void createTokenSendCorrectRequest() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.tokenResponse); createToken(ApiSample.testCard); HttpRequest request = Robolectric.getSentHttpRequest(0); assertEquals("POST", request.getRequestLine().getMethod()); assertEquals("https://api.webpay.jp/v1/tokens", request.getRequestLine().getUri()); assertEquals("application/json", request.getFirstHeader("Content-Type").getValue()); assertEquals("Bearer test_public_dummykey", request.getFirstHeader("Authorization").getValue()); assertEquals("WebPayTokenAndroid/" + BuildConfig.VERSION_NAME + " Android/unknown", request.getFirstHeader("User-Agent").getValue()); String requestBody = IOUtil.toString(((HttpPost) request).getEntity().getContent(), "UTF-8"); JSONObject value = (JSONObject) new JSONTokener(requestBody).nextValue(); JSONObject card = value.getJSONObject("card"); assertEquals("4242-4242-4242-0123", card.getString("number")); assertEquals("012", card.getString("cvc")); assertEquals("TEST USER", card.getString("name")); assertEquals(8, card.getInt("exp_month")); assertEquals(2020, card.getInt("exp_year")); } @Test public void createTokenSendsRequestInSpecifiedLanguage() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.tokenResponse); createToken(ApiSample.testCard); HttpRequest request = Robolectric.getSentHttpRequest(0); assertEquals("en", request.getFirstHeader("Accept-Language").getValue()); Robolectric.addPendingHttpResponse(ApiSample.tokenResponse); this.webpay.setLanguage("ja"); createToken(ApiSample.testCard); request = Robolectric.getSentHttpRequest(1); assertEquals("ja", request.getFirstHeader("Accept-Language").getValue()); } @Test public void createTokenReturnsCardErrorResponse() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.cardErrorResponse); Throwable throwable = createTokenThenError(ApiSample.testCard); assertThat(throwable, instanceOf(ErrorResponseException.class)); ErrorResponse error = ((ErrorResponseException) throwable).getResponse(); assertEquals(error.statusCode, 402); assertEquals(error.message, "The security code provided is invalid. For Visa, MasterCard, JCB, and Diners Club, enter the last 3 digits on the back of your card. For American Express, enter the 4 digits printed above your number."); assertEquals(error.causedBy, "buyer"); assertEquals(error.param, "cvc"); assertEquals(error.type, "card_error"); assertEquals(error.code, "invalid_cvc"); } @Test public void createTokenReturnsServerErrorResponse() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.serverErrorResponse); Throwable throwable = createTokenThenError(ApiSample.testCard); assertThat(throwable, instanceOf(ErrorResponseException.class)); ErrorResponse error = ((ErrorResponseException) throwable).getResponse(); assertEquals(error.statusCode, 500); assertEquals(error.message, "API server is currently unavailable"); assertEquals(error.causedBy, "service"); assertEquals(error.param, null); assertEquals(error.type, "api_error"); assertEquals(error.code, null); } @Test public void createTokenReturnsJSONException() throws Exception { TestHttpResponse response = new TestHttpResponse(201, "{:}", new BasicHeader("Content-Type", "application/json")); Robolectric.addPendingHttpResponse(response); Throwable throwable = createTokenThenError(ApiSample.testCard); assertThat(throwable, instanceOf(JSONException.class)); assertEquals(throwable.getMessage(), "Expected literal value at character 1 of {:}"); } @Test public void createTokenReturnsConnectionError() throws Exception { HttpResponseStub response = new TestHttpResponse(200, "") { @Override public HttpEntity getEntity() { return new InputStreamEntity(new StringInputStream("foo"), 3) { @Override public InputStream getContent() throws IOException { throw new IOException("Test exception"); } }; } }; Robolectric.addPendingHttpResponse(response); Throwable throwable = createTokenThenError(ApiSample.testCard); assertThat(throwable, instanceOf(IOException.class)); assertEquals(throwable.getMessage(), "Test exception"); } @Test public void createTokenRaiseErrorForNullCard() throws Exception { thrown.expect(IllegalArgumentException.class); createToken(null); } @Test public void createTokenRaiseErrorForNullListener() throws Exception { thrown.expect(IllegalArgumentException.class); webpay.createToken(ApiSample.testCard, null); } protected Token createToken(RawCard card) throws Exception { final CountDownLatch latch = new CountDownLatch(1); final Token[] result = new Token[1]; WebPayListener<Token> listener = new WebPayListener<Token>() { @Override public void onCreate(Token token) { result[0] = token; latch.countDown(); } @Override public void onException(Throwable cause) { fail("Error is not acceptable " + cause.getMessage()); } }; webpay.createToken(card, listener); latch.await(1, TimeUnit.SECONDS); return result[0]; } protected Throwable createTokenThenError(RawCard card) throws Exception { final CountDownLatch latch = new CountDownLatch(1); final Throwable[] result = new Throwable[1]; WebPayListener<Token> listener = new WebPayListener<Token>() { @Override public void onCreate(Token token) { fail("Token response is not acceptable"); } @Override public void onException(Throwable cause) { result[0] = cause; latch.countDown(); } }; webpay.createToken(card, listener); latch.await(1, TimeUnit.SECONDS); return result[0]; } @Test public void retrieveAvailabilityReturnsAccountAvailability() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.availabilityResponse); AccountAvailability availability = retrieveAvailabilityThenSuccess(); assertThat(availability.currenciesSupported, contains("jpy")); assertThat(availability.cardTypesSupported, contains(CardType.VISA, CardType.MASTERCARD, CardType.JCB, CardType.AMERICAN_EXPRESS, CardType.DINERS_CLUB)); } @Test public void retrieveAvailabilityRequestsCorrectPath() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.availabilityResponse); retrieveAvailabilityThenSuccess(); HttpRequest request = Robolectric.getSentHttpRequest(0); assertEquals("GET", request.getRequestLine().getMethod()); assertEquals("https://api.webpay.jp/v1/account/availability", request.getRequestLine().getUri()); assertEquals("Bearer test_public_dummykey", request.getFirstHeader("Authorization").getValue()); } @Test public void retrieveAvailabilityHandlesServerError() throws Exception { Robolectric.addPendingHttpResponse(ApiSample.serverErrorResponse); Throwable throwable = retrieveAvailabilityThenError(); assertThat(throwable, instanceOf(ErrorResponseException.class)); ErrorResponse error = ((ErrorResponseException) throwable).getResponse(); assertEquals(error.statusCode, 500); assertEquals(error.message, "API server is currently unavailable"); assertEquals(error.causedBy, "service"); assertEquals(error.param, null); assertEquals(error.type, "api_error"); assertEquals(error.code, null); } @Test public void retrieveAvailabilityRaiseErrorForNullListener() throws Exception { thrown.expect(IllegalArgumentException.class); webpay.retrieveAvailability(null); } protected AccountAvailability retrieveAvailabilityThenSuccess() throws Exception { final CountDownLatch latch = new CountDownLatch(1); final AccountAvailability[] result = new AccountAvailability[1]; WebPayListener<AccountAvailability> listener = new WebPayListener<AccountAvailability>() { @Override public void onCreate(AccountAvailability availability) { result[0] = availability; latch.countDown(); } @Override public void onException(Throwable cause) { fail("Error is not acceptable " + cause.getMessage()); } }; webpay.retrieveAvailability(listener); latch.await(1, TimeUnit.SECONDS); return result[0]; } protected Throwable retrieveAvailabilityThenError() throws Exception { final CountDownLatch latch = new CountDownLatch(1); final Throwable[] result = new Throwable[1]; WebPayListener<AccountAvailability> listener = new WebPayListener<AccountAvailability>() { @Override public void onCreate(AccountAvailability availability) { fail("AccountAvailability response is not acceptable"); } @Override public void onException(Throwable cause) { result[0] = cause; latch.countDown(); } }; webpay.retrieveAvailability(listener); latch.await(1, TimeUnit.SECONDS); return result[0]; } }