From 0b647ea5e7ff67747080b2ffcebc948da0fbecb5 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 9 Jan 2018 20:55:10 +0100 Subject: 8773 refactoring ProviderAPI for testability, setting up basic unit test framework --- .../test/java/se/leap/bitmaskclient/TestUtils.java | 26 -- .../bitmaskclient/eip/GatewaysManagerTest.java | 10 +- .../bitmaskclient/eip/ProviderApiManagerTest.java | 249 ++++++++++++ .../bitmaskclient/eip/VpnConfigGeneratorTest.java | 10 +- .../testutils/MockSharedPreferences.java | 165 ++++++++ .../bitmaskclient/testutils/TestSetupHelper.java | 443 +++++++++++++++++++++ .../testutils/answers/BackendAnswerFabric.java | 82 ++++ .../testutils/answers/NoErrorAnswer.java | 58 +++ .../testutils/matchers/BundleMatcher.java | 192 +++++++++ 9 files changed, 1199 insertions(+), 36 deletions(-) delete mode 100644 app/src/test/java/se/leap/bitmaskclient/TestUtils.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java (limited to 'app/src/test/java') diff --git a/app/src/test/java/se/leap/bitmaskclient/TestUtils.java b/app/src/test/java/se/leap/bitmaskclient/TestUtils.java deleted file mode 100644 index 96b11df6..00000000 --- a/app/src/test/java/se/leap/bitmaskclient/TestUtils.java +++ /dev/null @@ -1,26 +0,0 @@ -package se.leap.bitmaskclient; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -/** - * Created by cyberta on 08.10.17. - */ - -public class TestUtils { - - public static String getInputAsString(InputStream fileAsInputStream) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(fileAsInputStream)); - StringBuilder sb = new StringBuilder(); - String line = br.readLine(); - while (line != null) { - sb.append(line); - line = br.readLine(); - } - - return sb.toString(); - } - -} diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java index ea212480..4726cab7 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/GatewaysManagerTest.java @@ -16,7 +16,7 @@ import java.io.IOException; import se.leap.bitmaskclient.Constants; import se.leap.bitmaskclient.Provider; -import se.leap.bitmaskclient.TestUtils; +import se.leap.bitmaskclient.testutils.TestSetupHelper; import static junit.framework.Assert.assertEquals; import static org.mockito.ArgumentMatchers.anyInt; @@ -61,17 +61,17 @@ public class GatewaysManagerTest { @Test public void testFromEipServiceJson_ignoreDuplicateGateways() throws Exception { - String eipServiceJson = TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-two-gateways.json")); + String eipServiceJson = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-two-gateways.json")); gatewaysManager.fromEipServiceJson(new JSONObject(eipServiceJson)); assertEquals(2, gatewaysManager.size()); - eipServiceJson = TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-one-gateway.json")); + eipServiceJson = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-one-gateway.json")); gatewaysManager.fromEipServiceJson(new JSONObject(eipServiceJson)); assertEquals(2, gatewaysManager.size()); } @Test public void testClearGatewaysAndProfiles_resetGateways() throws Exception { - String eipServiceJson = TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-two-gateways.json")); + String eipServiceJson = TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("eip-service-two-gateways.json")); gatewaysManager.fromEipServiceJson(new JSONObject(eipServiceJson)); assertEquals(2, gatewaysManager.size()); gatewaysManager.clearGatewaysAndProfiles(); @@ -79,7 +79,7 @@ public class GatewaysManagerTest { } private String getJsonStringFor(String filename) throws IOException { - return TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream(filename)); + return TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream(filename)); } } \ No newline at end of file diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java new file mode 100644 index 00000000..c39681c4 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -0,0 +1,249 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package se.leap.bitmaskclient.eip; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; +import android.text.TextUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; + +import se.leap.bitmaskclient.ConfigHelper; +import se.leap.bitmaskclient.Provider; +import se.leap.bitmaskclient.ProviderAPI; +import se.leap.bitmaskclient.ProviderApiConnector; +import se.leap.bitmaskclient.ProviderApiManager; +import se.leap.bitmaskclient.ProviderApiManagerBase; +import se.leap.bitmaskclient.testutils.MockSharedPreferences; + +import static se.leap.bitmaskclient.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_NOK; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_OK; +import static se.leap.bitmaskclient.ProviderAPI.RESULT_KEY; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockBundle; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockClientGenerator; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockFingerprintForCertificate; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockIntent; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockProviderApiConnector; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockResources; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockResultReceiver; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockTextUtils; +import static se.leap.bitmaskclient.testutils.answers.BackendAnswerFabric.TestBackendErrorCase.NO_ERROR; + +/** + * Created by cyberta on 04.01.18. + */ + +@RunWith(PowerMockRunner.class) +@PrepareForTest({ProviderApiManager.class, TextUtils.class, ConfigHelper.class, ProviderApiConnector.class}) +public class ProviderApiManagerTest { + + private SharedPreferences mockPreferences; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Resources mockResources; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mockContext; + + private ProviderApiManager providerApiManager; + + class TestProviderApiServiceCallback implements ProviderApiManagerBase.ProviderApiServiceCallback { + + //Intent expectedIntent; + TestProviderApiServiceCallback(/*Intent expectedIntent*/) { + //this.expectedIntent = expectedIntent; + } + + @Override + public void broadcastProgress(Intent intent) { + //assertEquals("expected intent: ", expectedIntent, intent); + } + } + + @Before + public void setUp() throws Exception { + + Bundle bundle = mockBundle(); + PowerMockito.whenNew(Bundle.class).withAnyArguments().thenReturn(bundle); + Intent intent = mockIntent(); + PowerMockito.whenNew(Intent.class).withAnyArguments().thenReturn(intent); + mockTextUtils(); + mockPreferences = new MockSharedPreferences(); + mockResources = mockResources(getClass().getClassLoader().getResourceAsStream("error_messages.json")); + } + + + @Test + public void test_handleIntentSetupProvider_noProviderMainURL() { + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errors\":\"It doesn't seem to be a Bitmask provider.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, ""); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_happyPath_preseededProviderAndCA() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockProviderApiConnector(NO_ERROR); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, true); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + parameters.putString(Provider.CA_CERT, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))); + parameters.putString(Provider.KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_OK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_happyPath_no_preseededProviderAndCA() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockProviderApiConnector(NO_ERROR); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, true); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_OK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_happyPath_storedProviderAndCAFromPreviousSetup() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockProviderApiConnector(NO_ERROR); + mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply(); + mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))).apply(); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, true); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_OK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_preseededProviderAndCA_failedCAPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); + mockProviderApiConnector(NO_ERROR); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + parameters.putString(Provider.CA_CERT, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))); + parameters.putString(Provider.KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_no_preseededProviderAndCA_failedPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); + mockProviderApiConnector(NO_ERROR); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_failedPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); + mockProviderApiConnector(NO_ERROR); + mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply(); + mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))).apply(); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java index 7e60edb9..8c8cdb61 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java @@ -4,7 +4,7 @@ import org.json.JSONObject; import org.junit.Before; import org.junit.Test; -import se.leap.bitmaskclient.TestUtils; +import se.leap.bitmaskclient.testutils.TestSetupHelper; import static junit.framework.Assert.assertTrue; @@ -217,13 +217,13 @@ public class VpnConfigGeneratorTest { @Before public void setUp() throws Exception { - generalConfig = new JSONObject(TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("general_configuration.json"))); - secrets = new JSONObject(TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("secrets.json"))); + generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("general_configuration.json"))); + secrets = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("secrets.json"))); } @Test public void testGenerate_tcp_udp() throws Exception { - gateway = new JSONObject(TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_tcp_udp.json"))); + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_tcp_udp.json"))); vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway); String vpnConfig = vpnConfigGenerator.generate(); @@ -232,7 +232,7 @@ public class VpnConfigGeneratorTest { @Test public void testGenerate_udp_tcp() throws Exception { - gateway = new JSONObject(TestUtils.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_udp_tcp.json"))); + gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("gateway_udp_tcp.json"))); vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway); String vpnConfig = vpnConfigGenerator.generate(); diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java new file mode 100644 index 00000000..af35af31 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockSharedPreferences.java @@ -0,0 +1,165 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package se.leap.bitmaskclient.testutils; + +import android.content.SharedPreferences; +import android.support.annotation.Nullable; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Created by cyberta on 09.01.18. + */ + +public class MockSharedPreferences implements SharedPreferences { + HashMap mockedStringPrefs = new HashMap<>(); + HashMap mockedIntPrefs = new HashMap<>(); + HashMap mockedBooleanPrefs = new HashMap<>(); + + @Override + public Map getAll() { + return null; + } + + @Nullable + @Override + public String getString(String key, @Nullable String defValue) { + String value = mockedStringPrefs.get(key); + return value != null ? value : defValue; + } + + @Nullable + @Override + public Set getStringSet(String key, @Nullable Set defValues) { + return null; + } + + @Override + public int getInt(String key, int defValue) { + Integer value = mockedIntPrefs.get(key); + return value != null ? value : defValue; + } + + @Override + public long getLong(String key, long defValue) { + return 0; + } + + @Override + public float getFloat(String key, float defValue) { + return 0; + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + Boolean value = mockedBooleanPrefs.get(key); + return value != null ? value : defValue; + } + + @Override + public boolean contains(String key) { + return mockedStringPrefs.containsKey(key) || + mockedBooleanPrefs.containsKey(key) || + mockedIntPrefs.containsKey(key); + } + + @Override + public Editor edit() { + return new Editor() { + private HashMap tempStrings = new HashMap<>(mockedStringPrefs); + private HashMap tempIntegers = new HashMap<>(mockedIntPrefs); + private HashMap tempBoolean = new HashMap<>(mockedBooleanPrefs); + + @Override + public Editor putString(String key, @Nullable String value) { + tempStrings.put(key, value); + return this; + } + + @Override + public Editor putStringSet(String key, @Nullable Set values) { + return null; + } + + @Override + public Editor putInt(String key, int value) { + tempIntegers.put(key, value); + return this; + } + + @Override + public Editor putLong(String key, long value) { + return null; + } + + @Override + public Editor putFloat(String key, float value) { + return null; + } + + @Override + public Editor putBoolean(String key, boolean value) { + tempBoolean.put(key, value); + return this; + } + + @Override + public Editor remove(String key) { + tempBoolean.remove(key); + tempStrings.remove(key); + tempIntegers.remove(key); + return this; + } + + @Override + public Editor clear() { + tempBoolean.clear(); + tempStrings.clear(); + tempIntegers.clear(); + return this; + } + + @Override + public boolean commit() { + mockedStringPrefs = new HashMap<>(tempStrings); + mockedBooleanPrefs = new HashMap<>(tempBoolean); + mockedIntPrefs = new HashMap<>(tempIntegers); + return true; + } + + @Override + public void apply() { + mockedStringPrefs = new HashMap<>(tempStrings); + mockedBooleanPrefs = new HashMap<>(tempBoolean); + mockedIntPrefs = new HashMap<>(tempIntegers); + } + }; + } + + @Override + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + + } + + @Override + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + + } +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java new file mode 100644 index 00000000..19d7e13f --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java @@ -0,0 +1,443 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package se.leap.bitmaskclient.testutils; + +import android.content.Intent; +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 android.util.Pair; + +import org.json.JSONException; +import org.json.JSONObject; +import org.mockito.ArgumentMatchers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +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.OkHttpClientGenerator; +import se.leap.bitmaskclient.ProviderApiConnector; +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.testutils.answers.BackendAnswerFabric; +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.ArgumentMatchers.nullable; +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.testutils.answers.BackendAnswerFabric.TestBackendErrorCase.ERROR_NO_CONNECTION; +import static se.leap.bitmaskclient.testutils.answers.BackendAnswerFabric.getAnswerForErrorcase; + +/** + * Created by cyberta on 08.10.17. + */ + +public class TestSetupHelper { + + + + public static String getInputAsString(InputStream fileAsInputStream) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(fileAsInputStream)); + StringBuilder sb = new StringBuilder(); + String line = br.readLine(); + while (line != null) { + sb.append(line); + line = br.readLine(); + } + + return sb.toString(); + } + + @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 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.base64toHex(anyString())).thenCallRealMethod(); + when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod(); + } + + public static void mockProviderApiConnector(final BackendAnswerFabric.TestBackendErrorCase errorCase) throws IOException { + mockStatic(ProviderApiConnector.class); + when(ProviderApiConnector.canConnect(any(OkHttpClient.class), anyString())).thenReturn(errorCase != ERROR_NO_CONNECTION); + when(ProviderApiConnector.requestStringFromServer(anyString(), anyString(), nullable(String.class), ArgumentMatchers.>anyList(), any(OkHttpClient.class))).thenAnswer(getAnswerForErrorcase(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(any(JSONObject.class))).thenReturn(mockedOkHttpClient); + when(mockClientGenerator.initSelfSignedCAHttpClient(any(JSONObject.class), anyString())).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(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; + } + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java b/app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java new file mode 100644 index 00000000..00e276f4 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package se.leap.bitmaskclient.testutils.answers; + +import org.mockito.stubbing.Answer; + +/** + * Created by cyberta on 09.01.18. + */ + +public class BackendAnswerFabric { + /** + * This enum can be useful to provide different responses from a mocked ProviderApiConnector + * in order to test different error scenarios + */ + public enum TestBackendErrorCase { + NO_ERROR, + ERROR_NO_RESPONSE_BODY, // => NullPointerException + ERROR_DNS_RESOLUTION_ERROR, // => UnkownHostException + ERROR_SOCKET_TIMEOUT, // => SocketTimeoutException + ERROR_WRONG_PROTOCOL, // => MalformedURLException + ERROR_CERTIFICATE_INVALID, // => SSLHandshakeException + ERROR_WRONG_PORT, // => ConnectException + ERROR_PAYLOAD_MISSING, // => IllegalArgumentException + ERROR_TLS_1_2_NOT_SUPPORTED, // => UnknownServiceException + ERROR_UNKNOWN_IO_EXCEPTION, // => IOException + ERROR_NO_ACCESS, + ERROR_INVALID_SESSION_TOKEN, + ERROR_NO_CONNECTION, + ERROR_WRONG_SRP_CREDENTIALS + } + + public static Answer getAnswerForErrorcase(TestBackendErrorCase errorCase) { + switch (errorCase) { + case NO_ERROR: + return new NoErrorAnswer(); + case ERROR_NO_RESPONSE_BODY: + break; + case ERROR_DNS_RESOLUTION_ERROR: + break; + case ERROR_SOCKET_TIMEOUT: + break; + case ERROR_WRONG_PROTOCOL: + break; + case ERROR_CERTIFICATE_INVALID: + break; + case ERROR_WRONG_PORT: + break; + case ERROR_PAYLOAD_MISSING: + break; + case ERROR_TLS_1_2_NOT_SUPPORTED: + break; + case ERROR_UNKNOWN_IO_EXCEPTION: + break; + case ERROR_NO_ACCESS: + break; + case ERROR_INVALID_SESSION_TOKEN: + break; + case ERROR_NO_CONNECTION: + break; + case ERROR_WRONG_SRP_CREDENTIALS: + break; + } + return null; + } + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java b/app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java new file mode 100644 index 00000000..cbf9f6b8 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package se.leap.bitmaskclient.testutils.answers; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + +/** + * Created by cyberta on 09.01.18. + */ + +public class NoErrorAnswer implements Answer { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String url = (String) invocation.getArguments()[0]; + String requestMethod = (String) invocation.getArguments()[1]; + String jsonPayload = (String) invocation.getArguments()[2]; + + if (url.contains("/provider.json")) { + //download provider json + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json")); + } else if (url.contains("/ca.crt")) { + //download provider ca cert + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem")); + } else if (url.contains("config/eip-service.json")) { + // download provider service json containing gateways, locations and openvpn settings + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.service.json")); + } else if (url.contains("/users.json")) { + //create new user + //TODO: implement me + } else if (url.contains("/sessions.json")) { + //srp auth: sendAToSRPServer + //TODO: implement me + } else if (url.contains("/sessions/parmegvtest10.json")){ + //srp auth: sendM1ToSRPServer + //TODO: implement me + } + + return null; + } +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java b/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java new file mode 100644 index 00000000..a7867e08 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java @@ -0,0 +1,192 @@ +package se.leap.bitmaskclient.testutils.matchers; + +import android.os.Bundle; +import android.os.Parcelable; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; + +/** + * Created by cyberta on 09.01.18. + */ + +public class BundleMatcher extends BaseMatcher { + + private final HashMap expectedIntegers; + private final HashMap expectedStrings; + private final HashMap expectedBooleans; + private final HashMap expectedParcelables; + private HashMap unfoundExpectedInteger = new HashMap<>(); + private HashMap unfoundExpectedBoolean = new HashMap<>(); + private HashMap unfoundExpectedString = new HashMap<>(); + private HashMap unfoundExpectedParcelable = new HashMap<>(); + private HashMap unexpectedAdditionalObjects = new HashMap<>(); + + public BundleMatcher(HashMap expectedIntegers, HashMap expectedStrings, HashMap expectedBooleans, HashMap expectedParcelables) { + this.expectedBooleans = expectedBooleans; + this.expectedIntegers = expectedIntegers; + this.expectedStrings = expectedStrings; + this.expectedParcelables = expectedParcelables; + } + + @Override + public boolean matches(Object item) { + if (item instanceof Bundle) { + Bundle actualBundle = (Bundle) item; + return checkActualBundleHasAllExpectedBooleanValues(actualBundle) && + checkActualBundleHasAllExpectedStringValues(actualBundle) && + checkActualBundleHasAllExpectedIntValues(actualBundle) && + checkActualBundleHasAllExpectedParcelableValues(actualBundle) && + checkUnexpectedAdditionalValuesIn(actualBundle); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("Bundle didn't match expectation!"); + + if (!unfoundExpectedInteger.isEmpty()) { + Iterator iterator = unfoundExpectedInteger.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + if (unfoundExpectedInteger.get(key) == null) { + description.appendText("\n unfound Integer in actual Bundle: ").appendValue(iterator.next()); + } else { + description.appendText("\n expected Integer for key " + key).appendValue(expectedIntegers.get(key)). + appendText("\n found Integer was: ").appendValue(unfoundExpectedInteger.get(key)); + } + } + } + if (!unfoundExpectedBoolean.isEmpty()) { + Iterator iterator = unfoundExpectedBoolean.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + if (unfoundExpectedBoolean.get(key) == null) { + description.appendText("\n unfound Boolean in actual Bundle: ").appendValue(iterator.next()); + } else { + description.appendText("\n expected Boolean for key " + key).appendValue(expectedBooleans.get(key)). + appendText("\n found Boolean was: ").appendValue(unfoundExpectedBoolean.get(key)); + } + } + } + if (!unfoundExpectedString.isEmpty()) { + Iterator iterator = unfoundExpectedString.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + if (unfoundExpectedString.get(key) == null) { + description.appendText("\n unfound String in actual Bundle: ").appendValue(iterator.next()); + } else { + description.appendText("\n expected String for key " + key).appendValue(expectedStrings.get(key)). + appendText("\n found String was: ").appendValue(unfoundExpectedString.get(key)); + } + } + } + if (!unfoundExpectedParcelable.isEmpty()) { + Iterator iterator = unfoundExpectedInteger.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + if (unfoundExpectedParcelable.get(key) == null) { + description.appendText("\n unfound Parcelable in actual Bundle: ").appendValue(iterator.next()); + } else { + description.appendText("\n expected Parcelable or key " + key).appendValue(expectedParcelables.get(key)). + appendText("\n found Parcelable was: ").appendValue(unfoundExpectedParcelable.get(key)); + } + } + } + + if (!unexpectedAdditionalObjects.isEmpty()) { + Iterator iterator = unexpectedAdditionalObjects.keySet().iterator(); + while (iterator.hasNext()) { + String key = iterator.next(); + Object value = unexpectedAdditionalObjects.get(key); + if (value instanceof String) { + description.appendText("\n unexpected String found in actual Bundle: ").appendValue(key).appendText(", ").appendValue(value); + } else if (value instanceof Boolean) { + description.appendText("\n unexpected Boolean found in actual Bundle: ").appendValue(key).appendText(", ").appendValue(value); + } else if (value instanceof Integer) { + description.appendText("\n unexpected Integer found in actual Bundle: ").appendValue(key).appendText(", ").appendValue(value); + } else if (value instanceof Parcelable) { + description.appendText("\n unexpected Parcelable found in actual Bundle: ").appendValue(key).appendText(", ").appendValue(value); + } else { + description.appendText("\n unexpected Object found in actual Bundle: ").appendValue(key).appendText(", ").appendValue(value); + } + } + } + } + + private boolean checkActualBundleHasAllExpectedBooleanValues(Bundle actualBundle) { + Set booleanKeys = expectedBooleans.keySet(); + for (String key : booleanKeys) { + Object valueObject = actualBundle.get(key); + if (valueObject == null || + !(valueObject instanceof Boolean) || + valueObject != expectedBooleans.get(key)) { + unfoundExpectedBoolean.put(key, (Boolean) valueObject); + return false; + } + } + return true; + } + + private boolean checkActualBundleHasAllExpectedStringValues(Bundle actualBundle) { + Set stringKeys = expectedStrings.keySet(); + for (String key : stringKeys) { + Object valueObject = actualBundle.get(key); + if (valueObject == null || + !(valueObject instanceof String) || + !valueObject.equals(expectedStrings.get(key))) { + unfoundExpectedString.put(key, (String) valueObject); + return false; + } + } + return true; + } + + private boolean checkActualBundleHasAllExpectedIntValues(Bundle actualBundle) { + Set stringKeys = expectedIntegers.keySet(); + for (String key : stringKeys) { + Object valueObject = actualBundle.get(key); + if (valueObject == null || + !(valueObject instanceof Integer) || + ((Integer) valueObject).compareTo(expectedIntegers.get(key)) != 0) { + unfoundExpectedInteger.put(key, (Integer) valueObject); + return false; + } + } + return true; + } + + private boolean checkActualBundleHasAllExpectedParcelableValues(Bundle actualBundle) { + Set stringKeys = expectedParcelables.keySet(); + for (String key : stringKeys) { + Object valueObject = actualBundle.get(key); + if (valueObject == null || + !(valueObject instanceof Parcelable) || + !valueObject.equals(expectedParcelables.get(key))) { + unfoundExpectedParcelable.put(key, (Parcelable) valueObject); + return false; + } + } + return true; + } + + private boolean checkUnexpectedAdditionalValuesIn(Bundle actualBundle) { + Set keys = actualBundle.keySet(); + + for (String key : keys) { + if (!expectedStrings.containsKey(key) && + !expectedIntegers.containsKey(key) && + !expectedBooleans.containsKey(key) && + !expectedParcelables.containsKey(key) + ) { + unexpectedAdditionalObjects.put(key, actualBundle.getString(key)); + } + } + return unexpectedAdditionalObjects.isEmpty(); + } +} -- cgit v1.2.3 From ae8341cf1f563fbcf2bdd6a4eff9525f42e9e995 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 10 Jan 2018 17:14:45 +0100 Subject: 8773 more test cases and clean-up --- .../bitmaskclient/eip/ProviderApiManagerTest.java | 96 ++++++++++++++++++++- .../BackendMockResponses/BackendMockProvider.java | 86 +++++++++++++++++++ .../BackendMockResponses/BaseBackendResponse.java | 75 +++++++++++++++++ .../NoErrorBackendResponse.java | 89 ++++++++++++++++++++ .../UpdatedCertificateBackendResponse.java | 97 ++++++++++++++++++++++ .../bitmaskclient/testutils/TestSetupHelper.java | 15 +--- .../testutils/answers/BackendAnswerFabric.java | 82 ------------------ .../testutils/answers/NoErrorAnswer.java | 58 ------------- .../testutils/matchers/BundleMatcher.java | 16 ++++ 9 files changed, 458 insertions(+), 156 deletions(-) create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BaseBackendResponse.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponse.java create mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/UpdatedCertificateBackendResponse.java delete mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java delete mode 100644 app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java (limited to 'app/src/test/java') diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java index c39681c4..9ca90b17 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -49,6 +49,8 @@ import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.ProviderAPI.RESULT_KEY; +import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.ERROR_CASE_UPDATED_CERTIFICATE; +import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider.TestBackendErrorCase.NO_ERROR; import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockBundle; import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockClientGenerator; @@ -58,7 +60,7 @@ import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockProviderApiCon import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockResources; import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockResultReceiver; import static se.leap.bitmaskclient.testutils.TestSetupHelper.mockTextUtils; -import static se.leap.bitmaskclient.testutils.answers.BackendAnswerFabric.TestBackendErrorCase.NO_ERROR; + /** * Created by cyberta on 04.01.18. @@ -188,7 +190,7 @@ public class ProviderApiManagerTest { providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); expectedResult.putBoolean(RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); Intent provider_API_command = mockIntent(); Bundle parameters = mockBundle(); @@ -210,7 +212,7 @@ public class ProviderApiManagerTest { providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); expectedResult.putBoolean(RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); Intent provider_API_command = mockIntent(); Bundle parameters = mockBundle(); @@ -232,11 +234,33 @@ public class ProviderApiManagerTest { providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); expectedResult.putBoolean(RESULT_KEY, false); - expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is corrupted. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_CERTIFICATE_PINNING\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + + @Test + public void test_handleIntentSetupProvider_preseededProviderAndCA_outdatedCertificate() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockProviderApiConnector(NO_ERROR); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); Intent provider_API_command = mockIntent(); Bundle parameters = mockBundle(); parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + parameters.putString(Provider.CA_CERT, getInputAsString(getClass().getClassLoader().getResourceAsStream("outdated_cert.pem"))); + parameters.putString(Provider.KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))); provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); @@ -245,5 +269,69 @@ public class ProviderApiManagerTest { providerApiManager.handleIntent(provider_API_command); } + @Test + public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_outdatedCertificate() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockProviderApiConnector(NO_ERROR); + mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply(); + mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("outdated_cert.pem"))).apply(); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is expired. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_preseededProviderAndCA_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE); + + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + parameters.putString(Provider.CA_CERT, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))); + parameters.putString(Provider.KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } + + @Test + public void test_handleIntentSetupProvider_storedProviderAndCAFromPreviousSetup_ValidCertificateButUpdatedCertificateOnServerSide() throws IOException, CertificateEncodingException, NoSuchAlgorithmException { + mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockProviderApiConnector(ERROR_CASE_UPDATED_CERTIFICATE); + mockPreferences.edit().putString(Provider.KEY + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))).apply(); + mockPreferences.edit().putString(Provider.CA_CERT + ".riseup.net", getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))).apply(); + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errorId\":\"ERROR_INVALID_CERTIFICATE\",\"errors\":\"Stored provider certificate is invalid. You can either update Bitmask (recommended) or update the provider certificate using a commercial CA certificate.\"}"); + + Intent provider_API_command = mockIntent(); + Bundle parameters = mockBundle(); + parameters.putString(Provider.MAIN_URL, "https://riseup.net"); + + provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); + provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(provider_API_command); + } } diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java new file mode 100644 index 00000000..9069661f --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BackendMockProvider.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.testutils.BackendMockResponses; + +import java.io.IOException; + +/** + * Created by cyberta on 10.01.18. + */ + +public class BackendMockProvider { + /** + * This enum can be useful to provide different responses from a mocked ProviderApiConnector + * in order to test different error scenarios + */ + public enum TestBackendErrorCase { + NO_ERROR, + ERROR_CASE_UPDATED_CERTIFICATE, + ERROR_NO_RESPONSE_BODY, // => NullPointerException + ERROR_DNS_RESOLUTION_ERROR, // => UnkownHostException + ERROR_SOCKET_TIMEOUT, // => SocketTimeoutException + ERROR_WRONG_PROTOCOL, // => MalformedURLException + ERROR_CERTIFICATE_INVALID, // => SSLHandshakeException + ERROR_WRONG_PORT, // => ConnectException + ERROR_PAYLOAD_MISSING, // => IllegalArgumentException + ERROR_TLS_1_2_NOT_SUPPORTED, // => UnknownServiceException + ERROR_UNKNOWN_IO_EXCEPTION, // => IOException + ERROR_NO_ACCESS, + ERROR_INVALID_SESSION_TOKEN, + ERROR_NO_CONNECTION, + ERROR_WRONG_SRP_CREDENTIALS + } + + + public static void provideBackendResponsesFor(TestBackendErrorCase errorCase) throws IOException { + switch (errorCase) { + + case NO_ERROR: + new NoErrorBackendResponse(); + break; + case ERROR_CASE_UPDATED_CERTIFICATE: + new UpdatedCertificateBackendResponse(); + break; + case ERROR_NO_RESPONSE_BODY: + break; + case ERROR_DNS_RESOLUTION_ERROR: + break; + case ERROR_SOCKET_TIMEOUT: + break; + case ERROR_WRONG_PROTOCOL: + break; + case ERROR_CERTIFICATE_INVALID: + break; + case ERROR_WRONG_PORT: + break; + case ERROR_PAYLOAD_MISSING: + break; + case ERROR_TLS_1_2_NOT_SUPPORTED: + break; + case ERROR_UNKNOWN_IO_EXCEPTION: + break; + case ERROR_NO_ACCESS: + break; + case ERROR_INVALID_SESSION_TOKEN: + break; + case ERROR_NO_CONNECTION: + break; + case ERROR_WRONG_SRP_CREDENTIALS: + break; + } + } +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BaseBackendResponse.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BaseBackendResponse.java new file mode 100644 index 00000000..98224019 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/BaseBackendResponse.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.testutils.BackendMockResponses; + +import android.util.Pair; + +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; + +import java.io.IOException; + +import okhttp3.OkHttpClient; +import se.leap.bitmaskclient.ProviderApiConnector; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +/** + * Created by cyberta on 10.01.18. + */ + +public abstract class BaseBackendResponse { + + private Answer answerRequestStringFromServer; + private Answer answerCanConnect; + private Answer answerDelete; + + public BaseBackendResponse() throws IOException { + mockStatic(ProviderApiConnector.class); + this.answerRequestStringFromServer = getAnswerForRequestStringFromServer(); + this.answerCanConnect = getAnswerForCanConnect(); + this.answerDelete = getAnswerForDelete(); + + responseOnRequestStringFromServer(); + responseOnCanConnect(); + responseOnDelete(); + + } + + public abstract Answer getAnswerForRequestStringFromServer(); + public abstract Answer getAnswerForCanConnect(); + public abstract Answer getAnswerForDelete(); + + + public void responseOnRequestStringFromServer() throws IOException, RuntimeException { + Mockito.when(ProviderApiConnector.requestStringFromServer(anyString(), anyString(), nullable(String.class), ArgumentMatchers.>anyList(), any(OkHttpClient.class))). + thenAnswer(answerRequestStringFromServer); + } + + public void responseOnCanConnect() throws IOException, RuntimeException { + Mockito.when(ProviderApiConnector.canConnect(any(OkHttpClient.class), anyString())).thenAnswer(answerCanConnect); + } + + public void responseOnDelete() throws IOException, RuntimeException { + Mockito.when(ProviderApiConnector.delete(any(OkHttpClient.class), anyString())).thenAnswer(answerDelete); + } + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponse.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponse.java new file mode 100644 index 00000000..fa318e42 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/NoErrorBackendResponse.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.testutils.BackendMockResponses; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; + +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + +/** + * Created by cyberta on 10.01.18. + */ + +public class NoErrorBackendResponse extends BaseBackendResponse { + public NoErrorBackendResponse() throws IOException { + super(); + } + + @Override + public Answer getAnswerForRequestStringFromServer() { + return new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String url = (String) invocation.getArguments()[0]; + String requestMethod = (String) invocation.getArguments()[1]; + String jsonPayload = (String) invocation.getArguments()[2]; + + if (url.contains("/provider.json")) { + //download provider json + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json")); + } else if (url.contains("/ca.crt")) { + //download provider ca cert + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem")); + } else if (url.contains("config/eip-service.json")) { + // download provider service json containing gateways, locations and openvpn settings + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.service.json")); + } else if (url.contains("/users.json")) { + //create new user + //TODO: implement me + } else if (url.contains("/sessions.json")) { + //srp auth: sendAToSRPServer + //TODO: implement me + } else if (url.contains("/sessions/parmegvtest10.json")){ + //srp auth: sendM1ToSRPServer + //TODO: implement me + } + + return null; + } + }; + } + + @Override + public Answer getAnswerForCanConnect() { + return new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return true; + } + }; + } + + @Override + public Answer getAnswerForDelete() { + return new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + return true; + } + }; + } + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/UpdatedCertificateBackendResponse.java b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/UpdatedCertificateBackendResponse.java new file mode 100644 index 00000000..232649a1 --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/BackendMockResponses/UpdatedCertificateBackendResponse.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.testutils.BackendMockResponses; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.IOException; + +import javax.net.ssl.SSLHandshakeException; + +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + +/** + * Created by cyberta on 10.01.18. + */ + +public class UpdatedCertificateBackendResponse extends BaseBackendResponse { + static volatile boolean wasCACertCalled = false; + + + public UpdatedCertificateBackendResponse() throws IOException { + super(); + } + + @Override + public Answer getAnswerForRequestStringFromServer() { + return new Answer() { + + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + String url = (String) invocation.getArguments()[0]; + + if (url.contains("/provider.json")) { + if (!wasCACertCalled) { + throw new SSLHandshakeException("Updated certificate on server side"); + } + //download provider json + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json")); + } else if (url.contains("/ca.crt")) { + //download provider ca cert + wasCACertCalled = true; + return getInputAsString(getClass().getClassLoader().getResourceAsStream("updated_cert.pem")); + } else if (url.contains("config/eip-service.json")) { + // download provider service json containing gateways, locations and openvpn settings + if (!wasCACertCalled) { + throw new SSLHandshakeException("Updated certificate on server side"); + } + return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.service.json")); + } + + return null; + } + }; + } + + @Override + public Answer getAnswerForCanConnect() { + return new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + if (!wasCACertCalled) { + throw new SSLHandshakeException("Updated certificate on server side"); + } + return true; + } + }; + } + + @Override + public Answer getAnswerForDelete() { + return new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + if (!wasCACertCalled) { + throw new SSLHandshakeException("Updated certificate on server side"); + } + return true; + } + }; + } + +} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java index 19d7e13f..f8f70eaf 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/TestSetupHelper.java @@ -24,11 +24,9 @@ import android.os.Parcelable; import android.os.ResultReceiver; import android.support.annotation.NonNull; import android.text.TextUtils; -import android.util.Pair; import org.json.JSONException; import org.json.JSONObject; -import org.mockito.ArgumentMatchers; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -50,9 +48,8 @@ import java.util.Set; import okhttp3.OkHttpClient; import se.leap.bitmaskclient.ConfigHelper; import se.leap.bitmaskclient.OkHttpClientGenerator; -import se.leap.bitmaskclient.ProviderApiConnector; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.testutils.answers.BackendAnswerFabric; +import se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockProvider; import se.leap.bitmaskclient.testutils.matchers.BundleMatcher; import static org.junit.Assert.assertEquals; @@ -61,14 +58,11 @@ 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.ArgumentMatchers.nullable; 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.testutils.answers.BackendAnswerFabric.TestBackendErrorCase.ERROR_NO_CONNECTION; -import static se.leap.bitmaskclient.testutils.answers.BackendAnswerFabric.getAnswerForErrorcase; /** * Created by cyberta on 08.10.17. @@ -383,14 +377,11 @@ public class TestSetupHelper { mockStatic(ConfigHelper.class); when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod(); - when(ConfigHelper.base64toHex(anyString())).thenCallRealMethod(); when(ConfigHelper.parseX509CertificateFromString(anyString())).thenCallRealMethod(); } - public static void mockProviderApiConnector(final BackendAnswerFabric.TestBackendErrorCase errorCase) throws IOException { - mockStatic(ProviderApiConnector.class); - when(ProviderApiConnector.canConnect(any(OkHttpClient.class), anyString())).thenReturn(errorCase != ERROR_NO_CONNECTION); - when(ProviderApiConnector.requestStringFromServer(anyString(), anyString(), nullable(String.class), ArgumentMatchers.>anyList(), any(OkHttpClient.class))).thenAnswer(getAnswerForErrorcase(errorCase)); + public static void mockProviderApiConnector(final BackendMockProvider.TestBackendErrorCase errorCase) throws IOException { + BackendMockProvider.provideBackendResponsesFor(errorCase); } public static OkHttpClientGenerator mockClientGenerator() { diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java b/app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java deleted file mode 100644 index 00e276f4..00000000 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/answers/BackendAnswerFabric.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2018 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package se.leap.bitmaskclient.testutils.answers; - -import org.mockito.stubbing.Answer; - -/** - * Created by cyberta on 09.01.18. - */ - -public class BackendAnswerFabric { - /** - * This enum can be useful to provide different responses from a mocked ProviderApiConnector - * in order to test different error scenarios - */ - public enum TestBackendErrorCase { - NO_ERROR, - ERROR_NO_RESPONSE_BODY, // => NullPointerException - ERROR_DNS_RESOLUTION_ERROR, // => UnkownHostException - ERROR_SOCKET_TIMEOUT, // => SocketTimeoutException - ERROR_WRONG_PROTOCOL, // => MalformedURLException - ERROR_CERTIFICATE_INVALID, // => SSLHandshakeException - ERROR_WRONG_PORT, // => ConnectException - ERROR_PAYLOAD_MISSING, // => IllegalArgumentException - ERROR_TLS_1_2_NOT_SUPPORTED, // => UnknownServiceException - ERROR_UNKNOWN_IO_EXCEPTION, // => IOException - ERROR_NO_ACCESS, - ERROR_INVALID_SESSION_TOKEN, - ERROR_NO_CONNECTION, - ERROR_WRONG_SRP_CREDENTIALS - } - - public static Answer getAnswerForErrorcase(TestBackendErrorCase errorCase) { - switch (errorCase) { - case NO_ERROR: - return new NoErrorAnswer(); - case ERROR_NO_RESPONSE_BODY: - break; - case ERROR_DNS_RESOLUTION_ERROR: - break; - case ERROR_SOCKET_TIMEOUT: - break; - case ERROR_WRONG_PROTOCOL: - break; - case ERROR_CERTIFICATE_INVALID: - break; - case ERROR_WRONG_PORT: - break; - case ERROR_PAYLOAD_MISSING: - break; - case ERROR_TLS_1_2_NOT_SUPPORTED: - break; - case ERROR_UNKNOWN_IO_EXCEPTION: - break; - case ERROR_NO_ACCESS: - break; - case ERROR_INVALID_SESSION_TOKEN: - break; - case ERROR_NO_CONNECTION: - break; - case ERROR_WRONG_SRP_CREDENTIALS: - break; - } - return null; - } - -} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java b/app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java deleted file mode 100644 index cbf9f6b8..00000000 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/answers/NoErrorAnswer.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2018 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package se.leap.bitmaskclient.testutils.answers; - -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; - -import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; - -/** - * Created by cyberta on 09.01.18. - */ - -public class NoErrorAnswer implements Answer { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - String url = (String) invocation.getArguments()[0]; - String requestMethod = (String) invocation.getArguments()[1]; - String jsonPayload = (String) invocation.getArguments()[2]; - - if (url.contains("/provider.json")) { - //download provider json - return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json")); - } else if (url.contains("/ca.crt")) { - //download provider ca cert - return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem")); - } else if (url.contains("config/eip-service.json")) { - // download provider service json containing gateways, locations and openvpn settings - return getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.service.json")); - } else if (url.contains("/users.json")) { - //create new user - //TODO: implement me - } else if (url.contains("/sessions.json")) { - //srp auth: sendAToSRPServer - //TODO: implement me - } else if (url.contains("/sessions/parmegvtest10.json")){ - //srp auth: sendM1ToSRPServer - //TODO: implement me - } - - return null; - } -} diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java b/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java index a7867e08..d2d2a102 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/matchers/BundleMatcher.java @@ -1,3 +1,19 @@ +/** + * Copyright (c) 2018 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package se.leap.bitmaskclient.testutils.matchers; import android.os.Bundle; -- cgit v1.2.3