mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1182193 - Part 1: Add Gradle-based Robolectric JUnit 4 tests r=nalexander
This commit is contained in:
parent
98facf267a
commit
8bcbe8a667
@ -28,7 +28,6 @@ android {
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
exclude 'org/mozilla/gecko/tests/**'
|
||||
exclude 'org/mozilla/gecko/resources/**'
|
||||
|
||||
if (!mozconfig.substs.MOZ_CRASHREPORTER) {
|
||||
@ -57,6 +56,16 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
java {
|
||||
srcDir "src/background_junit4"
|
||||
}
|
||||
|
||||
resources {
|
||||
srcDir "resources/background_junit4"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,6 +84,9 @@ dependencies {
|
||||
compile project(':preprocessed_code')
|
||||
compile project(':preprocessed_resources')
|
||||
compile project(':thirdparty')
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile 'org.robolectric:robolectric:3.0'
|
||||
}
|
||||
|
||||
apply plugin: 'idea'
|
||||
@ -82,6 +94,5 @@ apply plugin: 'idea'
|
||||
idea {
|
||||
module {
|
||||
excludeDirs += file('org/mozilla/gecko/resources')
|
||||
excludeDirs += file('org/mozilla/gecko/tests')
|
||||
}
|
||||
}
|
||||
|
@ -30,10 +30,9 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// IntelliJ 14.0.2 wants 0.14.4; IntelliJ 14.0.3 and Android Studio want
|
||||
// 1.0.0. There are major issues with the combination of 0.14.4, Gradle
|
||||
// 2.2.1, and IntelliJ 14.0.2: see Bug 1120032.
|
||||
classpath 'com.android.tools.build:gradle:1.0.0'
|
||||
// Unit testing support was added in 1.1.0. IntelliJ 14.1.4 and Android
|
||||
// Studio 1.2.1.1 appear to work fine with plugin version 1.3.0.
|
||||
classpath 'com.android.tools.build:gradle:1.3.0'
|
||||
classpath('com.stanfy.spoon:spoon-gradle-plugin:1.0.3-SNAPSHOT') {
|
||||
// Without these, we get errors linting.
|
||||
exclude module: 'guava'
|
||||
|
@ -136,7 +136,7 @@ class MachCommands(MachCommandBase):
|
||||
# Test code.
|
||||
srcdir('app/src/robocop_harness/org/mozilla/gecko', 'build/mobile/robocop')
|
||||
srcdir('app/src/robocop/org/mozilla/gecko/tests', 'mobile/android/tests/browser/robocop')
|
||||
srcdir('app/src/background/org/mozilla/gecko', 'mobile/android/tests/background/junit3/src')
|
||||
srcdir('app/src/background/org/mozilla/gecko/background', 'mobile/android/tests/background/junit3/src')
|
||||
srcdir('app/src/browser', 'mobile/android/tests/browser/junit3/src')
|
||||
srcdir('app/src/javaaddons', 'mobile/android/tests/javaaddons/src')
|
||||
# Test libraries.
|
||||
@ -155,6 +155,9 @@ class MachCommands(MachCommandBase):
|
||||
srcdir('base/src/main/res', 'mobile/android/base/resources')
|
||||
srcdir('base/src/main/assets', 'mobile/android/app/assets')
|
||||
srcdir('base/src/crashreporter/res', 'mobile/android/base/crashreporter/res')
|
||||
# JUnit 4 test code.
|
||||
srcdir('base/src/background_junit4', 'mobile/android/tests/background/junit4/src')
|
||||
srcdir('base/resources/background_junit4', 'mobile/android/tests/background/junit4/resources')
|
||||
|
||||
manifest_path = os.path.join(self.topobjdir, 'mobile', 'android', 'gradle.manifest')
|
||||
with FileAvoidWrite(manifest_path) as f:
|
||||
|
@ -0,0 +1,2 @@
|
||||
sdk=21
|
||||
constants=org.mozilla.gecko.BuildConfig
|
@ -0,0 +1,171 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.testhelpers;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
/**
|
||||
* Implements waiting for asynchronous test events.
|
||||
*
|
||||
* Call WaitHelper.getTestWaiter() to get the unique instance.
|
||||
*
|
||||
* Call performWait(runnable) to execute runnable synchronously.
|
||||
* runnable *must* call performNotify() on all exit paths to signal to
|
||||
* the TestWaiter that the runnable has completed.
|
||||
*
|
||||
* @author rnewman
|
||||
* @author nalexander
|
||||
*/
|
||||
public class WaitHelper {
|
||||
|
||||
public static final String LOG_TAG = "WaitHelper";
|
||||
|
||||
public static class Result {
|
||||
public Throwable error;
|
||||
public Result() {
|
||||
error = null;
|
||||
}
|
||||
|
||||
public Result(Throwable error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class WaitHelperError extends Error {
|
||||
private static final long serialVersionUID = 7074690961681883619L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*
|
||||
* @author rnewman
|
||||
*/
|
||||
public static class TimeoutError extends WaitHelperError {
|
||||
private static final long serialVersionUID = 8591672555848651736L;
|
||||
public final int waitTimeInMillis;
|
||||
|
||||
public TimeoutError(int waitTimeInMillis) {
|
||||
this.waitTimeInMillis = waitTimeInMillis;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleNotificationsError extends WaitHelperError {
|
||||
private static final long serialVersionUID = -9072736521571635495L;
|
||||
}
|
||||
|
||||
public static class InterruptedError extends WaitHelperError {
|
||||
private static final long serialVersionUID = 8383948170038639308L;
|
||||
}
|
||||
|
||||
public static class InnerError extends WaitHelperError {
|
||||
private static final long serialVersionUID = 3008502618576773778L;
|
||||
public Throwable innerError;
|
||||
|
||||
public InnerError(Throwable e) {
|
||||
innerError = e;
|
||||
if (e != null) {
|
||||
// Eclipse prints the stack trace of the cause.
|
||||
this.initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BlockingQueue<Result> queue = new ArrayBlockingQueue<Result>(1);
|
||||
|
||||
/**
|
||||
* How long performWait should wait for, in milliseconds, with the
|
||||
* convention that a negative value means "wait forever".
|
||||
*/
|
||||
public static int defaultWaitTimeoutInMillis = -1;
|
||||
|
||||
public void performWait(Runnable action) throws WaitHelperError {
|
||||
this.performWait(defaultWaitTimeoutInMillis, action);
|
||||
}
|
||||
|
||||
public void performWait(int waitTimeoutInMillis, Runnable action) throws WaitHelperError {
|
||||
Logger.debug(LOG_TAG, "performWait called.");
|
||||
|
||||
Result result = null;
|
||||
|
||||
try {
|
||||
if (action != null) {
|
||||
try {
|
||||
action.run();
|
||||
Logger.debug(LOG_TAG, "Action done.");
|
||||
} catch (Exception ex) {
|
||||
Logger.debug(LOG_TAG, "Performing action threw: " + ex.getMessage());
|
||||
throw new InnerError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (waitTimeoutInMillis < 0) {
|
||||
result = queue.take();
|
||||
} else {
|
||||
result = queue.poll(waitTimeoutInMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Got result from queue: " + result);
|
||||
} catch (InterruptedException e) {
|
||||
// We were interrupted.
|
||||
Logger.debug(LOG_TAG, "performNotify interrupted with InterruptedException " + e);
|
||||
final InterruptedError interruptedError = new InterruptedError();
|
||||
interruptedError.initCause(e);
|
||||
throw interruptedError;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
// We timed out.
|
||||
throw new TimeoutError(waitTimeoutInMillis);
|
||||
} else if (result.error != null) {
|
||||
Logger.debug(LOG_TAG, "Notified with error: " + result.error.getMessage());
|
||||
|
||||
// Rethrow any assertion with which we were notified.
|
||||
InnerError innerError = new InnerError(result.error);
|
||||
throw innerError;
|
||||
}
|
||||
// Success!
|
||||
}
|
||||
|
||||
public void performNotify(final Throwable e) {
|
||||
if (e != null) {
|
||||
Logger.debug(LOG_TAG, "performNotify called with Throwable: " + e.getMessage());
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "performNotify called.");
|
||||
}
|
||||
|
||||
if (!queue.offer(new Result(e))) {
|
||||
// This could happen if performNotify is called multiple times (which is an error).
|
||||
throw new MultipleNotificationsError();
|
||||
}
|
||||
}
|
||||
|
||||
public void performNotify() {
|
||||
this.performNotify(null);
|
||||
}
|
||||
|
||||
public static Runnable onThreadRunnable(final Runnable r) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(r).start();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static WaitHelper singleWaiter = new WaitHelper();
|
||||
public static WaitHelper getTestWaiter() {
|
||||
return singleWaiter;
|
||||
}
|
||||
|
||||
public static void resetTestWaiter() {
|
||||
singleWaiter = new WaitHelper();
|
||||
}
|
||||
|
||||
public boolean isIdle() {
|
||||
return queue.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.fxa;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.SkewHandler;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricGradleTestRunner.class)
|
||||
public class TestSkewHandler {
|
||||
public TestSkewHandler() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkewUpdating() throws Throwable {
|
||||
SkewHandler h = new SkewHandler("foo.com");
|
||||
assertEquals(0L, h.getSkewInSeconds());
|
||||
assertEquals(0L, h.getSkewInMillis());
|
||||
|
||||
long server = 1390101197865L;
|
||||
long local = server - 4500L;
|
||||
h.updateSkewFromServerMillis(server, local);
|
||||
assertEquals(4500L, h.getSkewInMillis());
|
||||
assertEquals(4L, h.getSkewInSeconds());
|
||||
|
||||
local = server;
|
||||
h.updateSkewFromServerMillis(server, local);
|
||||
assertEquals(0L, h.getSkewInMillis());
|
||||
assertEquals(0L, h.getSkewInSeconds());
|
||||
|
||||
local = server + 500L;
|
||||
h.updateSkewFromServerMillis(server, local);
|
||||
assertEquals(-500L, h.getSkewInMillis());
|
||||
assertEquals(0L, h.getSkewInSeconds());
|
||||
|
||||
String date = "Sat, 18 Jan 2014 19:16:52 PST";
|
||||
long dateInMillis = 1390101412000L; // Obviously this can differ somewhat due to precision.
|
||||
long parsed = DateUtils.parseDate(date).getTime();
|
||||
assertEquals(parsed, dateInMillis);
|
||||
|
||||
h.updateSkewFromHTTPDateString(date, dateInMillis);
|
||||
assertEquals(0L, h.getSkewInMillis());
|
||||
assertEquals(0L, h.getSkewInSeconds());
|
||||
|
||||
h.updateSkewFromHTTPDateString(date, dateInMillis + 1100L);
|
||||
assertEquals(-1100L, h.getSkewInMillis());
|
||||
assertEquals(Math.round(-1100L / 1000L), h.getSkewInSeconds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkewSingleton() throws Exception {
|
||||
SkewHandler h1 = SkewHandler.getSkewHandlerFromEndpointString("http://foo.com/bar");
|
||||
SkewHandler h2 = SkewHandler.getSkewHandlerForHostname("foo.com");
|
||||
SkewHandler h3 = SkewHandler.getSkewHandlerForResource(new BaseResource("http://foo.com/baz"));
|
||||
assertTrue(h1 == h2);
|
||||
assertTrue(h1 == h3);
|
||||
|
||||
SkewHandler.getSkewHandlerForHostname("foo.com").updateSkewFromServerMillis(1390101412000L, 1390001412000L);
|
||||
final long actual = SkewHandler.getSkewHandlerForHostname("foo.com").getSkewInMillis();
|
||||
assertEquals(100000000L, actual);
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.fxa.login;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.StatusResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient10.TwoKeys;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.fxa.PasswordStretcher;
|
||||
import org.mozilla.gecko.browserid.MockMyIDTokenFactory;
|
||||
import org.mozilla.gecko.browserid.RSACryptoImplementation;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.ProtocolVersion;
|
||||
import ch.boye.httpclientandroidlib.entity.StringEntity;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
|
||||
|
||||
public class MockFxAccountClient implements FxAccountClient {
|
||||
protected static MockMyIDTokenFactory mockMyIdTokenFactory = new MockMyIDTokenFactory();
|
||||
|
||||
public final String serverURI = "http://testServer.com";
|
||||
|
||||
public final Map<String, User> users = new HashMap<String, User>();
|
||||
public final Map<String, String> sessionTokens = new HashMap<String, String>();
|
||||
public final Map<String, String> keyFetchTokens = new HashMap<String, String>();
|
||||
|
||||
public static class User {
|
||||
public final String email;
|
||||
public final byte[] quickStretchedPW;
|
||||
public final String uid;
|
||||
public boolean verified;
|
||||
public final byte[] kA;
|
||||
public final byte[] wrapkB;
|
||||
|
||||
public User(String email, byte[] quickStretchedPW) {
|
||||
this.email = email;
|
||||
this.quickStretchedPW = quickStretchedPW;
|
||||
this.uid = "uid/" + this.email;
|
||||
this.verified = false;
|
||||
this.kA = Utils.generateRandomBytes(FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES);
|
||||
this.wrapkB = Utils.generateRandomBytes(FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
protected LoginResponse addLogin(User user, byte[] sessionToken, byte[] keyFetchToken) {
|
||||
// byte[] sessionToken = Utils.generateRandomBytes(8);
|
||||
if (sessionToken != null) {
|
||||
sessionTokens.put(Utils.byte2Hex(sessionToken), user.email);
|
||||
}
|
||||
// byte[] keyFetchToken = Utils.generateRandomBytes(8);
|
||||
if (keyFetchToken != null) {
|
||||
keyFetchTokens.put(Utils.byte2Hex(keyFetchToken), user.email);
|
||||
}
|
||||
return new LoginResponse(user.email, user.uid, user.verified, sessionToken, keyFetchToken);
|
||||
}
|
||||
|
||||
public void addUser(String email, byte[] quickStretchedPW, boolean verified, byte[] sessionToken, byte[] keyFetchToken) {
|
||||
User user = new User(email, quickStretchedPW);
|
||||
users.put(email, user);
|
||||
if (verified) {
|
||||
verifyUser(email);
|
||||
}
|
||||
addLogin(user, sessionToken, keyFetchToken);
|
||||
}
|
||||
|
||||
public void verifyUser(String email) {
|
||||
users.get(email).verified = true;
|
||||
}
|
||||
|
||||
public void clearAllUserTokens() throws UnsupportedEncodingException {
|
||||
sessionTokens.clear();
|
||||
keyFetchTokens.clear();
|
||||
}
|
||||
|
||||
protected BasicHttpResponse makeHttpResponse(int statusCode, String body) {
|
||||
BasicHttpResponse httpResponse = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), statusCode, body);
|
||||
httpResponse.setEntity(new StringEntity(body, "UTF-8"));
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
protected <T> void handleFailure(RequestDelegate<T> requestDelegate, int code, int errno, String message) {
|
||||
requestDelegate.handleFailure(new FxAccountClientRemoteException(makeHttpResponse(code, message),
|
||||
code, errno, "Bad authorization", message, null, new ExtendedJSONObject()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void status(byte[] sessionToken, RequestDelegate<StatusResponse> requestDelegate) {
|
||||
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
|
||||
return;
|
||||
}
|
||||
requestDelegate.handleSuccess(new StatusResponse(email, user.verified));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loginAndGetKeys(byte[] emailUTF8, final PasswordStretcher passwordStretcher, final Map<String, String> queryParameters, RequestDelegate<LoginResponse> requestDelegate) {
|
||||
User user;
|
||||
try {
|
||||
user = users.get(new String(emailUTF8, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
user = null;
|
||||
}
|
||||
if (user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS, "invalid emailUTF8");
|
||||
return;
|
||||
}
|
||||
byte[] quickStretchedPW;
|
||||
try {
|
||||
quickStretchedPW = passwordStretcher.getQuickStretchedPW(emailUTF8);
|
||||
} catch (Exception e) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_INTERNAL_SERVER_ERROR, 999, "error stretching password");
|
||||
return;
|
||||
}
|
||||
if (user.quickStretchedPW == null || !Arrays.equals(user.quickStretchedPW, quickStretchedPW)) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.INCORRECT_PASSWORD, "invalid quickStretchedPW");
|
||||
return;
|
||||
}
|
||||
LoginResponse loginResponse = addLogin(user, Utils.generateRandomBytes(8), Utils.generateRandomBytes(8));
|
||||
requestDelegate.handleSuccess(loginResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate) {
|
||||
String email = keyFetchTokens.get(Utils.byte2Hex(keyFetchToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid keyFetchToken");
|
||||
return;
|
||||
}
|
||||
if (!user.verified) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
|
||||
return;
|
||||
}
|
||||
requestDelegate.handleSuccess(new TwoKeys(user.kA, user.wrapkB));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate) {
|
||||
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
|
||||
return;
|
||||
}
|
||||
if (!user.verified) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final long iat = System.currentTimeMillis();
|
||||
final long dur = certificateDurationInMilliseconds;
|
||||
final long exp = iat + dur;
|
||||
String certificate = mockMyIdTokenFactory.createMockMyIDCertificate(RSACryptoImplementation.createPublicKey(publicKey), "test", iat, exp);
|
||||
requestDelegate.handleSuccess(certificate);
|
||||
} catch (Exception e) {
|
||||
requestDelegate.handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resendCode(byte[] sessionToken, RequestDelegate<Void> requestDelegate) {
|
||||
String email = sessionTokens.get(Utils.byte2Hex(sessionToken));
|
||||
User user = users.get(email);
|
||||
if (email == null || user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken");
|
||||
return;
|
||||
}
|
||||
requestDelegate.handleSuccess(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resendUnlockCode(byte[] emailUTF8, RequestDelegate<Void> requestDelegate) {
|
||||
User user;
|
||||
try {
|
||||
user = users.get(new String(emailUTF8, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
user = null;
|
||||
}
|
||||
if (user == null) {
|
||||
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_ACCESS_AN_ACCOUNT_THAT_DOES_NOT_EXIST, "invalid emailUTF8");
|
||||
return;
|
||||
}
|
||||
requestDelegate.handleSuccess(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAccountAndGetKeys(byte[] emailUTF8, PasswordStretcher passwordStretcher, final Map<String, String> queryParameters, RequestDelegate<LoginResponse> delegate) {
|
||||
delegate.handleError(new RuntimeException("Not yet implemented"));
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.fxa.login;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.BuildConfig;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClient;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.RSACryptoImplementation;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
|
||||
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricGradleTestRunner.class)
|
||||
public class TestFxAccountLoginStateMachine {
|
||||
// private static final String TEST_AUDIENCE = "http://testAudience.com";
|
||||
private static final String TEST_EMAIL = "test@test.com";
|
||||
private static byte[] TEST_EMAIL_UTF8;
|
||||
private static final String TEST_PASSWORD = "testtest";
|
||||
private static byte[] TEST_PASSWORD_UTF8;
|
||||
private static byte[] TEST_QUICK_STRETCHED_PW;
|
||||
private static byte[] TEST_UNWRAPKB;
|
||||
private static final byte[] TEST_SESSION_TOKEN = Utils.generateRandomBytes(32);
|
||||
private static final byte[] TEST_KEY_FETCH_TOKEN = Utils.generateRandomBytes(32);
|
||||
|
||||
protected MockFxAccountClient client;
|
||||
protected FxAccountLoginStateMachine sm;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
if (TEST_EMAIL_UTF8 == null) {
|
||||
TEST_EMAIL_UTF8 = TEST_EMAIL.getBytes("UTF-8");
|
||||
}
|
||||
if (TEST_PASSWORD_UTF8 == null) {
|
||||
TEST_PASSWORD_UTF8 = TEST_PASSWORD.getBytes("UTF-8");
|
||||
}
|
||||
if (TEST_QUICK_STRETCHED_PW == null) {
|
||||
TEST_QUICK_STRETCHED_PW = FxAccountUtils.generateQuickStretchedPW(TEST_EMAIL_UTF8, TEST_PASSWORD_UTF8);
|
||||
}
|
||||
if (TEST_UNWRAPKB == null) {
|
||||
TEST_UNWRAPKB = FxAccountUtils.generateUnwrapBKey(TEST_QUICK_STRETCHED_PW);
|
||||
}
|
||||
client = new MockFxAccountClient();
|
||||
sm = new FxAccountLoginStateMachine();
|
||||
}
|
||||
|
||||
protected static class Trace {
|
||||
public final LinkedList<State> states;
|
||||
public final LinkedList<Transition> transitions;
|
||||
|
||||
public Trace(LinkedList<State> states, LinkedList<Transition> transitions) {
|
||||
this.states = states;
|
||||
this.transitions = transitions;
|
||||
}
|
||||
|
||||
public void assertEquals(String string) {
|
||||
Assert.assertArrayEquals(string.split(", "), toString().split(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final LinkedList<State> states = new LinkedList<State>(this.states);
|
||||
final LinkedList<Transition> transitions = new LinkedList<Transition>(this.transitions);
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
State state;
|
||||
while ((state = states.pollFirst()) != null) {
|
||||
names.add(state.getStateLabel().name());
|
||||
Transition transition = transitions.pollFirst();
|
||||
if (transition != null) {
|
||||
names.add(">" + transition.toString());
|
||||
}
|
||||
}
|
||||
return names.toString();
|
||||
}
|
||||
|
||||
public String stateString() {
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
for (State state : states) {
|
||||
names.add(state.getStateLabel().name());
|
||||
}
|
||||
return names.toString();
|
||||
}
|
||||
|
||||
public String transitionString() {
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
for (Transition transition : transitions) {
|
||||
names.add(transition.toString());
|
||||
}
|
||||
return names.toString();
|
||||
}
|
||||
}
|
||||
|
||||
protected Trace trace(final State initialState, final StateLabel desiredState) {
|
||||
final LinkedList<Transition> transitions = new LinkedList<Transition>();
|
||||
final LinkedList<State> states = new LinkedList<State>();
|
||||
states.add(initialState);
|
||||
|
||||
WaitHelper.getTestWaiter().performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sm.advance(initialState, desiredState, new LoginStateMachineDelegate() {
|
||||
@Override
|
||||
public void handleTransition(Transition transition, State state) {
|
||||
transitions.add(transition);
|
||||
states.add(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFinal(State state) {
|
||||
WaitHelper.getTestWaiter().performNotify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FxAccountClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCertificateDurationInMilliseconds() {
|
||||
return 30 * 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAssertionDurationInMilliseconds() {
|
||||
return 10 * 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
||||
return RSACryptoImplementation.generateKeyPair(512);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new Trace(states, transitions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEnagedUnverified() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, false, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married);
|
||||
trace.assertEquals("[Engaged, >AccountNeedsVerification, Engaged]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngagedTransitionToAccountVerified() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", false, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married);
|
||||
trace.assertEquals("[Engaged, >AccountVerified, Cohabiting, >LogMessage('sign succeeded'), Married]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEngagedVerified() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married);
|
||||
trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting, >LogMessage('sign succeeded'), Married]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartial() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
// What if we stop at Cohabiting?
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Cohabiting);
|
||||
trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadSessionToken() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
client.sessionTokens.clear();
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married);
|
||||
trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting, >Log(<FxAccountClientRemoteException 401 [110]: invalid sessionToken>), Separated, >PasswordRequired, Separated]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadKeyFetchToken() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
client.keyFetchTokens.clear();
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married);
|
||||
trace.assertEquals("[Engaged, >Log(<FxAccountClientRemoteException 401 [110]: invalid keyFetchToken>), Separated, >PasswordRequired, Separated]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMarried() throws Exception {
|
||||
client.addUser(TEST_EMAIL, TEST_QUICK_STRETCHED_PW, true, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN);
|
||||
Trace trace = trace(new Engaged(TEST_EMAIL, "uid", true, TEST_UNWRAPKB, TEST_SESSION_TOKEN, TEST_KEY_FETCH_TOKEN), StateLabel.Married);
|
||||
trace.assertEquals("[Engaged, >LogMessage('keys succeeded'), Cohabiting, >LogMessage('sign succeeded'), Married]");
|
||||
// What if we're already in the desired state?
|
||||
State married = trace.states.getLast();
|
||||
Assert.assertEquals(StateLabel.Married, married.getStateLabel());
|
||||
trace = trace(married, StateLabel.Married);
|
||||
trace.assertEquals("[Married]");
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.fxa.login;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.BuildConfig;
|
||||
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
|
||||
import org.mozilla.gecko.browserid.DSACryptoImplementation;
|
||||
import org.mozilla.gecko.fxa.login.State.StateLabel;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.robolectric.RobolectricGradleTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(RobolectricGradleTestRunner.class)
|
||||
public class TestStateFactory {
|
||||
@Test
|
||||
public void testGetStateV3() throws Exception {
|
||||
MigratedFromSync11 migrated = new MigratedFromSync11("email", "uid", true, "password");
|
||||
|
||||
// For the current version, we expect to read back what we wrote.
|
||||
ExtendedJSONObject o;
|
||||
State state;
|
||||
|
||||
o = migrated.toJSONObject();
|
||||
Assert.assertEquals(3, o.getLong("version").intValue());
|
||||
state = StateFactory.fromJSONObject(migrated.stateLabel, o);
|
||||
Assert.assertEquals(StateLabel.MigratedFromSync11, state.stateLabel);
|
||||
Assert.assertEquals(o, state.toJSONObject());
|
||||
|
||||
// Null passwords are OK.
|
||||
MigratedFromSync11 migratedNullPassword = new MigratedFromSync11("email", "uid", true, null);
|
||||
|
||||
o = migratedNullPassword.toJSONObject();
|
||||
Assert.assertEquals(3, o.getLong("version").intValue());
|
||||
state = StateFactory.fromJSONObject(migratedNullPassword.stateLabel, o);
|
||||
Assert.assertEquals(StateLabel.MigratedFromSync11, state.stateLabel);
|
||||
Assert.assertEquals(o, state.toJSONObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStateV2() throws Exception {
|
||||
byte[] sessionToken = Utils.generateRandomBytes(32);
|
||||
byte[] kA = Utils.generateRandomBytes(32);
|
||||
byte[] kB = Utils.generateRandomBytes(32);
|
||||
BrowserIDKeyPair keyPair = DSACryptoImplementation.generateKeyPair(512);
|
||||
Cohabiting cohabiting = new Cohabiting("email", "uid", sessionToken, kA, kB, keyPair);
|
||||
String certificate = "certificate";
|
||||
Married married = new Married("email", "uid", sessionToken, kA, kB, keyPair, certificate);
|
||||
|
||||
// For the current version, we expect to read back what we wrote.
|
||||
ExtendedJSONObject o;
|
||||
State state;
|
||||
|
||||
o = married.toJSONObject();
|
||||
Assert.assertEquals(3, o.getLong("version").intValue());
|
||||
state = StateFactory.fromJSONObject(married.stateLabel, o);
|
||||
Assert.assertEquals(StateLabel.Married, state.stateLabel);
|
||||
Assert.assertEquals(o, state.toJSONObject());
|
||||
|
||||
o = cohabiting.toJSONObject();
|
||||
Assert.assertEquals(3, o.getLong("version").intValue());
|
||||
state = StateFactory.fromJSONObject(cohabiting.stateLabel, o);
|
||||
Assert.assertEquals(StateLabel.Cohabiting, state.stateLabel);
|
||||
Assert.assertEquals(o, state.toJSONObject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetStateV1() throws Exception {
|
||||
// We can't rely on generating correct V1 objects (since the generation code
|
||||
// may change); so we hard code a few test examples here. These examples
|
||||
// have RSA key pairs; when they're parsed, we return DSA key pairs.
|
||||
ExtendedJSONObject o = new ExtendedJSONObject("{\"uid\":\"uid\",\"sessionToken\":\"4e2830da6ce466ddb401fbca25b96a621209eea83851254800f84cc4069ef011\",\"certificate\":\"certificate\",\"keyPair\":{\"publicKey\":{\"e\":\"65537\",\"n\":\"7598360104379019497828904063491254083855849024432238665262988260947462372141971045236693389494635158997975098558915846889960089362159921622822266839560631\",\"algorithm\":\"RS\"},\"privateKey\":{\"d\":\"6807533330618101360064115400338014782301295929300445938471117364691566605775022173055292460962170873583673516346599808612503093914221141089102289381448225\",\"n\":\"7598360104379019497828904063491254083855849024432238665262988260947462372141971045236693389494635158997975098558915846889960089362159921622822266839560631\",\"algorithm\":\"RS\"}},\"email\":\"email\",\"verified\":true,\"kB\":\"0b048f285c19067f200da7bfbe734ed213cefcd8f543f0fdd4a8ccab48cbbc89\",\"kA\":\"59a9edf2d41de8b24e69df9133bc88e96913baa75421882f4c55d842d18fc8a1\",\"version\":1}");
|
||||
// A Married state is regressed to a Cohabited state.
|
||||
Cohabiting state = (Cohabiting) StateFactory.fromJSONObject(StateLabel.Married, o);
|
||||
|
||||
Assert.assertEquals(StateLabel.Cohabiting, state.stateLabel);
|
||||
Assert.assertEquals("uid", state.uid);
|
||||
Assert.assertEquals("4e2830da6ce466ddb401fbca25b96a621209eea83851254800f84cc4069ef011", Utils.byte2Hex(state.sessionToken));
|
||||
Assert.assertEquals("DS128", state.keyPair.getPrivate().getAlgorithm());
|
||||
|
||||
o = new ExtendedJSONObject("{\"uid\":\"uid\",\"sessionToken\":\"4e2830da6ce466ddb401fbca25b96a621209eea83851254800f84cc4069ef011\",\"keyPair\":{\"publicKey\":{\"e\":\"65537\",\"n\":\"7598360104379019497828904063491254083855849024432238665262988260947462372141971045236693389494635158997975098558915846889960089362159921622822266839560631\",\"algorithm\":\"RS\"},\"privateKey\":{\"d\":\"6807533330618101360064115400338014782301295929300445938471117364691566605775022173055292460962170873583673516346599808612503093914221141089102289381448225\",\"n\":\"7598360104379019497828904063491254083855849024432238665262988260947462372141971045236693389494635158997975098558915846889960089362159921622822266839560631\",\"algorithm\":\"RS\"}},\"email\":\"email\",\"verified\":true,\"kB\":\"0b048f285c19067f200da7bfbe734ed213cefcd8f543f0fdd4a8ccab48cbbc89\",\"kA\":\"59a9edf2d41de8b24e69df9133bc88e96913baa75421882f4c55d842d18fc8a1\",\"version\":1}");
|
||||
state = (Cohabiting) StateFactory.fromJSONObject(StateLabel.Cohabiting, o);
|
||||
|
||||
Assert.assertEquals(StateLabel.Cohabiting, state.stateLabel);
|
||||
Assert.assertEquals("uid", state.uid);
|
||||
Assert.assertEquals("4e2830da6ce466ddb401fbca25b96a621209eea83851254800f84cc4069ef011", Utils.byte2Hex(state.sessionToken));
|
||||
Assert.assertEquals("DS128", state.keyPair.getPrivate().getAlgorithm());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user