package se.leap.bitmaskclient.testutils; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; import android.os.Parcelable; import android.os.ResultReceiver; import android.support.annotation.NonNull; import android.text.TextUtils; import org.json.JSONException; import org.json.JSONObject; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.io.IOException; import java.io.InputStream; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import okhttp3.OkHttpClient; import se.leap.bitmaskclient.ConfigHelper; import se.leap.bitmaskclient.Constants; import se.leap.bitmaskclient.OkHttpClientGenerator; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider; import se.leap.bitmaskclient.testutils.matchers.BundleMatcher; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; /** * Created by cyberta on 29.01.18. */ public class MockHelper { @NonNull public static Bundle mockBundle() { final Map fakeBooleanBundle = new HashMap<>(); final Map fakeStringBundle = new HashMap<>(); final Map fakeIntBundle = new HashMap<>(); final Map fakeParcelableBundle = new HashMap<>(); Bundle bundle = mock(Bundle.class); //mock String values in Bundle doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); String value = ((String) arguments[1]); fakeStringBundle.put(key, value); return null; } }).when(bundle).putString(anyString(), anyString()); when(bundle.getString(anyString())).thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); return fakeStringBundle.get(key); } }); //mock Boolean values in Bundle doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); Boolean value = ((boolean) arguments[1]); fakeBooleanBundle.put(key, value); return null; } }).when(bundle).putBoolean(anyString(), anyBoolean()); when(bundle.getBoolean(anyString())).thenAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); return fakeBooleanBundle.get(key); } }); //mock Integer values in Bundle doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); Integer value = ((int) arguments[1]); fakeIntBundle.put(key, value); return null; } }).when(bundle).putInt(anyString(), anyInt()); when(bundle.getInt(anyString())).thenAnswer(new Answer() { @Override public Integer answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); return fakeIntBundle.get(key); } }); //mock Parcelable values in Bundle doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); Parcelable value = ((Parcelable) arguments[1]); fakeParcelableBundle.put(key, value); return null; } }).when(bundle).putParcelable(anyString(), any(Parcelable.class)); when(bundle.getParcelable(anyString())).thenAnswer(new Answer() { @Override public Parcelable answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); return fakeParcelableBundle.get(key); } }); //mock get when(bundle.get(anyString())).thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); if (fakeBooleanBundle.containsKey(key)) { return fakeBooleanBundle.get(key); } else if (fakeIntBundle.containsKey(key)) { return fakeIntBundle.get(key); } else if (fakeStringBundle.containsKey(key)) { return fakeStringBundle.get(key); } else { return fakeParcelableBundle.get(key); } } }); //mock getKeySet when(bundle.keySet()).thenAnswer(new Answer>() { @Override public Set answer(InvocationOnMock invocation) throws Throwable { //this whole approach as a drawback: //you should not add the same keys for values of different types HashSet keys = new HashSet(); keys.addAll(fakeBooleanBundle.keySet()); keys.addAll(fakeIntBundle.keySet()); keys.addAll(fakeStringBundle.keySet()); keys.addAll(fakeParcelableBundle.keySet()); return keys; } }); //mock containsKey when(bundle.containsKey(anyString())).thenAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { String key = (String) invocation.getArguments()[0]; return fakeBooleanBundle.containsKey(key) || fakeStringBundle.containsKey(key) || fakeIntBundle.containsKey(key) || fakeParcelableBundle.containsKey(key); } }); return bundle; } public static Intent mockIntent() { Intent intent = mock(Intent.class); final String[] action = new String[1]; final Map fakeExtras = new HashMap<>(); final List categories = new ArrayList<>(); //mock Action in intent doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); action[0] = ((String) arguments[0]); return null; } }).when(intent).setAction(anyString()); when(intent.getAction()).thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return action[0]; } }); //mock Bundle in intent extras doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); Bundle value = ((Bundle) arguments[1]); fakeExtras.put(key, value); return null; } }).when(intent).putExtra(anyString(), any(Bundle.class)); when(intent.getBundleExtra(anyString())).thenAnswer(new Answer() { @Override public Bundle answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); return (Bundle) fakeExtras.get(key); } }); //mock Parcelable in intent extras doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); Parcelable value = ((Parcelable) arguments[1]); fakeExtras.put(key, value); return null; } }).when(intent).putExtra(anyString(), any(Parcelable.class)); when(intent.getParcelableExtra(anyString())).thenAnswer(new Answer() { @Override public Parcelable answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String key = ((String) arguments[0]); return (Parcelable) fakeExtras.get(key); } }); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); categories.add(((String) arguments[0])); return null; } }).when(intent).addCategory(anyString()); when(intent.getCategories()).thenAnswer(new Answer>() { @Override public Set answer(InvocationOnMock invocation) throws Throwable { return new HashSet<>(categories); } }); return intent; } public static void mockTextUtils() { mockStatic(TextUtils.class); when(TextUtils.equals(any(CharSequence.class), any(CharSequence.class))).thenAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { CharSequence a = (CharSequence) invocation.getArguments()[0]; CharSequence b = (CharSequence) invocation.getArguments()[1]; if (a == b) return true; int length; if (a != null && b != null && (length = a.length()) == b.length()) { if (a instanceof String && b instanceof String) { return a.equals(b); } else { for (int i = 0; i < length; i++) { if (a.charAt(i) != b.charAt(i)) return false; } return true; } } return false; } }); when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer(new Answer() { @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { CharSequence param = (CharSequence) invocation.getArguments()[0]; return param == null || param.length() == 0; } }); } public static ResultReceiver mockResultReceiver(final int expectedResultCode, final Bundle expectedBundle) { ResultReceiver resultReceiver = mock(ResultReceiver.class); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] arguments = invocation.getArguments(); int resultCode = (int) arguments[0]; Bundle bundle = (Bundle) arguments[1]; Set keys = expectedBundle.keySet(); Iterator iterator = keys.iterator(); HashMap expectedIntegers = new HashMap<>(); HashMap expectedStrings = new HashMap<>(); HashMap expectedBooleans = new HashMap<>(); HashMap expectedParcelables = new HashMap<>(); while (iterator.hasNext()) { String key = iterator.next(); Object value = expectedBundle.get(key); if (value instanceof Boolean) { expectedBooleans.put(key, (Boolean) value); } else if (value instanceof Integer) { expectedIntegers.put(key, (Integer) value); } else if (value instanceof String) { expectedStrings.put(key, (String) value); } else if (value instanceof Parcelable) { expectedParcelables.put(key, (Parcelable) value); } } assertThat("expected bundle: ", bundle, new BundleMatcher(expectedIntegers, expectedStrings, expectedBooleans, expectedParcelables)); assertEquals("expected resultCode: ", expectedResultCode, resultCode); return null; } }).when(resultReceiver).send(anyInt(), any(Bundle.class)); return resultReceiver; } public static void mockConfigHelper(String mockedFingerprint, final Provider providerFromPrefs) throws CertificateEncodingException, NoSuchAlgorithmException { // FIXME use MockSharedPreferences instead of provider mockStatic(ConfigHelper.class); when(ConfigHelper.getFromPersistedProvider(anyString(), anyString(), any(SharedPreferences.class))).thenAnswer(new Answer() { @Override public String answer(InvocationOnMock invocation) throws Throwable { String key = (String) invocation.getArguments()[0]; switch (key) { case PROVIDER_PRIVATE_KEY: return providerFromPrefs.getPrivateKey(); case PROVIDER_VPN_CERTIFICATE: return providerFromPrefs.getVpnCertificate(); case Provider.KEY: return providerFromPrefs.getDefinition().toString(); case Provider.CA_CERT_FINGERPRINT: return providerFromPrefs.getCaCertFingerprint(); } return null; } }); when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod(); when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod(); } public static void mockFingerprintForCertificate(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException { mockStatic(ConfigHelper.class); when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod(); when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod(); } public static void mockProviderApiConnector(final BackendMockProvider.TestBackendErrorCase errorCase) throws IOException { BackendMockProvider.provideBackendResponsesFor(errorCase); } public static OkHttpClientGenerator mockClientGenerator() { OkHttpClientGenerator mockClientGenerator = mock(OkHttpClientGenerator.class); OkHttpClient mockedOkHttpClient = mock(OkHttpClient.class); when(mockClientGenerator.initCommercialCAHttpClient(any(JSONObject.class))).thenReturn(mockedOkHttpClient); when(mockClientGenerator.initSelfSignedCAHttpClient(anyString(), any(JSONObject.class))).thenReturn(mockedOkHttpClient); return mockClientGenerator; } public static Resources mockResources(InputStream inputStream) throws IOException, JSONException { Resources mockedResources = mock(Resources.class, RETURNS_DEEP_STUBS); JSONObject errorMessages = new JSONObject(TestSetupHelper.getInputAsString(inputStream)); when(mockedResources.getString(R.string.warning_corrupted_provider_details)). thenReturn(errorMessages.getString("warning_corrupted_provider_details")); when(mockedResources.getString(R.string.server_unreachable_message)). thenReturn(errorMessages.getString("server_unreachable_message")); when(mockedResources.getString(R.string.error_security_pinnedcertificate)). thenReturn(errorMessages.getString("error.security.pinnedcertificate")); when(mockedResources.getString(R.string.malformed_url)). thenReturn(errorMessages.getString("malformed_url")); when(mockedResources.getString(R.string.certificate_error)). thenReturn(errorMessages.getString("certificate_error")); when(mockedResources.getString(R.string.error_srp_math_error_user_message)). thenReturn(errorMessages.getString("error_srp_math_error_user_message")); when(mockedResources.getString(R.string.error_bad_user_password_user_message)). thenReturn(errorMessages.getString("error_bad_user_password_user_message")); when(mockedResources.getString(R.string.error_not_valid_password_user_message)). thenReturn(errorMessages.getString("error_not_valid_password_user_message")); when(mockedResources.getString(R.string.error_client_http_user_message)). thenReturn(errorMessages.getString("error_client_http_user_message")); when(mockedResources.getString(R.string.error_io_exception_user_message)). thenReturn(errorMessages.getString("error_io_exception_user_message")); when(mockedResources.getString(R.string.error_json_exception_user_message)). thenReturn(errorMessages.getString("error_json_exception_user_message")); when(mockedResources.getString(R.string.error_no_such_algorithm_exception_user_message)). thenReturn(errorMessages.getString("error_no_such_algorithm_exception_user_message")); when(mockedResources.getString(R.string.warning_corrupted_provider_details)). thenReturn(errorMessages.getString("warning_corrupted_provider_details")); when(mockedResources.getString(R.string.warning_corrupted_provider_cert)). thenReturn(errorMessages.getString("warning_corrupted_provider_cert")); when(mockedResources.getString(R.string.warning_expired_provider_cert)). thenReturn(errorMessages.getString("warning_expired_provider_cert")); return mockedResources; } }