From f855c9ea282a5e0cab15281f2495bc0b6eb6d201 Mon Sep 17 00:00:00 2001
From: Ernir Erlingsson <ernir@ymirmobile.com>
Date: Wed, 4 May 2016 21:48:13 +0200
Subject: [PATCH] Finished unit tests for SetupActivity and its Controller

---
 briar-android/build.gradle                    |   3 +
 .../briarproject/android/SetupActivity.java   |   2 +-
 .../controller/SetupControllerImp.java        |   8 +
 .../system/AndroidSeedProvider.java           |   7 +-
 .../activity/MockedSetupActivity.java         |  85 --------
 .../activity/SetupActivityTest.java           | 184 +++++++++++++-----
 .../activity/TestSetupActivity.java           |  20 ++
 .../controller/SetupControllerTest.java       |  19 --
 .../system/LinuxSeedProvider.java             |   2 -
 9 files changed, 176 insertions(+), 154 deletions(-)
 delete mode 100644 briar-android/test/java/briarproject/activity/MockedSetupActivity.java
 create mode 100644 briar-android/test/java/briarproject/activity/TestSetupActivity.java
 delete mode 100644 briar-android/test/java/briarproject/controller/SetupControllerTest.java

diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 815c8de62e..51b2584e05 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -105,6 +105,9 @@ android {
 		test.setRoot('test')
 		test {
 			java.srcDirs = ['test/java']
+			testOptions {
+				unitTests.returnDefaultValues = true
+			}
 		}
 
 
diff --git a/briar-android/src/org/briarproject/android/SetupActivity.java b/briar-android/src/org/briarproject/android/SetupActivity.java
index 1b101d2e01..13bb49f4b2 100644
--- a/briar-android/src/org/briarproject/android/SetupActivity.java
+++ b/briar-android/src/org/briarproject/android/SetupActivity.java
@@ -36,7 +36,7 @@ public class SetupActivity extends BaseActivity implements OnClickListener,
 		OnEditorActionListener {
 
 	@Inject
-	SetupController setupController;
+	protected SetupController setupController;
 
 	TextInputLayout nicknameEntryWrapper;
 	TextInputLayout passwordEntryWrapper;
diff --git a/briar-android/src/org/briarproject/android/controller/SetupControllerImp.java b/briar-android/src/org/briarproject/android/controller/SetupControllerImp.java
index 61a933e677..22eeeb84f9 100644
--- a/briar-android/src/org/briarproject/android/controller/SetupControllerImp.java
+++ b/briar-android/src/org/briarproject/android/controller/SetupControllerImp.java
@@ -84,15 +84,23 @@ public class SetupControllerImp implements SetupController {
 	@Override
 	public void createIdentity(final String nickname, final String password,
 			final ResultHandler<Long> resultHandler) {
+		LOG.info("createIdentity called");
 		cryptoExecutor.execute(new Runnable() {
 			public void run() {
+				LOG.info("createIdentity running");
 				SecretKey key = crypto.generateSecretKey();
+				LOG.info("createIdentity 1");
 				databaseConfig.setEncryptionKey(key);
+				LOG.info("createIdentity 2");
 				String hex = encryptDatabaseKey(key, password);
+				LOG.info("createIdentity 3");
 				storeEncryptedDatabaseKey(hex);
+				LOG.info("createIdentity 4");
 				final LocalAuthor localAuthor = createLocalAuthor(nickname);
+				LOG.info("createIdentity 5");
 				long handle = referenceManager.putReference(localAuthor,
 						LocalAuthor.class);
+				LOG.info("createIdentity 6");
 				resultHandler.onResult(handle);
 			}
 		});
diff --git a/briar-android/src/org/briarproject/system/AndroidSeedProvider.java b/briar-android/src/org/briarproject/system/AndroidSeedProvider.java
index 749d1c68bc..1b51be767d 100644
--- a/briar-android/src/org/briarproject/system/AndroidSeedProvider.java
+++ b/briar-android/src/org/briarproject/system/AndroidSeedProvider.java
@@ -34,7 +34,10 @@ class AndroidSeedProvider extends LinuxSeedProvider {
 		if (Build.FINGERPRINT != null) out.writeUTF(Build.FINGERPRINT);
 		if (Build.SERIAL != null) out.writeUTF(Build.SERIAL);
 		ContentResolver contentResolver = appContext.getContentResolver();
-		out.writeUTF(Settings.Secure.getString(contentResolver, ANDROID_ID));
-		super.writeToEntropyPool(out);
+		String str = Settings.Secure.getString(contentResolver, ANDROID_ID);
+		if (str != null) {
+			out.writeUTF(str);
+			super.writeToEntropyPool(out);
+		}
 	}
 }
diff --git a/briar-android/test/java/briarproject/activity/MockedSetupActivity.java b/briar-android/test/java/briarproject/activity/MockedSetupActivity.java
deleted file mode 100644
index 77a830f32a..0000000000
--- a/briar-android/test/java/briarproject/activity/MockedSetupActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package briarproject.activity;
-
-import org.briarproject.android.ActivityModule;
-import org.briarproject.android.SetupActivity;
-import org.briarproject.android.controller.SetupController;
-import org.briarproject.android.controller.SetupControllerImp;
-import org.briarproject.android.controller.handler.ResultHandler;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.logging.Logger;
-
-import static org.briarproject.api.crypto.PasswordStrengthEstimator.NONE;
-import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
-import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
-import static org.briarproject.api.crypto.PasswordStrengthEstimator.STRONG;
-import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-
-public class MockedSetupActivity extends SetupActivity {
-
-	private static final Logger LOG =
-			Logger.getLogger(MockedSetupActivity.class.getName());
-
-	final static String STRONG_PASS = "strong";
-	final static String QSTRONG_PASS = "qstrong";
-	final static String QWEAK_PASS = "qweak";
-	final static String WEAK_PASS = "weak";
-	final static String NO_PASS = "none";
-
-	@Override
-	protected ActivityModule getActivityModule() {
-		return new ActivityModule(this) {
-
-			@Override
-			protected SetupController provideSetupController(
-					SetupControllerImp setupControllerImp) {
-				SetupController setupController =
-						Mockito.mock(SetupControllerImp.class);
-
-				Mockito.doAnswer(new Answer<Void>() {
-					@Override
-					public Void answer(InvocationOnMock invocation)
-							throws Throwable {
-						((ResultHandler<Long>) invocation.getArguments()[2])
-								.onResult(1L);
-						return null;
-					}
-				}).when(setupController)
-						.createIdentity(anyString(), anyString(),
-								(ResultHandler<Long>) any());
-				Mockito.when(
-						setupController
-								.estimatePasswordStrength(anyString()))
-						.thenAnswer(new Answer<Float>() {
-							@Override
-							public Float answer(
-									InvocationOnMock invocation)
-									throws Throwable {
-								String p = (String) invocation
-										.getArguments()[0];
-								LOG.info("p = " + p);
-								if (p.equals(STRONG_PASS)) {
-									return STRONG;
-								} else if (p.equals(QSTRONG_PASS)) {
-									return QUITE_STRONG;
-								} else if (p.equals(QWEAK_PASS)) {
-									return QUITE_WEAK;
-								} else if (p.equals(WEAK_PASS)) {
-									return WEAK;
-								} else if (p.equals(NO_PASS)) {
-									return NONE;
-								} else {
-									return STRONG;
-								}
-							}
-						});
-
-				return setupController;
-			}
-		};
-	}
-}
diff --git a/briar-android/test/java/briarproject/activity/SetupActivityTest.java b/briar-android/test/java/briarproject/activity/SetupActivityTest.java
index 1317c61809..586cec9895 100644
--- a/briar-android/test/java/briarproject/activity/SetupActivityTest.java
+++ b/briar-android/test/java/briarproject/activity/SetupActivityTest.java
@@ -1,6 +1,8 @@
 package briarproject.activity;
 
+import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.support.design.widget.TextInputLayout;
 import android.widget.Button;
 import android.widget.EditText;
@@ -10,47 +12,45 @@ import com.google.common.base.Strings;
 import org.briarproject.BuildConfig;
 import org.briarproject.R;
 import org.briarproject.android.NavDrawerActivity;
-import org.briarproject.android.SetupActivity;
+import org.briarproject.android.controller.SetupController;
+import org.briarproject.android.controller.handler.ResultHandler;
 import org.briarproject.android.util.StrengthMeter;
 import org.briarproject.api.identity.AuthorConstants;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 import org.robolectric.Robolectric;
 import org.robolectric.RobolectricGradleTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowActivity;
 
-import java.util.logging.Logger;
-
-import static briarproject.activity.MockedSetupActivity.NO_PASS;
-import static briarproject.activity.MockedSetupActivity.QSTRONG_PASS;
-import static briarproject.activity.MockedSetupActivity.QWEAK_PASS;
-import static briarproject.activity.MockedSetupActivity.STRONG_PASS;
-import static briarproject.activity.MockedSetupActivity.WEAK_PASS;
 import static junit.framework.Assert.assertEquals;
-import static org.briarproject.android.util.StrengthMeter.GREEN;
-import static org.briarproject.android.util.StrengthMeter.LIME;
-import static org.briarproject.android.util.StrengthMeter.ORANGE;
-import static org.briarproject.android.util.StrengthMeter.RED;
-import static org.briarproject.android.util.StrengthMeter.YELLOW;
 import static org.briarproject.api.crypto.PasswordStrengthEstimator.NONE;
 import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_STRONG;
 import static org.briarproject.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
 import static org.briarproject.api.crypto.PasswordStrengthEstimator.STRONG;
 import static org.briarproject.api.crypto.PasswordStrengthEstimator.WEAK;
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
 
 @RunWith(RobolectricGradleTestRunner.class)
 @Config(constants = BuildConfig.class, sdk = 21)
 public class SetupActivityTest {
 
-	private static final Logger LOG =
-			Logger.getLogger(SetupActivityTest.class.getName());
-
-
-	private SetupActivity setupActivity;
+	TestSetupActivity setupActivity;
 	TextInputLayout nicknameEntryWrapper;
 	TextInputLayout passwordEntryWrapper;
 	TextInputLayout passwordConfirmationWrapper;
@@ -60,9 +60,15 @@ public class SetupActivityTest {
 	StrengthMeter strengthMeter;
 	Button createAccountButton;
 
+	@Mock
+	SetupController setupController;
+	@Captor
+	ArgumentCaptor<ResultHandler<Long>> resultCaptor;
+
 	@Before
 	public void setUp() {
-		setupActivity = Robolectric.setupActivity(MockedSetupActivity.class);
+		MockitoAnnotations.initMocks(this);
+		setupActivity = Robolectric.setupActivity(TestSetupActivity.class);
 		nicknameEntryWrapper = (TextInputLayout) setupActivity
 				.findViewById(R.id.nickname_entry_wrapper);
 		passwordEntryWrapper = (TextInputLayout) setupActivity
@@ -89,46 +95,134 @@ public class SetupActivityTest {
 	}
 
 	@Test
-	public void testUI() {
-		// Nick
-		String longNick =
-				Strings.padEnd("*", AuthorConstants.MAX_AUTHOR_NAME_LENGTH + 1,
-						'*');
-		nicknameEntry.setText(longNick);
-		assertEquals(nicknameEntryWrapper.getError(),
-				setupActivity.getString(R.string.name_too_long));
-		assertEquals(createAccountButton.isEnabled(), false);
-		// strength estimator
-		testStrengthMeter(STRONG_PASS, STRONG, GREEN);
-		assertEquals(createAccountButton.isEnabled(), false);
-		testStrengthMeter(QSTRONG_PASS, QUITE_STRONG, LIME);
-		assertEquals(createAccountButton.isEnabled(), false);
-		testStrengthMeter(QWEAK_PASS, QUITE_WEAK, YELLOW);
-		assertEquals(createAccountButton.isEnabled(), false);
-		testStrengthMeter(WEAK_PASS, WEAK, ORANGE);
-		assertEquals(createAccountButton.isEnabled(), false);
-		testStrengthMeter(NO_PASS, NONE, RED);
-		assertEquals(createAccountButton.isEnabled(), false);
-
-		// pass confirmation
-		nicknameEntry.setText("nick.nickerton");
+	public void testPasswordMatchUI() {
+		// Password mismatch
 		passwordEntry.setText("really.safe.password");
 		passwordConfirmation.setText("really.safe.pass");
 		assertEquals(createAccountButton.isEnabled(), false);
 		assertEquals(passwordConfirmationWrapper.getError(),
 				setupActivity.getString(R.string.passwords_do_not_match));
+		// Button enabled
 		passwordEntry.setText("really.safe.pass");
 		passwordConfirmation.setText("really.safe.pass");
-		assertNotEquals(passwordConfirmationWrapper.getError(),
+		// Confirm that the password mismatch error message is not visible
+		Assert.assertNotEquals(passwordConfirmationWrapper.getError(),
 				setupActivity.getString(R.string.passwords_do_not_match));
+		// Nick has not been set, expect the button to be disabled
+		assertEquals(createAccountButton.isEnabled(), false);
+	}
+
+	@Test
+	public void testCreateAccountUI() {
+
+		SetupController mockedController = this.setupController;
+		setupActivity.setController(mockedController);
+		// Mock strong password strength answer
+		when(mockedController.estimatePasswordStrength(anyString())).thenReturn(
+				STRONG);
+		String safePass = "really.safe.password";
+		String nick = "nick.nickerton";
+		passwordEntry.setText(safePass);
+		passwordConfirmation.setText(safePass);
+		nicknameEntry.setText(nick);
+		// Confirm that the create account button is clickable
 		assertEquals(createAccountButton.isEnabled(), true);
-		// confirm correct Activity started
 		createAccountButton.performClick();
+		// Verify that the controller's method was called with the correct
+		// params and get the callback
+		verify(mockedController, times(1))
+				.createIdentity(eq(nick), eq(safePass), resultCaptor.capture());
+		// execute the callback
+		resultCaptor.getValue().onResult(1L);
 		assertEquals(setupActivity.isFinishing(), true);
+		// Confirm that the correct Activity has been started
 		ShadowActivity shadowActivity = shadowOf(setupActivity);
 		Intent intent = shadowActivity.peekNextStartedActivity();
 		assertEquals(intent.getComponent().getClassName(),
 				NavDrawerActivity.class.getName());
 	}
 
+	@Test
+	public void testNickUI() {
+		Assert.assertNotNull(setupActivity);
+		String longNick =
+				Strings.padEnd("*", AuthorConstants.MAX_AUTHOR_NAME_LENGTH + 1,
+						'*');
+		nicknameEntry.setText(longNick);
+		// Password should be too long
+		assertEquals(nicknameEntryWrapper.getError(),
+				setupActivity.getString(R.string.name_too_long));
+	}
+
+	@Test
+	public void testAccountCreation() {
+		SetupController controller = setupActivity.getController();
+		// mock a resulthandler
+		ResultHandler<Long> resultHandler =
+				(ResultHandler<Long>) mock(ResultHandler.class);
+		controller
+				.createIdentity("nick", "some.strong.pass", resultHandler);
+		// blocking verification call with timeout that waits until the mocked
+		// result gets called with handle 0L, the expected value
+		verify(resultHandler, timeout(2000).times(1)).onResult(0L);
+		SharedPreferences prefs =
+				setupActivity.getSharedPreferences("db", Context.MODE_PRIVATE);
+		// Confirm database key
+		assertTrue(prefs.contains("key"));
+		// Note that Robolectric uses its own persistant storage that it
+		// wipes clean after each test run, no need to clean up manually.
+	}
+
+	@Test
+	public void testStrengthMeter() {
+		SetupController controller = setupActivity.getController();
+
+		String strongPass = "very.strong.password.123";
+		String weakPass = "we";
+		String quiteStrongPass = "quite.strong";
+
+		float val = controller.estimatePasswordStrength(strongPass);
+		assertTrue(val == STRONG);
+		val = controller.estimatePasswordStrength(weakPass);
+		assertTrue(val < WEAK && val > NONE);
+		val = controller.estimatePasswordStrength(quiteStrongPass);
+		assertTrue(val < STRONG && val > QUITE_WEAK);
+	}
+
+	@Test
+	public void testStrengthMeterUI() {
+		Assert.assertNotNull(setupActivity);
+		// replace the setup controller with our mocked copy
+		SetupController mockedController = this.setupController;
+		setupActivity.setController(mockedController);
+		// Mock answers for UI testing only
+		when(mockedController.estimatePasswordStrength("strong")).thenReturn(
+				STRONG);
+		when(mockedController.estimatePasswordStrength("qstring")).thenReturn(
+				QUITE_STRONG);
+		when(mockedController.estimatePasswordStrength("qweak")).thenReturn(
+				QUITE_WEAK);
+		when(mockedController.estimatePasswordStrength("weak")).thenReturn(
+				WEAK);
+		when(mockedController.estimatePasswordStrength("empty")).thenReturn(
+				NONE);
+		// Test the meters progress and color for several values
+		testStrengthMeter("strong", STRONG, StrengthMeter.GREEN);
+		Mockito.verify(mockedController, Mockito.times(1))
+				.estimatePasswordStrength(eq("strong"));
+		testStrengthMeter("qstring", QUITE_STRONG, StrengthMeter.LIME);
+		Mockito.verify(mockedController, Mockito.times(1))
+				.estimatePasswordStrength(eq("qstring"));
+		testStrengthMeter("qweak", QUITE_WEAK, StrengthMeter.YELLOW);
+		Mockito.verify(mockedController, Mockito.times(1))
+				.estimatePasswordStrength(eq("qweak"));
+		testStrengthMeter("weak", WEAK, StrengthMeter.ORANGE);
+		Mockito.verify(mockedController, Mockito.times(1))
+				.estimatePasswordStrength(eq("weak"));
+		// Not sure this should be the correct behaviour on an empty input ?
+		testStrengthMeter("empty", NONE, StrengthMeter.RED);
+		Mockito.verify(mockedController, Mockito.times(1))
+				.estimatePasswordStrength(eq("empty"));
+	}
+
 }
diff --git a/briar-android/test/java/briarproject/activity/TestSetupActivity.java b/briar-android/test/java/briarproject/activity/TestSetupActivity.java
new file mode 100644
index 0000000000..923372a494
--- /dev/null
+++ b/briar-android/test/java/briarproject/activity/TestSetupActivity.java
@@ -0,0 +1,20 @@
+package briarproject.activity;
+
+import org.briarproject.android.SetupActivity;
+import org.briarproject.android.controller.SetupController;
+
+/**
+ * This class exposes the SetupController and offers the possibility to
+ * override it.
+ */
+public class TestSetupActivity extends SetupActivity {
+
+	public SetupController getController() {
+		return this.setupController;
+	}
+
+	public void setController(SetupController setupController) {
+		this.setupController = setupController;
+	}
+
+}
diff --git a/briar-android/test/java/briarproject/controller/SetupControllerTest.java b/briar-android/test/java/briarproject/controller/SetupControllerTest.java
deleted file mode 100644
index 85bcfe8095..0000000000
--- a/briar-android/test/java/briarproject/controller/SetupControllerTest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package briarproject.controller;
-
-import org.briarproject.BriarTestCase;
-import org.briarproject.android.controller.SetupController;
-import org.junit.Before;
-
-import javax.inject.Inject;
-
-public class SetupControllerTest extends BriarTestCase {
-
-
-
-	@Inject
-	SetupController setupController;
-
-	@Before public void setUp() {
-
-	}
-}
diff --git a/briar-core/src/org/briarproject/system/LinuxSeedProvider.java b/briar-core/src/org/briarproject/system/LinuxSeedProvider.java
index 730beef97a..6481b4a5f2 100644
--- a/briar-core/src/org/briarproject/system/LinuxSeedProvider.java
+++ b/briar-core/src/org/briarproject/system/LinuxSeedProvider.java
@@ -43,8 +43,6 @@ class LinuxSeedProvider implements SeedProvider {
 		} catch (IOException e) {
 			// On some devices /dev/urandom isn't writable - this isn't fatal
 			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
-		} catch (NullPointerException e) {
-			if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
 		}
 		// Read the seed from the pool
 		try {
-- 
GitLab