package jp.webpay.android.token.ui;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.view.MotionEvent;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import org.apache.http.HttpRequest;
import org.apache.http.client.methods.HttpPost;
import org.apache.maven.artifact.ant.shaded.IOUtil;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowAlertDialog;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jp.webpay.android.token.ApiSample;
import jp.webpay.android.token.ErrorResponseException;
import jp.webpay.android.token.R;
import jp.webpay.android.token.model.ErrorResponse;
import jp.webpay.android.token.ui.field.CvcField;
import jp.webpay.android.token.ui.field.NumberField;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Robolectric.shadowOf;
@Config(manifest = "./src/main/AndroidManifestTest.xml", emulateSdk = 18)
@RunWith(RobolectricTestRunnerWithDummyResources.class)
public class WebPayTokenFragmentTest {
private FragmentContainerActivity activity;
private CountDownLatch latch;
private WebPayTokenFragment fragment;
private Button openDialogButton;
private CardDialogFragment dialogFragment;
private AlertDialog dialog;
private int currentYear;
@Before
public void setUp() throws Exception {
Robolectric.addPendingHttpResponse(ApiSample.availabilityVMResponse);
activity = Robolectric.buildActivity(FragmentContainerActivity.class).create().visible().start().get();
latch = new CountDownLatch(1);
activity.setLatch(latch);
fragment = (WebPayTokenFragment) activity.getSupportFragmentManager().findFragmentByTag(FragmentContainerActivity.FRAGMENT_TAG);
openDialogButton = (Button) fragment.getView().findViewById(R.id.open_dialog_button);
openDialogButton.performClick();
dialogFragment = (CardDialogFragment)fragment.getChildFragmentManager().findFragmentByTag("card_dialog");
dialog = (AlertDialog)dialogFragment.getDialog();
currentYear = Calendar.getInstance().get(Calendar.YEAR);
}
@Test
public void testFragmentPlacesPayButtonOnActivity() throws Exception {
assertNotNull(fragment);
assertEquals(getString(R.string.token_fragment_open_dialog), openDialogButton.getText());
// adding stub resource from test is difficult, substitute with indifferent string
fragment.setOpenButtonTitle(R.string.field_name_hint);
assertEquals(getString(R.string.field_name_hint), openDialogButton.getText());
}
@Test
public void testFragmentShowsAvailableCardTypesFromApi() throws Exception {
LinearLayout layout = (LinearLayout) dialog.findViewById(R.id.cardTypeIconList);
assertEquals(2, layout.getChildCount());
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 testFragmentSetCardDialogSendButtonTextAfterReopen() throws Exception {
fragment.setCardDialogSendButtonTitle(R.string.field_name_hint);
assertEquals(getString(R.string.card_send), ((Button)dialog.findViewById(R.id.button_submit)).getText());
dialog.findViewById(R.id.button_cancel).performClick();
assertFalse(dialog.isShowing());
openDialogButton.performClick();
Robolectric.runUiThreadTasks();
CardDialogFragment cardDialog = (CardDialogFragment) fragment.getChildFragmentManager().findFragmentByTag("card_dialog");
assertEquals(getString(R.string.field_name_hint),
((Button) cardDialog.getDialog().findViewById(R.id.button_submit)).getText());
}
@Test
public void testFragmentApplyDetectedCardBrandToIconAndCvcHelp() throws Exception {
NumberField numberField = (NumberField) dialog.findViewById(R.id.cardNumberField);
CvcField cvcField = (CvcField) dialog.findViewById(R.id.cardCvcField);
assertNull(numberField.getCompoundDrawables()[2]);
numberField.requestFocus();
numberField.setText("4242"); // starting of Visa
numberField.clearFocus();
BitmapDrawable drawable = (BitmapDrawable)numberField.getCompoundDrawables()[2];
assertNotNull(drawable);
assertTrue(shadowOf(drawable.getBitmap()).toString().
contains("resource:jp.webpay.android.token:drawable/card_visa"));
numberField.requestFocus();
numberField.setText("3782"); // amex
numberField.clearFocus();
assertTrue(shadowOf(((BitmapDrawable)numberField.getCompoundDrawables()[2]).getBitmap()).toString().
contains("resource:jp.webpay.android.token:drawable/card_amex"));
// press CVC buttonp
int [] location = {0,0};
cvcField.getLocationOnScreen(location);
cvcField.onTouchEvent(MotionEvent.obtain(1, 1, MotionEvent.ACTION_UP,
location[0] + cvcField.getWidth() - 10,
location[1] + cvcField.getHeight() / 2,
0));
ShadowAlertDialog helpDialog = shadowOf(ShadowAlertDialog.getLatestAlertDialog());
drawable = (BitmapDrawable)((ImageView)helpDialog.getView().findViewById(R.id.cvc_help)).getDrawable();
assertTrue(shadowOf(drawable.getBitmap()).toString().
contains("resource:jp.webpay.android.token:drawable/cvc_amex"));
}
@Test
public void testFragmentHandleNotSupportedCardTypeAsInvalid() throws Exception {
LinearLayout layout = (LinearLayout) dialog.findViewById(R.id.cardTypeIconList);
assertEquals(2, layout.getChildCount());
NumberField numberField = (NumberField) dialog.findViewById(R.id.cardNumberField);
int errorTextColor = activity.getResources().getColor(R.color.error_text);
numberField.requestFocus();
numberField.setText("4242424242424242"); // Visa is ok
numberField.clearFocus();
assertEquals("4242 4242 4242 4242", numberField.getText().toString());
assertNotEquals(numberField.getCurrentTextColor(), errorTextColor);
numberField.requestFocus();
numberField.setText("378282246310005"); // amex is unacceptable
numberField.clearFocus();
assertEquals("3782 822463 10005", numberField.getText().toString());
assertEquals(numberField.getCurrentTextColor(), errorTextColor);
}
@Test
public void testFragmentNotifiesTokenOnGenerated() throws Exception {
Robolectric.addPendingHttpResponse(ApiSample.tokenResponse);
assertTrue(fragment.isAdded());
assertTrue(dialogFragment.isAdded());
generateTokenFromForm();
assertFalse(dialog.isShowing());
assertEquals(getString(R.string.token_fragment_token_generated), openDialogButton.getText());
assertEquals("tok_3ybc93ckR01qeKx", activity.getLastToken().id);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Test
public void testFragmentSendsGivenCardData() throws Exception {
Robolectric.addPendingHttpResponse(ApiSample.tokenResponse);
Robolectric.application.getResources().getConfiguration().setLocale(new Locale("en"));
generateTokenFromForm();
HttpRequest request = Robolectric.getSentHttpRequest(1);
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("en", request.getFirstHeader("Accept-Language").getValue());
String requestBody = IOUtil.toString(((HttpPost) request).getEntity().getContent(), "UTF-8");
JSONObject value = (JSONObject) new JSONTokener(requestBody).nextValue();
JSONObject card = value.getJSONObject("card");
assertEquals("4242424242424242", card.getString("number"));
assertEquals("012", card.getString("cvc"));
assertEquals("TEST USER", card.getString("name"));
assertEquals(7, card.getInt("exp_month"));
assertEquals(currentYear + 1, card.getInt("exp_year"));
}
@Test
@Config(qualifiers = "ja-normal-port-hdpi")
public void testFragmentUsesUILanguage() throws Exception {
shadowOf(activity.getResources().getConfiguration()).setLocale(new Locale("ja"));
assertEquals("カードで支払う", openDialogButton.getText());
Robolectric.addPendingHttpResponse(ApiSample.tokenResponse);
generateTokenFromForm();
HttpRequest request = Robolectric.getSentHttpRequest(1);
assertEquals("https://api.webpay.jp/v1/tokens", request.getRequestLine().getUri());
assertEquals("ja", request.getFirstHeader("Accept-Language").getValue());
}
@Test
public void testFragmentShowErrorToUser() throws Exception {
Robolectric.addPendingHttpResponse(ApiSample.cardErrorResponse);
generateTokenFromForm();
assertTrue(dialog.isShowing());
ShadowAlertDialog errorDialog = shadowOf(ShadowAlertDialog.getLatestAlertDialog());
assertEquals("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.",
errorDialog.getMessage());
}
@Test
public void testFragmentNotifiesLastErrorOnCancel() throws Exception {
Robolectric.addPendingHttpResponse(ApiSample.cardErrorResponse);
generateTokenFromForm();
dialog.findViewById(R.id.button_cancel).performClick();
assertFalse(dialog.isShowing());
Throwable throwable = activity.getLastThrowable();
assertThat(throwable, instanceOf(ErrorResponseException.class));
ErrorResponse error = ((ErrorResponseException) throwable).getResponse();
assertEquals(error.statusCode, 402);
assertEquals(error.causedBy, "buyer");
assertEquals(error.param, "cvc");
assertEquals(error.type, "card_error");
assertEquals(error.code, "invalid_cvc");
}
private String getString(int id) {
return activity.getString(id);
}
private void generateTokenFromForm() throws InterruptedException {
((EditText) dialog.findViewById(R.id.cardNumberField)).setText("4242424242424242");
((EditText) dialog.findViewById(R.id.cardNameField)).setText("TEST USER");
((EditText) dialog.findViewById(R.id.cardCvcField)).setText("012");
((EditText) dialog.findViewById(R.id.cardExpiryField)).setText("07 / " + (currentYear + 1));
dialog.findViewById(R.id.button_submit).performClick();
latch.await(1, TimeUnit.SECONDS);
}
}