Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android

This commit is contained in:
Matt A. Tobin 2019-04-23 15:32:23 -04:00 committed by Roy Tam
commit c9b411d6cd
3891 changed files with 0 additions and 428084 deletions

View file

@ -1,142 +0,0 @@
package com.keepsafe.switchboard;
import android.content.Context;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.Experiments;
import org.mozilla.gecko.util.IOUtils;
import org.robolectric.RuntimeEnvironment;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.*;
@RunWith(TestRunner.class)
public class TestSwitchboard {
/**
* Create a JSON response from a JSON file.
*/
private String readFromFile(String fileName) throws IOException {
URL url = getClass().getResource("/" + fileName);
if (url == null) {
throw new FileNotFoundException(fileName);
}
InputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(url.getPath()));
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferReader = new BufferedReader(inputStreamReader, 8192);
String line;
StringBuilder resultContent = new StringBuilder();
while ((line = bufferReader.readLine()) != null) {
resultContent.append(line);
}
bufferReader.close();
return resultContent.toString();
} finally {
IOUtils.safeStreamClose(inputStream);
}
}
@Before
public void setUp() throws IOException {
final Context c = RuntimeEnvironment.application;
Preferences.setDynamicConfigJson(c, readFromFile("experiments.json"));
}
@Test
public void testDeviceUuidFactory() {
final Context c = RuntimeEnvironment.application;
final DeviceUuidFactory df = new DeviceUuidFactory(c);
final UUID uuid = df.getDeviceUuid();
assertNotNull("UUID is not null", uuid);
assertEquals("DeviceUuidFactory always returns the same UUID", df.getDeviceUuid(), uuid);
}
@Test
public void testIsInExperiment() {
final Context c = RuntimeEnvironment.application;
assertTrue("active-experiment is active", SwitchBoard.isInExperiment(c, "active-experiment"));
assertFalse("inactive-experiment is inactive", SwitchBoard.isInExperiment(c, "inactive-experiment"));
}
@Test
public void testExperimentValues() throws JSONException {
final Context c = RuntimeEnvironment.application;
assertTrue("active-experiment has values", SwitchBoard.hasExperimentValues(c, "active-experiment"));
assertFalse("inactive-experiment doesn't have values", SwitchBoard.hasExperimentValues(c, "inactive-experiment"));
final JSONObject values = SwitchBoard.getExperimentValuesFromJson(c, "active-experiment");
assertNotNull("active-experiment values are not null", values);
assertTrue("\"foo\" extra value is true", values.getBoolean("foo"));
}
@Test
public void testGetActiveExperiments() {
final Context c = RuntimeEnvironment.application;
final List<String> experiments = SwitchBoard.getActiveExperiments(c);
assertNotNull("List of active experiments is not null", experiments);
assertTrue("List of active experiments contains active-experiment", experiments.contains("active-experiment"));
assertFalse("List of active experiments does not contain inactive-experiment", experiments.contains("inactive-experiment"));
}
@Test
public void testOverride() {
final Context c = RuntimeEnvironment.application;
Experiments.setOverride(c, "active-experiment", false);
assertFalse("active-experiment is not active because of override", SwitchBoard.isInExperiment(c, "active-experiment"));
assertFalse("List of active experiments does not contain active-experiment", SwitchBoard.getActiveExperiments(c).contains("active-experiment"));
Experiments.clearOverride(c, "active-experiment");
assertTrue("active-experiment is active after override is cleared", SwitchBoard.isInExperiment(c, "active-experiment"));
assertTrue("List of active experiments contains active-experiment again", SwitchBoard.getActiveExperiments(c).contains("active-experiment"));
Experiments.setOverride(c, "inactive-experiment", true);
assertTrue("inactive-experiment is active because of override", SwitchBoard.isInExperiment(c, "inactive-experiment"));
assertTrue("List of active experiments contains inactive-experiment", SwitchBoard.getActiveExperiments(c).contains("inactive-experiment"));
Experiments.clearOverride(c, "inactive-experiment");
assertFalse("inactive-experiment is inactive after override is cleared", SwitchBoard.isInExperiment(c, "inactive-experiment"));
assertFalse("List of active experiments does not contain inactive-experiment again", SwitchBoard.getActiveExperiments(c).contains("inactive-experiment"));
}
@Test
public void testMatching() {
final Context c = RuntimeEnvironment.application;
assertTrue("is-experiment is matching", SwitchBoard.isInExperiment(c, "is-matching"));
assertFalse("is-not-matching is not matching", SwitchBoard.isInExperiment(c, "is-not-matching"));
}
@Test
public void testNotExisting() {
final Context c = RuntimeEnvironment.application;
assertFalse("F0O does not exists", SwitchBoard.isInExperiment(c, "F0O"));
assertFalse("BaAaz does not exists", SwitchBoard.hasExperimentValues(c, "BaAaz"));
}
}

View file

@ -1,114 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestBackoff {
private final String TEST_USERNAME = "johndoe";
private final String TEST_PASSWORD = "password";
private final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
private final long TEST_BACKOFF_IN_SECONDS = 1201;
/**
* Test that interpretHTTPFailure calls requestBackoff if
* X-Weave-Backoff is present.
*/
@Test
public void testBackoffCalledIfBackoffHeaderPresent() {
try {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback);
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
response.addHeader("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); // Backoff given in seconds.
session.interpretHTTPFailure(response); // This is synchronous...
assertEquals(false, callback.calledSuccess); // ... so we can test immediately.
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(true, callback.calledRequestBackoff);
assertEquals(TEST_BACKOFF_IN_SECONDS * 1000, callback.weaveBackoff); // Backoff returned in milliseconds.
} catch (Exception e) {
e.printStackTrace();
fail("Got exception.");
}
}
/**
* Test that interpretHTTPFailure does not call requestBackoff if
* X-Weave-Backoff is not present.
*/
@Test
public void testBackoffNotCalledIfBackoffHeaderNotPresent() {
try {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback);
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
session.interpretHTTPFailure(response); // This is synchronous...
assertEquals(false, callback.calledSuccess); // ... so we can test immediately.
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(false, callback.calledRequestBackoff);
} catch (Exception e) {
e.printStackTrace();
fail("Got exception.");
}
}
/**
* Test that interpretHTTPFailure calls requestBackoff with the
* largest specified value if X-Weave-Backoff and Retry-After are
* present.
*/
@Test
public void testBackoffCalledIfMultipleBackoffHeadersPresent() {
try {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback);
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
response.addHeader("Retry-After", Long.toString(TEST_BACKOFF_IN_SECONDS)); // Backoff given in seconds.
response.addHeader("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS + 1)); // If we now add a second header, the larger should be returned.
session.interpretHTTPFailure(response); // This is synchronous...
assertEquals(false, callback.calledSuccess); // ... so we can test immediately.
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(true, callback.calledRequestBackoff);
assertEquals((TEST_BACKOFF_IN_SECONDS + 1) * 1000, callback.weaveBackoff); // Backoff returned in milliseconds.
} catch (Exception e) {
e.printStackTrace();
fail("Got exception.");
}
}
}

View file

@ -1,23 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.Header;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.net.BrowserIDAuthHeaderProvider;
import static org.junit.Assert.assertEquals;
@RunWith(TestRunner.class)
public class TestBrowserIDAuthHeaderProvider {
@Test
public void testHeader() {
Header header = new BrowserIDAuthHeaderProvider("assertion").getAuthHeader(null, null, null);
assertEquals("authorization", header.getName().toLowerCase());
assertEquals("BrowserID assertion", header.getValue());
}
}

View file

@ -1,806 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.HttpStatus;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.android.sync.test.helpers.MockSyncClientsEngineStage;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.CommandHelpers;
import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
import org.mozilla.gecko.background.testhelpers.MockClientsDatabaseAccessor;
import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.CollectionKeys;
import org.mozilla.gecko.sync.CommandProcessor.Command;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* Some tests in this class run client/server multi-threaded code but JUnit assertions triggered
* from background threads do not fail the test. If you see unexplained connection-related test failures,
* an assertion on the server may have been thrown. Unfortunately, it is non-trivial to get the background
* threads to transfer failures back to the test thread so we leave the tests in this state for now.
*
* One reason the server might throw an assertion is if you have not installed the crypto policies. See
* https://wiki.mozilla.org/Mobile/Fennec/Android/Testing#JUnit4_tests for more information.
*/
@RunWith(TestRunner.class)
public class TestClientsEngineStage extends MockSyncClientsEngineStage {
public final static String LOG_TAG = "TestClientsEngSta";
public TestClientsEngineStage() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, URISyntaxException {
super();
session = initializeSession();
}
// Static so we can set it during the constructor. This is so evil.
private static MockGlobalSessionCallback callback;
private static GlobalSession initializeSession() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, URISyntaxException {
callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(USERNAME, new BasicAuthHeaderProvider(USERNAME, PASSWORD), new MockSharedPreferences());
config.syncKeyBundle = new KeyBundle(USERNAME, SYNC_KEY);
GlobalSession session = new MockClientsGlobalSession(config, callback);
session.config.setClusterURL(new URI(TEST_SERVER));
session.config.setCollectionKeys(CollectionKeys.generateCollectionKeys());
return session;
}
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
private static final String USERNAME = "john";
private static final String PASSWORD = "password";
private static final String SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
private HTTPServerTestHelper data = new HTTPServerTestHelper();
private int numRecordsFromGetRequest = 0;
private ArrayList<ClientRecord> expectedClients = new ArrayList<ClientRecord>();
private ArrayList<ClientRecord> downloadedClients = new ArrayList<ClientRecord>();
// For test purposes.
private ClientRecord lastComputedLocalClientRecord;
private ClientRecord uploadedRecord;
private String uploadBodyTimestamp;
private long uploadHeaderTimestamp;
private MockServer currentUploadMockServer;
private MockServer currentDownloadMockServer;
private boolean stubUpload = false;
protected static WaitHelper testWaiter() {
return WaitHelper.getTestWaiter();
}
@Override
protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) {
lastComputedLocalClientRecord = super.newLocalClientRecord(delegate);
return lastComputedLocalClientRecord;
}
@After
public void teardown() {
stubUpload = false;
getMockDataAccessor().resetVars();
}
@Override
public synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
if (db == null) {
db = new MockClientsDatabaseAccessor();
}
return db;
}
// For test use.
private MockClientsDatabaseAccessor getMockDataAccessor() {
return (MockClientsDatabaseAccessor) getClientsDatabaseAccessor();
}
private synchronized boolean mockDataAccessorIsClosed() {
if (db == null) {
return true;
}
return ((MockClientsDatabaseAccessor) db).closed;
}
@Override
protected ClientDownloadDelegate makeClientDownloadDelegate() {
return clientDownloadDelegate;
}
@Override
protected void downloadClientRecords() {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(currentDownloadMockServer);
super.downloadClientRecords();
}
@Override
protected void uploadClientRecord(CryptoRecord record) {
BaseResource.rewriteLocalhost = false;
if (stubUpload) {
session.advance();
return;
}
data.startHTTPServer(currentUploadMockServer);
super.uploadClientRecord(record);
}
@Override
protected void uploadClientRecords(JSONArray records) {
BaseResource.rewriteLocalhost = false;
if (stubUpload) {
return;
}
data.startHTTPServer(currentUploadMockServer);
super.uploadClientRecords(records);
}
public static class MockClientsGlobalSession extends MockGlobalSession {
private ClientsDataDelegate clientsDataDelegate = new MockClientsDataDelegate();
public MockClientsGlobalSession(SyncConfiguration config,
GlobalSessionCallback callback)
throws SyncConfigurationException,
IllegalArgumentException,
IOException,
NonObjectJSONException {
super(config, callback);
}
@Override
public ClientsDataDelegate getClientsDelegate() {
return clientsDataDelegate;
}
}
public class TestSuccessClientDownloadDelegate extends TestClientDownloadDelegate {
public TestSuccessClientDownloadDelegate(HTTPServerTestHelper data) {
super(data);
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
super.handleRequestFailure(response);
assertTrue(getMockDataAccessor().closed);
fail("Should not error.");
}
@Override
public void handleRequestError(Exception ex) {
super.handleRequestError(ex);
assertTrue(getMockDataAccessor().closed);
fail("Should not fail.");
}
@Override
public void handleWBO(CryptoRecord record) {
ClientRecord r;
try {
r = (ClientRecord) factory.createRecord(record.decrypt());
downloadedClients.add(r);
numRecordsFromGetRequest++;
} catch (Exception e) {
fail("handleWBO failed.");
}
}
}
public class TestHandleWBODownloadDelegate extends TestClientDownloadDelegate {
public TestHandleWBODownloadDelegate(HTTPServerTestHelper data) {
super(data);
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
super.handleRequestFailure(response);
assertTrue(getMockDataAccessor().closed);
fail("Should not error.");
}
@Override
public void handleRequestError(Exception ex) {
super.handleRequestError(ex);
assertTrue(getMockDataAccessor().closed);
ex.printStackTrace();
fail("Should not fail.");
}
}
public class MockSuccessClientUploadDelegate extends MockClientUploadDelegate {
public MockSuccessClientUploadDelegate(HTTPServerTestHelper data) {
super(data);
}
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
uploadHeaderTimestamp = response.normalizedWeaveTimestamp();
super.handleRequestSuccess(response);
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
super.handleRequestFailure(response);
fail("Should not fail.");
}
@Override
public void handleRequestError(Exception ex) {
super.handleRequestError(ex);
ex.printStackTrace();
fail("Should not error.");
}
}
public class MockFailureClientUploadDelegate extends MockClientUploadDelegate {
public MockFailureClientUploadDelegate(HTTPServerTestHelper data) {
super(data);
}
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
super.handleRequestSuccess(response);
fail("Should not succeed.");
}
@Override
public void handleRequestError(Exception ex) {
super.handleRequestError(ex);
fail("Should not fail.");
}
}
public class UploadMockServer extends MockServer {
@SuppressWarnings("unchecked")
private String postBodyForRecord(ClientRecord cr) {
final long now = cr.lastModified;
final BigDecimal modified = Utils.millisecondsToDecimalSeconds(now);
Logger.debug(LOG_TAG, "Now is " + now + " (" + modified + ")");
final JSONArray idArray = new JSONArray();
idArray.add(cr.guid);
final JSONObject result = new JSONObject();
result.put("modified", modified);
result.put("success", idArray);
result.put("failed", new JSONObject());
uploadBodyTimestamp = modified.toString();
return result.toJSONString();
}
private String putBodyForRecord(ClientRecord cr) {
final String modified = Utils.millisecondsToDecimalSecondsString(cr.lastModified);
uploadBodyTimestamp = modified;
return modified;
}
protected void handleUploadPUT(Request request, Response response) throws Exception {
Logger.debug(LOG_TAG, "Handling PUT: " + request.getPath());
// Save uploadedRecord to test against.
CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(request.getContent());
cryptoRecord.keyBundle = session.keyBundleForCollection(COLLECTION_NAME);
uploadedRecord = (ClientRecord) factory.createRecord(cryptoRecord.decrypt());
// Note: collection is not saved in CryptoRecord.toJSONObject() upon upload.
// So its value is null and is set here so ClientRecord.equals() may be used.
uploadedRecord.collection = lastComputedLocalClientRecord.collection;
// Create response body containing current timestamp.
long now = System.currentTimeMillis();
PrintStream bodyStream = this.handleBasicHeaders(request, response, 200, "application/json", now);
uploadedRecord.lastModified = now;
bodyStream.println(putBodyForRecord(uploadedRecord));
bodyStream.close();
}
protected void handleUploadPOST(Request request, Response response) throws Exception {
Logger.debug(LOG_TAG, "Handling POST: " + request.getPath());
String content = request.getContent();
Logger.debug(LOG_TAG, "Content is " + content);
JSONArray array = ExtendedJSONObject.parseJSONArray(content);
Logger.debug(LOG_TAG, "Content is " + array);
KeyBundle keyBundle = session.keyBundleForCollection(COLLECTION_NAME);
if (array.size() != 1) {
Logger.debug(LOG_TAG, "Expecting only one record! Fail!");
PrintStream bodyStream = this.handleBasicHeaders(request, response, 400, "text/plain");
bodyStream.println("Expecting only one record! Fail!");
bodyStream.close();
return;
}
CryptoRecord r = CryptoRecord.fromJSONRecord(new ExtendedJSONObject((JSONObject) array.get(0)));
r.keyBundle = keyBundle;
ClientRecord cr = (ClientRecord) factory.createRecord(r.decrypt());
cr.collection = lastComputedLocalClientRecord.collection;
uploadedRecord = cr;
Logger.debug(LOG_TAG, "Record is " + cr);
long now = System.currentTimeMillis();
PrintStream bodyStream = this.handleBasicHeaders(request, response, 200, "application/json", now);
cr.lastModified = now;
final String responseBody = postBodyForRecord(cr);
Logger.debug(LOG_TAG, "Response is " + responseBody);
bodyStream.println(responseBody);
bodyStream.close();
}
@Override
public void handle(Request request, Response response) {
try {
String method = request.getMethod();
Logger.debug(LOG_TAG, "Handling " + method);
if (method.equalsIgnoreCase("post")) {
handleUploadPOST(request, response);
} else if (method.equalsIgnoreCase("put")) {
handleUploadPUT(request, response);
} else {
PrintStream bodyStream = this.handleBasicHeaders(request, response, 404, "text/plain");
bodyStream.close();
}
} catch (Exception e) {
fail("Error handling uploaded client record in UploadMockServer.");
}
}
}
public class DownloadMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
try {
PrintStream bodyStream = this.handleBasicHeaders(request, response, 200, "application/newlines");
for (int i = 0; i < 5; i++) {
ClientRecord record = new ClientRecord();
if (i != 2) { // So we test null version.
record.version = Integer.toString(28 + i);
}
expectedClients.add(record);
CryptoRecord cryptoRecord = cryptoFromClient(record);
bodyStream.print(cryptoRecord.toJSONString() + "\n");
}
bodyStream.close();
} catch (IOException e) {
fail("Error handling downloaded client records in DownloadMockServer.");
}
}
}
public class DownloadLocalRecordMockServer extends MockServer {
@SuppressWarnings("unchecked")
@Override
public void handle(Request request, Response response) {
try {
PrintStream bodyStream = this.handleBasicHeaders(request, response, 200, "application/newlines");
ClientRecord record = new ClientRecord(session.getClientsDelegate().getAccountGUID());
// Timestamp on server is 10 seconds after local timestamp
// (would trigger 412 if upload was attempted).
CryptoRecord cryptoRecord = cryptoFromClient(record);
JSONObject object = cryptoRecord.toJSONObject();
final long modified = (setRecentClientRecordTimestamp() + 10000) / 1000;
Logger.debug(LOG_TAG, "Setting modified to " + modified);
object.put("modified", modified);
bodyStream.print(object.toJSONString() + "\n");
bodyStream.close();
} catch (IOException e) {
fail("Error handling downloaded client records in DownloadLocalRecordMockServer.");
}
}
}
private CryptoRecord cryptoFromClient(ClientRecord record) {
CryptoRecord cryptoRecord = record.getEnvelope();
cryptoRecord.keyBundle = clientDownloadDelegate.keyBundle();
try {
cryptoRecord.encrypt();
} catch (Exception e) {
fail("Cannot encrypt client record.");
}
return cryptoRecord;
}
private long setRecentClientRecordTimestamp() {
long timestamp = System.currentTimeMillis() - (CLIENTS_TTL_REFRESH - 1000);
session.config.persistServerClientRecordTimestamp(timestamp);
return timestamp;
}
private void performFailingUpload() {
// performNotify() occurs in MockGlobalSessionCallback.
testWaiter().performWait(new Runnable() {
@Override
public void run() {
clientUploadDelegate = new MockFailureClientUploadDelegate(data);
checkAndUpload();
}
});
}
@SuppressWarnings("unchecked")
@Test
public void testShouldUploadNoCommandsToProcess() throws NullCursorException {
// shouldUpload() returns true.
assertEquals(0, session.config.getPersistedServerClientRecordTimestamp());
assertFalse(shouldUploadLocalRecord);
assertTrue(shouldUpload());
// Set the timestamp to be a little earlier than refresh time,
// so shouldUpload() returns false.
setRecentClientRecordTimestamp();
assertFalse(0 == session.config.getPersistedServerClientRecordTimestamp());
assertFalse(shouldUploadLocalRecord);
assertFalse(shouldUpload());
// Now simulate observing a client record with the incorrect version.
ClientRecord outdatedRecord = new ClientRecord("dontmatter12", "clients", System.currentTimeMillis(), false);
outdatedRecord.version = getLocalClientVersion();
outdatedRecord.protocols = getLocalClientProtocols();
handleDownloadedLocalRecord(outdatedRecord);
assertEquals(outdatedRecord.lastModified, session.config.getPersistedServerClientRecordTimestamp());
assertFalse(shouldUploadLocalRecord);
assertFalse(shouldUpload());
outdatedRecord.version = outdatedRecord.version + "a1";
handleDownloadedLocalRecord(outdatedRecord);
// Now we think we need to upload because the version is outdated.
assertTrue(shouldUploadLocalRecord);
assertTrue(shouldUpload());
shouldUploadLocalRecord = false;
assertFalse(shouldUpload());
// If the protocol list is missing or wrong, we should reupload.
outdatedRecord.protocols = new JSONArray();
handleDownloadedLocalRecord(outdatedRecord);
assertTrue(shouldUpload());
shouldUploadLocalRecord = false;
assertFalse(shouldUpload());
outdatedRecord.protocols.add("1.0");
handleDownloadedLocalRecord(outdatedRecord);
assertTrue(shouldUpload());
}
@SuppressWarnings("unchecked")
@Test
public void testShouldUploadProcessCommands() throws NullCursorException {
// shouldUpload() returns false since array is size 0 and
// it has not been long enough yet to require an upload.
processCommands(new JSONArray());
setRecentClientRecordTimestamp();
assertFalse(shouldUploadLocalRecord);
assertFalse(shouldUpload());
// shouldUpload() returns true since array is size 1 even though
// it has not been long enough yet to require an upload.
JSONArray commands = new JSONArray();
commands.add(new JSONObject());
processCommands(commands);
setRecentClientRecordTimestamp();
assertEquals(1, commands.size());
assertTrue(shouldUploadLocalRecord);
assertTrue(shouldUpload());
}
@Test
public void testWipeAndStoreShouldNotWipe() {
assertFalse(shouldWipe);
wipeAndStore(new ClientRecord());
assertFalse(shouldWipe);
assertFalse(getMockDataAccessor().clientsTableWiped);
assertTrue(getMockDataAccessor().storedRecord);
}
@Test
public void testWipeAndStoreShouldWipe() {
assertFalse(shouldWipe);
shouldWipe = true;
wipeAndStore(new ClientRecord());
assertFalse(shouldWipe);
assertTrue(getMockDataAccessor().clientsTableWiped);
assertTrue(getMockDataAccessor().storedRecord);
}
@Test
public void testDownloadClientRecord() {
// Make sure no upload occurs after a download so we can
// test download in isolation.
stubUpload = true;
currentDownloadMockServer = new DownloadMockServer();
// performNotify() occurs in MockGlobalSessionCallback.
testWaiter().performWait(new Runnable() {
@Override
public void run() {
clientDownloadDelegate = new TestSuccessClientDownloadDelegate(data);
downloadClientRecords();
}
});
assertEquals(expectedClients.size(), numRecordsFromGetRequest);
for (int i = 0; i < downloadedClients.size(); i++) {
final ClientRecord downloaded = downloadedClients.get(i);
final ClientRecord expected = expectedClients.get(i);
assertTrue(expected.guid.equals(downloaded.guid));
assertEquals(expected.version, downloaded.version);
}
assertTrue(mockDataAccessorIsClosed());
}
@Test
public void testCheckAndUploadClientRecord() {
uploadAttemptsCount.set(MAX_UPLOAD_FAILURE_COUNT);
assertFalse(shouldUploadLocalRecord);
assertEquals(0, session.config.getPersistedServerClientRecordTimestamp());
currentUploadMockServer = new UploadMockServer();
// performNotify() occurs in MockGlobalSessionCallback.
testWaiter().performWait(new Runnable() {
@Override
public void run() {
clientUploadDelegate = new MockSuccessClientUploadDelegate(data);
checkAndUpload();
}
});
// Test ClientUploadDelegate.handleRequestSuccess().
Logger.debug(LOG_TAG, "Last computed local client record: " + lastComputedLocalClientRecord.guid);
Logger.debug(LOG_TAG, "Uploaded client record: " + uploadedRecord.guid);
assertTrue(lastComputedLocalClientRecord.equalPayloads(uploadedRecord));
assertEquals(0, uploadAttemptsCount.get());
assertTrue(callback.calledSuccess);
assertFalse(0 == session.config.getPersistedServerClientRecordTimestamp());
// Body and header are the same.
assertEquals(Utils.decimalSecondsToMilliseconds(uploadBodyTimestamp),
session.config.getPersistedServerClientsTimestamp());
assertEquals(uploadedRecord.lastModified,
session.config.getPersistedServerClientRecordTimestamp());
assertEquals(uploadHeaderTimestamp, session.config.getPersistedServerClientsTimestamp());
}
@Test // client/server multi-threaded
public void testDownloadHasOurRecord() {
// Make sure no upload occurs after a download so we can
// test download in isolation.
stubUpload = true;
// We've uploaded our local record recently.
long initialTimestamp = setRecentClientRecordTimestamp();
currentDownloadMockServer = new DownloadLocalRecordMockServer();
// performNotify() occurs in MockGlobalSessionCallback.
testWaiter().performWait(new Runnable() {
@Override
public void run() {
clientDownloadDelegate = new TestHandleWBODownloadDelegate(data);
downloadClientRecords();
}
});
// Timestamp got updated (but not reset) since we downloaded our record
assertFalse(0 == session.config.getPersistedServerClientRecordTimestamp());
assertTrue(initialTimestamp < session.config.getPersistedServerClientRecordTimestamp());
assertTrue(mockDataAccessorIsClosed());
}
@Test
public void testResetTimestampOnDownload() {
// Make sure no upload occurs after a download so we can
// test download in isolation.
stubUpload = true;
currentDownloadMockServer = new DownloadMockServer();
// performNotify() occurs in MockGlobalSessionCallback.
testWaiter().performWait(new Runnable() {
@Override
public void run() {
clientDownloadDelegate = new TestHandleWBODownloadDelegate(data);
downloadClientRecords();
}
});
// Timestamp got reset since our record wasn't downloaded.
assertEquals(0, session.config.getPersistedServerClientRecordTimestamp());
assertTrue(mockDataAccessorIsClosed());
}
/**
* The following 8 tests are for ClientUploadDelegate.handleRequestFailure().
* for the varying values of uploadAttemptsCount, shouldUploadLocalRecord,
* and the type of server error.
*
* The first 4 are for 412 Precondition Failures.
* The second 4 represent the functionality given any other type of variable.
*/
@Test
public void testHandle412UploadFailureLowCount() {
assertFalse(shouldUploadLocalRecord);
currentUploadMockServer = new MockServer(HttpStatus.SC_PRECONDITION_FAILED, null);
assertEquals(0, uploadAttemptsCount.get());
performFailingUpload();
assertEquals(0, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandle412UploadFailureHighCount() {
assertFalse(shouldUploadLocalRecord);
currentUploadMockServer = new MockServer(HttpStatus.SC_PRECONDITION_FAILED, null);
uploadAttemptsCount.set(MAX_UPLOAD_FAILURE_COUNT);
performFailingUpload();
assertEquals(MAX_UPLOAD_FAILURE_COUNT, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandle412UploadFailureLowCountWithCommand() {
shouldUploadLocalRecord = true;
currentUploadMockServer = new MockServer(HttpStatus.SC_PRECONDITION_FAILED, null);
assertEquals(0, uploadAttemptsCount.get());
performFailingUpload();
assertEquals(0, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandle412UploadFailureHighCountWithCommand() {
shouldUploadLocalRecord = true;
currentUploadMockServer = new MockServer(HttpStatus.SC_PRECONDITION_FAILED, null);
uploadAttemptsCount.set(MAX_UPLOAD_FAILURE_COUNT);
performFailingUpload();
assertEquals(MAX_UPLOAD_FAILURE_COUNT, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandleMiscUploadFailureLowCount() {
currentUploadMockServer = new MockServer(HttpStatus.SC_BAD_REQUEST, null);
assertFalse(shouldUploadLocalRecord);
assertEquals(0, uploadAttemptsCount.get());
performFailingUpload();
assertEquals(0, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandleMiscUploadFailureHighCount() {
currentUploadMockServer = new MockServer(HttpStatus.SC_BAD_REQUEST, null);
assertFalse(shouldUploadLocalRecord);
uploadAttemptsCount.set(MAX_UPLOAD_FAILURE_COUNT);
performFailingUpload();
assertEquals(MAX_UPLOAD_FAILURE_COUNT, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandleMiscUploadFailureHighCountWithCommands() {
currentUploadMockServer = new MockServer(HttpStatus.SC_BAD_REQUEST, null);
shouldUploadLocalRecord = true;
uploadAttemptsCount.set(MAX_UPLOAD_FAILURE_COUNT);
performFailingUpload();
assertEquals(MAX_UPLOAD_FAILURE_COUNT + 1, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
@Test
public void testHandleMiscUploadFailureMaxAttempts() {
currentUploadMockServer = new MockServer(HttpStatus.SC_BAD_REQUEST, null);
shouldUploadLocalRecord = true;
assertEquals(0, uploadAttemptsCount.get());
performFailingUpload();
assertEquals(MAX_UPLOAD_FAILURE_COUNT + 1, uploadAttemptsCount.get());
assertTrue(callback.calledError);
}
class TestAddCommandsMockClientsDatabaseAccessor extends MockClientsDatabaseAccessor {
@Override
public List<Command> fetchCommandsForClient(String accountGUID) throws NullCursorException {
List<Command> commands = new ArrayList<Command>();
commands.add(CommandHelpers.getCommand1());
commands.add(CommandHelpers.getCommand2());
commands.add(CommandHelpers.getCommand3());
commands.add(CommandHelpers.getCommand4());
return commands;
}
}
@Test
public void testAddCommandsToUnversionedClient() throws NullCursorException {
db = new TestAddCommandsMockClientsDatabaseAccessor();
final ClientRecord remoteRecord = new ClientRecord();
remoteRecord.version = null;
final String expectedGUID = remoteRecord.guid;
this.addCommands(remoteRecord);
assertEquals(1, modifiedClientsToUpload.size());
final ClientRecord recordToUpload = modifiedClientsToUpload.get(0);
assertEquals(4, recordToUpload.commands.size());
assertEquals(expectedGUID, recordToUpload.guid);
assertEquals(null, recordToUpload.version);
}
@Test
public void testAddCommandsToVersionedClient() throws NullCursorException {
db = new TestAddCommandsMockClientsDatabaseAccessor();
final ClientRecord remoteRecord = new ClientRecord();
remoteRecord.version = "12a1";
final String expectedGUID = remoteRecord.guid;
this.addCommands(remoteRecord);
assertEquals(1, modifiedClientsToUpload.size());
final ClientRecord recordToUpload = modifiedClientsToUpload.get(0);
assertEquals(4, recordToUpload.commands.size());
assertEquals(expectedGUID, recordToUpload.guid);
assertEquals("12a1", recordToUpload.version);
}
@Test
public void testLastModifiedTimestamp() throws NullCursorException {
// If we uploaded a record a moment ago, we shouldn't upload another.
final long now = System.currentTimeMillis() - 1;
session.config.persistServerClientRecordTimestamp(now);
assertEquals(now, session.config.getPersistedServerClientRecordTimestamp());
assertFalse(shouldUploadLocalRecord);
assertFalse(shouldUpload());
// But if we change our client data, we should upload.
session.getClientsDelegate().setClientName("new name", System.currentTimeMillis());
assertTrue(shouldUpload());
}
}

View file

@ -1,72 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import ch.boye.httpclientandroidlib.Header;
import static org.junit.Assert.assertEquals;
/**
* Test the transfer of a UTF-8 string from desktop, and ensure that it results in the
* correct hashed Basic Auth header.
*/
@RunWith(TestRunner.class)
public class TestCredentialsEndToEnd {
public static final String REAL_PASSWORD = "pïgéons1";
public static final String USERNAME = "utvm3mk6hnngiir2sp4jsxf2uvoycrv6";
public static final String DESKTOP_PASSWORD_JSON = "{\"password\":\"pïgéons1\"}";
public static final String BTOA_PASSWORD = "cMOvZ8Opb25zMQ==";
public static final int DESKTOP_ASSERTED_SIZE = 10;
public static final String DESKTOP_BASIC_AUTH = "Basic dXR2bTNtazZobm5naWlyMnNwNGpzeGYydXZveWNydjY6cMOvZ8Opb25zMQ==";
private String getCreds(String password) {
Header authenticate = new BasicAuthHeaderProvider(USERNAME, password).getAuthHeader(null, null, null);
return authenticate.getValue();
}
@SuppressWarnings("static-method")
@Test
public void testUTF8() throws UnsupportedEncodingException {
final String in = "pïgéons1";
final String out = "pïgéons1";
assertEquals(out, Utils.decodeUTF8(in));
}
@Test
public void testAuthHeaderFromPassword() throws NonObjectJSONException, IOException {
final ExtendedJSONObject parsed = new ExtendedJSONObject(DESKTOP_PASSWORD_JSON);
final String password = parsed.getString("password");
final String decoded = Utils.decodeUTF8(password);
final byte[] expectedBytes = Utils.decodeBase64(BTOA_PASSWORD);
final String expected = new String(expectedBytes, "UTF-8");
assertEquals(DESKTOP_ASSERTED_SIZE, password.length());
assertEquals(expected, decoded);
System.out.println("Retrieved password: " + password);
System.out.println("Expected password: " + expected);
System.out.println("Rescued password: " + decoded);
assertEquals(getCreds(expected), getCreds(decoded));
assertEquals(getCreds(decoded), DESKTOP_BASIC_AUTH);
}
// Note that we do *not* have a test for the J-PAKE setup process
// (SetupSyncActivity) that actually stores credentials and requires
// decodeUTF8. This will have to suffice.
}

View file

@ -1,436 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
import junit.framework.AssertionFailedError;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
import org.mozilla.android.sync.test.helpers.MockResourceDelegate;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.testhelpers.MockAbstractNonRepositorySyncStage;
import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
import org.mozilla.gecko.background.testhelpers.MockPrefsGlobalSession;
import org.mozilla.gecko.background.testhelpers.MockServerSyncStage;
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.MetaGlobal;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
import org.mozilla.gecko.sync.stage.AndroidBrowserBookmarksServerSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import org.mozilla.gecko.sync.stage.NoSuchStageException;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestGlobalSession {
private int TEST_PORT = HTTPServerTestHelper.getTestPort();
private final String TEST_CLUSTER_URL = "http://localhost:" + TEST_PORT;
private final String TEST_USERNAME = "johndoe";
private final String TEST_PASSWORD = "password";
private final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
private final long TEST_BACKOFF_IN_SECONDS = 2401;
public static WaitHelper getTestWaiter() {
return WaitHelper.getTestWaiter();
}
@Test
public void testGetSyncStagesBy() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException, NoSuchStageException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
GlobalSession s = MockPrefsGlobalSession.getSession(TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY),
callback, /* context */ null, null);
assertTrue(s.getSyncStageByName(Stage.syncBookmarks) instanceof AndroidBrowserBookmarksServerSyncStage);
final Set<String> empty = new HashSet<String>();
final Set<String> bookmarksAndTabsNames = new HashSet<String>();
bookmarksAndTabsNames.add("bookmarks");
bookmarksAndTabsNames.add("tabs");
final Set<GlobalSyncStage> bookmarksAndTabsSyncStages = new HashSet<GlobalSyncStage>();
GlobalSyncStage bookmarksStage = s.getSyncStageByName("bookmarks");
GlobalSyncStage tabsStage = s.getSyncStageByName(Stage.syncTabs);
bookmarksAndTabsSyncStages.add(bookmarksStage);
bookmarksAndTabsSyncStages.add(tabsStage);
final Set<Stage> bookmarksAndTabsEnums = new HashSet<Stage>();
bookmarksAndTabsEnums.add(Stage.syncBookmarks);
bookmarksAndTabsEnums.add(Stage.syncTabs);
assertTrue(s.getSyncStagesByName(empty).isEmpty());
assertEquals(bookmarksAndTabsSyncStages, new HashSet<GlobalSyncStage>(s.getSyncStagesByName(bookmarksAndTabsNames)));
assertEquals(bookmarksAndTabsSyncStages, new HashSet<GlobalSyncStage>(s.getSyncStagesByEnum(bookmarksAndTabsEnums)));
}
/**
* Test that handleHTTPError does in fact backoff.
*/
@Test
public void testBackoffCalledByHandleHTTPError() {
try {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback);
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.setHeader("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); // Backoff given in seconds.
getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
session.handleHTTPError(new SyncStorageResponse(response), "Illegal method/protocol");
}
}));
assertEquals(false, callback.calledSuccess);
assertEquals(true, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(true, callback.calledRequestBackoff);
assertEquals(TEST_BACKOFF_IN_SECONDS * 1000, callback.weaveBackoff); // Backoff returned in milliseconds.
} catch (Exception e) {
e.printStackTrace();
fail("Got exception.");
}
}
/**
* Test that a trivially successful GlobalSession does not fail or backoff.
*/
@Test
public void testSuccessCalledAfterStages() {
try {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback);
getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
try {
session.start();
} catch (Exception e) {
final AssertionFailedError error = new AssertionFailedError();
error.initCause(e);
getTestWaiter().performNotify(error);
}
}
}));
assertEquals(true, callback.calledSuccess);
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(false, callback.calledRequestBackoff);
} catch (Exception e) {
e.printStackTrace();
fail("Got exception.");
}
}
/**
* Test that a failing GlobalSession does in fact fail and back off.
*/
@Test
public void testBackoffCalledInStages() {
try {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
// Stage fakes a 503 and sets X-Weave-Backoff header to the given seconds.
final GlobalSyncStage stage = new MockAbstractNonRepositorySyncStage() {
@Override
public void execute() {
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.addHeader("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS)); // Backoff given in seconds.
session.handleHTTPError(new SyncStorageResponse(response), "Failure fetching info/collections.");
}
};
// Session installs fake stage to fetch info/collections.
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback)
.withStage(Stage.fetchInfoCollections, stage);
getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
try {
session.start();
} catch (Exception e) {
final AssertionFailedError error = new AssertionFailedError();
error.initCause(e);
getTestWaiter().performNotify(error);
}
}
}));
assertEquals(false, callback.calledSuccess);
assertEquals(true, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(true, callback.calledRequestBackoff);
assertEquals(TEST_BACKOFF_IN_SECONDS * 1000, callback.weaveBackoff); // Backoff returned in milliseconds.
} catch (Exception e) {
e.printStackTrace();
fail("Got exception.");
}
}
private HTTPServerTestHelper data = new HTTPServerTestHelper();
@SuppressWarnings("static-method")
@Before
public void setUp() {
BaseResource.rewriteLocalhost = false;
}
public void doRequest() {
final WaitHelper innerWaitHelper = new WaitHelper();
innerWaitHelper.performWait(new Runnable() {
@Override
public void run() {
try {
final BaseResource r = new BaseResource(TEST_CLUSTER_URL);
r.delegate = new MockResourceDelegate(innerWaitHelper);
r.get();
} catch (URISyntaxException e) {
innerWaitHelper.performNotify(e);
}
}
});
}
public MockGlobalSessionCallback doTestSuccess(final boolean stageShouldBackoff, final boolean stageShouldAdvance) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException {
MockServer server = new MockServer() {
@Override
public void handle(Request request, Response response) {
if (stageShouldBackoff) {
response.addValue("X-Weave-Backoff", Long.toString(TEST_BACKOFF_IN_SECONDS));
}
super.handle(request, response);
}
};
final MockServerSyncStage stage = new MockServerSyncStage() {
@Override
public void execute() {
// We should have installed our HTTP response observer before starting the sync.
assertTrue(BaseResource.isHttpResponseObserver(session));
doRequest();
if (stageShouldAdvance) {
session.advance();
return;
}
session.abort(null, "Stage intentionally failed.");
}
};
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), new MockSharedPreferences(), new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY));
final GlobalSession session = new MockGlobalSession(config, callback)
.withStage(Stage.syncBookmarks, stage);
data.startHTTPServer(server);
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
try {
session.start();
} catch (Exception e) {
final AssertionFailedError error = new AssertionFailedError();
error.initCause(e);
WaitHelper.getTestWaiter().performNotify(error);
}
}
}));
data.stopHTTPServer();
// We should have uninstalled our HTTP response observer when the session is terminated.
assertFalse(BaseResource.isHttpResponseObserver(session));
return callback;
}
@Test
public void testOnSuccessBackoffAdvanced() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(true, true);
assertTrue(callback.calledError); // TODO: this should be calledAborted.
assertTrue(callback.calledRequestBackoff);
assertEquals(1000 * TEST_BACKOFF_IN_SECONDS, callback.weaveBackoff);
}
@Test
public void testOnSuccessBackoffAborted() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(true, false);
assertTrue(callback.calledError); // TODO: this should be calledAborted.
assertTrue(callback.calledRequestBackoff);
assertEquals(1000 * TEST_BACKOFF_IN_SECONDS, callback.weaveBackoff);
}
@Test
public void testOnSuccessNoBackoffAdvanced() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(false, true);
assertTrue(callback.calledSuccess);
assertFalse(callback.calledRequestBackoff);
}
@Test
public void testOnSuccessNoBackoffAborted() throws SyncConfigurationException,
IllegalArgumentException, NonObjectJSONException, IOException,
CryptoException {
MockGlobalSessionCallback callback = doTestSuccess(false, false);
assertTrue(callback.calledError); // TODO: this should be calledAborted.
assertFalse(callback.calledRequestBackoff);
}
@Test
public void testGenerateNewMetaGlobalNonePersisted() throws Exception {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
final GlobalSession session = MockPrefsGlobalSession.getSession(TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null);
// Verify we fill in all of our known engines when none are persisted.
session.config.enabledEngineNames = null;
MetaGlobal mg = session.generateNewMetaGlobal();
assertEquals(Long.valueOf(GlobalSession.STORAGE_VERSION), mg.getStorageVersion());
assertEquals(VersionConstants.BOOKMARKS_ENGINE_VERSION, mg.getEngines().getObject("bookmarks").getIntegerSafely("version").intValue());
assertEquals(VersionConstants.CLIENTS_ENGINE_VERSION, mg.getEngines().getObject("clients").getIntegerSafely("version").intValue());
List<String> namesList = new ArrayList<String>(mg.getEnabledEngineNames());
Collections.sort(namesList);
String[] names = namesList.toArray(new String[namesList.size()]);
String[] expected = new String[] { "bookmarks", "clients", "forms", "history", "passwords", "tabs" };
assertArrayEquals(expected, names);
}
@Test
public void testGenerateNewMetaGlobalSomePersisted() throws Exception {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
final GlobalSession session = MockPrefsGlobalSession.getSession(TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null);
// Verify we preserve engines with version 0 if some are persisted.
session.config.enabledEngineNames = new HashSet<String>();
session.config.enabledEngineNames.add("bookmarks");
session.config.enabledEngineNames.add("clients");
session.config.enabledEngineNames.add("addons");
session.config.enabledEngineNames.add("prefs");
MetaGlobal mg = session.generateNewMetaGlobal();
assertEquals(Long.valueOf(GlobalSession.STORAGE_VERSION), mg.getStorageVersion());
assertEquals(VersionConstants.BOOKMARKS_ENGINE_VERSION, mg.getEngines().getObject("bookmarks").getIntegerSafely("version").intValue());
assertEquals(VersionConstants.CLIENTS_ENGINE_VERSION, mg.getEngines().getObject("clients").getIntegerSafely("version").intValue());
assertEquals(0, mg.getEngines().getObject("addons").getIntegerSafely("version").intValue());
assertEquals(0, mg.getEngines().getObject("prefs").getIntegerSafely("version").intValue());
List<String> namesList = new ArrayList<String>(mg.getEnabledEngineNames());
Collections.sort(namesList);
String[] names = namesList.toArray(new String[namesList.size()]);
String[] expected = new String[] { "addons", "bookmarks", "clients", "prefs" };
assertArrayEquals(expected, names);
}
@Test
public void testUploadUpdatedMetaGlobal() throws Exception {
// Set up session with meta/global.
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
final GlobalSession session = MockPrefsGlobalSession.getSession(TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null);
session.config.metaGlobal = session.generateNewMetaGlobal();
session.enginesToUpdate.clear();
// Set enabledEngines in meta/global, including a "new engine."
String[] origEngines = new String[] { "bookmarks", "clients", "forms", "history", "tabs", "new-engine" };
ExtendedJSONObject origEnginesJSONObject = new ExtendedJSONObject();
for (String engineName : origEngines) {
EngineSettings mockEngineSettings = new EngineSettings(Utils.generateGuid(), Integer.valueOf(0));
origEnginesJSONObject.put(engineName, mockEngineSettings.toJSONObject());
}
session.config.metaGlobal.setEngines(origEnginesJSONObject);
// Engines to remove.
String[] toRemove = new String[] { "bookmarks", "tabs" };
for (String name : toRemove) {
session.removeEngineFromMetaGlobal(name);
}
// Engines to add.
String[] toAdd = new String[] { "passwords" };
for (String name : toAdd) {
String syncId = Utils.generateGuid();
session.recordForMetaGlobalUpdate(name, new EngineSettings(syncId, Integer.valueOf(1)));
}
// Update engines.
session.uploadUpdatedMetaGlobal();
// Check resulting enabledEngines.
Set<String> expected = new HashSet<String>();
for (String name : origEngines) {
expected.add(name);
}
for (String name : toRemove) {
expected.remove(name);
}
for (String name : toAdd) {
expected.add(name);
}
assertEquals(expected, session.config.metaGlobal.getEnabledEngineNames());
}
public void testStageAdvance() {
assertEquals(GlobalSession.nextStage(Stage.idle), Stage.checkPreconditions);
assertEquals(GlobalSession.nextStage(Stage.completed), Stage.idle);
}
}

View file

@ -1,29 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.Utils;
import static org.junit.Assert.assertEquals;
@RunWith(TestRunner.class)
public class TestHeaderParsing {
@SuppressWarnings("static-method")
@Test
public void testDecimalSecondsToMilliseconds() {
assertEquals(Utils.decimalSecondsToMilliseconds(""), -1);
assertEquals(Utils.decimalSecondsToMilliseconds("1234.1.1"), -1);
assertEquals(Utils.decimalSecondsToMilliseconds("1234"), 1234000);
assertEquals(Utils.decimalSecondsToMilliseconds("1234.123"), 1234123);
assertEquals(Utils.decimalSecondsToMilliseconds("1234.12"), 1234120);
assertEquals("1234.000", Utils.millisecondsToDecimalSecondsString(1234000));
assertEquals("1234.123", Utils.millisecondsToDecimalSecondsString(1234123));
assertEquals("1234.120", Utils.millisecondsToDecimalSecondsString(1234120));
}
}

View file

@ -1,115 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.SyncStorageCollectionRequest;
import org.mozilla.gecko.sync.net.SyncStorageCollectionRequestDelegate;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestLineByLineHandling {
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
private static final String LOG_TAG = "TestLineByLineHandling";
static String STORAGE_URL = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/lines";
private HTTPServerTestHelper data = new HTTPServerTestHelper();
public ArrayList<String> lines = new ArrayList<String>();
public class LineByLineMockServer extends MockServer {
public void handle(Request request, Response response) {
try {
System.out.println("Handling line-by-line request...");
PrintStream bodyStream = this.handleBasicHeaders(request, response, 200, "application/newlines");
bodyStream.print("First line.\n");
bodyStream.print("Second line.\n");
bodyStream.print("Third line.\n");
bodyStream.print("Fourth line.\n");
bodyStream.close();
} catch (IOException e) {
System.err.println("Oops.");
}
}
}
public class BaseLineByLineDelegate extends
SyncStorageCollectionRequestDelegate {
@Override
public void handleRequestProgress(String progress) {
lines.add(progress);
}
@Override
public AuthHeaderProvider getAuthHeaderProvider() {
return null;
}
@Override
public String ifUnmodifiedSince() {
return null;
}
@Override
public void handleRequestSuccess(SyncStorageResponse res) {
Logger.info(LOG_TAG, "Request success.");
assertTrue(res.wasSuccessful());
assertTrue(res.httpResponse().containsHeader("X-Weave-Timestamp"));
assertEquals(lines.size(), 4);
assertEquals(lines.get(0), "First line.");
assertEquals(lines.get(1), "Second line.");
assertEquals(lines.get(2), "Third line.");
assertEquals(lines.get(3), "Fourth line.");
data.stopHTTPServer();
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
Logger.info(LOG_TAG, "Got request failure: " + response);
BaseResource.consumeEntity(response);
fail("Should not be called.");
}
@Override
public void handleRequestError(Exception ex) {
Logger.error(LOG_TAG, "Got request error: ", ex);
fail("Should not be called.");
}
}
@Test
public void testLineByLine() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new LineByLineMockServer());
Logger.info(LOG_TAG, "Server started.");
SyncStorageCollectionRequest r = new SyncStorageCollectionRequest(new URI(STORAGE_URL));
SyncStorageCollectionRequestDelegate delegate = new BaseLineByLineDelegate();
r.delegate = delegate;
r.get();
// Server is stopped in the callback.
}
}

View file

@ -1,347 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.MetaGlobal;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.delegates.MetaGlobalDelegate;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestMetaGlobal {
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
private static final String TEST_SYNC_ID = "foobar";
public static final String USER_PASS = "c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd:password";
public static final String META_URL = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/meta/global";
private HTTPServerTestHelper data = new HTTPServerTestHelper();
public static final String TEST_DECLINED_META_GLOBAL_RESPONSE =
"{\"id\":\"global\"," +
"\"payload\":" +
"\"{\\\"syncID\\\":\\\"zPSQTm7WBVWB\\\"," +
"\\\"declined\\\":[\\\"bookmarks\\\"]," +
"\\\"storageVersion\\\":5," +
"\\\"engines\\\":{" +
"\\\"clients\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"fDg0MS5bDtV7\\\"}," +
"\\\"forms\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"GXF29AFprnvc\\\"}," +
"\\\"history\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"av75g4vm-_rp\\\"}," +
"\\\"passwords\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"LT_ACGpuKZ6a\\\"}," +
"\\\"prefs\\\":{\\\"version\\\":2,\\\"syncID\\\":\\\"-3nsksP9wSAs\\\"}," +
"\\\"tabs\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"W4H5lOMChkYA\\\"}}}\"," +
"\"username\":\"5817483\"," +
"\"modified\":1.32046073744E9}";
public static final String TEST_META_GLOBAL_RESPONSE =
"{\"id\":\"global\"," +
"\"payload\":" +
"\"{\\\"syncID\\\":\\\"zPSQTm7WBVWB\\\"," +
"\\\"storageVersion\\\":5," +
"\\\"engines\\\":{" +
"\\\"clients\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"fDg0MS5bDtV7\\\"}," +
"\\\"bookmarks\\\":{\\\"version\\\":2,\\\"syncID\\\":\\\"NNaQr6_F-9dm\\\"}," +
"\\\"forms\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"GXF29AFprnvc\\\"}," +
"\\\"history\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"av75g4vm-_rp\\\"}," +
"\\\"passwords\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"LT_ACGpuKZ6a\\\"}," +
"\\\"prefs\\\":{\\\"version\\\":2,\\\"syncID\\\":\\\"-3nsksP9wSAs\\\"}," +
"\\\"tabs\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"W4H5lOMChkYA\\\"}}}\"," +
"\"username\":\"5817483\"," +
"\"modified\":1.32046073744E9}";
public static final String TEST_META_GLOBAL_NO_PAYLOAD_RESPONSE = "{\"id\":\"global\"," +
"\"username\":\"5817483\",\"modified\":1.32046073744E9}";
public static final String TEST_META_GLOBAL_MALFORMED_PAYLOAD_RESPONSE = "{\"id\":\"global\"," +
"\"payload\":\"{!!!}\"," +
"\"username\":\"5817483\",\"modified\":1.32046073744E9}";
public static final String TEST_META_GLOBAL_EMPTY_PAYLOAD_RESPONSE = "{\"id\":\"global\"," +
"\"payload\":\"{}\"," +
"\"username\":\"5817483\",\"modified\":1.32046073744E9}";
public MetaGlobal global;
@SuppressWarnings("static-method")
@Before
public void setUp() {
BaseResource.rewriteLocalhost = false;
global = new MetaGlobal(META_URL, new BasicAuthHeaderProvider(USER_PASS));
}
@SuppressWarnings("static-method")
@Test
public void testSyncID() {
global.setSyncID("foobar");
assertEquals(global.getSyncID(), "foobar");
}
public class MockMetaGlobalFetchDelegate implements MetaGlobalDelegate {
boolean successCalled = false;
MetaGlobal successGlobal = null;
SyncStorageResponse successResponse = null;
boolean failureCalled = false;
SyncStorageResponse failureResponse = null;
boolean errorCalled = false;
Exception errorException = null;
boolean missingCalled = false;
MetaGlobal missingGlobal = null;
SyncStorageResponse missingResponse = null;
public void handleSuccess(MetaGlobal global, SyncStorageResponse response) {
successCalled = true;
successGlobal = global;
successResponse = response;
WaitHelper.getTestWaiter().performNotify();
}
public void handleFailure(SyncStorageResponse response) {
failureCalled = true;
failureResponse = response;
WaitHelper.getTestWaiter().performNotify();
}
public void handleError(Exception e) {
errorCalled = true;
errorException = e;
WaitHelper.getTestWaiter().performNotify();
}
public void handleMissing(MetaGlobal global, SyncStorageResponse response) {
missingCalled = true;
missingGlobal = global;
missingResponse = response;
WaitHelper.getTestWaiter().performNotify();
}
}
public MockMetaGlobalFetchDelegate doFetch(final MetaGlobal global) {
final MockMetaGlobalFetchDelegate delegate = new MockMetaGlobalFetchDelegate();
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
global.fetch(delegate);
}
}));
return delegate;
}
@Test
public void testFetchMissing() {
MockServer missingMetaGlobalServer = new MockServer(404, "{}");
global.setSyncID(TEST_SYNC_ID);
assertEquals(TEST_SYNC_ID, global.getSyncID());
data.startHTTPServer(missingMetaGlobalServer);
final MockMetaGlobalFetchDelegate delegate = doFetch(global);
data.stopHTTPServer();
assertTrue(delegate.missingCalled);
assertEquals(404, delegate.missingResponse.getStatusCode());
assertEquals(TEST_SYNC_ID, delegate.missingGlobal.getSyncID());
}
@Test
public void testFetchExisting() {
MockServer existingMetaGlobalServer = new MockServer(200, TEST_META_GLOBAL_RESPONSE);
assertNull(global.getSyncID());
assertNull(global.getEngines());
assertNull(global.getStorageVersion());
data.startHTTPServer(existingMetaGlobalServer);
final MockMetaGlobalFetchDelegate delegate = doFetch(global);
data.stopHTTPServer();
assertTrue(delegate.successCalled);
assertEquals(200, delegate.successResponse.getStatusCode());
assertEquals("zPSQTm7WBVWB", global.getSyncID());
assertTrue(global.getEngines() instanceof ExtendedJSONObject);
assertEquals(Long.valueOf(5), global.getStorageVersion());
}
/**
* A record that is valid JSON but invalid as a meta/global record will be
* downloaded successfully, but will fail later.
*/
@Test
public void testFetchNoPayload() {
MockServer existingMetaGlobalServer = new MockServer(200, TEST_META_GLOBAL_NO_PAYLOAD_RESPONSE);
data.startHTTPServer(existingMetaGlobalServer);
final MockMetaGlobalFetchDelegate delegate = doFetch(global);
data.stopHTTPServer();
assertTrue(delegate.successCalled);
}
@Test
public void testFetchEmptyPayload() {
MockServer existingMetaGlobalServer = new MockServer(200, TEST_META_GLOBAL_EMPTY_PAYLOAD_RESPONSE);
data.startHTTPServer(existingMetaGlobalServer);
final MockMetaGlobalFetchDelegate delegate = doFetch(global);
data.stopHTTPServer();
assertTrue(delegate.successCalled);
}
/**
* A record that is invalid JSON will fail to download at all.
*/
@Test
public void testFetchMalformedPayload() {
MockServer existingMetaGlobalServer = new MockServer(200, TEST_META_GLOBAL_MALFORMED_PAYLOAD_RESPONSE);
data.startHTTPServer(existingMetaGlobalServer);
final MockMetaGlobalFetchDelegate delegate = doFetch(global);
data.stopHTTPServer();
assertTrue(delegate.errorCalled);
assertNotNull(delegate.errorException);
assertEquals(NonObjectJSONException.class, delegate.errorException.getClass());
}
@SuppressWarnings("static-method")
@Test
public void testSetFromRecord() throws Exception {
MetaGlobal mg = new MetaGlobal(null, null);
mg.setFromRecord(CryptoRecord.fromJSONRecord(TEST_META_GLOBAL_RESPONSE));
assertEquals("zPSQTm7WBVWB", mg.getSyncID());
assertTrue(mg.getEngines() instanceof ExtendedJSONObject);
assertEquals(Long.valueOf(5), mg.getStorageVersion());
}
@SuppressWarnings("static-method")
@Test
public void testAsCryptoRecord() throws Exception {
MetaGlobal mg = new MetaGlobal(null, null);
mg.setFromRecord(CryptoRecord.fromJSONRecord(TEST_META_GLOBAL_RESPONSE));
CryptoRecord rec = mg.asCryptoRecord();
assertEquals("global", rec.guid);
mg.setFromRecord(rec);
assertEquals("zPSQTm7WBVWB", mg.getSyncID());
assertTrue(mg.getEngines() instanceof ExtendedJSONObject);
assertEquals(Long.valueOf(5), mg.getStorageVersion());
}
@SuppressWarnings("static-method")
@Test
public void testGetEnabledEngineNames() throws Exception {
MetaGlobal mg = new MetaGlobal(null, null);
mg.setFromRecord(CryptoRecord.fromJSONRecord(TEST_META_GLOBAL_RESPONSE));
assertEquals("zPSQTm7WBVWB", mg.getSyncID());
final Set<String> actual = mg.getEnabledEngineNames();
final Set<String> expected = new HashSet<String>();
for (String name : new String[] { "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs" }) {
expected.add(name);
}
assertEquals(expected, actual);
}
@SuppressWarnings("static-method")
@Test
public void testGetEmptyDeclinedEngineNames() throws Exception {
MetaGlobal mg = new MetaGlobal(null, null);
mg.setFromRecord(CryptoRecord.fromJSONRecord(TEST_META_GLOBAL_RESPONSE));
assertEquals(0, mg.getDeclinedEngineNames().size());
}
@SuppressWarnings("static-method")
@Test
public void testGetDeclinedEngineNames() throws Exception {
MetaGlobal mg = new MetaGlobal(null, null);
mg.setFromRecord(CryptoRecord.fromJSONRecord(TEST_DECLINED_META_GLOBAL_RESPONSE));
assertEquals(1, mg.getDeclinedEngineNames().size());
assertEquals("bookmarks", mg.getDeclinedEngineNames().iterator().next());
}
@SuppressWarnings("static-method")
@Test
public void testRoundtripDeclinedEngineNames() throws Exception {
MetaGlobal mg = new MetaGlobal(null, null);
mg.setFromRecord(CryptoRecord.fromJSONRecord(TEST_DECLINED_META_GLOBAL_RESPONSE));
assertEquals("bookmarks", mg.getDeclinedEngineNames().iterator().next());
assertEquals("bookmarks", mg.asCryptoRecord().payload.getArray("declined").get(0));
MetaGlobal again = new MetaGlobal(null, null);
again.setFromRecord(mg.asCryptoRecord());
assertEquals("bookmarks", again.getDeclinedEngineNames().iterator().next());
}
public MockMetaGlobalFetchDelegate doUpload(final MetaGlobal global) {
final MockMetaGlobalFetchDelegate delegate = new MockMetaGlobalFetchDelegate();
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
@Override
public void run() {
global.upload(delegate);
}
}));
return delegate;
}
@Test
public void testUpload() {
long TEST_STORAGE_VERSION = 111;
String TEST_SYNC_ID = "testSyncID";
global.setSyncID(TEST_SYNC_ID);
global.setStorageVersion(Long.valueOf(TEST_STORAGE_VERSION));
final AtomicBoolean mgUploaded = new AtomicBoolean(false);
final MetaGlobal uploadedMg = new MetaGlobal(null, null);
MockServer server = new MockServer() {
public void handle(Request request, Response response) {
if (request.getMethod().equals("PUT")) {
try {
ExtendedJSONObject body = new ExtendedJSONObject(request.getContent());
System.out.println(body.toJSONString());
assertTrue(body.containsKey("payload"));
assertFalse(body.containsKey("default"));
CryptoRecord rec = CryptoRecord.fromJSONRecord(body);
uploadedMg.setFromRecord(rec);
mgUploaded.set(true);
} catch (Exception e) {
throw new RuntimeException(e);
}
this.handle(request, response, 200, "success");
return;
}
this.handle(request, response, 404, "missing");
}
};
data.startHTTPServer(server);
final MockMetaGlobalFetchDelegate delegate = doUpload(global);
data.stopHTTPServer();
assertTrue(delegate.successCalled);
assertTrue(mgUploaded.get());
assertEquals(TEST_SYNC_ID, uploadedMg.getSyncID());
assertEquals(TEST_STORAGE_VERSION, uploadedMg.getStorageVersion().longValue());
}
}

View file

@ -1,102 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockResourceDelegate;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.HttpResponseObserver;
import java.net.URISyntaxException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestResource {
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
private HTTPServerTestHelper data = new HTTPServerTestHelper();
@SuppressWarnings("static-method")
@Before
public void setUp() {
BaseResource.rewriteLocalhost = false;
}
@SuppressWarnings("static-method")
@Test
public void testLocalhostRewriting() throws URISyntaxException {
BaseResource r = new BaseResource("http://localhost:5000/foo/bar", true);
assertEquals("http://10.0.2.2:5000/foo/bar", r.getURI().toASCIIString());
}
@SuppressWarnings("static-method")
public MockResourceDelegate doGet() throws URISyntaxException {
final BaseResource r = new BaseResource(TEST_SERVER + "/foo/bar");
MockResourceDelegate delegate = new MockResourceDelegate();
r.delegate = delegate;
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
r.get();
}
});
return delegate;
}
@Test
public void testTrivialFetch() throws URISyntaxException {
MockServer server = data.startHTTPServer();
server.expectedBasicAuthHeader = MockResourceDelegate.EXPECT_BASIC;
MockResourceDelegate delegate = doGet();
assertTrue(delegate.handledHttpResponse);
data.stopHTTPServer();
}
public static class MockHttpResponseObserver implements HttpResponseObserver {
public HttpResponse response = null;
@Override
public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
this.response = response;
}
}
@Test
public void testObservers() throws URISyntaxException {
data.startHTTPServer();
// Check that null observer doesn't fail.
BaseResource.addHttpResponseObserver(null);
doGet(); // HTTP server stopped in callback.
// Check that multiple non-null observers gets called with reasonable HttpResponse.
MockHttpResponseObserver observers[] = { new MockHttpResponseObserver(), new MockHttpResponseObserver() };
for (MockHttpResponseObserver observer : observers) {
BaseResource.addHttpResponseObserver(observer);
assertTrue(BaseResource.isHttpResponseObserver(observer));
assertNull(observer.response);
}
doGet(); // HTTP server stopped in callback.
for (MockHttpResponseObserver observer : observers) {
assertNotNull(observer.response);
assertEquals(200, observer.response.getStatusLine().getStatusCode());
}
data.stopHTTPServer();
}
}

View file

@ -1,87 +0,0 @@
package org.mozilla.android.sync.net.test;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.net.SyncResponse;
import java.util.Date;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestRetryAfter {
private int TEST_SECONDS = 120;
@Test
public void testRetryAfterParsesSeconds() {
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.addHeader("Retry-After", Long.toString(TEST_SECONDS)); // Retry-After given in seconds.
final SyncResponse syncResponse = new SyncResponse(response);
assertEquals(TEST_SECONDS, syncResponse.retryAfterInSeconds());
}
@Test
public void testRetryAfterParsesHTTPDate() {
Date future = new Date(System.currentTimeMillis() + TEST_SECONDS * 1000);
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.addHeader("Retry-After", DateUtils.formatDate(future));
final SyncResponse syncResponse = new SyncResponse(response);
assertTrue(syncResponse.retryAfterInSeconds() > TEST_SECONDS - 15);
assertTrue(syncResponse.retryAfterInSeconds() < TEST_SECONDS + 15);
}
@SuppressWarnings("static-method")
@Test
public void testRetryAfterParsesMalformed() {
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.addHeader("Retry-After", "10X");
final SyncResponse syncResponse = new SyncResponse(response);
assertEquals(-1, syncResponse.retryAfterInSeconds());
}
@SuppressWarnings("static-method")
@Test
public void testRetryAfterParsesNeither() {
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
final SyncResponse syncResponse = new SyncResponse(response);
assertEquals(-1, syncResponse.retryAfterInSeconds());
}
@Test
public void testRetryAfterParsesLargerRetryAfter() {
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.addHeader("Retry-After", Long.toString(TEST_SECONDS + 1));
response.addHeader("X-Weave-Backoff", Long.toString(TEST_SECONDS));
final SyncResponse syncResponse = new SyncResponse(response);
assertEquals(1000 * (TEST_SECONDS + 1), syncResponse.totalBackoffInMilliseconds());
}
@Test
public void testRetryAfterParsesLargerXWeaveBackoff() {
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 503, "Illegal method/protocol"));
response.addHeader("Retry-After", Long.toString(TEST_SECONDS));
response.addHeader("X-Weave-Backoff", Long.toString(TEST_SECONDS + 1));
final SyncResponse syncResponse = new SyncResponse(response);
assertEquals(1000 * (TEST_SECONDS + 1), syncResponse.totalBackoffInMilliseconds());
}
}

View file

@ -1,48 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.InfoCollections;
import org.mozilla.gecko.sync.InfoConfiguration;
import org.mozilla.gecko.sync.repositories.Server11Repository;
import java.net.URI;
import java.net.URISyntaxException;
@RunWith(TestRunner.class)
public class TestServer11Repository {
private static final String COLLECTION = "bookmarks";
private static final String COLLECTION_URL = "http://foo.com/1.1/n6ec3u5bee3tixzp2asys7bs6fve4jfw/storage";
protected final InfoCollections infoCollections = new InfoCollections();
protected final InfoConfiguration infoConfiguration = new InfoConfiguration();
public static void assertQueryEquals(String expected, URI u) {
Assert.assertEquals(expected, u.getRawQuery());
}
@SuppressWarnings("static-method")
@Test
public void testCollectionURIFull() throws URISyntaxException {
Server11Repository r = new Server11Repository(COLLECTION, COLLECTION_URL, null, infoCollections, infoConfiguration);
assertQueryEquals("full=1&newer=5000.000", r.collectionURI(true, 5000000L, -1, null, null, null));
assertQueryEquals("newer=1230.000", r.collectionURI(false, 1230000L, -1, null, null, null));
assertQueryEquals("newer=5000.000&limit=10", r.collectionURI(false, 5000000L, 10, null, null, null));
assertQueryEquals("full=1&newer=5000.000&sort=index", r.collectionURI(true, 5000000L, 0, "index", null, null));
assertQueryEquals("full=1&ids=123,abc", r.collectionURI(true, -1L, -1, null, "123,abc", null));
}
@Test
public void testCollectionURI() throws URISyntaxException {
Server11Repository noTrailingSlash = new Server11Repository(COLLECTION, COLLECTION_URL, null, infoCollections, infoConfiguration);
Server11Repository trailingSlash = new Server11Repository(COLLECTION, COLLECTION_URL + "/", null, infoCollections, infoConfiguration);
Assert.assertEquals("http://foo.com/1.1/n6ec3u5bee3tixzp2asys7bs6fve4jfw/storage/bookmarks", noTrailingSlash.collectionURI().toASCIIString());
Assert.assertEquals("http://foo.com/1.1/n6ec3u5bee3tixzp2asys7bs6fve4jfw/storage/bookmarks", trailingSlash.collectionURI().toASCIIString());
}
}

View file

@ -1,269 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.net.test;
import org.json.simple.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.BaseTestStorageRequestDelegate;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestSyncStorageRequest {
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT;
private static final String LOCAL_META_URL = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/meta/global";
private static final String LOCAL_BAD_REQUEST_URL = TEST_SERVER + "/1.1/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/storage/bad";
private static final String EXPECTED_ERROR_CODE = "12";
private static final String EXPECTED_RETRY_AFTER_ERROR_MESSAGE = "{error:'informative error message'}";
// Corresponds to rnewman+testandroid@mozilla.com.
private static final String TEST_USERNAME = "c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd";
private static final String TEST_PASSWORD = "password";
private final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD);
private HTTPServerTestHelper data = new HTTPServerTestHelper();
public class TestSyncStorageRequestDelegate extends
BaseTestStorageRequestDelegate {
public TestSyncStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
super(authHeaderProvider);
}
@Override
public void handleRequestSuccess(SyncStorageResponse res) {
assertTrue(res.wasSuccessful());
assertTrue(res.httpResponse().containsHeader("X-Weave-Timestamp"));
// Make sure we consume the rest of the body, so we can reuse the
// connection. Even test code has to be correct in this regard!
try {
System.out.println("Success body: " + res.body());
} catch (Exception e) {
e.printStackTrace();
}
BaseResource.consumeEntity(res);
data.stopHTTPServer();
}
}
public class TestBadSyncStorageRequestDelegate extends
BaseTestStorageRequestDelegate {
public TestBadSyncStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
super(authHeaderProvider);
}
@Override
public void handleRequestFailure(SyncStorageResponse res) {
assertTrue(!res.wasSuccessful());
assertTrue(res.httpResponse().containsHeader("X-Weave-Timestamp"));
try {
String responseMessage = res.getErrorMessage();
String expectedMessage = SyncStorageResponse.SERVER_ERROR_MESSAGES.get(EXPECTED_ERROR_CODE);
assertEquals(expectedMessage, responseMessage);
} catch (Exception e) {
fail("Got exception fetching error message.");
}
BaseResource.consumeEntity(res);
data.stopHTTPServer();
}
}
@Test
public void testSyncStorageRequest() throws URISyntaxException, IOException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer();
SyncStorageRecordRequest r = new SyncStorageRecordRequest(new URI(LOCAL_META_URL));
TestSyncStorageRequestDelegate delegate = new TestSyncStorageRequestDelegate(authHeaderProvider);
r.delegate = delegate;
r.get();
// Server is stopped in the callback.
}
public class ErrorMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
super.handle(request, response, 400, EXPECTED_ERROR_CODE);
}
}
@Test
public void testErrorResponse() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new ErrorMockServer());
SyncStorageRecordRequest r = new SyncStorageRecordRequest(new URI(LOCAL_BAD_REQUEST_URL));
TestBadSyncStorageRequestDelegate delegate = new TestBadSyncStorageRequestDelegate(authHeaderProvider);
r.delegate = delegate;
r.post(new JSONObject());
// Server is stopped in the callback.
}
// Test that the Retry-After header is correctly parsed and that handleRequestFailure
// is being called.
public class TestRetryAfterSyncStorageRequestDelegate extends BaseTestStorageRequestDelegate {
public TestRetryAfterSyncStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
super(authHeaderProvider);
}
@Override
public void handleRequestFailure(SyncStorageResponse res) {
assertTrue(!res.wasSuccessful());
assertTrue(res.httpResponse().containsHeader("Retry-After"));
assertEquals(res.retryAfterInSeconds(), 3001);
try {
String responseMessage = res.getErrorMessage();
String expectedMessage = EXPECTED_RETRY_AFTER_ERROR_MESSAGE;
assertEquals(expectedMessage, responseMessage);
} catch (Exception e) {
fail("Got exception fetching error message.");
}
BaseResource.consumeEntity(res);
data.stopHTTPServer();
}
}
public class RetryAfterMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
String errorBody = EXPECTED_RETRY_AFTER_ERROR_MESSAGE;
response.setValue("Retry-After", "3001");
super.handle(request, response, 503, errorBody);
}
}
@Test
public void testRetryAfterResponse() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new RetryAfterMockServer());
SyncStorageRecordRequest r = new SyncStorageRecordRequest(new URI(LOCAL_BAD_REQUEST_URL)); // URL not used -- we 503 every response
TestRetryAfterSyncStorageRequestDelegate delegate = new TestRetryAfterSyncStorageRequestDelegate(authHeaderProvider);
r.delegate = delegate;
r.post(new JSONObject());
// Server is stopped in the callback.
}
// Test that the X-Weave-Backoff header is correctly parsed and that handleRequestSuccess
// is still being called.
public class TestWeaveBackoffSyncStorageRequestDelegate extends
TestSyncStorageRequestDelegate {
public TestWeaveBackoffSyncStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
super(authHeaderProvider);
}
@Override
public void handleRequestSuccess(SyncStorageResponse res) {
assertTrue(res.httpResponse().containsHeader("X-Weave-Backoff"));
assertEquals(res.weaveBackoffInSeconds(), 1801);
super.handleRequestSuccess(res);
}
}
public class WeaveBackoffMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
response.setValue("X-Weave-Backoff", "1801");
super.handle(request, response);
}
}
@Test
public void testWeaveBackoffResponse() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new WeaveBackoffMockServer());
SyncStorageRecordRequest r = new SyncStorageRecordRequest(new URI(LOCAL_META_URL)); // URL re-used -- we need any successful response
TestWeaveBackoffSyncStorageRequestDelegate delegate = new TestWeaveBackoffSyncStorageRequestDelegate(new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD));
r.delegate = delegate;
r.post(new JSONObject());
// Server is stopped in the callback.
}
// Test that the X-Weave-{Quota-Remaining, Alert, Records} headers are correctly parsed and
// that handleRequestSuccess is still being called.
public class TestHeadersSyncStorageRequestDelegate extends
TestSyncStorageRequestDelegate {
public TestHeadersSyncStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
super(authHeaderProvider);
}
@Override
public void handleRequestSuccess(SyncStorageResponse res) {
assertTrue(res.httpResponse().containsHeader("X-Weave-Quota-Remaining"));
assertTrue(res.httpResponse().containsHeader("X-Weave-Alert"));
assertTrue(res.httpResponse().containsHeader("X-Weave-Records"));
assertEquals(65536, res.weaveQuotaRemaining());
assertEquals("First weave alert string", res.weaveAlert());
assertEquals(50, res.weaveRecords());
super.handleRequestSuccess(res);
}
}
public class HeadersMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
response.setValue("X-Weave-Quota-Remaining", "65536");
response.setValue("X-Weave-Alert", "First weave alert string");
response.addValue("X-Weave-Alert", "Second weave alert string");
response.setValue("X-Weave-Records", "50");
super.handle(request, response);
}
}
@Test
public void testHeadersResponse() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new HeadersMockServer());
SyncStorageRecordRequest r = new SyncStorageRecordRequest(new URI(LOCAL_META_URL)); // URL re-used -- we need any successful response
TestHeadersSyncStorageRequestDelegate delegate = new TestHeadersSyncStorageRequestDelegate(authHeaderProvider);
r.delegate = delegate;
r.post(new JSONObject());
// Server is stopped in the callback.
}
public class DeleteMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
assertNotNull(request.getValue("x-confirm-delete"));
assertEquals("1", request.getValue("x-confirm-delete"));
super.handle(request, response);
}
}
@Test
public void testDelete() throws URISyntaxException {
BaseResource.rewriteLocalhost = false;
data.startHTTPServer(new DeleteMockServer());
SyncStorageRecordRequest r = new SyncStorageRecordRequest(new URI(LOCAL_META_URL)); // URL re-used -- we need any successful response
TestSyncStorageRequestDelegate delegate = new TestSyncStorageRequestDelegate(authHeaderProvider);
r.delegate = delegate;
r.delete();
// Server is stopped in the callback.
}
}

View file

@ -1,282 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import android.content.Context;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.WBORepository;
import org.mozilla.gecko.sync.repositories.FetchFailedException;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
import org.mozilla.gecko.sync.repositories.StoreFailedException;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
import org.mozilla.gecko.sync.repositories.domain.Record;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
public class SynchronizerHelpers {
public static final String FAIL_SENTINEL = "Fail";
/**
* Store one at a time, failing if the guid contains FAIL_SENTINEL.
*/
public static class FailFetchWBORepository extends WBORepository {
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this) {
@Override
public void fetchSince(long timestamp,
final RepositorySessionFetchRecordsDelegate delegate) {
super.fetchSince(timestamp, new RepositorySessionFetchRecordsDelegate() {
@Override
public void onFetchedRecord(Record record) {
if (record.guid.contains(FAIL_SENTINEL)) {
delegate.onFetchFailed(new FetchFailedException(), record);
} else {
delegate.onFetchedRecord(record);
}
}
@Override
public void onFetchFailed(Exception ex, Record record) {
delegate.onFetchFailed(ex, record);
}
@Override
public void onFetchCompleted(long fetchEnd) {
delegate.onFetchCompleted(fetchEnd);
}
@Override
public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
return this;
}
});
}
});
}
}
/**
* Store one at a time, failing if the guid contains FAIL_SENTINEL.
*/
public static class SerialFailStoreWBORepository extends WBORepository {
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this) {
@Override
public void store(final Record record) throws NoStoreDelegateException {
if (delegate == null) {
throw new NoStoreDelegateException();
}
if (record.guid.contains(FAIL_SENTINEL)) {
delegate.onRecordStoreFailed(new StoreFailedException(), record.guid);
} else {
super.store(record);
}
}
});
}
}
/**
* Store in batches, failing if any of the batch guids contains "Fail".
* <p>
* This will drop the final batch.
*/
public static class BatchFailStoreWBORepository extends WBORepository {
public final int batchSize;
public ArrayList<Record> batch = new ArrayList<Record>();
public boolean batchShouldFail = false;
public class BatchFailStoreWBORepositorySession extends WBORepositorySession {
public BatchFailStoreWBORepositorySession(WBORepository repository) {
super(repository);
}
public void superStore(final Record record) throws NoStoreDelegateException {
super.store(record);
}
@Override
public void store(final Record record) throws NoStoreDelegateException {
if (delegate == null) {
throw new NoStoreDelegateException();
}
synchronized (batch) {
batch.add(record);
if (record.guid.contains("Fail")) {
batchShouldFail = true;
}
if (batch.size() >= batchSize) {
flush();
}
}
}
public void flush() {
final ArrayList<Record> thisBatch = new ArrayList<Record>(batch);
final boolean thisBatchShouldFail = batchShouldFail;
batchShouldFail = false;
batch.clear();
storeWorkQueue.execute(new Runnable() {
@Override
public void run() {
Logger.trace("XXX", "Notifying about batch. Failure? " + thisBatchShouldFail);
for (Record batchRecord : thisBatch) {
if (thisBatchShouldFail) {
delegate.onRecordStoreFailed(new StoreFailedException(), batchRecord.guid);
} else {
try {
superStore(batchRecord);
} catch (NoStoreDelegateException e) {
delegate.onRecordStoreFailed(e, batchRecord.guid);
}
}
}
}
});
}
@Override
public void storeDone() {
synchronized (batch) {
flush();
// Do this in a Runnable so that the timestamp is grabbed after any upload.
final Runnable r = new Runnable() {
@Override
public void run() {
synchronized (batch) {
Logger.trace("XXX", "Calling storeDone.");
storeDone(now());
}
}
};
storeWorkQueue.execute(r);
}
}
}
public BatchFailStoreWBORepository(int batchSize) {
super();
this.batchSize = batchSize;
}
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new BatchFailStoreWBORepositorySession(this));
}
}
public static class TrackingWBORepository extends WBORepository {
@Override
public synchronized boolean shouldTrack() {
return true;
}
}
public static class BeginFailedException extends Exception {
private static final long serialVersionUID = -2349459755976915096L;
}
public static class FinishFailedException extends Exception {
private static final long serialVersionUID = -4644528423867070934L;
}
public static class BeginErrorWBORepository extends TrackingWBORepository {
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new BeginErrorWBORepositorySession(this));
}
public class BeginErrorWBORepositorySession extends WBORepositorySession {
public BeginErrorWBORepositorySession(WBORepository repository) {
super(repository);
}
@Override
public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
delegate.onBeginFailed(new BeginFailedException());
}
}
}
public static class FinishErrorWBORepository extends TrackingWBORepository {
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new FinishErrorWBORepositorySession(this));
}
public class FinishErrorWBORepositorySession extends WBORepositorySession {
public FinishErrorWBORepositorySession(WBORepository repository) {
super(repository);
}
@Override
public void finish(final RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
delegate.onFinishFailed(new FinishFailedException());
}
}
}
public static class DataAvailableWBORepository extends TrackingWBORepository {
public boolean dataAvailable = true;
public DataAvailableWBORepository(boolean dataAvailable) {
this.dataAvailable = dataAvailable;
}
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new DataAvailableWBORepositorySession(this));
}
public class DataAvailableWBORepositorySession extends WBORepositorySession {
public DataAvailableWBORepositorySession(WBORepository repository) {
super(repository);
}
@Override
public boolean dataAvailable() {
return dataAvailable;
}
}
}
public static class ShouldSkipWBORepository extends TrackingWBORepository {
public boolean shouldSkip = true;
public ShouldSkipWBORepository(boolean shouldSkip) {
this.shouldSkip = shouldSkip;
}
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new ShouldSkipWBORepositorySession(this));
}
public class ShouldSkipWBORepositorySession extends WBORepositorySession {
public ShouldSkipWBORepositorySession(WBORepository repository) {
super(repository);
}
@Override
public boolean shouldSkip() {
return shouldSkip;
}
}
}
}

View file

@ -1,197 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.json.simple.JSONArray;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.CollectionKeys;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestCollectionKeys {
@Test
public void testDefaultKeys() throws CryptoException, NoCollectionKeysSetException {
CollectionKeys ck = new CollectionKeys();
try {
ck.defaultKeyBundle();
fail("defaultKeys should throw.");
} catch (NoCollectionKeysSetException ex) {
// Good.
}
KeyBundle testKeys = KeyBundle.withRandomKeys();
ck.setDefaultKeyBundle(testKeys);
assertEquals(testKeys, ck.defaultKeyBundle());
}
@Test
public void testKeyForCollection() throws CryptoException, NoCollectionKeysSetException {
CollectionKeys ck = new CollectionKeys();
try {
ck.keyBundleForCollection("test");
fail("keyForCollection should throw.");
} catch (NoCollectionKeysSetException ex) {
// Good.
}
KeyBundle testKeys = KeyBundle.withRandomKeys();
KeyBundle otherKeys = KeyBundle.withRandomKeys();
ck.setDefaultKeyBundle(testKeys);
assertEquals(testKeys, ck.defaultKeyBundle());
assertEquals(testKeys, ck.keyBundleForCollection("test")); // Returns default.
ck.setKeyBundleForCollection("test", otherKeys);
assertEquals(otherKeys, ck.keyBundleForCollection("test")); // Returns default.
}
public static void assertSame(byte[] arrayOne, byte[] arrayTwo) {
assertTrue(Arrays.equals(arrayOne, arrayTwo));
}
@Test
public void testSetKeysFromWBO() throws IOException, NonObjectJSONException, CryptoException, NoCollectionKeysSetException {
String json = "{\"default\":[\"3fI6k1exImMgAKjilmMaAWxGqEIzFX/9K5EjEgH99vc=\",\"/AMaoCX4hzic28WY94XtokNi7N4T0nv+moS1y5wlbug=\"],\"collections\":{},\"collection\":\"crypto\",\"id\":\"keys\"}";
CryptoRecord rec = new CryptoRecord(json);
KeyBundle syncKeyBundle = new KeyBundle("slyjcrjednxd6rf4cr63vqilmkus6zbe", "6m8mv8ex2brqnrmsb9fjuvfg7y");
rec.keyBundle = syncKeyBundle;
rec.encrypt();
CollectionKeys ck = new CollectionKeys();
ck.setKeyPairsFromWBO(rec, syncKeyBundle);
byte[] input = "3fI6k1exImMgAKjilmMaAWxGqEIzFX/9K5EjEgH99vc=".getBytes("UTF-8");
byte[] expected = Base64.decodeBase64(input);
assertSame(expected, ck.defaultKeyBundle().getEncryptionKey());
}
@Test
public void testCryptoRecordFromCollectionKeys() throws CryptoException, NoCollectionKeysSetException, IOException, NonObjectJSONException {
CollectionKeys ck1 = CollectionKeys.generateCollectionKeys();
assertNotNull(ck1.defaultKeyBundle());
assertEquals(ck1.keyBundleForCollection("foobar"), ck1.defaultKeyBundle());
CryptoRecord rec = ck1.asCryptoRecord();
assertEquals(rec.collection, "crypto");
assertEquals(rec.guid, "keys");
JSONArray defaultKey = (JSONArray) rec.payload.get("default");
assertSame(Base64.decodeBase64((String) (defaultKey.get(0))), ck1.defaultKeyBundle().getEncryptionKey());
CollectionKeys ck2 = new CollectionKeys();
ck2.setKeyPairsFromWBO(rec, null);
assertSame(ck1.defaultKeyBundle().getEncryptionKey(), ck2.defaultKeyBundle().getEncryptionKey());
}
@Test
public void testCreateKeysBundle() throws CryptoException, NonObjectJSONException, IOException, NoCollectionKeysSetException {
String username = "b6evr62dptbxz7fvebek7btljyu322wp";
String friendlyBase32SyncKey = "basuxv2426eqj7frhvpcwkavdi";
KeyBundle syncKeyBundle = new KeyBundle(username, friendlyBase32SyncKey);
CollectionKeys ck = CollectionKeys.generateCollectionKeys();
CryptoRecord unencrypted = ck.asCryptoRecord();
unencrypted.keyBundle = syncKeyBundle;
CryptoRecord encrypted = unencrypted.encrypt();
CollectionKeys ckDecrypted = new CollectionKeys();
ckDecrypted.setKeyPairsFromWBO(encrypted, syncKeyBundle);
// Compare decrypted keys to the keys that were set upon creation
assertArrayEquals(ck.defaultKeyBundle().getEncryptionKey(), ckDecrypted.defaultKeyBundle().getEncryptionKey());
assertArrayEquals(ck.defaultKeyBundle().getHMACKey(), ckDecrypted.defaultKeyBundle().getHMACKey());
}
@Test
public void testDifferences() throws Exception {
KeyBundle kb1 = KeyBundle.withRandomKeys();
KeyBundle kb2 = KeyBundle.withRandomKeys();
KeyBundle kb3 = KeyBundle.withRandomKeys();
CollectionKeys a = CollectionKeys.generateCollectionKeys();
CollectionKeys b = CollectionKeys.generateCollectionKeys();
Set<String> diffs;
a.setKeyBundleForCollection("1", kb1);
b.setKeyBundleForCollection("1", kb1);
diffs = CollectionKeys.differences(a, b);
assertTrue(diffs.isEmpty());
a.setKeyBundleForCollection("2", kb2);
diffs = CollectionKeys.differences(a, b);
assertArrayEquals(new String[] { "2" }, diffs.toArray(new String[diffs.size()]));
b.setKeyBundleForCollection("3", kb3);
diffs = CollectionKeys.differences(a, b);
assertEquals(2, diffs.size());
assertTrue(diffs.contains("2"));
assertTrue(diffs.contains("3"));
b.setKeyBundleForCollection("1", KeyBundle.withRandomKeys());
diffs = CollectionKeys.differences(a, b);
assertEquals(3, diffs.size());
// This tests that explicitly setting a default key works.
a = CollectionKeys.generateCollectionKeys();
b = CollectionKeys.generateCollectionKeys();
b.setDefaultKeyBundle(a.defaultKeyBundle());
a.setKeyBundleForCollection("a", a.defaultKeyBundle());
b.setKeyBundleForCollection("b", b.defaultKeyBundle());
assertTrue(CollectionKeys.differences(a, b).isEmpty());
assertTrue(CollectionKeys.differences(b, a).isEmpty());
}
@Test
public void testEquals() throws Exception {
KeyBundle kb1 = KeyBundle.withRandomKeys();
KeyBundle kb2 = KeyBundle.withRandomKeys();
CollectionKeys a = CollectionKeys.generateCollectionKeys();
CollectionKeys b = CollectionKeys.generateCollectionKeys();
// Random keys are different.
assertFalse(a.equals(b));
assertFalse(b.equals(a));
// keys with unset default key bundles are different.
b.setDefaultKeyBundle(null);
assertFalse(a.equals(b));
// keys with equal default key bundles and no other collections are the same.
b.setDefaultKeyBundle(a.defaultKeyBundle());
assertTrue(a.equals(b));
// keys with equal defaults and equal collections are the same.
a.setKeyBundleForCollection("1", kb1);
b.setKeyBundleForCollection("1", kb1);
assertTrue(a.equals(b));
// keys with equal defaults but some collection missing are different.
a.setKeyBundleForCollection("2", kb2);
assertFalse(a.equals(b));
assertFalse(b.equals(a));
// keys with equal defaults and some collection set to the default are the same.
a.setKeyBundleForCollection("2", a.defaultKeyBundle());
b.setKeyBundleForCollection("3", b.defaultKeyBundle());
assertTrue(a.equals(b));
assertTrue(b.equals(a));
}
}

View file

@ -1,117 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.CommandRunner;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestCommandProcessor extends CommandProcessor {
public static final String commandType = "displayURI";
public static final String commandWithNoArgs = "{\"command\":\"displayURI\"}";
public static final String commandWithNoType = "{\"args\":[\"https://bugzilla.mozilla.org/show_bug.cgi?id=731341\",\"PKsljsuqYbGg\"]}";
public static final String wellFormedCommand = "{\"args\":[\"https://bugzilla.mozilla.org/show_bug.cgi?id=731341\",\"PKsljsuqYbGg\"],\"command\":\"displayURI\"}";
public static final String wellFormedCommandWithNullArgs = "{\"args\":[\"https://bugzilla.mozilla.org/show_bug.cgi?id=731341\",null,\"PKsljsuqYbGg\",null],\"command\":\"displayURI\"}";
private boolean commandExecuted;
// Session is not used in these tests.
protected final GlobalSession session = null;
public class MockCommandRunner extends CommandRunner {
public MockCommandRunner(int argCount) {
super(argCount);
}
@Override
public void executeCommand(final GlobalSession session, List<String> args) {
commandExecuted = true;
}
}
@Test
public void testRegisterCommand() throws NonObjectJSONException, IOException {
assertNull(commands.get(commandType));
this.registerCommand(commandType, new MockCommandRunner(1));
assertNotNull(commands.get(commandType));
}
@Test
public void testProcessRegisteredCommand() throws NonObjectJSONException, IOException {
commandExecuted = false;
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommand);
this.registerCommand(commandType, new MockCommandRunner(1));
this.processCommand(session, unparsedCommand);
assertTrue(commandExecuted);
}
@Test
public void testProcessUnregisteredCommand() throws NonObjectJSONException, IOException {
commandExecuted = false;
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommand);
this.processCommand(session, unparsedCommand);
assertFalse(commandExecuted);
}
@Test
public void testProcessInvalidCommand() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(commandWithNoType);
this.registerCommand(commandType, new MockCommandRunner(1));
this.processCommand(session, unparsedCommand);
assertFalse(commandExecuted);
}
@Test
public void testParseCommandNoType() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(commandWithNoType);
assertNull(CommandProcessor.parseCommand(unparsedCommand));
}
@Test
public void testParseCommandNoArgs() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(commandWithNoArgs);
assertNull(CommandProcessor.parseCommand(unparsedCommand));
}
@Test
public void testParseWellFormedCommand() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommand);
Command parsedCommand = CommandProcessor.parseCommand(unparsedCommand);
assertNotNull(parsedCommand);
assertEquals(2, parsedCommand.args.size());
assertEquals(commandType, parsedCommand.commandType);
}
@Test
public void testParseCommandNullArg() throws NonObjectJSONException, IOException {
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(wellFormedCommandWithNullArgs);
Command parsedCommand = CommandProcessor.parseCommand(unparsedCommand);
assertNotNull(parsedCommand);
assertEquals(4, parsedCommand.args.size());
assertEquals(commandType, parsedCommand.commandType);
final List<String> expectedArgs = new ArrayList<String>();
expectedArgs.add("https://bugzilla.mozilla.org/show_bug.cgi?id=731341");
expectedArgs.add(null);
expectedArgs.add("PKsljsuqYbGg");
expectedArgs.add(null);
assertEquals(expectedArgs, parsedCommand.getArgsList());
}
}

View file

@ -1,302 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestCryptoRecord {
String base64EncryptionKey = "9K/wLdXdw+nrTtXo4ZpECyHFNr4d7aYHqeg3KW9+m6Q=";
String base64HmacKey = "MMntEfutgLTc8FlTLQFms8/xMPmCldqPlq/QQXEjx70=";
@Test
public void testBaseCryptoRecordEncrypt() throws IOException, NonObjectJSONException, CryptoException {
ExtendedJSONObject clearPayload = new ExtendedJSONObject("{\"id\":\"5qRsgXWRJZXr\"," +
"\"title\":\"Index of file:///Users/jason/Library/Application " +
"Support/Firefox/Profiles/ksgd7wpk.LocalSyncServer/weave/logs/\"," +
"\"histUri\":\"file:///Users/jason/Library/Application%20Support/Firefox/Profiles" +
"/ksgd7wpk.LocalSyncServer/weave/logs/\",\"visits\":[{\"type\":1," +
"\"date\":1319149012372425}]}");
CryptoRecord record = new CryptoRecord();
record.payload = clearPayload;
String expectedGUID = "5qRsgXWRJZXr";
record.guid = expectedGUID;
record.keyBundle = KeyBundle.fromBase64EncodedKeys(base64EncryptionKey, base64HmacKey);
record.encrypt();
assertTrue(record.payload.get("title") == null);
assertTrue(record.payload.get("ciphertext") != null);
assertEquals(expectedGUID, record.guid);
assertEquals(expectedGUID, record.toJSONObject().get("id"));
record.decrypt();
assertEquals(expectedGUID, record.toJSONObject().get("id"));
}
@Test
public void testEntireRecord() throws Exception {
// Check a raw JSON blob from a real Sync account.
String inputString = "{\"sortindex\": 131, \"payload\": \"{\\\"ciphertext\\\":\\\"YJB4dr0vZEIWPirfU2FCJvfzeSLiOP5QWasol2R6ILUxdHsJWuUuvTZVhxYQfTVNou6hVV67jfAvi5Cs+bqhhQsv7icZTiZhPTiTdVGt+uuMotxauVA5OryNGVEZgCCTvT3upzhDFdDbJzVd9O3/gU/b7r/CmAHykX8bTlthlbWeZ8oz6gwHJB5tPRU15nM/m/qW1vyKIw5pw/ZwtAy630AieRehGIGDk+33PWqsfyuT4EUFY9/Ly+8JlnqzxfiBCunIfuXGdLuqTjJOxgrK8mI4wccRFEdFEnmHvh5x7fjl1ID52qumFNQl8zkB75C8XK25alXqwvRR6/AQSP+BgQ==\\\",\\\"IV\\\":\\\"v/0BFgicqYQsd70T39rraA==\\\",\\\"hmac\\\":\\\"59605ed696f6e0e6e062a03510cff742bf6b50d695c042e8372a93f4c2d37dac\\\"}\", \"id\": \"0-P9fabp9vJD\", \"modified\": 1326254123.65}";
CryptoRecord record = CryptoRecord.fromJSONRecord(inputString);
assertEquals("0-P9fabp9vJD", record.guid);
assertEquals(1326254123650L, record.lastModified);
assertEquals(131, record.sortIndex);
String b64E = "0A7mU5SZ/tu7ZqwXW1og4qHVHN+zgEi4Xwfwjw+vEJw=";
String b64H = "11GN34O9QWXkjR06g8t0gWE1sGgQeWL0qxxWwl8Dmxs=";
record.keyBundle = KeyBundle.fromBase64EncodedKeys(b64E, b64H);
record.decrypt();
assertEquals("0-P9fabp9vJD", record.guid);
assertEquals(1326254123650L, record.lastModified);
assertEquals(131, record.sortIndex);
assertEquals("Customize Firefox", record.payload.get("title"));
assertEquals("0-P9fabp9vJD", record.payload.get("id"));
assertTrue(record.payload.get("tags") instanceof JSONArray);
}
@Test
public void testBaseCryptoRecordDecrypt() throws Exception {
String base64CipherText =
"NMsdnRulLwQsVcwxKW9XwaUe7ouJk5Wn"
+ "80QhbD80l0HEcZGCynh45qIbeYBik0lg"
+ "cHbKmlIxTJNwU+OeqipN+/j7MqhjKOGI"
+ "lvbpiPQQLC6/ffF2vbzL0nzMUuSyvaQz"
+ "yGGkSYM2xUFt06aNivoQTvU2GgGmUK6M"
+ "vadoY38hhW2LCMkoZcNfgCqJ26lO1O0s"
+ "EO6zHsk3IVz6vsKiJ2Hq6VCo7hu123wN"
+ "egmujHWQSGyf8JeudZjKzfi0OFRRvvm4"
+ "QAKyBWf0MgrW1F8SFDnVfkq8amCB7Nhd"
+ "whgLWbN+21NitNwWYknoEWe1m6hmGZDg"
+ "DT32uxzWxCV8QqqrpH/ZggViEr9uMgoy"
+ "4lYaWqP7G5WKvvechc62aqnsNEYhH26A"
+ "5QgzmlNyvB+KPFvPsYzxDnSCjOoRSLx7"
+ "GG86wT59QZw=";
String base64IV = "GX8L37AAb2FZJMzIoXlX8w==";
String base16Hmac =
"b1e6c18ac30deb70236bc0d65a46f7a4"
+ "dce3b8b0e02cf92182b914e3afa5eebc";
ExtendedJSONObject body = new ExtendedJSONObject();
ExtendedJSONObject payload = new ExtendedJSONObject();
payload.put("ciphertext", base64CipherText);
payload.put("IV", base64IV);
payload.put("hmac", base16Hmac);
body.put("payload", payload.toJSONString());
CryptoRecord record = CryptoRecord.fromJSONRecord(body);
byte[] decodedKey = Base64.decodeBase64(base64EncryptionKey.getBytes("UTF-8"));
byte[] decodedHMAC = Base64.decodeBase64(base64HmacKey.getBytes("UTF-8"));
record.keyBundle = new KeyBundle(decodedKey, decodedHMAC);
record.decrypt();
String id = (String) record.payload.get("id");
assertTrue(id.equals("5qRsgXWRJZXr"));
}
@Test
public void testBaseCryptoRecordSyncKeyBundle() throws UnsupportedEncodingException, CryptoException {
// These values pulled straight out of Firefox.
String key = "6m8mv8ex2brqnrmsb9fjuvfg7y";
String user = "c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd";
// Check our friendly base32 decoding.
assertTrue(Arrays.equals(Utils.decodeFriendlyBase32(key), Base64.decodeBase64("8xbKrJfQYwbFkguKmlSm/g==".getBytes("UTF-8"))));
KeyBundle bundle = new KeyBundle(user, key);
String expectedEncryptKeyBase64 = "/8RzbFT396htpZu5rwgIg2WKfyARgm7dLzsF5pwrVz8=";
String expectedHMACKeyBase64 = "NChGjrqoXYyw8vIYP2334cvmMtsjAMUZNqFwV2LGNkM=";
byte[] computedEncryptKey = bundle.getEncryptionKey();
byte[] computedHMACKey = bundle.getHMACKey();
assertTrue(Arrays.equals(computedEncryptKey, Base64.decodeBase64(expectedEncryptKeyBase64.getBytes("UTF-8"))));
assertTrue(Arrays.equals(computedHMACKey, Base64.decodeBase64(expectedHMACKeyBase64.getBytes("UTF-8"))));
}
@Test
public void testDecrypt() throws Exception {
String jsonInput = "{\"sortindex\": 90, \"payload\":" +
"\"{\\\"ciphertext\\\":\\\"F4ukf0" +
"LM+vhffiKyjaANXeUhfmOPPmQYX1XBoG" +
"Rh1LiHeKHB5rqjhzd7yAoxqgmFnkIgQF" +
"YPSqRAoCxWiAeGULTX+KM4MU5drbNyR/" +
"690JBWSyE1vQSiMGwNIbTKnOLGHKkQVY" +
"HDpajg5BNFfvHNQ5Jx7uM9uJcmuEjCI6" +
"GRMDKyKjhsTqCd99MONkY5rISutaWQ0e" +
"EXFgpA9RZPv4jgWlQhe+YrVnpcrTi20b" +
"NgKp3IfIeqEelrZ5FJd2WGZOA021d3e7" +
"P3Z4qptefH4Q9/hySrWsELWngBaydyn/" +
"IjsheZuKra3kJSST/4SvRZ7qXn\\\",\\" +
"\"IV\\\":\\\"GadPajeXhpk75K2YH+L" +
"y4w==\\\",\\\"hmac\\\":\\\"71442" +
"d946502e3ca475c70a633d3d37f4b4e9" +
"313a6d1041d0c0550cd354e7605\\\"}" +
"\", \"id\": \"hkZYpC-BH4Xi\", \"" +
"modified\": 1320183464.21}";
String base64EncryptionKey = "K8fV6PHG8RgugfHexGesbzTeOs2o12cr" +
"N/G3bz0Bx1M=";
String base64HmacKey = "nbceuI6w1RJbBzh+iCJHEs8p4lElsOma" +
"yUhx+OztVgM=";
String expectedDecryptedText = "{\"id\":\"hkZYpC-BH4Xi\",\"histU" +
"ri\":\"http://hathology.com/2008" +
"/06/how-to-edit-your-path-enviro" +
"nment-variables-on-mac-os-x/\",\"" +
"title\":\"How To Edit Your PATH " +
"Environment Variables On Mac OS " +
"X\",\"visits\":[{\"date\":131898" +
"2074310889,\"type\":1}]}";
KeyBundle keyBundle = KeyBundle.fromBase64EncodedKeys(base64EncryptionKey, base64HmacKey);
CryptoRecord encrypted = CryptoRecord.fromJSONRecord(jsonInput);
encrypted.keyBundle = keyBundle;
CryptoRecord decrypted = encrypted.decrypt();
// We don't necessarily produce exactly the same JSON but we do have the same values.
ExtendedJSONObject expectedJson = new ExtendedJSONObject(expectedDecryptedText);
assertEquals(expectedJson.get("id"), decrypted.payload.get("id"));
assertEquals(expectedJson.get("title"), decrypted.payload.get("title"));
assertEquals(expectedJson.get("histUri"), decrypted.payload.get("histUri"));
}
@Test
public void testEncryptDecrypt() throws Exception {
String originalText = "{\"id\":\"hkZYpC-BH4Xi\",\"histU" +
"ri\":\"http://hathology.com/2008" +
"/06/how-to-edit-your-path-enviro" +
"nment-variables-on-mac-os-x/\",\"" +
"title\":\"How To Edit Your PATH " +
"Environment Variables On Mac OS " +
"X\",\"visits\":[{\"date\":131898" +
"2074310889,\"type\":1}]}";
String base64EncryptionKey = "K8fV6PHG8RgugfHexGesbzTeOs2o12cr" +
"N/G3bz0Bx1M=";
String base64HmacKey = "nbceuI6w1RJbBzh+iCJHEs8p4lElsOma" +
"yUhx+OztVgM=";
KeyBundle keyBundle = KeyBundle.fromBase64EncodedKeys(base64EncryptionKey, base64HmacKey);
// Encrypt.
CryptoRecord unencrypted = new CryptoRecord(originalText);
unencrypted.keyBundle = keyBundle;
CryptoRecord encrypted = unencrypted.encrypt();
// Decrypt after round-trip through JSON.
CryptoRecord undecrypted = CryptoRecord.fromJSONRecord(encrypted.toJSONString());
undecrypted.keyBundle = keyBundle;
CryptoRecord decrypted = undecrypted.decrypt();
// We don't necessarily produce exactly the same JSON but we do have the same values.
ExtendedJSONObject expectedJson = new ExtendedJSONObject(originalText);
assertEquals(expectedJson.get("id"), decrypted.payload.get("id"));
assertEquals(expectedJson.get("title"), decrypted.payload.get("title"));
assertEquals(expectedJson.get("histUri"), decrypted.payload.get("histUri"));
}
@Test
public void testDecryptKeysBundle() throws Exception {
String jsonInput = "{\"payload\": \"{\\\"ciphertext\\" +
"\":\\\"L1yRyZBkVYKXC1cTpeUqqfmKg" +
"CinYV9YntGiG0PfYZSTLQ2s86WPI0VBb" +
"QbLZfx7udk6sf6CFE4w5EgiPx0XP3Fbj" +
"L7r4qIT0vjbAOrLKedZwA3cgiquc+PXM" +
"Etml8B4Dfm0crJK0iROlRkb+lePAYkzI" +
"iQn5Ba8mSWQEFoLy3zAcfCYXumA7E0Fj" +
"XYD+TqTG5bqYJY4zvPaB9mn9y3WHw==\\" +
"\",\\\"IV\\\":\\\"Jjb2oVI5uvvFfm" +
"ZYRY4GaA==\\\",\\\"hmac\\\":\\\"" +
"0b59731cb1aaedc85f54917b7058f361" +
"60826b70050b0d70cd42b0b609b1d717" +
"\\\"}\", \"id\": \"keys\", \"mod" +
"ified\": 1320183463.91}";
String username = "b6evr62dptbxz7fvebek7btljyu322wp";
String friendlyBase32SyncKey = "basuxv2426eqj7frhvpcwkavdi";
String expectedDecryptedText = "{\"default\":[\"K8fV6PHG8RgugfHe" +
"xGesbzTeOs2o12crN/G3bz0Bx1M=\",\"" +
"nbceuI6w1RJbBzh+iCJHEs8p4lElsOma" +
"yUhx+OztVgM=\"],\"collections\":" +
"{},\"collection\":\"crypto\",\"i" +
"d\":\"keys\"}";
String expectedBase64EncryptionKey = "K8fV6PHG8RgugfHexGesbzTeOs2o12cr" +
"N/G3bz0Bx1M=";
String expectedBase64HmacKey = "nbceuI6w1RJbBzh+iCJHEs8p4lElsOma" +
"yUhx+OztVgM=";
KeyBundle syncKeyBundle = new KeyBundle(username, friendlyBase32SyncKey);
ExtendedJSONObject json = new ExtendedJSONObject(jsonInput);
assertEquals("keys", json.get("id"));
CryptoRecord encrypted = CryptoRecord.fromJSONRecord(jsonInput);
encrypted.keyBundle = syncKeyBundle;
CryptoRecord decrypted = encrypted.decrypt();
// We don't necessarily produce exactly the same JSON but we do have the same values.
ExtendedJSONObject expectedJson = new ExtendedJSONObject(expectedDecryptedText);
assertEquals(expectedJson.get("id"), decrypted.payload.get("id"));
assertEquals(expectedJson.get("default"), decrypted.payload.get("default"));
assertEquals(expectedJson.get("collection"), decrypted.payload.get("collection"));
assertEquals(expectedJson.get("collections"), decrypted.payload.get("collections"));
// Check that the extracted keys were as expected.
JSONArray keys = new ExtendedJSONObject(decrypted.payload.toJSONString()).getArray("default");
KeyBundle keyBundle = KeyBundle.fromBase64EncodedKeys((String)keys.get(0), (String)keys.get(1));
assertArrayEquals(Base64.decodeBase64(expectedBase64EncryptionKey.getBytes("UTF-8")), keyBundle.getEncryptionKey());
assertArrayEquals(Base64.decodeBase64(expectedBase64HmacKey.getBytes("UTF-8")), keyBundle.getHMACKey());
}
@Test
public void testTTL() throws UnsupportedEncodingException, CryptoException {
Record historyRecord = new HistoryRecord();
CryptoRecord cryptoRecord = historyRecord.getEnvelope();
assertEquals(historyRecord.ttl, cryptoRecord.ttl);
// Very important that ttls are set in outbound envelopes.
JSONObject o = cryptoRecord.toJSONObject();
assertEquals(cryptoRecord.ttl, o.get("ttl"));
// Most important of all, outbound encrypted record envelopes.
KeyBundle keyBundle = KeyBundle.withRandomKeys();
cryptoRecord.keyBundle = keyBundle;
cryptoRecord.encrypt();
assertEquals(historyRecord.ttl, cryptoRecord.ttl); // Should be preserved.
o = cryptoRecord.toJSONObject();
assertEquals(cryptoRecord.ttl, o.get("ttl"));
// But we should ignore negative ttls.
Record clientRecord = new ClientRecord();
clientRecord.ttl = -1; // Don't ttl this record.
o = clientRecord.getEnvelope().toJSONObject();
assertNull(o.get("ttl"));
// But we should ignore negative ttls in outbound encrypted record envelopes.
cryptoRecord = clientRecord.getEnvelope();
cryptoRecord.keyBundle = keyBundle;
cryptoRecord.encrypt();
o = cryptoRecord.toJSONObject();
assertNull(o.get("ttl"));
}
}

View file

@ -1,330 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.db.Tab;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
import org.mozilla.gecko.sync.repositories.domain.RecordParseException;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
import java.io.IOException;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestRecord {
@SuppressWarnings("static-method")
@Test
public void testQueryRecord() throws NonObjectJSONException, IOException {
final String expectedGUID = "Bl3n3gpKag3s";
final String testRecord =
"{\"id\":\"" + expectedGUID + "\"," +
" \"type\":\"query\"," +
" \"title\":\"Downloads\"," +
" \"parentName\":\"\"," +
" \"bmkUri\":\"place:transition=7&sort=4\"," +
" \"tags\":[]," +
" \"keyword\":null," +
" \"description\":null," +
" \"loadInSidebar\":false," +
" \"parentid\":\"BxfRgGiNeITG\"}";
final ExtendedJSONObject o = new ExtendedJSONObject(testRecord);
final CryptoRecord cr = new CryptoRecord(o);
cr.guid = expectedGUID;
cr.lastModified = System.currentTimeMillis();
cr.collection = "bookmarks";
final BookmarkRecord r = new BookmarkRecord("Bl3n3gpKag3s", "bookmarks");
r.initFromEnvelope(cr);
assertEquals(expectedGUID, r.guid);
assertEquals("query", r.type);
assertEquals("places:uri=place%3Atransition%3D7%26sort%3D4", r.bookmarkURI);
// Check that we get the same bookmark URI out the other end,
// once we've parsed it into a CryptoRecord, a BookmarkRecord, then
// back into a CryptoRecord.
assertEquals("place:transition=7&sort=4", r.getEnvelope().payload.getString("bmkUri"));
}
@SuppressWarnings("static-method")
@Test
public void testRecordGUIDs() {
for (int i = 0; i < 50; ++i) {
CryptoRecord cryptoRecord = new HistoryRecord().getEnvelope();
assertEquals(12, cryptoRecord.guid.length());
}
}
@Test
public void testRecordEquality() {
long now = System.currentTimeMillis();
BookmarkRecord bOne = new BookmarkRecord("abcdefghijkl", "bookmarks", now , false);
BookmarkRecord bTwo = new BookmarkRecord("abcdefghijkl", "bookmarks", now , false);
HistoryRecord hOne = new HistoryRecord("mbcdefghijkm", "history", now , false);
HistoryRecord hTwo = new HistoryRecord("mbcdefghijkm", "history", now , false);
// Identical records.
assertFalse(bOne == bTwo);
assertTrue(bOne.equals(bTwo));
assertTrue(bOne.equalPayloads(bTwo));
assertTrue(bOne.congruentWith(bTwo));
assertTrue(bTwo.equals(bOne));
assertTrue(bTwo.equalPayloads(bOne));
assertTrue(bTwo.congruentWith(bOne));
// Null checking.
assertFalse(bOne.equals(null));
assertFalse(bOne.equalPayloads(null));
assertFalse(bOne.congruentWith(null));
// Different types.
hOne.guid = bOne.guid;
assertFalse(bOne.equals(hOne));
assertFalse(bOne.equalPayloads(hOne));
assertFalse(bOne.congruentWith(hOne));
hOne.guid = hTwo.guid;
// Congruent androidID.
bOne.androidID = 1;
assertFalse(bOne.equals(bTwo));
assertTrue(bOne.equalPayloads(bTwo));
assertTrue(bOne.congruentWith(bTwo));
assertFalse(bTwo.equals(bOne));
assertTrue(bTwo.equalPayloads(bOne));
assertTrue(bTwo.congruentWith(bOne));
// Non-congruent androidID.
bTwo.androidID = 2;
assertFalse(bOne.equals(bTwo));
assertTrue(bOne.equalPayloads(bTwo));
assertFalse(bOne.congruentWith(bTwo));
assertFalse(bTwo.equals(bOne));
assertTrue(bTwo.equalPayloads(bOne));
assertFalse(bTwo.congruentWith(bOne));
// Identical androidID.
bOne.androidID = 2;
assertTrue(bOne.equals(bTwo));
assertTrue(bOne.equalPayloads(bTwo));
assertTrue(bOne.congruentWith(bTwo));
assertTrue(bTwo.equals(bOne));
assertTrue(bTwo.equalPayloads(bOne));
assertTrue(bTwo.congruentWith(bOne));
// Different times.
bTwo.lastModified += 1000;
assertFalse(bOne.equals(bTwo));
assertTrue(bOne.equalPayloads(bTwo));
assertTrue(bOne.congruentWith(bTwo));
assertFalse(bTwo.equals(bOne));
assertTrue(bTwo.equalPayloads(bOne));
assertTrue(bTwo.congruentWith(bOne));
// Add some visits.
JSONObject v1 = fakeVisit(now - 1000);
JSONObject v2 = fakeVisit(now - 500);
hOne.fennecDateVisited = now + 2000;
hOne.fennecVisitCount = 1;
assertFalse(hOne.equals(hTwo));
assertTrue(hOne.equalPayloads(hTwo));
assertTrue(hOne.congruentWith(hTwo));
addVisit(hOne, v1);
assertFalse(hOne.equals(hTwo));
assertFalse(hOne.equalPayloads(hTwo));
assertTrue(hOne.congruentWith(hTwo));
addVisit(hTwo, v2);
assertFalse(hOne.equals(hTwo));
assertFalse(hOne.equalPayloads(hTwo));
assertTrue(hOne.congruentWith(hTwo));
// Now merge the visits.
addVisit(hTwo, v1);
addVisit(hOne, v2);
assertFalse(hOne.equals(hTwo));
assertTrue(hOne.equalPayloads(hTwo));
assertTrue(hOne.congruentWith(hTwo));
hTwo.fennecDateVisited = hOne.fennecDateVisited;
hTwo.fennecVisitCount = hOne.fennecVisitCount = 2;
assertTrue(hOne.equals(hTwo));
assertTrue(hOne.equalPayloads(hTwo));
assertTrue(hOne.congruentWith(hTwo));
}
@SuppressWarnings("unchecked")
private void addVisit(HistoryRecord r, JSONObject visit) {
if (r.visits == null) {
r.visits = new JSONArray();
}
r.visits.add(visit);
}
@SuppressWarnings("unchecked")
private JSONObject fakeVisit(long time) {
JSONObject object = new JSONObject();
object.put("type", 1L);
object.put("date", time * 1000);
return object;
}
@SuppressWarnings("static-method")
@Test
public void testTabParsing() throws Exception {
String json = "{\"title\":\"mozilla-central mozilla/browser/base/content/syncSetup.js\"," +
" \"urlHistory\":[\"http://mxr.mozilla.org/mozilla-central/source/browser/base/content/syncSetup.js#72\"]," +
" \"icon\":\"http://mxr.mozilla.org/mxr.png\"," +
" \"lastUsed\":\"1306374531\"}";
Tab tab = TabsRecord.tabFromJSONObject(new ExtendedJSONObject(json).object);
assertEquals("mozilla-central mozilla/browser/base/content/syncSetup.js", tab.title);
assertEquals("http://mxr.mozilla.org/mxr.png", tab.icon);
assertEquals("http://mxr.mozilla.org/mozilla-central/source/browser/base/content/syncSetup.js#72", tab.history.get(0));
assertEquals(1306374531000L, tab.lastUsed);
String zeroJSON = "{\"title\":\"a\"," +
" \"urlHistory\":[\"http://example.com\"]," +
" \"icon\":\"\"," +
" \"lastUsed\":0}";
Tab zero = TabsRecord.tabFromJSONObject(new ExtendedJSONObject(zeroJSON).object);
assertEquals("a", zero.title);
assertEquals("", zero.icon);
assertEquals("http://example.com", zero.history.get(0));
assertEquals(0L, zero.lastUsed);
}
@SuppressWarnings({ "unchecked", "static-method" })
@Test
public void testTabsRecordCreation() throws Exception {
final TabsRecord record = new TabsRecord("testGuid");
record.clientName = "test client name";
final JSONArray history1 = new JSONArray();
history1.add("http://test.com/test1.html");
final Tab tab1 = new Tab("test title 1", "http://test.com/test1.png", history1, 1000);
final JSONArray history2 = new JSONArray();
history2.add("http://test.com/test2.html#1");
history2.add("http://test.com/test2.html#2");
history2.add("http://test.com/test2.html#3");
final Tab tab2 = new Tab("test title 2", "http://test.com/test2.png", history2, 2000);
record.tabs = new ArrayList<Tab>();
record.tabs.add(tab1);
record.tabs.add(tab2);
final TabsRecord parsed = new TabsRecord();
parsed.initFromEnvelope(CryptoRecord.fromJSONRecord(record.getEnvelope().toJSONString()));
assertEquals(record.guid, parsed.guid);
assertEquals(record.clientName, parsed.clientName);
assertEquals(record.tabs, parsed.tabs);
// Verify that equality test doesn't always return true.
parsed.tabs.get(0).history.add("http://test.com/different.html");
assertFalse(record.tabs.equals(parsed.tabs));
}
public static class URITestBookmarkRecord extends BookmarkRecord {
public static void doTest() {
assertEquals("places:uri=abc%26def+baz&p1=123&p2=bar+baz",
encodeUnsupportedTypeURI("abc&def baz", "p1", "123", "p2", "bar baz"));
assertEquals("places:uri=abc%26def+baz&p1=123",
encodeUnsupportedTypeURI("abc&def baz", "p1", "123", null, "bar baz"));
assertEquals("places:p1=123",
encodeUnsupportedTypeURI(null, "p1", "123", "p2", null));
}
}
@SuppressWarnings("static-method")
@Test
public void testEncodeURI() {
URITestBookmarkRecord.doTest();
}
private static final String payload =
"{\"id\":\"M5bwUKK8hPyF\"," +
"\"type\":\"livemark\"," +
"\"siteUri\":\"http://www.bbc.co.uk/go/rss/int/news/-/news/\"," +
"\"feedUri\":\"http://fxfeeds.mozilla.com/en-US/firefox/headlines.xml\"," +
"\"parentName\":\"Bookmarks Toolbar\"," +
"\"parentid\":\"toolbar\"," +
"\"title\":\"Latest Headlines\"," +
"\"description\":\"\"," +
"\"children\":" +
"[\"7oBdEZB-8BMO\", \"SUd1wktMNCTB\", \"eZe4QWzo1BcY\", \"YNBhGwhVnQsN\"," +
"\"mNTdpgoRZMbW\", \"-L8Vci6CbkJY\", \"bVzudKSQERc1\", \"Gxl9lb4DXsmL\"," +
"\"3Qr13GucOtEh\"]}";
public class PayloadBookmarkRecord extends BookmarkRecord {
public PayloadBookmarkRecord() {
super("abcdefghijkl", "bookmarks", 1234, false);
}
public void doTest() throws NonObjectJSONException, IOException {
this.initFromPayload(new ExtendedJSONObject(payload));
assertEquals("abcdefghijkl", this.guid); // Ignores payload.
assertEquals("livemark", this.type);
assertEquals("Bookmarks Toolbar", this.parentName);
assertEquals("toolbar", this.parentID);
assertEquals("", this.description);
assertEquals(null, this.children);
final String encodedSite = "http%3A%2F%2Fwww.bbc.co.uk%2Fgo%2Frss%2Fint%2Fnews%2F-%2Fnews%2F";
final String encodedFeed = "http%3A%2F%2Ffxfeeds.mozilla.com%2Fen-US%2Ffirefox%2Fheadlines.xml";
final String expectedURI = "places:siteUri=" + encodedSite + "&feedUri=" + encodedFeed;
assertEquals(expectedURI, this.bookmarkURI);
}
}
@Test
public void testUnusualBookmarkRecords() throws NonObjectJSONException, IOException {
PayloadBookmarkRecord record = new PayloadBookmarkRecord();
record.doTest();
}
@SuppressWarnings("static-method")
@Test
public void testTTL() {
Record record = new HistoryRecord();
assertEquals(HistoryRecord.HISTORY_TTL, record.ttl);
// ClientRecords are transient, HistoryRecords are not.
Record clientRecord = new ClientRecord();
assertTrue(clientRecord.ttl < record.ttl);
CryptoRecord cryptoRecord = record.getEnvelope();
assertEquals(record.ttl, cryptoRecord.ttl);
}
@Test
public void testStringModified() throws Exception {
// modified member is a string, expected a floating point number with 2
// decimal digits.
String badJson = "{\"sortindex\":\"0\",\"payload\":\"{\\\"syncID\\\":\\\"ZJOqMBjhBthH\\\",\\\"storageVersion\\\":5,\\\"engines\\\":{\\\"clients\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"4oTBXG20rJH5\\\"},\\\"bookmarks\\\":{\\\"version\\\":2,\\\"syncID\\\":\\\"JiMJXy8xI3fr\\\"},\\\"forms\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"J17vSloroXBU\\\"},\\\"history\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"y1HgpbSc3LJT\\\"},\\\"passwords\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"v3y-RidcCuT5\\\"},\\\"prefs\\\":{\\\"version\\\":2,\\\"syncID\\\":\\\"LvfqmT7cUUm4\\\"},\\\"tabs\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"MKMRlBah2d9D\\\"},\\\"addons\\\":{\\\"version\\\":1,\\\"syncID\\\":\\\"Ih2hhRrcGjh4\\\"}}}\",\"id\":\"global\",\"modified\":\"1370689360.28\"}";
try {
CryptoRecord.fromJSONRecord(badJson);
fail("Expected exception.");
} catch (Exception e) {
assertTrue(e instanceof RecordParseException);
}
}
}

View file

@ -1,229 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.SynchronizerHelpers.FailFetchWBORepository;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionCreationDelegate;
import org.mozilla.android.sync.test.helpers.ExpectSuccessRepositorySessionFinishDelegate;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WBORepository;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.synchronizer.RecordsChannel;
import org.mozilla.gecko.sync.synchronizer.RecordsChannelDelegate;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestRecordsChannel {
protected WBORepository remote;
protected WBORepository local;
protected RepositorySession source;
protected RepositorySession sink;
protected RecordsChannelDelegate rcDelegate;
protected AtomicInteger numFlowFetchFailed;
protected AtomicInteger numFlowStoreFailed;
protected AtomicInteger numFlowCompleted;
protected AtomicBoolean flowBeginFailed;
protected AtomicBoolean flowFinishFailed;
public void doFlow(final Repository remote, final Repository local) throws Exception {
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
remote.createSession(new ExpectSuccessRepositorySessionCreationDelegate(WaitHelper.getTestWaiter()) {
@Override
public void onSessionCreated(RepositorySession session) {
source = session;
local.createSession(new ExpectSuccessRepositorySessionCreationDelegate(WaitHelper.getTestWaiter()) {
@Override
public void onSessionCreated(RepositorySession session) {
sink = session;
WaitHelper.getTestWaiter().performNotify();
}
}, null);
}
}, null);
}
});
assertNotNull(source);
assertNotNull(sink);
numFlowFetchFailed = new AtomicInteger(0);
numFlowStoreFailed = new AtomicInteger(0);
numFlowCompleted = new AtomicInteger(0);
flowBeginFailed = new AtomicBoolean(false);
flowFinishFailed = new AtomicBoolean(false);
rcDelegate = new RecordsChannelDelegate() {
@Override
public void onFlowFetchFailed(RecordsChannel recordsChannel, Exception ex) {
numFlowFetchFailed.incrementAndGet();
}
@Override
public void onFlowStoreFailed(RecordsChannel recordsChannel, Exception ex, String recordGuid) {
numFlowStoreFailed.incrementAndGet();
}
@Override
public void onFlowFinishFailed(RecordsChannel recordsChannel, Exception ex) {
flowFinishFailed.set(true);
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void onFlowCompleted(RecordsChannel recordsChannel, long fetchEnd, long storeEnd) {
numFlowCompleted.incrementAndGet();
try {
sink.finish(new ExpectSuccessRepositorySessionFinishDelegate(WaitHelper.getTestWaiter()) {
@Override
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
try {
source.finish(new ExpectSuccessRepositorySessionFinishDelegate(WaitHelper.getTestWaiter()) {
@Override
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
performNotify();
}
});
} catch (InactiveSessionException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
});
} catch (InactiveSessionException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
@Override
public void onFlowBeginFailed(RecordsChannel recordsChannel, Exception ex) {
flowBeginFailed.set(true);
WaitHelper.getTestWaiter().performNotify();
}
};
final RecordsChannel rc = new RecordsChannel(source, sink, rcDelegate);
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
try {
rc.beginAndFlow();
} catch (InvalidSessionTransitionException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
});
}
public static final BookmarkRecord[] inbounds = new BookmarkRecord[] {
new BookmarkRecord("inboundSucc1", "bookmarks", 1, false),
new BookmarkRecord("inboundSucc2", "bookmarks", 1, false),
new BookmarkRecord("inboundFail1", "bookmarks", 1, false),
new BookmarkRecord("inboundSucc3", "bookmarks", 1, false),
new BookmarkRecord("inboundSucc4", "bookmarks", 1, false),
new BookmarkRecord("inboundFail2", "bookmarks", 1, false),
};
public static final BookmarkRecord[] outbounds = new BookmarkRecord[] {
new BookmarkRecord("outboundSucc1", "bookmarks", 1, false),
new BookmarkRecord("outboundSucc2", "bookmarks", 1, false),
new BookmarkRecord("outboundSucc3", "bookmarks", 1, false),
new BookmarkRecord("outboundSucc4", "bookmarks", 1, false),
new BookmarkRecord("outboundSucc5", "bookmarks", 1, false),
new BookmarkRecord("outboundFail6", "bookmarks", 1, false),
};
protected WBORepository empty() {
WBORepository repo = new SynchronizerHelpers.TrackingWBORepository();
return repo;
}
protected WBORepository full() {
WBORepository repo = new SynchronizerHelpers.TrackingWBORepository();
for (BookmarkRecord outbound : outbounds) {
repo.wbos.put(outbound.guid, outbound);
}
return repo;
}
protected WBORepository failingFetch() {
WBORepository repo = new FailFetchWBORepository();
for (BookmarkRecord outbound : outbounds) {
repo.wbos.put(outbound.guid, outbound);
}
return repo;
}
@Test
public void testSuccess() throws Exception {
WBORepository source = full();
WBORepository sink = empty();
doFlow(source, sink);
assertEquals(1, numFlowCompleted.get());
assertEquals(0, numFlowFetchFailed.get());
assertEquals(0, numFlowStoreFailed.get());
assertEquals(source.wbos, sink.wbos);
}
@Test
public void testFetchFail() throws Exception {
WBORepository source = failingFetch();
WBORepository sink = empty();
doFlow(source, sink);
assertEquals(1, numFlowCompleted.get());
assertTrue(numFlowFetchFailed.get() > 0);
assertEquals(0, numFlowStoreFailed.get());
assertTrue(sink.wbos.size() < 6);
}
@Test
public void testStoreSerialFail() throws Exception {
WBORepository source = full();
WBORepository sink = new SynchronizerHelpers.SerialFailStoreWBORepository();
doFlow(source, sink);
assertEquals(1, numFlowCompleted.get());
assertEquals(0, numFlowFetchFailed.get());
assertEquals(1, numFlowStoreFailed.get());
assertEquals(5, sink.wbos.size());
}
@Test
public void testStoreBatchesFail() throws Exception {
WBORepository source = full();
WBORepository sink = new SynchronizerHelpers.BatchFailStoreWBORepository(3);
doFlow(source, sink);
assertEquals(1, numFlowCompleted.get());
assertEquals(0, numFlowFetchFailed.get());
assertEquals(3, numFlowStoreFailed.get()); // One batch fails.
assertEquals(3, sink.wbos.size()); // One batch succeeds.
}
@Test
public void testStoreOneBigBatchFail() throws Exception {
WBORepository source = full();
WBORepository sink = new SynchronizerHelpers.BatchFailStoreWBORepository(50);
doFlow(source, sink);
assertEquals(1, numFlowCompleted.get());
assertEquals(0, numFlowFetchFailed.get());
assertEquals(6, numFlowStoreFailed.get()); // One (big) batch fails.
assertEquals(0, sink.wbos.size()); // No batches succeed.
}
}

View file

@ -1,153 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import android.content.SharedPreferences;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback;
import org.mozilla.gecko.background.testhelpers.MockPrefsGlobalSession;
import org.mozilla.gecko.background.testhelpers.MockServerSyncStage;
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.MetaGlobalException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import java.io.IOException;
import java.util.HashMap;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
/**
* Test that reset commands properly invoke the reset methods on the correct stage.
*/
@RunWith(TestRunner.class)
public class TestResetCommands {
private static final String TEST_USERNAME = "johndoe";
private static final String TEST_PASSWORD = "password";
private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
public static void performNotify() {
WaitHelper.getTestWaiter().performNotify();
}
public static void performNotify(Throwable e) {
WaitHelper.getTestWaiter().performNotify(e);
}
public static void performWait(Runnable runnable) {
WaitHelper.getTestWaiter().performWait(runnable);
}
@Before
public void setUp() {
assertTrue(WaitHelper.getTestWaiter().isIdle());
}
@Test
public void testHandleResetCommand() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, CryptoException {
// Create a global session.
// Set up stage mappings for a real stage name (because they're looked up by name
// in an enumeration) pointing to our fake stage.
// Send a reset command.
// Verify that reset is called on our stage.
class Result {
public boolean called = false;
}
final Result yes = new Result();
final Result no = new Result();
final GlobalSessionCallback callback = createGlobalSessionCallback();
// So we can poke at stages separately.
final HashMap<Stage, GlobalSyncStage> stagesToRun = new HashMap<Stage, GlobalSyncStage>();
// Side-effect: modifies global command processor.
final SharedPreferences prefs = new MockSharedPreferences();
final SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), prefs);
config.syncKeyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY);
final GlobalSession session = new MockPrefsGlobalSession(config, callback, null, null) {
@Override
public boolean isEngineRemotelyEnabled(String engineName,
EngineSettings engineSettings)
throws MetaGlobalException {
return true;
}
@Override
public void advance() {
// So we don't proceed and run other stages.
}
@Override
public void prepareStages() {
this.stages = stagesToRun;
}
};
final MockServerSyncStage stageGetsReset = new MockServerSyncStage() {
@Override
public void resetLocal() {
yes.called = true;
}
};
final MockServerSyncStage stageNotReset = new MockServerSyncStage() {
@Override
public void resetLocal() {
no.called = true;
}
};
stagesToRun.put(Stage.syncBookmarks, stageGetsReset);
stagesToRun.put(Stage.syncHistory, stageNotReset);
final String resetBookmarks = "{\"args\":[\"bookmarks\"],\"command\":\"resetEngine\"}";
ExtendedJSONObject unparsedCommand = new ExtendedJSONObject(resetBookmarks);
CommandProcessor processor = CommandProcessor.getProcessor();
processor.processCommand(session, unparsedCommand);
assertTrue(yes.called);
assertFalse(no.called);
}
public void testHandleWipeCommand() {
// TODO
}
private static GlobalSessionCallback createGlobalSessionCallback() {
return new DefaultGlobalSessionCallback() {
@Override
public void handleAborted(GlobalSession globalSession, String reason) {
performNotify(new Exception("Aborted"));
}
@Override
public void handleError(GlobalSession globalSession, Exception ex) {
performNotify(ex);
}
@Override
public void handleSuccess(GlobalSession globalSession) {
}
};
}
}

View file

@ -1,231 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
import org.mozilla.android.sync.test.helpers.BaseTestStorageRequestDelegate;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.InfoCollections;
import org.mozilla.gecko.sync.InfoConfiguration;
import org.mozilla.gecko.sync.JSONRecordFetcher;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.middleware.Crypto5MiddlewareRepository;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.repositories.FetchFailedException;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.Server11Repository;
import org.mozilla.gecko.sync.repositories.StoreFailedException;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecordFactory;
import org.mozilla.gecko.sync.stage.SafeConstrainedServer11Repository;
import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.simpleframework.http.ContentType;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestServer11RepositorySession {
public class POSTMockServer extends MockServer {
@Override
public void handle(Request request, Response response) {
try {
String content = request.getContent();
System.out.println("Content:" + content);
} catch (IOException e) {
e.printStackTrace();
}
ContentType contentType = request.getContentType();
System.out.println("Content-Type:" + contentType);
super.handle(request, response, 200, "{success:[]}");
}
}
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT + "/";
static final String LOCAL_BASE_URL = TEST_SERVER + "1.1/n6ec3u5bee3tixzp2asys7bs6fve4jfw/";
static final String LOCAL_INFO_BASE_URL = LOCAL_BASE_URL + "info/";
static final String LOCAL_COUNTS_URL = LOCAL_INFO_BASE_URL + "collection_counts";
// Corresponds to rnewman+atest1@mozilla.com, local.
static final String TEST_USERNAME = "n6ec3u5bee3tixzp2asys7bs6fve4jfw";
static final String TEST_PASSWORD = "passowrd";
static final String SYNC_KEY = "eh7ppnb82iwr5kt3z3uyi5vr44";
public final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD);
protected final InfoCollections infoCollections = new InfoCollections();
protected final InfoConfiguration infoConfiguration = new InfoConfiguration();
// Few-second timeout so that our longer operations don't time out and cause spurious error-handling results.
private static final int SHORT_TIMEOUT = 10000;
public AuthHeaderProvider getAuthHeaderProvider() {
return new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD);
}
private HTTPServerTestHelper data = new HTTPServerTestHelper();
public class TestSyncStorageRequestDelegate extends
BaseTestStorageRequestDelegate {
public TestSyncStorageRequestDelegate(String username, String password) {
super(username, password);
}
@Override
public void handleRequestSuccess(SyncStorageResponse res) {
assertTrue(res.wasSuccessful());
assertTrue(res.httpResponse().containsHeader("X-Weave-Timestamp"));
BaseResource.consumeEntity(res);
data.stopHTTPServer();
}
}
@SuppressWarnings("static-method")
protected TrackingWBORepository getLocal(int numRecords) {
final TrackingWBORepository local = new TrackingWBORepository();
for (int i = 0; i < numRecords; i++) {
BookmarkRecord outbound = new BookmarkRecord("outboundFail" + i, "bookmarks", 1, false);
local.wbos.put(outbound.guid, outbound);
}
return local;
}
protected Exception doSynchronize(MockServer server) throws Exception {
final String COLLECTION = "test";
final TrackingWBORepository local = getLocal(100);
final Server11Repository remote = new Server11Repository(COLLECTION, getCollectionURL(COLLECTION), authHeaderProvider, infoCollections, infoConfiguration);
KeyBundle collectionKey = new KeyBundle(TEST_USERNAME, SYNC_KEY);
Crypto5MiddlewareRepository cryptoRepo = new Crypto5MiddlewareRepository(remote, collectionKey);
cryptoRepo.recordFactory = new BookmarkRecordFactory();
final Synchronizer synchronizer = new ServerLocalSynchronizer();
synchronizer.repositoryA = cryptoRepo;
synchronizer.repositoryB = local;
data.startHTTPServer(server);
try {
Exception e = TestServerLocalSynchronizer.doSynchronize(synchronizer);
return e;
} finally {
data.stopHTTPServer();
}
}
protected String getCollectionURL(String collection) {
return LOCAL_BASE_URL + "/storage/" + collection;
}
@Test
public void testFetchFailure() throws Exception {
MockServer server = new MockServer(404, "error");
Exception e = doSynchronize(server);
assertNotNull(e);
assertEquals(FetchFailedException.class, e.getClass());
}
@Test
public void testStorePostSuccessWithFailingRecords() throws Exception {
MockServer server = new MockServer(200, "{ modified: \" + " + Utils.millisecondsToDecimalSeconds(System.currentTimeMillis()) + ", " +
"success: []," +
"failed: { outboundFail2: [] } }");
Exception e = doSynchronize(server);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testStorePostFailure() throws Exception {
MockServer server = new MockServer() {
@Override
public void handle(Request request, Response response) {
if (request.getMethod().equals("POST")) {
this.handle(request, response, 404, "missing");
}
this.handle(request, response, 200, "success");
return;
}
};
Exception e = doSynchronize(server);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testConstraints() throws Exception {
MockServer server = new MockServer() {
@Override
public void handle(Request request, Response response) {
if (request.getMethod().equals("GET")) {
if (request.getPath().getPath().endsWith("/info/collection_counts")) {
this.handle(request, response, 200, "{\"bookmarks\": 5001}");
}
}
this.handle(request, response, 400, "NOOOO");
}
};
final JSONRecordFetcher countsFetcher = new JSONRecordFetcher(LOCAL_COUNTS_URL, getAuthHeaderProvider());
String collection = "bookmarks";
final SafeConstrainedServer11Repository remote = new SafeConstrainedServer11Repository(collection,
getCollectionURL(collection),
getAuthHeaderProvider(),
infoCollections,
infoConfiguration,
5000, 5000, "sortindex", countsFetcher);
data.startHTTPServer(server);
final AtomicBoolean out = new AtomicBoolean(false);
// Verify that shouldSkip returns true due to a fetch of too large counts,
// rather than due to a timeout failure waiting to fetch counts.
try {
WaitHelper.getTestWaiter().performWait(
SHORT_TIMEOUT,
new Runnable() {
@Override
public void run() {
remote.createSession(new RepositorySessionCreationDelegate() {
@Override
public void onSessionCreated(RepositorySession session) {
out.set(session.shouldSkip());
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void onSessionCreateFailed(Exception ex) {
WaitHelper.getTestWaiter().performNotify(ex);
}
@Override
public RepositorySessionCreationDelegate deferredCreationDelegate() {
return this;
}
}, null);
}
});
assertTrue(out.get());
} finally {
data.stopHTTPServer();
}
}
}

View file

@ -1,237 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.SynchronizerHelpers.BatchFailStoreWBORepository;
import org.mozilla.android.sync.test.SynchronizerHelpers.BeginErrorWBORepository;
import org.mozilla.android.sync.test.SynchronizerHelpers.BeginFailedException;
import org.mozilla.android.sync.test.SynchronizerHelpers.FailFetchWBORepository;
import org.mozilla.android.sync.test.SynchronizerHelpers.FinishErrorWBORepository;
import org.mozilla.android.sync.test.SynchronizerHelpers.FinishFailedException;
import org.mozilla.android.sync.test.SynchronizerHelpers.SerialFailStoreWBORepository;
import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WBORepository;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.FetchFailedException;
import org.mozilla.gecko.sync.repositories.StoreFailedException;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.synchronizer.ServerLocalSynchronizer;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
import java.util.ArrayList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
@RunWith(TestRunner.class)
public class TestServerLocalSynchronizer {
public static final String LOG_TAG = "TestServLocSync";
protected Synchronizer getSynchronizer(WBORepository remote, WBORepository local) {
BookmarkRecord[] inbounds = new BookmarkRecord[] {
new BookmarkRecord("inboundSucc1", "bookmarks", 1, false),
new BookmarkRecord("inboundSucc2", "bookmarks", 1, false),
new BookmarkRecord("inboundFail1", "bookmarks", 1, false),
new BookmarkRecord("inboundSucc3", "bookmarks", 1, false),
new BookmarkRecord("inboundFail2", "bookmarks", 1, false),
new BookmarkRecord("inboundFail3", "bookmarks", 1, false),
};
BookmarkRecord[] outbounds = new BookmarkRecord[] {
new BookmarkRecord("outboundFail1", "bookmarks", 1, false),
new BookmarkRecord("outboundFail2", "bookmarks", 1, false),
new BookmarkRecord("outboundFail3", "bookmarks", 1, false),
new BookmarkRecord("outboundFail4", "bookmarks", 1, false),
new BookmarkRecord("outboundFail5", "bookmarks", 1, false),
new BookmarkRecord("outboundFail6", "bookmarks", 1, false),
};
for (BookmarkRecord inbound : inbounds) {
remote.wbos.put(inbound.guid, inbound);
}
for (BookmarkRecord outbound : outbounds) {
local.wbos.put(outbound.guid, outbound);
}
final Synchronizer synchronizer = new ServerLocalSynchronizer();
synchronizer.repositoryA = remote;
synchronizer.repositoryB = local;
return synchronizer;
}
protected static Exception doSynchronize(final Synchronizer synchronizer) {
final ArrayList<Exception> a = new ArrayList<Exception>();
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
synchronizer.synchronize(null, new SynchronizerDelegate() {
@Override
public void onSynchronized(Synchronizer synchronizer) {
Logger.trace(LOG_TAG, "Got onSynchronized.");
a.add(null);
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void onSynchronizeFailed(Synchronizer synchronizer, Exception lastException, String reason) {
Logger.trace(LOG_TAG, "Got onSynchronizedFailed.");
a.add(lastException);
WaitHelper.getTestWaiter().performNotify();
}
});
}
});
assertEquals(1, a.size()); // Should not be called multiple times!
return a.get(0);
}
@Test
public void testNoErrors() {
WBORepository remote = new TrackingWBORepository();
WBORepository local = new TrackingWBORepository();
Synchronizer synchronizer = getSynchronizer(remote, local);
assertNull(doSynchronize(synchronizer));
assertEquals(12, local.wbos.size());
assertEquals(12, remote.wbos.size());
}
@Test
public void testLocalFetchErrors() {
WBORepository remote = new TrackingWBORepository();
WBORepository local = new FailFetchWBORepository();
Synchronizer synchronizer = getSynchronizer(remote, local);
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(FetchFailedException.class, e.getClass());
// Neither session gets finished successfully, so all records are dropped.
assertEquals(6, local.wbos.size());
assertEquals(6, remote.wbos.size());
}
@Test
public void testRemoteFetchErrors() {
WBORepository remote = new FailFetchWBORepository();
WBORepository local = new TrackingWBORepository();
Synchronizer synchronizer = getSynchronizer(remote, local);
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(FetchFailedException.class, e.getClass());
// Neither session gets finished successfully, so all records are dropped.
assertEquals(6, local.wbos.size());
assertEquals(6, remote.wbos.size());
}
@Test
public void testLocalSerialStoreErrorsAreIgnored() {
WBORepository remote = new TrackingWBORepository();
WBORepository local = new SerialFailStoreWBORepository();
Synchronizer synchronizer = getSynchronizer(remote, local);
assertNull(doSynchronize(synchronizer));
assertEquals(9, local.wbos.size());
assertEquals(12, remote.wbos.size());
}
@Test
public void testLocalBatchStoreErrorsAreIgnored() {
final int BATCH_SIZE = 3;
Synchronizer synchronizer = getSynchronizer(new TrackingWBORepository(), new BatchFailStoreWBORepository(BATCH_SIZE));
Exception e = doSynchronize(synchronizer);
assertNull(e);
}
@Test
public void testRemoteSerialStoreErrorsAreNotIgnored() throws Exception {
Synchronizer synchronizer = getSynchronizer(new SerialFailStoreWBORepository(), new TrackingWBORepository()); // Tracking so we don't send incoming records back.
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testRemoteBatchStoreErrorsAreNotIgnoredManyBatches() throws Exception {
final int BATCH_SIZE = 3;
Synchronizer synchronizer = getSynchronizer(new BatchFailStoreWBORepository(BATCH_SIZE), new TrackingWBORepository()); // Tracking so we don't send incoming records back.
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testRemoteBatchStoreErrorsAreNotIgnoredOneBigBatch() throws Exception {
final int BATCH_SIZE = 20;
Synchronizer synchronizer = getSynchronizer(new BatchFailStoreWBORepository(BATCH_SIZE), new TrackingWBORepository()); // Tracking so we don't send incoming records back.
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(StoreFailedException.class, e.getClass());
}
@Test
public void testSessionRemoteBeginError() {
Synchronizer synchronizer = getSynchronizer(new BeginErrorWBORepository(), new TrackingWBORepository());
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(BeginFailedException.class, e.getClass());
}
@Test
public void testSessionLocalBeginError() {
Synchronizer synchronizer = getSynchronizer(new TrackingWBORepository(), new BeginErrorWBORepository());
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(BeginFailedException.class, e.getClass());
}
@Test
public void testSessionRemoteFinishError() {
Synchronizer synchronizer = getSynchronizer(new FinishErrorWBORepository(), new TrackingWBORepository());
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(FinishFailedException.class, e.getClass());
}
@Test
public void testSessionLocalFinishError() {
Synchronizer synchronizer = getSynchronizer(new TrackingWBORepository(), new FinishErrorWBORepository());
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(FinishFailedException.class, e.getClass());
}
@Test
public void testSessionBothBeginError() {
Synchronizer synchronizer = getSynchronizer(new BeginErrorWBORepository(), new BeginErrorWBORepository());
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(BeginFailedException.class, e.getClass());
}
@Test
public void testSessionBothFinishError() {
Synchronizer synchronizer = getSynchronizer(new FinishErrorWBORepository(), new FinishErrorWBORepository());
Exception e = doSynchronize(synchronizer);
assertNotNull(e);
assertEquals(FinishFailedException.class, e.getClass());
}
}

View file

@ -1,39 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.Sync11Configuration;
import org.mozilla.gecko.sync.SyncConfiguration;
import java.net.URI;
@RunWith(TestRunner.class)
public class TestSyncConfiguration {
@Test
public void testURLs() throws Exception {
final MockSharedPreferences prefs = new MockSharedPreferences();
// N.B., the username isn't used in the cluster path.
SyncConfiguration fxaConfig = new SyncConfiguration("username", null, prefs);
fxaConfig.clusterURL = new URI("http://db1.oldsync.dev.lcip.org/1.1/174");
Assert.assertEquals("http://db1.oldsync.dev.lcip.org/1.1/174/info/collections", fxaConfig.infoCollectionsURL());
Assert.assertEquals("http://db1.oldsync.dev.lcip.org/1.1/174/info/collection_counts", fxaConfig.infoCollectionCountsURL());
Assert.assertEquals("http://db1.oldsync.dev.lcip.org/1.1/174/storage/meta/global", fxaConfig.metaURL());
Assert.assertEquals("http://db1.oldsync.dev.lcip.org/1.1/174/storage", fxaConfig.storageURL());
Assert.assertEquals("http://db1.oldsync.dev.lcip.org/1.1/174/storage/collection", fxaConfig.collectionURI("collection").toASCIIString());
SyncConfiguration oldConfig = new Sync11Configuration("username", null, prefs);
oldConfig.clusterURL = new URI("https://db.com/internal/");
Assert.assertEquals("https://db.com/internal/1.1/username/info/collections", oldConfig.infoCollectionsURL());
Assert.assertEquals("https://db.com/internal/1.1/username/info/collection_counts", oldConfig.infoCollectionCountsURL());
Assert.assertEquals("https://db.com/internal/1.1/username/storage/meta/global", oldConfig.metaURL());
Assert.assertEquals("https://db.com/internal/1.1/username/storage", oldConfig.storageURL());
Assert.assertEquals("https://db.com/internal/1.1/username/storage/collection", oldConfig.collectionURI("collection").toASCIIString());
}
}

View file

@ -1,398 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import android.content.Context;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.SynchronizerHelpers.TrackingWBORepository;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WBORepository;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.mozilla.gecko.sync.synchronizer.SynchronizerDelegate;
import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
import org.mozilla.gecko.sync.synchronizer.SynchronizerSessionDelegate;
import java.util.Date;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestSynchronizer {
public static final String LOG_TAG = "TestSynchronizer";
public static void assertInRangeInclusive(long earliest, long value, long latest) {
assertTrue(earliest <= value);
assertTrue(latest >= value);
}
public static void recordEquals(BookmarkRecord r, String guid, long lastModified, boolean deleted, String collection) {
assertEquals(r.guid, guid);
assertEquals(r.lastModified, lastModified);
assertEquals(r.deleted, deleted);
assertEquals(r.collection, collection);
}
public static void recordEquals(BookmarkRecord a, BookmarkRecord b) {
assertEquals(a.guid, b.guid);
assertEquals(a.lastModified, b.lastModified);
assertEquals(a.deleted, b.deleted);
assertEquals(a.collection, b.collection);
}
@Before
public void setUp() {
WaitHelper.resetTestWaiter();
}
@After
public void tearDown() {
WaitHelper.resetTestWaiter();
}
@Test
public void testSynchronizerSession() {
final Context context = null;
final WBORepository repoA = new TrackingWBORepository();
final WBORepository repoB = new TrackingWBORepository();
final String collection = "bookmarks";
final boolean deleted = false;
final String guidA = "abcdabcdabcd";
final String guidB = "ffffffffffff";
final String guidC = "xxxxxxxxxxxx";
final long lastModifiedA = 312345;
final long lastModifiedB = 412340;
final long lastModifiedC = 412345;
BookmarkRecord bookmarkRecordA = new BookmarkRecord(guidA, collection, lastModifiedA, deleted);
BookmarkRecord bookmarkRecordB = new BookmarkRecord(guidB, collection, lastModifiedB, deleted);
BookmarkRecord bookmarkRecordC = new BookmarkRecord(guidC, collection, lastModifiedC, deleted);
repoA.wbos.put(guidA, bookmarkRecordA);
repoB.wbos.put(guidB, bookmarkRecordB);
repoB.wbos.put(guidC, bookmarkRecordC);
Synchronizer synchronizer = new Synchronizer();
synchronizer.repositoryA = repoA;
synchronizer.repositoryB = repoB;
final SynchronizerSession syncSession = new SynchronizerSession(synchronizer, new SynchronizerSessionDelegate() {
@Override
public void onInitialized(SynchronizerSession session) {
assertFalse(repoA.wbos.containsKey(guidB));
assertFalse(repoA.wbos.containsKey(guidC));
assertFalse(repoB.wbos.containsKey(guidA));
assertTrue(repoA.wbos.containsKey(guidA));
assertTrue(repoB.wbos.containsKey(guidB));
assertTrue(repoB.wbos.containsKey(guidC));
session.synchronize();
}
@Override
public void onSynchronized(SynchronizerSession session) {
try {
assertEquals(1, session.getInboundCount());
assertEquals(2, session.getOutboundCount());
WaitHelper.getTestWaiter().performNotify();
} catch (Throwable e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
@Override
public void onSynchronizeFailed(SynchronizerSession session,
Exception lastException, String reason) {
WaitHelper.getTestWaiter().performNotify(lastException);
}
@Override
public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
WaitHelper.getTestWaiter().performNotify(new RuntimeException());
}
});
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
syncSession.init(context, new RepositorySessionBundle(0), new RepositorySessionBundle(0));
}
});
// Verify contents.
assertTrue(repoA.wbos.containsKey(guidA));
assertTrue(repoA.wbos.containsKey(guidB));
assertTrue(repoA.wbos.containsKey(guidC));
assertTrue(repoB.wbos.containsKey(guidA));
assertTrue(repoB.wbos.containsKey(guidB));
assertTrue(repoB.wbos.containsKey(guidC));
BookmarkRecord aa = (BookmarkRecord) repoA.wbos.get(guidA);
BookmarkRecord ab = (BookmarkRecord) repoA.wbos.get(guidB);
BookmarkRecord ac = (BookmarkRecord) repoA.wbos.get(guidC);
BookmarkRecord ba = (BookmarkRecord) repoB.wbos.get(guidA);
BookmarkRecord bb = (BookmarkRecord) repoB.wbos.get(guidB);
BookmarkRecord bc = (BookmarkRecord) repoB.wbos.get(guidC);
recordEquals(aa, guidA, lastModifiedA, deleted, collection);
recordEquals(ab, guidB, lastModifiedB, deleted, collection);
recordEquals(ac, guidC, lastModifiedC, deleted, collection);
recordEquals(ba, guidA, lastModifiedA, deleted, collection);
recordEquals(bb, guidB, lastModifiedB, deleted, collection);
recordEquals(bc, guidC, lastModifiedC, deleted, collection);
recordEquals(aa, ba);
recordEquals(ab, bb);
recordEquals(ac, bc);
}
public abstract class SuccessfulSynchronizerDelegate implements SynchronizerDelegate {
public long syncAOne = 0;
public long syncBOne = 0;
@Override
public void onSynchronizeFailed(Synchronizer synchronizer,
Exception lastException, String reason) {
fail("Should not fail.");
}
}
@Test
public void testSynchronizerPersists() {
final Object monitor = new Object();
final long earliest = new Date().getTime();
Context context = null;
final WBORepository repoA = new WBORepository();
final WBORepository repoB = new WBORepository();
Synchronizer synchronizer = new Synchronizer();
synchronizer.bundleA = new RepositorySessionBundle(0);
synchronizer.bundleB = new RepositorySessionBundle(0);
synchronizer.repositoryA = repoA;
synchronizer.repositoryB = repoB;
final SuccessfulSynchronizerDelegate delegateOne = new SuccessfulSynchronizerDelegate() {
@Override
public void onSynchronized(Synchronizer synchronizer) {
Logger.trace(LOG_TAG, "onSynchronized. Success!");
syncAOne = synchronizer.bundleA.getTimestamp();
syncBOne = synchronizer.bundleB.getTimestamp();
synchronized (monitor) {
monitor.notify();
}
}
};
final SuccessfulSynchronizerDelegate delegateTwo = new SuccessfulSynchronizerDelegate() {
@Override
public void onSynchronized(Synchronizer synchronizer) {
Logger.trace(LOG_TAG, "onSynchronized. Success!");
syncAOne = synchronizer.bundleA.getTimestamp();
syncBOne = synchronizer.bundleB.getTimestamp();
synchronized (monitor) {
monitor.notify();
}
}
};
synchronized (monitor) {
synchronizer.synchronize(context, delegateOne);
try {
monitor.wait();
} catch (InterruptedException e) {
fail("Interrupted.");
}
}
long now = new Date().getTime();
Logger.trace(LOG_TAG, "Earliest is " + earliest);
Logger.trace(LOG_TAG, "syncAOne is " + delegateOne.syncAOne);
Logger.trace(LOG_TAG, "syncBOne is " + delegateOne.syncBOne);
Logger.trace(LOG_TAG, "Now: " + now);
assertInRangeInclusive(earliest, delegateOne.syncAOne, now);
assertInRangeInclusive(earliest, delegateOne.syncBOne, now);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
fail("Thread interrupted!");
}
synchronized (monitor) {
synchronizer.synchronize(context, delegateTwo);
try {
monitor.wait();
} catch (InterruptedException e) {
fail("Interrupted.");
}
}
now = new Date().getTime();
Logger.trace(LOG_TAG, "Earliest is " + earliest);
Logger.trace(LOG_TAG, "syncAOne is " + delegateTwo.syncAOne);
Logger.trace(LOG_TAG, "syncBOne is " + delegateTwo.syncBOne);
Logger.trace(LOG_TAG, "Now: " + now);
assertInRangeInclusive(earliest, delegateTwo.syncAOne, now);
assertInRangeInclusive(earliest, delegateTwo.syncBOne, now);
assertTrue(delegateTwo.syncAOne > delegateOne.syncAOne);
assertTrue(delegateTwo.syncBOne > delegateOne.syncBOne);
Logger.trace(LOG_TAG, "Reached end of test.");
}
private Synchronizer getTestSynchronizer(long tsA, long tsB) {
WBORepository repoA = new TrackingWBORepository();
WBORepository repoB = new TrackingWBORepository();
Synchronizer synchronizer = new Synchronizer();
synchronizer.bundleA = new RepositorySessionBundle(tsA);
synchronizer.bundleB = new RepositorySessionBundle(tsB);
synchronizer.repositoryA = repoA;
synchronizer.repositoryB = repoB;
return synchronizer;
}
/**
* Let's put data in two repos and synchronize them with last sync
* timestamps later than all of the records. Verify that no records
* are exchanged.
*/
@Test
public void testSynchronizerFakeTimestamps() {
final Context context = null;
final String collection = "bookmarks";
final boolean deleted = false;
final String guidA = "abcdabcdabcd";
final String guidB = "ffffffffffff";
final long lastModifiedA = 312345;
final long lastModifiedB = 412345;
BookmarkRecord bookmarkRecordA = new BookmarkRecord(guidA, collection, lastModifiedA, deleted);
BookmarkRecord bookmarkRecordB = new BookmarkRecord(guidB, collection, lastModifiedB, deleted);
final Synchronizer synchronizer = getTestSynchronizer(lastModifiedA + 10, lastModifiedB + 10);
final WBORepository repoA = (WBORepository) synchronizer.repositoryA;
final WBORepository repoB = (WBORepository) synchronizer.repositoryB;
repoA.wbos.put(guidA, bookmarkRecordA);
repoB.wbos.put(guidB, bookmarkRecordB);
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
synchronizer.synchronize(context, new SynchronizerDelegate() {
@Override
public void onSynchronized(Synchronizer synchronizer) {
try {
// No records get sent either way.
final SynchronizerSession synchronizerSession = synchronizer.getSynchronizerSession();
assertNotNull(synchronizerSession);
assertEquals(0, synchronizerSession.getInboundCount());
assertEquals(0, synchronizerSession.getOutboundCount());
WaitHelper.getTestWaiter().performNotify();
} catch (Throwable e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
@Override
public void onSynchronizeFailed(Synchronizer synchronizer,
Exception lastException, String reason) {
WaitHelper.getTestWaiter().performNotify(lastException);
}
});
}
});
// Verify contents.
assertTrue(repoA.wbos.containsKey(guidA));
assertTrue(repoB.wbos.containsKey(guidB));
assertFalse(repoB.wbos.containsKey(guidA));
assertFalse(repoA.wbos.containsKey(guidB));
BookmarkRecord aa = (BookmarkRecord) repoA.wbos.get(guidA);
BookmarkRecord ab = (BookmarkRecord) repoA.wbos.get(guidB);
BookmarkRecord ba = (BookmarkRecord) repoB.wbos.get(guidA);
BookmarkRecord bb = (BookmarkRecord) repoB.wbos.get(guidB);
assertNull(ab);
assertNull(ba);
recordEquals(aa, guidA, lastModifiedA, deleted, collection);
recordEquals(bb, guidB, lastModifiedB, deleted, collection);
}
@Test
public void testSynchronizer() {
final Context context = null;
final String collection = "bookmarks";
final boolean deleted = false;
final String guidA = "abcdabcdabcd";
final String guidB = "ffffffffffff";
final String guidC = "gggggggggggg";
final long lastModifiedA = 312345;
final long lastModifiedB = 412340;
final long lastModifiedC = 412345;
BookmarkRecord bookmarkRecordA = new BookmarkRecord(guidA, collection, lastModifiedA, deleted);
BookmarkRecord bookmarkRecordB = new BookmarkRecord(guidB, collection, lastModifiedB, deleted);
BookmarkRecord bookmarkRecordC = new BookmarkRecord(guidC, collection, lastModifiedC, deleted);
final Synchronizer synchronizer = getTestSynchronizer(0, 0);
final WBORepository repoA = (WBORepository) synchronizer.repositoryA;
final WBORepository repoB = (WBORepository) synchronizer.repositoryB;
repoA.wbos.put(guidA, bookmarkRecordA);
repoB.wbos.put(guidB, bookmarkRecordB);
repoB.wbos.put(guidC, bookmarkRecordC);
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
synchronizer.synchronize(context, new SynchronizerDelegate() {
@Override
public void onSynchronized(Synchronizer synchronizer) {
try {
// No records get sent either way.
final SynchronizerSession synchronizerSession = synchronizer.getSynchronizerSession();
assertNotNull(synchronizerSession);
assertEquals(1, synchronizerSession.getInboundCount());
assertEquals(2, synchronizerSession.getOutboundCount());
WaitHelper.getTestWaiter().performNotify();
} catch (Throwable e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
@Override
public void onSynchronizeFailed(Synchronizer synchronizer,
Exception lastException, String reason) {
WaitHelper.getTestWaiter().performNotify(lastException);
}
});
}
});
// Verify contents.
assertTrue(repoA.wbos.containsKey(guidA));
assertTrue(repoA.wbos.containsKey(guidB));
assertTrue(repoA.wbos.containsKey(guidC));
assertTrue(repoB.wbos.containsKey(guidA));
assertTrue(repoB.wbos.containsKey(guidB));
assertTrue(repoB.wbos.containsKey(guidC));
BookmarkRecord aa = (BookmarkRecord) repoA.wbos.get(guidA);
BookmarkRecord ab = (BookmarkRecord) repoA.wbos.get(guidB);
BookmarkRecord ac = (BookmarkRecord) repoA.wbos.get(guidC);
BookmarkRecord ba = (BookmarkRecord) repoB.wbos.get(guidA);
BookmarkRecord bb = (BookmarkRecord) repoB.wbos.get(guidB);
BookmarkRecord bc = (BookmarkRecord) repoB.wbos.get(guidC);
recordEquals(aa, guidA, lastModifiedA, deleted, collection);
recordEquals(ab, guidB, lastModifiedB, deleted, collection);
recordEquals(ac, guidC, lastModifiedC, deleted, collection);
recordEquals(ba, guidA, lastModifiedA, deleted, collection);
recordEquals(bb, guidB, lastModifiedB, deleted, collection);
recordEquals(bc, guidC, lastModifiedC, deleted, collection);
recordEquals(aa, ba);
recordEquals(ab, bb);
recordEquals(ac, bc);
}
}

View file

@ -1,306 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import android.content.Context;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.SynchronizerHelpers.DataAvailableWBORepository;
import org.mozilla.android.sync.test.SynchronizerHelpers.ShouldSkipWBORepository;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.background.testhelpers.WBORepository;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
import org.mozilla.gecko.sync.repositories.domain.Record;
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
import org.mozilla.gecko.sync.synchronizer.SynchronizerSession;
import org.mozilla.gecko.sync.synchronizer.SynchronizerSessionDelegate;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestSynchronizerSession {
public static final String LOG_TAG = TestSynchronizerSession.class.getSimpleName();
protected static void assertFirstContainsSecond(Map<String, Record> first, Map<String, Record> second) {
for (Entry<String, Record> entry : second.entrySet()) {
assertTrue("Expected key " + entry.getKey(), first.containsKey(entry.getKey()));
Record record = first.get(entry.getKey());
assertEquals(entry.getValue(), record);
}
}
protected static void assertFirstDoesNotContainSecond(Map<String, Record> first, Map<String, Record> second) {
for (Entry<String, Record> entry : second.entrySet()) {
assertFalse("Unexpected key " + entry.getKey(), first.containsKey(entry.getKey()));
}
}
protected WBORepository repoA = null;
protected WBORepository repoB = null;
protected SynchronizerSession syncSession = null;
protected Map<String, Record> originalWbosA = null;
protected Map<String, Record> originalWbosB = null;
@Before
public void setUp() {
repoA = new DataAvailableWBORepository(false);
repoB = new DataAvailableWBORepository(false);
final String collection = "bookmarks";
final boolean deleted = false;
final String guidA = "abcdabcdabcd";
final String guidB = "ffffffffffff";
final String guidC = "xxxxxxxxxxxx";
final long lastModifiedA = 312345;
final long lastModifiedB = 412340;
final long lastModifiedC = 412345;
final BookmarkRecord bookmarkRecordA = new BookmarkRecord(guidA, collection, lastModifiedA, deleted);
final BookmarkRecord bookmarkRecordB = new BookmarkRecord(guidB, collection, lastModifiedB, deleted);
final BookmarkRecord bookmarkRecordC = new BookmarkRecord(guidC, collection, lastModifiedC, deleted);
repoA.wbos.put(guidA, bookmarkRecordA);
repoB.wbos.put(guidB, bookmarkRecordB);
repoB.wbos.put(guidC, bookmarkRecordC);
originalWbosA = new HashMap<String, Record>(repoA.wbos);
originalWbosB = new HashMap<String, Record>(repoB.wbos);
Synchronizer synchronizer = new Synchronizer();
synchronizer.repositoryA = repoA;
synchronizer.repositoryB = repoB;
syncSession = new SynchronizerSession(synchronizer, new SynchronizerSessionDelegate() {
@Override
public void onInitialized(SynchronizerSession session) {
session.synchronize();
}
@Override
public void onSynchronized(SynchronizerSession session) {
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason) {
WaitHelper.getTestWaiter().performNotify(lastException);
}
@Override
public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
WaitHelper.getTestWaiter().performNotify(new RuntimeException("Not expecting onSynchronizeSkipped"));
}
});
}
protected void logStats() {
// Uncomment this line to print stats to console:
// Logger.startLoggingTo(new PrintLogWriter(new PrintWriter(System.out, true)));
Logger.debug(LOG_TAG, "Repo A fetch done: " + repoA.stats.fetchCompleted);
Logger.debug(LOG_TAG, "Repo B store done: " + repoB.stats.storeCompleted);
Logger.debug(LOG_TAG, "Repo B fetch done: " + repoB.stats.fetchCompleted);
Logger.debug(LOG_TAG, "Repo A store done: " + repoA.stats.storeCompleted);
SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
Logger.debug(LOG_TAG, "Repo A timestamp: " + sc.remoteBundle.getTimestamp());
Logger.debug(LOG_TAG, "Repo B timestamp: " + sc.localBundle.getTimestamp());
}
protected void doTest(boolean remoteDataAvailable, boolean localDataAvailable) {
((DataAvailableWBORepository) repoA).dataAvailable = remoteDataAvailable;
((DataAvailableWBORepository) repoB).dataAvailable = localDataAvailable;
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
final Context context = null;
syncSession.init(context,
new RepositorySessionBundle(0),
new RepositorySessionBundle(0));
}
});
logStats();
}
@Test
public void testSynchronizerSessionBothHaveData() {
long before = System.currentTimeMillis();
boolean remoteDataAvailable = true;
boolean localDataAvailable = true;
doTest(remoteDataAvailable, localDataAvailable);
long after = System.currentTimeMillis();
assertEquals(1, syncSession.getInboundCount());
assertEquals(2, syncSession.getOutboundCount());
// Didn't lose any records.
assertFirstContainsSecond(repoA.wbos, originalWbosA);
assertFirstContainsSecond(repoB.wbos, originalWbosB);
// Got new records.
assertFirstContainsSecond(repoA.wbos, originalWbosB);
assertFirstContainsSecond(repoB.wbos, originalWbosA);
// Timestamps updated.
SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
TestSynchronizer.assertInRangeInclusive(before, sc.localBundle.getTimestamp(), after);
TestSynchronizer.assertInRangeInclusive(before, sc.remoteBundle.getTimestamp(), after);
}
@Test
public void testSynchronizerSessionOnlyLocalHasData() {
long before = System.currentTimeMillis();
boolean remoteDataAvailable = false;
boolean localDataAvailable = true;
doTest(remoteDataAvailable, localDataAvailable);
long after = System.currentTimeMillis();
// Record counts updated.
assertEquals(0, syncSession.getInboundCount());
assertEquals(2, syncSession.getOutboundCount());
// Didn't lose any records.
assertFirstContainsSecond(repoA.wbos, originalWbosA);
assertFirstContainsSecond(repoB.wbos, originalWbosB);
// Got new records.
assertFirstContainsSecond(repoA.wbos, originalWbosB);
// Didn't get records we shouldn't have fetched.
assertFirstDoesNotContainSecond(repoB.wbos, originalWbosA);
// Timestamps updated.
SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
TestSynchronizer.assertInRangeInclusive(before, sc.localBundle.getTimestamp(), after);
TestSynchronizer.assertInRangeInclusive(before, sc.remoteBundle.getTimestamp(), after);
}
@Test
public void testSynchronizerSessionOnlyRemoteHasData() {
long before = System.currentTimeMillis();
boolean remoteDataAvailable = true;
boolean localDataAvailable = false;
doTest(remoteDataAvailable, localDataAvailable);
long after = System.currentTimeMillis();
// Record counts updated.
assertEquals(1, syncSession.getInboundCount());
assertEquals(0, syncSession.getOutboundCount());
// Didn't lose any records.
assertFirstContainsSecond(repoA.wbos, originalWbosA);
assertFirstContainsSecond(repoB.wbos, originalWbosB);
// Got new records.
assertFirstContainsSecond(repoB.wbos, originalWbosA);
// Didn't get records we shouldn't have fetched.
assertFirstDoesNotContainSecond(repoA.wbos, originalWbosB);
// Timestamps updated.
SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
TestSynchronizer.assertInRangeInclusive(before, sc.localBundle.getTimestamp(), after);
TestSynchronizer.assertInRangeInclusive(before, sc.remoteBundle.getTimestamp(), after);
}
@Test
public void testSynchronizerSessionNeitherHaveData() {
long before = System.currentTimeMillis();
boolean remoteDataAvailable = false;
boolean localDataAvailable = false;
doTest(remoteDataAvailable, localDataAvailable);
long after = System.currentTimeMillis();
// Record counts updated.
assertEquals(0, syncSession.getInboundCount());
assertEquals(0, syncSession.getOutboundCount());
// Didn't lose any records.
assertFirstContainsSecond(repoA.wbos, originalWbosA);
assertFirstContainsSecond(repoB.wbos, originalWbosB);
// Didn't get records we shouldn't have fetched.
assertFirstDoesNotContainSecond(repoA.wbos, originalWbosB);
assertFirstDoesNotContainSecond(repoB.wbos, originalWbosA);
// Timestamps updated.
SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
TestSynchronizer.assertInRangeInclusive(before, sc.localBundle.getTimestamp(), after);
TestSynchronizer.assertInRangeInclusive(before, sc.remoteBundle.getTimestamp(), after);
}
protected void doSkipTest(boolean remoteShouldSkip, boolean localShouldSkip) {
repoA = new ShouldSkipWBORepository(remoteShouldSkip);
repoB = new ShouldSkipWBORepository(localShouldSkip);
Synchronizer synchronizer = new Synchronizer();
synchronizer.repositoryA = repoA;
synchronizer.repositoryB = repoB;
syncSession = new SynchronizerSession(synchronizer, new SynchronizerSessionDelegate() {
@Override
public void onInitialized(SynchronizerSession session) {
session.synchronize();
}
@Override
public void onSynchronized(SynchronizerSession session) {
WaitHelper.getTestWaiter().performNotify(new RuntimeException("Not expecting onSynchronized"));
}
@Override
public void onSynchronizeFailed(SynchronizerSession session, Exception lastException, String reason) {
WaitHelper.getTestWaiter().performNotify(lastException);
}
@Override
public void onSynchronizeSkipped(SynchronizerSession synchronizerSession) {
WaitHelper.getTestWaiter().performNotify();
}
});
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
final Context context = null;
syncSession.init(context,
new RepositorySessionBundle(100),
new RepositorySessionBundle(200));
}
});
// If we skip, we don't update timestamps or even un-bundle.
SynchronizerConfiguration sc = syncSession.getSynchronizer().save();
assertNotNull(sc);
assertNull(sc.localBundle);
assertNull(sc.remoteBundle);
assertEquals(-1, syncSession.getInboundCount());
assertEquals(-1, syncSession.getOutboundCount());
}
@Test
public void testSynchronizerSessionShouldSkip() {
// These combinations should all skip.
doSkipTest(true, false);
doSkipTest(false, true);
doSkipTest(true, true);
try {
doSkipTest(false, false);
fail("Expected exception.");
} catch (WaitHelper.InnerError e) {
assertTrue(e.innerError instanceof RuntimeException);
assertEquals("Not expecting onSynchronized", e.innerError.getMessage());
}
}
}

View file

@ -1,154 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.SyncConstants;
import org.mozilla.gecko.sync.Utils;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestUtils extends Utils {
@Test
public void testGenerateGUID() {
for (int i = 0; i < 1000; ++i) {
assertEquals(12, Utils.generateGuid().length());
}
}
public static final byte[][] BYTE_ARRS = {
new byte[] {' '}, // Tab.
new byte[] {'0'},
new byte[] {'A'},
new byte[] {'a'},
new byte[] {'I', 'U'},
new byte[] {'`', 'h', 'g', ' ', 's', '`'},
new byte[] {}
};
// Indices correspond with the above array.
public static final String[] STRING_ARR = {
"09",
"30",
"41",
"61",
"4955",
"606867207360",
""
};
@Test
public void testByte2Hex() throws Exception {
for (int i = 0; i < BYTE_ARRS.length; ++i) {
final byte[] b = BYTE_ARRS[i];
final String expected = STRING_ARR[i];
assertEquals(expected, Utils.byte2Hex(b));
}
}
@Test
public void testHex2Byte() throws Exception {
for (int i = 0; i < STRING_ARR.length; ++i) {
final String s = STRING_ARR[i];
final byte[] expected = BYTE_ARRS[i];
assertTrue(Arrays.equals(expected, Utils.hex2Byte(s)));
}
}
@Test
public void testByte2Hex2ByteAndViceVersa() throws Exception { // There and back again!
for (int i = 0; i < BYTE_ARRS.length; ++i) {
// byte2Hex2Byte
final byte[] b = BYTE_ARRS[i];
final String s = Utils.byte2Hex(b);
assertTrue(Arrays.equals(b, Utils.hex2Byte(s)));
}
// hex2Byte2Hex
for (int i = 0; i < STRING_ARR.length; ++i) {
final String s = STRING_ARR[i];
final byte[] b = Utils.hex2Byte(s);
assertEquals(s, Utils.byte2Hex(b));
}
}
@Test
public void testByte2HexLength() throws Exception {
for (int i = 0; i < BYTE_ARRS.length; ++i) {
final byte[] b = BYTE_ARRS[i];
final String expected = STRING_ARR[i];
assertEquals(expected, Utils.byte2Hex(b, b.length));
assertEquals("0" + expected, Utils.byte2Hex(b, 2 * b.length + 1));
assertEquals("00" + expected, Utils.byte2Hex(b, 2 * b.length + 2));
}
}
@Test
public void testHex2ByteLength() throws Exception {
for (int i = 0; i < STRING_ARR.length; ++i) {
final String s = STRING_ARR[i];
final byte[] expected = BYTE_ARRS[i];
assertTrue(Arrays.equals(expected, Utils.hex2Byte(s)));
final byte[] expected1 = new byte[expected.length + 1];
System.arraycopy(expected, 0, expected1, 1, expected.length);
assertTrue(Arrays.equals(expected1, Utils.hex2Byte("00" + s)));
final byte[] expected2 = new byte[expected.length + 2];
System.arraycopy(expected, 0, expected2, 2, expected.length);
assertTrue(Arrays.equals(expected2, Utils.hex2Byte("0000" + s)));
}
}
@Test
public void testToCommaSeparatedString() {
ArrayList<String> xs = new ArrayList<String>();
assertEquals("", Utils.toCommaSeparatedString(null));
assertEquals("", Utils.toCommaSeparatedString(xs));
xs.add("test1");
assertEquals("test1", Utils.toCommaSeparatedString(xs));
xs.add("test2");
assertEquals("test1, test2", Utils.toCommaSeparatedString(xs));
xs.add("test3");
assertEquals("test1, test2, test3", Utils.toCommaSeparatedString(xs));
}
@Test
public void testUsernameFromAccount() throws NoSuchAlgorithmException, UnsupportedEncodingException {
assertEquals("xee7ffonluzpdp66l6xgpyh2v2w6ojkc", Utils.sha1Base32("foobar@baz.com"));
assertEquals("xee7ffonluzpdp66l6xgpyh2v2w6ojkc", Utils.usernameFromAccount("foobar@baz.com"));
assertEquals("xee7ffonluzpdp66l6xgpyh2v2w6ojkc", Utils.usernameFromAccount("FooBar@Baz.com"));
assertEquals("xee7ffonluzpdp66l6xgpyh2v2w6ojkc", Utils.usernameFromAccount("xee7ffonluzpdp66l6xgpyh2v2w6ojkc"));
assertEquals("foobar", Utils.usernameFromAccount("foobar"));
assertEquals("foobar", Utils.usernameFromAccount("FOOBAr"));
}
@Test
public void testGetPrefsPath() throws NoSuchAlgorithmException, UnsupportedEncodingException {
assertEquals("ore7dlrwqi6xr7honxdtpvmh6tly4r7k", Utils.sha1Base32("test.url.com:xee7ffonluzpdp66l6xgpyh2v2w6ojkc"));
assertEquals("sync.prefs.ore7dlrwqi6xr7honxdtpvmh6tly4r7k", Utils.getPrefsPath("product", "foobar@baz.com", "test.url.com", "default", 0));
assertEquals("sync.prefs.ore7dlrwqi6xr7honxdtpvmh6tly4r7k", Utils.getPrefsPath("org.mozilla.firefox_beta", "FooBar@Baz.com", "test.url.com", "default", 0));
assertEquals("sync.prefs.ore7dlrwqi6xr7honxdtpvmh6tly4r7k", Utils.getPrefsPath("org.mozilla.firefox", "xee7ffonluzpdp66l6xgpyh2v2w6ojkc", "test.url.com", "profile", 0));
assertEquals("sync.prefs.product.ore7dlrwqi6xr7honxdtpvmh6tly4r7k.default.1", Utils.getPrefsPath("product", "foobar@baz.com", "test.url.com", "default", 1));
assertEquals("sync.prefs.with!spaces_underbars!periods.ore7dlrwqi6xr7honxdtpvmh6tly4r7k.default.1", Utils.getPrefsPath("with spaces_underbars.periods", "foobar@baz.com", "test.url.com", "default", 1));
assertEquals("sync.prefs.org!mozilla!firefox_beta.ore7dlrwqi6xr7honxdtpvmh6tly4r7k.default.2", Utils.getPrefsPath("org.mozilla.firefox_beta", "FooBar@Baz.com", "test.url.com", "default", 2));
assertEquals("sync.prefs.org!mozilla!firefox.ore7dlrwqi6xr7honxdtpvmh6tly4r7k.profile.3", Utils.getPrefsPath("org.mozilla.firefox", "xee7ffonluzpdp66l6xgpyh2v2w6ojkc", "test.url.com", "profile", 3));
}
@Test
public void testObfuscateEmail() {
assertEquals("XXX@XXX.XXX", Utils.obfuscateEmail("foo@bar.com"));
assertEquals("XXXX@XXX.XXXX.XX", Utils.obfuscateEmail("foot@bar.test.ca"));
}
}

View file

@ -1,61 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import java.io.IOException;
import static org.junit.Assert.fail;
public class BaseTestStorageRequestDelegate implements
SyncStorageRequestDelegate {
protected final AuthHeaderProvider authHeaderProvider;
public BaseTestStorageRequestDelegate(AuthHeaderProvider authHeaderProvider) {
this.authHeaderProvider = authHeaderProvider;
}
public BaseTestStorageRequestDelegate(String username, String password) {
this(new BasicAuthHeaderProvider(username, password));
}
@Override
public AuthHeaderProvider getAuthHeaderProvider() {
return authHeaderProvider;
}
@Override
public String ifUnmodifiedSince() {
return null;
}
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
BaseResource.consumeEntity(response);
fail("Should not be called.");
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
System.out.println("Response: " + response.httpResponse().getStatusLine().getStatusCode());
BaseResource.consumeEntity(response);
fail("Should not be called.");
}
@Override
public void handleRequestError(Exception e) {
if (e instanceof IOException) {
System.out.println("WARNING: TEST FAILURE IGNORED!");
// Assume that this is because Jenkins doesn't have network access.
return;
}
fail("Should not error.");
}
}

View file

@ -1,35 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
public class ExpectSuccessDelegate {
public WaitHelper waitHelper;
public ExpectSuccessDelegate(WaitHelper waitHelper) {
this.waitHelper = waitHelper;
}
public void performNotify() {
this.waitHelper.performNotify();
}
public void performNotify(Throwable e) {
this.waitHelper.performNotify(e);
}
public String logTag() {
return this.getClass().getSimpleName();
}
public void log(String message) {
Logger.info(logTag(), message);
}
public void log(String message, Throwable throwable) {
Logger.warn(logTag(), message, throwable);
}
}

View file

@ -1,38 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import junit.framework.AssertionFailedError;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
import java.util.concurrent.ExecutorService;
public class ExpectSuccessRepositorySessionBeginDelegate
extends ExpectSuccessDelegate
implements RepositorySessionBeginDelegate {
public ExpectSuccessRepositorySessionBeginDelegate(WaitHelper waitHelper) {
super(waitHelper);
}
@Override
public void onBeginFailed(Exception ex) {
log("Session begin failed.", ex);
performNotify(new AssertionFailedError("Session begin failed: " + ex.getMessage()));
}
@Override
public void onBeginSucceeded(RepositorySession session) {
log("Session begin succeeded.");
performNotify();
}
@Override
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
log("Session begin delegate deferred.");
return this;
}
}

View file

@ -1,36 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import junit.framework.AssertionFailedError;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
public class ExpectSuccessRepositorySessionCreationDelegate extends
ExpectSuccessDelegate implements RepositorySessionCreationDelegate {
public ExpectSuccessRepositorySessionCreationDelegate(WaitHelper waitHelper) {
super(waitHelper);
}
@Override
public void onSessionCreateFailed(Exception ex) {
log("Session creation failed.", ex);
performNotify(new AssertionFailedError("onSessionCreateFailed: session creation should not have failed."));
}
@Override
public void onSessionCreated(RepositorySession session) {
log("Session creation succeeded.");
performNotify();
}
@Override
public RepositorySessionCreationDelegate deferredCreationDelegate() {
log("Session creation deferred.");
return this;
}
}

View file

@ -1,44 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import junit.framework.AssertionFailedError;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
import org.mozilla.gecko.sync.repositories.domain.Record;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
public class ExpectSuccessRepositorySessionFetchRecordsDelegate extends
ExpectSuccessDelegate implements RepositorySessionFetchRecordsDelegate {
public ArrayList<Record> fetchedRecords = new ArrayList<Record>();
public ExpectSuccessRepositorySessionFetchRecordsDelegate(WaitHelper waitHelper) {
super(waitHelper);
}
@Override
public void onFetchFailed(Exception ex, Record record) {
log("Fetch failed.", ex);
performNotify(new AssertionFailedError("onFetchFailed: fetch should not have failed."));
}
@Override
public void onFetchedRecord(Record record) {
fetchedRecords.add(record);
log("Fetched record with guid '" + record.guid + "'.");
}
@Override
public void onFetchCompleted(long end) {
log("Fetch completed.");
performNotify();
}
@Override
public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
return this;
}
}

View file

@ -1,37 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import junit.framework.AssertionFailedError;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.RepositorySession;
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
import java.util.concurrent.ExecutorService;
public class ExpectSuccessRepositorySessionFinishDelegate extends
ExpectSuccessDelegate implements RepositorySessionFinishDelegate {
public ExpectSuccessRepositorySessionFinishDelegate(WaitHelper waitHelper) {
super(waitHelper);
}
@Override
public void onFinishFailed(Exception ex) {
log("Finish failed.", ex);
performNotify(new AssertionFailedError("onFinishFailed: finish should not have failed."));
}
@Override
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
log("Finish succeeded.");
performNotify();
}
@Override
public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
return this;
}
}

View file

@ -1,39 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import junit.framework.AssertionFailedError;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
import java.util.concurrent.ExecutorService;
public class ExpectSuccessRepositorySessionStoreDelegate extends
ExpectSuccessDelegate implements RepositorySessionStoreDelegate {
public ExpectSuccessRepositorySessionStoreDelegate(WaitHelper waitHelper) {
super(waitHelper);
}
@Override
public void onRecordStoreFailed(Exception ex, String guid) {
log("Record store failed.", ex);
performNotify(new AssertionFailedError("onRecordStoreFailed: record store should not have failed."));
}
@Override
public void onRecordStoreSucceeded(String guid) {
log("Record store succeeded.");
}
@Override
public void onStoreCompleted(long storeEnd) {
log("Record store completed at " + storeEnd);
}
@Override
public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService executor) {
return this;
}
}

View file

@ -1,36 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import junit.framework.AssertionFailedError;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import java.util.concurrent.ExecutorService;
public class ExpectSuccessRepositoryWipeDelegate extends ExpectSuccessDelegate
implements RepositorySessionWipeDelegate {
public ExpectSuccessRepositoryWipeDelegate(WaitHelper waitHelper) {
super(waitHelper);
}
@Override
public void onWipeSucceeded() {
log("Wipe succeeded.");
performNotify();
}
@Override
public void onWipeFailed(Exception ex) {
log("Wipe failed.", ex);
performNotify(new AssertionFailedError("onWipeFailed: wipe should not have failed."));
}
@Override
public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService executor) {
log("Wipe deferred.");
return this;
}
}

View file

@ -1,226 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
import org.simpleframework.http.core.ContainerSocketProcessor;
import org.simpleframework.transport.connect.Connection;
import org.simpleframework.transport.connect.SocketConnection;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.IdentityHashMap;
import java.util.Map;
import static org.junit.Assert.fail;
/**
* Test helper code to bind <code>MockServer</code> instances to ports.
* <p>
* Maintains a collection of running servers and (by default) throws helpful
* errors if two servers are started "on top" of each other. The
* <b>unchecked</b> exception thrown contains a stack trace pointing to where
* the new server is being created and where the pre-existing server was
* created.
* <p>
* Parses a system property to determine current test port, which is fixed for
* the duration of a test execution.
*/
public class HTTPServerTestHelper {
private static final String LOG_TAG = "HTTPServerTestHelper";
/**
* Port to run HTTP servers on during this test execution.
* <p>
* Lazily initialized on first call to {@link #getTestPort}.
*/
public static Integer testPort = null;
public static final String LOCAL_HTTP_PORT_PROPERTY = "android.sync.local.http.port";
public static final int LOCAL_HTTP_PORT_DEFAULT = 15125;
public final int port;
public Connection connection;
public MockServer server;
/**
* Create a helper to bind <code>MockServer</code> instances.
* <p>
* Use {@link #getTestPort} to determine the port this helper will bind to.
*/
public HTTPServerTestHelper() {
this.port = getTestPort();
}
// For testing only.
protected HTTPServerTestHelper(int port) {
this.port = port;
}
/**
* Lazily initialize test port for this test execution.
* <p>
* Only called from {@link #getTestPort}.
* <p>
* If the test port has not been determined, we try to parse it from a system
* property; if that fails, we return the default test port.
*/
protected synchronized static void ensureTestPort() {
if (testPort != null) {
return;
}
String value = System.getProperty(LOCAL_HTTP_PORT_PROPERTY);
if (value != null) {
try {
testPort = Integer.valueOf(value);
} catch (NumberFormatException e) {
Logger.warn(LOG_TAG, "Got exception parsing local test port; ignoring. ", e);
}
}
if (testPort == null) {
testPort = Integer.valueOf(LOCAL_HTTP_PORT_DEFAULT);
}
}
/**
* The port to which all HTTP servers will be found for the duration of this
* test execution.
* <p>
* We try to parse the port from a system property; if that fails, we return
* the default test port.
*
* @return port number.
*/
public synchronized static int getTestPort() {
if (testPort == null) {
ensureTestPort();
}
return testPort.intValue();
}
/**
* Used to maintain a stack trace pointing to where a server was started.
*/
public static class HTTPServerStartedError extends Error {
private static final long serialVersionUID = -6778447718799087274L;
public final HTTPServerTestHelper httpServer;
public HTTPServerStartedError(HTTPServerTestHelper httpServer) {
this.httpServer = httpServer;
}
}
/**
* Thrown when a server is started "on top" of another server. The cause error
* will be an <code>HTTPServerStartedError</code> with a stack trace pointing
* to where the pre-existing server was started.
*/
public static class HTTPServerAlreadyRunningError extends Error {
private static final long serialVersionUID = -6778447718799087275L;
public HTTPServerAlreadyRunningError(Throwable e) {
super(e);
}
}
/**
* Maintain a hash of running servers. Each value is an error with a stack
* traces pointing to where that server was started.
* <p>
* We don't key on the server itself because each server is a <it>helper</it>
* that may be started many times with different <code>MockServer</code>
* instances.
* <p>
* Synchronize access on the class.
*/
protected static Map<Connection, HTTPServerStartedError> runningServers =
new IdentityHashMap<Connection, HTTPServerStartedError>();
protected synchronized static void throwIfServerAlreadyRunning() {
for (HTTPServerStartedError value : runningServers.values()) {
throw new HTTPServerAlreadyRunningError(value);
}
}
protected synchronized static void registerServerAsRunning(HTTPServerTestHelper httpServer) {
if (httpServer == null || httpServer.connection == null) {
throw new IllegalArgumentException("HTTPServerTestHelper or connection was null; perhaps server has not been started?");
}
HTTPServerStartedError old = runningServers.put(httpServer.connection, new HTTPServerStartedError(httpServer));
if (old != null) {
// Should never happen.
throw old;
}
}
protected synchronized static void unregisterServerAsRunning(HTTPServerTestHelper httpServer) {
if (httpServer == null || httpServer.connection == null) {
throw new IllegalArgumentException("HTTPServerTestHelper or connection was null; perhaps server has not been started?");
}
runningServers.remove(httpServer.connection);
}
public MockServer startHTTPServer(MockServer server, boolean allowMultipleServers) {
BaseResource.rewriteLocalhost = false; // No sense rewriting when we're running the unit tests.
BaseResourceDelegate.connectionTimeoutInMillis = 1000; // No sense waiting a long time for a local connection.
if (!allowMultipleServers) {
throwIfServerAlreadyRunning();
}
try {
this.server = server;
connection = new SocketConnection(new ContainerSocketProcessor(server));
SocketAddress address = new InetSocketAddress(port);
connection.connect(address);
registerServerAsRunning(this);
Logger.info(LOG_TAG, "Started HTTP server on port " + port + ".");
} catch (IOException ex) {
Logger.error(LOG_TAG, "Error starting HTTP server on port " + port + ".", ex);
fail(ex.toString());
}
return server;
}
public MockServer startHTTPServer(MockServer server) {
return startHTTPServer(server, false);
}
public MockServer startHTTPServer() {
return startHTTPServer(new MockServer());
}
public void stopHTTPServer() {
try {
if (connection != null) {
unregisterServerAsRunning(this);
connection.close();
}
server = null;
connection = null;
Logger.info(LOG_TAG, "Stopped HTTP server on port " + port + ".");
Logger.debug(LOG_TAG, "Closing connection pool...");
BaseResource.shutdownConnectionManager();
} catch (IOException ex) {
Logger.error(LOG_TAG, "Error stopping HTTP server on port " + port + ".", ex);
fail(ex.toString());
}
}
}

View file

@ -1,91 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import java.net.URI;
import static org.junit.Assert.assertEquals;
/**
* A callback for use with a GlobalSession that records what happens for later
* inspection.
*
* This callback is expected to be used from within the friendly confines of a
* WaitHelper performWait.
*/
public class MockGlobalSessionCallback implements GlobalSessionCallback {
protected WaitHelper testWaiter() {
return WaitHelper.getTestWaiter();
}
public int stageCounter = Stage.values().length - 1; // Exclude starting state.
public boolean calledSuccess = false;
public boolean calledError = false;
public Exception calledErrorException = null;
public boolean calledAborted = false;
public boolean calledRequestBackoff = false;
public boolean calledInformUnauthorizedResponse = false;
public boolean calledInformUpgradeRequiredResponse = false;
public boolean calledInformMigrated = false;
public URI calledInformUnauthorizedResponseClusterURL = null;
public long weaveBackoff = -1;
@Override
public void handleSuccess(GlobalSession globalSession) {
this.calledSuccess = true;
assertEquals(0, this.stageCounter);
this.testWaiter().performNotify();
}
@Override
public void handleAborted(GlobalSession globalSession, String reason) {
this.calledAborted = true;
this.testWaiter().performNotify();
}
@Override
public void handleError(GlobalSession globalSession, Exception ex) {
this.calledError = true;
this.calledErrorException = ex;
this.testWaiter().performNotify();
}
@Override
public void handleStageCompleted(Stage currentState,
GlobalSession globalSession) {
stageCounter--;
}
@Override
public void requestBackoff(long backoff) {
this.calledRequestBackoff = true;
this.weaveBackoff = backoff;
}
@Override
public void informUnauthorizedResponse(GlobalSession session, URI clusterURL) {
this.calledInformUnauthorizedResponse = true;
this.calledInformUnauthorizedResponseClusterURL = clusterURL;
}
@Override
public void informUpgradeRequiredResponse(GlobalSession session) {
this.calledInformUpgradeRequiredResponse = true;
}
@Override
public void informMigrated(GlobalSession session) {
this.calledInformMigrated = true;
}
@Override
public boolean shouldBackOffStorage() {
return false;
}
}

View file

@ -1,85 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
import org.mozilla.gecko.background.testhelpers.WaitHelper;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.net.ResourceDelegate;
import java.io.IOException;
import java.security.GeneralSecurityException;
import static org.junit.Assert.assertEquals;
public class MockResourceDelegate implements ResourceDelegate {
public WaitHelper waitHelper = null;
public static String USER_PASS = "john:password";
public static String EXPECT_BASIC = "Basic am9objpwYXNzd29yZA==";
public boolean handledHttpResponse = false;
public HttpResponse httpResponse = null;
public MockResourceDelegate(WaitHelper waitHelper) {
this.waitHelper = waitHelper;
}
public MockResourceDelegate() {
this.waitHelper = WaitHelper.getTestWaiter();
}
@Override
public String getUserAgent() {
return null;
}
@Override
public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
}
@Override
public int connectionTimeout() {
return 0;
}
@Override
public int socketTimeout() {
return 0;
}
@Override
public AuthHeaderProvider getAuthHeaderProvider() {
return new BasicAuthHeaderProvider(USER_PASS);
}
@Override
public void handleHttpProtocolException(ClientProtocolException e) {
waitHelper.performNotify(e);
}
@Override
public void handleHttpIOException(IOException e) {
waitHelper.performNotify(e);
}
@Override
public void handleTransportException(GeneralSecurityException e) {
waitHelper.performNotify(e);
}
@Override
public void handleHttpResponse(HttpResponse response) {
handledHttpResponse = true;
httpResponse = response;
assertEquals(response.getStatusLine().getStatusCode(), 200);
BaseResource.consumeEntity(response);
waitHelper.performNotify();
}
}

View file

@ -1,73 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.Utils;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import org.simpleframework.http.core.Container;
import java.io.IOException;
import java.io.PrintStream;
import static org.junit.Assert.assertEquals;
public class MockServer implements Container {
public static final String LOG_TAG = "MockServer";
public int statusCode = 200;
public String body = "Hello World";
public MockServer() {
}
public MockServer(int statusCode, String body) {
this.statusCode = statusCode;
this.body = body;
}
public String expectedBasicAuthHeader;
protected PrintStream handleBasicHeaders(Request request, Response response, int code, String contentType) throws IOException {
return this.handleBasicHeaders(request, response, code, contentType, System.currentTimeMillis());
}
protected PrintStream handleBasicHeaders(Request request, Response response, int code, String contentType, long time) throws IOException {
Logger.debug(LOG_TAG, "< Auth header: " + request.getValue("Authorization"));
PrintStream bodyStream = response.getPrintStream();
response.setCode(code);
response.setValue("Content-Type", contentType);
response.setValue("Server", "HelloWorld/1.0 (Simple 4.0)");
response.setDate("Date", time);
response.setDate("Last-Modified", time);
final String timestampHeader = Utils.millisecondsToDecimalSecondsString(time);
response.setValue("X-Weave-Timestamp", timestampHeader);
Logger.debug(LOG_TAG, "> X-Weave-Timestamp header: " + timestampHeader);
response.setValue("X-Last-Modified", "12345678");
return bodyStream;
}
protected void handle(Request request, Response response, int code, String body) {
try {
Logger.debug(LOG_TAG, "Handling request...");
PrintStream bodyStream = this.handleBasicHeaders(request, response, code, "application/json");
if (expectedBasicAuthHeader != null) {
Logger.debug(LOG_TAG, "Expecting auth header " + expectedBasicAuthHeader);
assertEquals(request.getValue("Authorization"), expectedBasicAuthHeader);
}
bodyStream.println(body);
bodyStream.close();
} catch (IOException e) {
Logger.error(LOG_TAG, "Oops.");
}
}
public void handle(Request request, Response response) {
this.handle(request, response, statusCode, body);
}
}

View file

@ -1,71 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.net.SyncStorageResponse;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import static org.junit.Assert.assertTrue;
public class MockSyncClientsEngineStage extends SyncClientsEngineStage {
public class MockClientUploadDelegate extends ClientUploadDelegate {
HTTPServerTestHelper data;
public MockClientUploadDelegate(HTTPServerTestHelper data) {
this.data = data;
}
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
assertTrue(response.wasSuccessful());
data.stopHTTPServer();
super.handleRequestSuccess(response);
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
BaseResource.consumeEntity(response);
data.stopHTTPServer();
super.handleRequestFailure(response);
}
@Override
public void handleRequestError(Exception ex) {
ex.printStackTrace();
data.stopHTTPServer();
super.handleRequestError(ex);
}
}
public class TestClientDownloadDelegate extends ClientDownloadDelegate {
HTTPServerTestHelper data;
public TestClientDownloadDelegate(HTTPServerTestHelper data) {
this.data = data;
}
@Override
public void handleRequestSuccess(SyncStorageResponse response) {
assertTrue(response.wasSuccessful());
BaseResource.consumeEntity(response);
data.stopHTTPServer();
super.handleRequestSuccess(response);
}
@Override
public void handleRequestFailure(SyncStorageResponse response) {
BaseResource.consumeEntity(response);
super.handleRequestFailure(response);
data.stopHTTPServer();
}
@Override
public void handleRequestError(Exception ex) {
ex.printStackTrace();
super.handleRequestError(ex);
data.stopHTTPServer();
}
}
}

View file

@ -1,28 +0,0 @@
package org.mozilla.android.sync.test.helpers;
import org.simpleframework.http.Path;
import org.simpleframework.http.Request;
import org.simpleframework.http.Response;
import java.util.HashMap;
/**
* A trivial server that collects and returns WBOs.
*
* @author rnewman
*
*/
public class MockWBOServer extends MockServer {
public HashMap<String, HashMap<String, String> > collections;
public MockWBOServer() {
collections = new HashMap<String, HashMap<String, String> >();
}
@Override
public void handle(Request request, Response response) {
Path path = request.getPath();
path.getPath(0);
// TODO
}
}

View file

@ -1,102 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test.helpers.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper.HTTPServerAlreadyRunningError;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.io.PrintWriter;
import java.io.StringWriter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(TestRunner.class)
public class TestHTTPServerTestHelper {
public static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
protected MockServer mockServer = new MockServer();
@Test
public void testStartStop() {
// Need to be able to start and stop multiple times.
for (int i = 0; i < 2; i++) {
HTTPServerTestHelper httpServer = new HTTPServerTestHelper();
assertNull(httpServer.connection);
httpServer.startHTTPServer(mockServer);
assertNotNull(httpServer.connection);
httpServer.stopHTTPServer();
}
}
public void startAgain() {
HTTPServerTestHelper httpServer = new HTTPServerTestHelper();
httpServer.startHTTPServer(mockServer);
}
@Test
public void testStartTwice() {
HTTPServerTestHelper httpServer = new HTTPServerTestHelper();
httpServer.startHTTPServer(mockServer);
assertNotNull(httpServer.connection);
// Should not be able to start multiple times.
try {
try {
startAgain();
fail("Expected exception.");
} catch (Throwable e) {
assertEquals(HTTPServerAlreadyRunningError.class, e.getClass());
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
String s = sw.toString();
// Ensure we get a useful stack trace.
// We should have the method trying to start the server the second time...
assertTrue(s.contains("startAgain"));
// ... as well as the the method that started the server the first time.
assertTrue(s.contains("testStartTwice"));
}
} finally {
httpServer.stopHTTPServer();
}
}
protected static class LeakyHTTPServerTestHelper extends HTTPServerTestHelper {
// Make this constructor public, just for this test.
public LeakyHTTPServerTestHelper(int port) {
super(port);
}
}
@Test
public void testForceStartTwice() {
HTTPServerTestHelper httpServer1 = new HTTPServerTestHelper();
HTTPServerTestHelper httpServer2 = new LeakyHTTPServerTestHelper(httpServer1.port + 1);
// Should be able to start multiple times if we specify it.
try {
httpServer1.startHTTPServer(mockServer);
assertNotNull(httpServer1.connection);
httpServer2.startHTTPServer(mockServer, true);
assertNotNull(httpServer2.connection);
} finally {
httpServer1.stopHTTPServer();
httpServer2.stopHTTPServer();
}
}
}

View file

@ -1,51 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.GeckoNetworkManager.ManagerState;
import org.mozilla.gecko.GeckoNetworkManager.ManagerEvent;
import static org.junit.Assert.*;
@RunWith(TestRunner.class)
public class GeckoNetworkManagerTest {
/**
* Tests the transition matrix.
*/
@Test
public void testGetNextState() {
ManagerState testingState;
testingState = ManagerState.OffNoListeners;
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.disableNotifications));
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.stop));
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.receivedUpdate));
assertEquals(ManagerState.OnNoListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.start));
assertEquals(ManagerState.OffWithListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.enableNotifications));
testingState = ManagerState.OnNoListeners;
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.start));
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.disableNotifications));
assertEquals(ManagerState.OnWithListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.enableNotifications));
assertEquals(ManagerState.OffNoListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.stop));
assertEquals(ManagerState.OnNoListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.receivedUpdate));
testingState = ManagerState.OnWithListeners;
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.start));
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.enableNotifications));
assertEquals(ManagerState.OffWithListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.stop));
assertEquals(ManagerState.OnNoListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.disableNotifications));
assertEquals(ManagerState.OnWithListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.receivedUpdate));
testingState = ManagerState.OffWithListeners;
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.stop));
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.enableNotifications));
assertNull(GeckoNetworkManager.getNextState(testingState, ManagerEvent.receivedUpdate));
assertEquals(ManagerState.OnWithListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.start));
assertEquals(ManagerState.OffNoListeners, GeckoNetworkManager.getNextState(testingState, ManagerEvent.disableNotifications));
}
}

View file

@ -1,174 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.RemoteException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.PageMetadata;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.BrowserProvider;
import org.mozilla.gecko.db.LocalBrowserDB;
import org.robolectric.shadows.ShadowContentResolver;
import static org.junit.Assert.*;
@RunWith(TestRunner.class)
public class GlobalPageMetadataTest {
@Test
public void testQueueing() throws Exception {
BrowserDB db = new LocalBrowserDB("default");
BrowserProvider provider = new BrowserProvider();
try {
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
ShadowContentResolver cr = new ShadowContentResolver();
ContentProviderClient pageMetadataClient = cr.acquireContentProviderClient(PageMetadata.CONTENT_URI);
assertEquals(0, GlobalPageMetadata.getInstance().getMetadataQueueSize());
// There's not history record for this uri, so test that queueing works.
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", false, "{type: 'article'}");
assertPageMetadataCountForGUID(0, "guid1", pageMetadataClient);
assertEquals(1, GlobalPageMetadata.getInstance().getMetadataQueueSize());
// Test that queue doesn't duplicate metadata for the same history item.
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", false, "{type: 'article'}");
assertEquals(1, GlobalPageMetadata.getInstance().getMetadataQueueSize());
// Test that queue is limited to 15 metadata items.
for (int i = 0; i < 20; i++) {
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org/" + i, false, "{type: 'article'}");
}
assertEquals(15, GlobalPageMetadata.getInstance().getMetadataQueueSize());
} finally {
provider.shutdown();
}
}
@Test
public void testInsertingMetadata() throws Exception {
BrowserDB db = new LocalBrowserDB("default");
// Start listening for events.
GlobalPageMetadata.getInstance().init();
BrowserProvider provider = new BrowserProvider();
try {
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
ShadowContentResolver cr = new ShadowContentResolver();
ContentProviderClient historyClient = cr.acquireContentProviderClient(BrowserContract.History.CONTENT_URI);
ContentProviderClient pageMetadataClient = cr.acquireContentProviderClient(PageMetadata.CONTENT_URI);
// Insert required history item...
ContentValues cv = new ContentValues();
cv.put(BrowserContract.History.GUID, "guid1");
cv.put(BrowserContract.History.URL, "https://mozilla.org");
historyClient.insert(BrowserContract.History.CONTENT_URI, cv);
// TODO: Main test runner thread finishes before EventDispatcher events are processed...
// Fire off a message saying that history has been inserted.
// Bundle message = new Bundle();
// message.putString(GlobalHistory.EVENT_PARAM_URI, "https://mozilla.org");
// EventDispatcher.getInstance().dispatch(GlobalHistory.EVENT_URI_AVAILABLE_IN_HISTORY, message);
// For now, let's just try inserting again.
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", false, "{type: 'article', description: 'test article'}");
assertPageMetadataCountForGUID(1, "guid1", pageMetadataClient);
assertPageMetadataValues(pageMetadataClient, "guid1", false, "{\"type\":\"article\",\"description\":\"test article\"}");
// Test that inserting empty metadata deletes existing metadata record.
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", false, "{}");
assertPageMetadataCountForGUID(0, "guid1", pageMetadataClient);
// Test that inserting new metadata overrides existing metadata record.
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", true, "{type: 'article', description: 'test article', image_url: 'https://example.com/test.png'}");
assertPageMetadataValues(pageMetadataClient, "guid1", true, "{\"type\":\"article\",\"description\":\"test article\",\"image_url\":\"https:\\/\\/example.com\\/test.png\"}");
// Insert another history item...
cv = new ContentValues();
cv.put(BrowserContract.History.GUID, "guid2");
cv.put(BrowserContract.History.URL, "https://planet.mozilla.org");
historyClient.insert(BrowserContract.History.CONTENT_URI, cv);
// Test that empty metadata doesn't get inserted for a new history.
GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://planet.mozilla.org", false, "{}");
assertPageMetadataCountForGUID(0, "guid2", pageMetadataClient);
} finally {
provider.shutdown();
}
}
/**
* Expects cursor to be at the correct position.
*/
private void assertCursorValues(Cursor cursor, String json, int hasImage, String guid) {
assertNotNull(cursor);
assertEquals(json, cursor.getString(cursor.getColumnIndexOrThrow(PageMetadata.JSON)));
assertEquals(hasImage, cursor.getInt(cursor.getColumnIndexOrThrow(PageMetadata.HAS_IMAGE)));
assertEquals(guid, cursor.getString(cursor.getColumnIndexOrThrow(PageMetadata.HISTORY_GUID)));
}
private void assertPageMetadataValues(ContentProviderClient client, String guid, boolean hasImage, String json) {
final Cursor cursor;
try {
cursor = client.query(PageMetadata.CONTENT_URI, new String[]{
PageMetadata.HISTORY_GUID,
PageMetadata.HAS_IMAGE,
PageMetadata.JSON,
PageMetadata.DATE_CREATED
}, PageMetadata.HISTORY_GUID + " = ?", new String[]{guid}, null);
} catch (RemoteException e) {
fail();
return;
}
assertNotNull(cursor);
try {
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertCursorValues(cursor, json, hasImage ? 1 : 0, guid);
} finally {
cursor.close();
}
}
private void assertPageMetadataCountForGUID(int expected, String guid, ContentProviderClient client) {
final Cursor cursor;
try {
cursor = client.query(PageMetadata.CONTENT_URI, new String[]{
PageMetadata.HISTORY_GUID,
PageMetadata.HAS_IMAGE,
PageMetadata.JSON,
PageMetadata.DATE_CREATED
}, PageMetadata.HISTORY_GUID + " = ?", new String[]{guid}, null);
} catch (RemoteException e) {
fail();
return;
}
assertNotNull(cursor);
try {
assertEquals(expected, cursor.getCount());
} finally {
cursor.close();
}
}
}

View file

@ -1,254 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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;
import android.content.Context;
import org.json.JSONObject;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.util.FileUtils;
import org.robolectric.RuntimeEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import static org.junit.Assert.*;
/**
* Unit test methods of the GeckoProfile class.
*/
@RunWith(TestRunner.class)
public class TestGeckoProfile {
private static final String PROFILE_NAME = "profileName";
private static final String CLIENT_ID_JSON_ATTR = "clientID";
private static final String PROFILE_CREATION_DATE_JSON_ATTR = "created";
@Rule
public TemporaryFolder dirContainingProfile = new TemporaryFolder();
private File profileDir;
private GeckoProfile profile;
private File clientIdFile;
private File timesFile;
@Before
public void setUp() throws IOException {
final Context context = RuntimeEnvironment.application;
profileDir = dirContainingProfile.newFolder();
profile = GeckoProfile.get(context, PROFILE_NAME, profileDir);
clientIdFile = new File(profileDir, "datareporting/state.json");
timesFile = new File(profileDir, "times.json");
}
public void assertValidClientId(final String clientId) {
// This isn't the method we use in the main GeckoProfile code, but it should be equivalent.
UUID.fromString(clientId); // assert: will throw if null or invalid UUID.
}
@Test
public void testGetDir() {
assertEquals("Profile dir argument during construction and returned value are equal",
profileDir, profile.getDir());
}
@Test
public void testGetClientIdFreshProfile() throws Exception {
assertFalse("client ID file does not exist", clientIdFile.exists());
// No existing client ID file: we're expected to create one.
final String clientId = profile.getClientId();
assertValidClientId(clientId);
assertTrue("client ID file exists", clientIdFile.exists());
assertEquals("Returned client ID is the same as the one previously returned", clientId, profile.getClientId());
assertEquals("clientID file format matches expectations", clientId, readClientIdFromFile(clientIdFile));
}
@Test
public void testGetClientIdFileAlreadyExists() throws Exception {
final String validClientId = "905de1c0-0ea6-4a43-95f9-6170035f5a82";
assertTrue("Created the parent dirs of the client ID file", clientIdFile.getParentFile().mkdirs());
writeClientIdToFile(clientIdFile, validClientId);
final String clientIdFromProfile = profile.getClientId();
assertEquals("Client ID from method matches ID written to disk", validClientId, clientIdFromProfile);
}
@Test
public void testGetClientIdMigrateFromFHR() throws Exception {
final File fhrClientIdFile = new File(profileDir, "healthreport/state.json");
final String fhrClientId = "905de1c0-0ea6-4a43-95f9-6170035f5a82";
assertFalse("client ID file does not exist", clientIdFile.exists());
assertTrue("Created FHR data directory", new File(profileDir, "healthreport").mkdirs());
writeClientIdToFile(fhrClientIdFile, fhrClientId);
assertEquals("Migrated Client ID equals FHR client ID", fhrClientId, profile.getClientId());
// Verify migration wrote to contemporary client ID file.
assertTrue("Client ID file created during migration", clientIdFile.exists());
assertEquals("Migrated client ID on disk equals value returned from method",
fhrClientId, readClientIdFromFile(clientIdFile));
assertTrue("Deleted FHR clientID file", fhrClientIdFile.delete());
assertEquals("Ensure method calls read from newly created client ID file & not FHR client ID file",
fhrClientId, profile.getClientId());
}
@Test
public void testGetClientIdInvalidIdOnDisk() throws Exception {
assertTrue("Created the parent dirs of the client ID file", clientIdFile.getParentFile().mkdirs());
writeClientIdToFile(clientIdFile, "");
final String clientIdForEmptyString = profile.getClientId();
assertValidClientId(clientIdForEmptyString);
assertNotEquals("A new client ID was created when the empty String was written to disk", "", clientIdForEmptyString);
writeClientIdToFile(clientIdFile, "invalidClientId");
final String clientIdForInvalidClientId = profile.getClientId();
assertValidClientId(clientIdForInvalidClientId);
assertNotEquals("A new client ID was created when an invalid client ID was written to disk",
"invalidClientId", clientIdForInvalidClientId);
}
@Test
public void testGetClientIdMissingClientIdJSONAttr() throws Exception {
final String validClientId = "905de1c0-0ea6-4a43-95f9-6170035f5a82";
final JSONObject objMissingClientId = new JSONObject();
objMissingClientId.put("irrelevantKey", validClientId);
assertTrue("Created the parent dirs of the client ID file", clientIdFile.getParentFile().mkdirs());
FileUtils.writeJSONObjectToFile(clientIdFile, objMissingClientId);
final String clientIdForMissingAttr = profile.getClientId();
assertValidClientId(clientIdForMissingAttr);
assertNotEquals("Did not use other attr when JSON attr was missing", validClientId, clientIdForMissingAttr);
}
@Test
public void testGetClientIdInvalidIdFileFormat() throws Exception {
final String validClientId = "905de1c0-0ea6-4a43-95f9-6170035f5a82";
assertTrue("Created the parent dirs of the client ID file", clientIdFile.getParentFile().mkdirs());
FileUtils.writeStringToFile(clientIdFile, "clientID: \"" + validClientId + "\"");
final String clientIdForInvalidFormat = profile.getClientId();
assertValidClientId(clientIdForInvalidFormat);
assertNotEquals("Created new ID when file format was invalid", validClientId, clientIdForInvalidFormat);
}
@Test
public void testEnsureParentDirs() {
final File grandParentDir = new File(profileDir, "grandParent");
final File parentDir = new File(grandParentDir, "parent");
final File childFile = new File(parentDir, "child");
// Assert initial state.
assertFalse("Topmost parent dir should not exist yet", grandParentDir.exists());
assertFalse("Bottommost parent dir should not exist yet", parentDir.exists());
assertFalse("Child file should not exist", childFile.exists());
final String fakeFullPath = "grandParent/parent/child";
assertTrue("Parent directories should be created", profile.ensureParentDirs(fakeFullPath));
assertTrue("Topmost parent dir should have been created", grandParentDir.exists());
assertTrue("Bottommost parent dir should have been created", parentDir.exists());
assertFalse("Child file should not have been created", childFile.exists());
// Parents already exist because this is the second time we're calling ensureParentDirs.
assertTrue("Expect true if parent directories already exist", profile.ensureParentDirs(fakeFullPath));
// Assert error condition.
assertTrue("Ensure we can change permissions on profile dir for testing", profileDir.setReadOnly());
assertFalse("Expect false if the parent dir could not be created", profile.ensureParentDirs("unwritableDir/child"));
}
@Test
public void testIsClientIdValid() {
final String[] validClientIds = new String[] {
"905de1c0-0ea6-4a43-95f9-6170035f5a82",
"905de1c0-0ea6-4a43-95f9-6170035f5a83",
"57472f82-453d-4c55-b59c-d3c0e97b76a1",
"895745d1-f31e-46c3-880e-b4dd72963d4f",
};
for (final String validClientId : validClientIds) {
assertTrue("Client ID, " + validClientId + ", is valid", profile.isClientIdValid(validClientId));
}
final String[] invalidClientIds = new String[] {
null,
"",
"a",
"anInvalidClientId",
"905de1c0-0ea6-4a43-95f9-6170035f5a820", // too long (last section)
"905de1c0-0ea6-4a43-95f9-6170035f5a8", // too short (last section)
"05de1c0-0ea6-4a43-95f9-6170035f5a82", // too short (first section)
"905de1c0-0ea6-4a43-95f9-6170035f5a8!", // contains a symbol
};
for (final String invalidClientId : invalidClientIds) {
assertFalse("Client ID, " + invalidClientId + ", is invalid", profile.isClientIdValid(invalidClientId));
}
// We generate client IDs using UUID - better make sure they're valid.
for (int i = 0; i < 30; ++i) {
final String generatedClientId = UUID.randomUUID().toString();
assertTrue("Generated client ID from UUID, " + generatedClientId + ", is valid",
profile.isClientIdValid(generatedClientId));
}
}
@Test
public void testGetProfileCreationDateFromTimesFile() throws Exception {
final long expectedDate = System.currentTimeMillis();
final JSONObject expectedObj = new JSONObject();
expectedObj.put(PROFILE_CREATION_DATE_JSON_ATTR, expectedDate);
FileUtils.writeJSONObjectToFile(timesFile, expectedObj);
final Context context = RuntimeEnvironment.application;
final long actualDate = profile.getAndPersistProfileCreationDate(context);
assertEquals("Date from disk equals date inserted to disk", expectedDate, actualDate);
final long actualDateFromDisk = readProfileCreationDateFromFile(timesFile);
assertEquals("Date in times.json has not changed after accessing profile creation date",
expectedDate, actualDateFromDisk);
}
@Test
public void testGetProfileCreationDateTimesFileDoesNotExist() throws Exception {
assertFalse("Times.json does not already exist", timesFile.exists());
final Context context = RuntimeEnvironment.application;
final long actualDate = profile.getAndPersistProfileCreationDate(context);
// I'd prefer to mock so we can return and verify a specific value but we can't mock
// GeckoProfile because it's final. Instead, we check if the value is at least reasonable.
assertTrue("Date from method is positive", actualDate >= 0);
assertTrue("Date from method is less than current time", actualDate < System.currentTimeMillis());
assertTrue("Times.json exists after getting profile", timesFile.exists());
final long actualDateFromDisk = readProfileCreationDateFromFile(timesFile);
assertEquals("Date from disk equals returned value", actualDate, actualDateFromDisk);
}
private static long readProfileCreationDateFromFile(final File file) throws Exception {
final JSONObject actualObj = FileUtils.readJSONObjectFromFile(file);
return actualObj.getLong(PROFILE_CREATION_DATE_JSON_ATTR);
}
private String readClientIdFromFile(final File file) throws Exception {
final JSONObject obj = FileUtils.readJSONObjectFromFile(file);
return obj.getString(CLIENT_ID_JSON_ATTR);
}
private void writeClientIdToFile(final File file, final String clientId) throws Exception {
final JSONObject obj = new JSONObject();
obj.put(CLIENT_ID_JSON_ATTR, clientId);
FileUtils.writeJSONObjectToFile(file, obj);
}
}

View file

@ -1,85 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.activitystream;
import android.os.SystemClock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowLooper;
import static org.junit.Assert.assertEquals;
@RunWith(TestRunner.class)
public class TestActivityStream {
/**
* Unit tests for ActivityStream.extractLabel().
*
* Most test cases are based on this list:
* https://gist.github.com/nchapman/36502ad115e8825d522a66549971a3f0
*/
@Test
public void testExtractLabelWithPath() {
// Empty values
assertLabelEquals("", "", true);
assertLabelEquals("", null, true);
// Without path
assertLabelEquals("news.ycombinator", "https://news.ycombinator.com/", true);
assertLabelEquals("sql.telemetry.mozilla", "https://sql.telemetry.mozilla.org/", true);
assertLabelEquals("sso.mozilla", "http://sso.mozilla.com/", true);
assertLabelEquals("youtube", "http://youtube.com/", true);
assertLabelEquals("images.google", "http://images.google.com/", true);
assertLabelEquals("smile.amazon", "http://smile.amazon.com/", true);
assertLabelEquals("localhost", "http://localhost:5000/", true);
assertLabelEquals("independent", "http://www.independent.co.uk/", true);
// With path
assertLabelEquals("firefox", "https://addons.mozilla.org/en-US/firefox/", true);
assertLabelEquals("activity-stream", "https://trello.com/b/KX3hV8XS/activity-stream", true);
assertLabelEquals("activity-stream", "https://github.com/mozilla/activity-stream", true);
assertLabelEquals("sidekiq", "https://dispatch-news.herokuapp.com/sidekiq", true);
assertLabelEquals("nchapman", "https://github.com/nchapman/", true);
// Unusable paths
assertLabelEquals("phonebook.mozilla","https://phonebook.mozilla.org/mellon/login?ReturnTo=https%3A%2F%2Fphonebook.mozilla.org%2F&IdP=http%3A%2F%2Fwww.okta.com", true);
assertLabelEquals("ipay.adp", "https://ipay.adp.com/iPay/index.jsf", true);
assertLabelEquals("calendar.google", "https://calendar.google.com/calendar/render?pli=1#main_7", true);
assertLabelEquals("myworkday", "https://www.myworkday.com/vhr_mozilla/d/home.htmld", true);
assertLabelEquals("mail.google", "https://mail.google.com/mail/u/1/#inbox", true);
assertLabelEquals("docs.google", "https://docs.google.com/presentation/d/11cyrcwhKTmBdEBIZ3szLO0-_Imrx2CGV2B9_LZHDrds/edit#slide=id.g15d41bb0f3_0_82", true);
// Special cases
assertLabelEquals("irccloud.mozilla", "https://irccloud.mozilla.com/#!/ircs://irc1.dmz.scl3.mozilla.com:6697/%23universal-search", true);
}
@Test
public void testExtractLabelWithoutPath() {
assertLabelEquals("addons.mozilla", "https://addons.mozilla.org/en-US/firefox/", false);
assertLabelEquals("trello", "https://trello.com/b/KX3hV8XS/activity-stream", false);
assertLabelEquals("github", "https://github.com/mozilla/activity-stream", false);
assertLabelEquals("dispatch-news", "https://dispatch-news.herokuapp.com/sidekiq", false);
assertLabelEquals("github", "https://github.com/nchapman/", false);
}
private void assertLabelEquals(String expectedLabel, String url, boolean usePath) {
final String[] actualLabel = new String[1];
ActivityStream.LabelCallback callback = new ActivityStream.LabelCallback() {
@Override
public void onLabelExtracted(String label) {
actualLabel[0] = label;
}
};
ActivityStream.extractLabel(RuntimeEnvironment.application, url, usePath, callback);
ShadowLooper.runUiThreadTasks();
assertEquals(expectedLabel, actualLabel[0]);
}
}

View file

@ -1,179 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.common.log.writers.test;
import android.util.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.common.log.writers.LevelFilteringLogWriter;
import org.mozilla.gecko.background.common.log.writers.LogWriter;
import org.mozilla.gecko.background.common.log.writers.PrintLogWriter;
import org.mozilla.gecko.background.common.log.writers.SimpleTagLogWriter;
import org.mozilla.gecko.background.common.log.writers.StringLogWriter;
import org.mozilla.gecko.background.common.log.writers.ThreadLocalTagLogWriter;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.class)
public class TestLogWriters {
public static final String TEST_LOG_TAG_1 = "TestLogTag1";
public static final String TEST_LOG_TAG_2 = "TestLogTag2";
public static final String TEST_MESSAGE_1 = "LOG TEST MESSAGE one";
public static final String TEST_MESSAGE_2 = "LOG TEST MESSAGE two";
public static final String TEST_MESSAGE_3 = "LOG TEST MESSAGE three";
@Before
public void setUp() {
Logger.stopLoggingToAll();
}
@After
public void tearDown() {
Logger.stopLoggingToAll();
}
@Test
public void testStringLogWriter() {
StringLogWriter lw = new StringLogWriter();
Logger.error(TEST_LOG_TAG_1, TEST_MESSAGE_1, new RuntimeException());
Logger.startLoggingTo(lw);
Logger.error(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.warn(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.info(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.debug(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.trace(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.stopLoggingTo(lw);
Logger.error(TEST_LOG_TAG_2, TEST_MESSAGE_3, new RuntimeException());
String s = lw.toString();
assertFalse(s.contains("RuntimeException"));
assertFalse(s.contains(".java"));
assertTrue(s.contains(TEST_LOG_TAG_1));
assertFalse(s.contains(TEST_LOG_TAG_2));
assertFalse(s.contains(TEST_MESSAGE_1));
assertTrue(s.contains(TEST_MESSAGE_2));
assertFalse(s.contains(TEST_MESSAGE_3));
}
@Test
public void testSingleTagLogWriter() {
final String SINGLE_TAG = "XXX";
StringLogWriter lw = new StringLogWriter();
Logger.startLoggingTo(new SimpleTagLogWriter(SINGLE_TAG, lw));
Logger.error(TEST_LOG_TAG_1, TEST_MESSAGE_1);
Logger.warn(TEST_LOG_TAG_2, TEST_MESSAGE_2);
String s = lw.toString();
for (String line : s.split("\n")) {
assertTrue(line.startsWith(SINGLE_TAG));
}
assertTrue(s.startsWith(SINGLE_TAG + " :: E :: " + TEST_LOG_TAG_1));
}
@Test
public void testLevelFilteringLogWriter() {
StringLogWriter lw = new StringLogWriter();
assertFalse(new LevelFilteringLogWriter(Log.WARN, lw).shouldLogVerbose(TEST_LOG_TAG_1));
assertTrue(new LevelFilteringLogWriter(Log.VERBOSE, lw).shouldLogVerbose(TEST_LOG_TAG_1));
Logger.startLoggingTo(new LevelFilteringLogWriter(Log.WARN, lw));
Logger.error(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.warn(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.info(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.debug(TEST_LOG_TAG_1, TEST_MESSAGE_2);
Logger.trace(TEST_LOG_TAG_1, TEST_MESSAGE_2);
String s = lw.toString();
assertTrue(s.contains(PrintLogWriter.ERROR));
assertTrue(s.contains(PrintLogWriter.WARN));
assertFalse(s.contains(PrintLogWriter.INFO));
assertFalse(s.contains(PrintLogWriter.DEBUG));
assertFalse(s.contains(PrintLogWriter.VERBOSE));
}
@Test
public void testThreadLocalLogWriter() throws InterruptedException {
final InheritableThreadLocal<String> logTag = new InheritableThreadLocal<String>() {
@Override
protected String initialValue() {
return "PARENT";
}
};
final StringLogWriter stringLogWriter = new StringLogWriter();
final LogWriter logWriter = new ThreadLocalTagLogWriter(logTag, stringLogWriter);
try {
Logger.startLoggingTo(logWriter);
Logger.info("parent tag before", "parent message before");
int threads = 3;
final CountDownLatch latch = new CountDownLatch(threads);
for (int thread = 0; thread < threads; thread++) {
final int threadNumber = thread;
new Thread(new Runnable() {
@Override
public void run() {
try {
logTag.set("CHILD" + threadNumber);
Logger.info("child tag " + threadNumber, "child message " + threadNumber);
} finally {
latch.countDown();
}
}
}).start();
}
latch.await();
Logger.info("parent tag after", "parent message after");
String s = stringLogWriter.toString();
List<String> lines = Arrays.asList(s.split("\n"));
// Because tests are run in a multi-threaded environment, we get
// additional logs that are not generated by this test. So we test that we
// get all the messages in a reasonable order.
try {
int parent1 = lines.indexOf("PARENT :: I :: parent tag before :: parent message before");
int parent2 = lines.indexOf("PARENT :: I :: parent tag after :: parent message after");
assertTrue(parent1 >= 0);
assertTrue(parent2 >= 0);
assertTrue(parent1 < parent2);
for (int thread = 0; thread < threads; thread++) {
int child = lines.indexOf("CHILD" + thread + " :: I :: child tag " + thread + " :: child message " + thread);
assertTrue(child >= 0);
assertTrue(parent1 < child);
assertTrue(child < parent2);
}
} catch (Throwable e) {
// Shouldn't happen. Let's dump to aid debugging.
e.printStackTrace();
assertEquals("\0", s);
}
} finally {
Logger.stopLoggingTo(logWriter);
}
}
}

View file

@ -1,86 +0,0 @@
/* 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.background.db;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri;
import org.mozilla.gecko.db.BrowserContract;
import java.util.ArrayList;
/**
* Wrap a ContentProvider, appending &test=1 to all queries.
*/
public class DelegatingTestContentProvider extends ContentProvider {
protected final ContentProvider mTargetProvider;
protected static Uri appendUriParam(Uri uri, String param, String value) {
return uri.buildUpon().appendQueryParameter(param, value).build();
}
public DelegatingTestContentProvider(ContentProvider targetProvider) {
super();
mTargetProvider = targetProvider;
}
private Uri appendTestParam(Uri uri) {
return appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
}
@Override
public boolean onCreate() {
return mTargetProvider.onCreate();
}
@Override
public String getType(Uri uri) {
return mTargetProvider.getType(uri);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return mTargetProvider.delete(appendTestParam(uri), selection, selectionArgs);
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return mTargetProvider.insert(appendTestParam(uri), values);
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return mTargetProvider.update(appendTestParam(uri), values,
selection, selectionArgs);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return mTargetProvider.query(appendTestParam(uri), projection, selection,
selectionArgs, sortOrder);
}
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
return mTargetProvider.applyBatch(operations);
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
return mTargetProvider.bulkInsert(appendTestParam(uri), values);
}
public ContentProvider getTargetProvider() {
return mTargetProvider;
}
}

View file

@ -1,338 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.db;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.json.simple.JSONArray;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.TabsProvider;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
import org.robolectric.shadows.ShadowContentResolver;
@RunWith(TestRunner.class)
public class TestTabsProvider {
public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
public static final String TEST_CLIENT_NAME = "test client name";
public static final String CLIENTS_GUID_IS = BrowserContract.Clients.GUID + " = ?";
public static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
protected Tab testTab1;
protected Tab testTab2;
protected Tab testTab3;
protected TabsProvider provider;
@Before
public void setUp() {
provider = new TabsProvider();
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
}
@After
public void tearDown() throws Exception {
provider.shutdown();
provider = null;
}
protected ContentProviderClient getClientsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
}
protected ContentProviderClient getTabsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.TABS_CONTENT_URI);
}
protected int deleteTestClient(final ContentProviderClient clientsClient) throws RemoteException {
if (clientsClient == null) {
throw new IllegalStateException("Provided ContentProviderClient is null");
}
return clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String[] { TEST_CLIENT_GUID });
}
protected int deleteAllTestTabs(final ContentProviderClient tabsClient) throws RemoteException {
if (tabsClient == null) {
throw new IllegalStateException("Provided ContentProviderClient is null");
}
return tabsClient.delete(BrowserContractHelpers.TABS_CONTENT_URI, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID });
}
protected void insertTestClient(final ContentProviderClient clientsClient) throws RemoteException {
ContentValues cv = new ContentValues();
cv.put(BrowserContract.Clients.GUID, TEST_CLIENT_GUID);
cv.put(BrowserContract.Clients.NAME, TEST_CLIENT_NAME);
clientsClient.insert(BrowserContractHelpers.CLIENTS_CONTENT_URI, cv);
}
@SuppressWarnings("unchecked")
protected void insertSomeTestTabs(ContentProviderClient tabsClient) throws RemoteException {
final JSONArray history1 = new JSONArray();
history1.add("http://test.com/test1.html");
testTab1 = new Tab("test title 1", "http://test.com/test1.png", history1, 1000);
final JSONArray history2 = new JSONArray();
history2.add("http://test.com/test2.html#1");
history2.add("http://test.com/test2.html#2");
history2.add("http://test.com/test2.html#3");
testTab2 = new Tab("test title 2", "http://test.com/test2.png", history2, 2000);
final JSONArray history3 = new JSONArray();
history3.add("http://test.com/test3.html#1");
history3.add("http://test.com/test3.html#2");
testTab3 = new Tab("test title 3", "http://test.com/test3.png", history3, 3000);
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab1.toContentValues(TEST_CLIENT_GUID, 0));
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab2.toContentValues(TEST_CLIENT_GUID, 1));
tabsClient.insert(BrowserContractHelpers.TABS_CONTENT_URI, testTab3.toContentValues(TEST_CLIENT_GUID, 2));
}
// Sanity.
@Test
public void testObtainCP() {
final ContentProviderClient clientsClient = getClientsClient();
Assert.assertNotNull(clientsClient);
clientsClient.release();
final ContentProviderClient tabsClient = getTabsClient();
Assert.assertNotNull(tabsClient);
tabsClient.release();
}
@Test
public void testDeleteEmptyClients() throws RemoteException {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient clientsClient = getClientsClient();
// Have to ensure that it's empty
clientsClient.delete(uri, null, null);
int deleted = clientsClient.delete(uri, null, null);
Assert.assertEquals(0, deleted);
}
@Test
public void testDeleteEmptyTabs() throws RemoteException {
final ContentProviderClient tabsClient = getTabsClient();
// Have to ensure that it's empty
deleteAllTestTabs(tabsClient);
int deleted = deleteAllTestTabs(tabsClient);
Assert.assertEquals(0, deleted);
}
@Test
public void testStoreAndRetrieveClients() throws RemoteException {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient clientsClient = getClientsClient();
// Have to ensure that it's empty
clientsClient.delete(uri, null, null);
final long now = System.currentTimeMillis();
final ContentValues first = new ContentValues();
final ContentValues second = new ContentValues();
first.put(BrowserContract.Clients.GUID, "abcdefghijkl");
first.put(BrowserContract.Clients.NAME, "Frist Psot");
first.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
second.put(BrowserContract.Clients.GUID, "mnopqrstuvwx");
second.put(BrowserContract.Clients.NAME, "Second!!1!");
second.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
ContentValues[] values = new ContentValues[] { first, second };
final int inserted = clientsClient.bulkInsert(uri, values);
Assert.assertEquals(2, inserted);
final String since = BrowserContract.Clients.LAST_MODIFIED + " >= ?";
final String[] nowArg = new String[] { String.valueOf(now) };
final String guidAscending = BrowserContract.Clients.GUID + " ASC";
Cursor cursor = clientsClient.query(uri, null, since, nowArg, guidAscending);
Assert.assertNotNull(cursor);
try {
Assert.assertTrue(cursor.moveToFirst());
Assert.assertEquals(2, cursor.getCount());
final String g1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
final String n1 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
final long m1 = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
Assert.assertEquals(first.get(BrowserContract.Clients.GUID), g1);
Assert.assertEquals(first.get(BrowserContract.Clients.NAME), n1);
Assert.assertEquals(now + 1, m1);
Assert.assertTrue(cursor.moveToNext());
final String g2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.GUID));
final String n2 = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Clients.NAME));
final long m2 = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Clients.LAST_MODIFIED));
Assert.assertEquals(second.get(BrowserContract.Clients.GUID), g2);
Assert.assertEquals(second.get(BrowserContract.Clients.NAME), n2);
Assert.assertEquals(now + 2, m2);
Assert.assertFalse(cursor.moveToNext());
} finally {
cursor.close();
}
int deleted = clientsClient.delete(uri, null, null);
Assert.assertEquals(2, deleted);
}
@Test
public void testTabFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final ContentProviderClient clientsClient = getClientsClient();
deleteAllTestTabs(tabsClient);
deleteTestClient(clientsClient);
insertTestClient(clientsClient);
insertSomeTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
Assert.assertEquals(3, cursor.getCount());
cursor.moveToFirst();
final Tab parsed1 = Tab.fromCursor(cursor);
Assert.assertEquals(testTab1, parsed1);
cursor.moveToNext();
final Tab parsed2 = Tab.fromCursor(cursor);
Assert.assertEquals(testTab2, parsed2);
cursor.moveToPosition(2);
final Tab parsed3 = Tab.fromCursor(cursor);
Assert.assertEquals(testTab3, parsed3);
} finally {
cursor.close();
}
}
@Test
public void testDeletingClientDeletesTabs() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final ContentProviderClient clientsClient = getClientsClient();
deleteAllTestTabs(tabsClient);
deleteTestClient(clientsClient);
insertTestClient(clientsClient);
insertSomeTestTabs(tabsClient);
// Delete just the client...
clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, CLIENTS_GUID_IS, new String [] { TEST_CLIENT_GUID });
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, null);
// ... and all that client's tabs should be removed.
Assert.assertEquals(0, cursor.getCount());
} finally {
cursor.close();
}
}
@Test
public void testTabsRecordFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
deleteAllTestTabs(tabsClient);
insertTestClient(getClientsClient());
insertSomeTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
Assert.assertEquals(3, cursor.getCount());
cursor.moveToPosition(1);
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
// Make sure we clean up after ourselves.
Assert.assertEquals(1, cursor.getPosition());
Assert.assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
Assert.assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
Assert.assertEquals(3, tabsRecord.tabs.size());
Assert.assertEquals(testTab1, tabsRecord.tabs.get(0));
Assert.assertEquals(testTab2, tabsRecord.tabs.get(1));
Assert.assertEquals(testTab3, tabsRecord.tabs.get(2));
Assert.assertEquals(Math.max(Math.max(testTab1.lastUsed, testTab2.lastUsed), testTab3.lastUsed), tabsRecord.lastModified);
} finally {
cursor.close();
}
}
// Verify that we can fetch a record when there are no local tabs at all.
@Test
public void testEmptyTabsRecordFromCursor() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
deleteAllTestTabs(tabsClient);
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, TABS_CLIENT_GUID_IS, new String[] { TEST_CLIENT_GUID }, positionAscending);
Assert.assertEquals(0, cursor.getCount());
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
Assert.assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
Assert.assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
Assert.assertNotNull(tabsRecord.tabs);
Assert.assertEquals(0, tabsRecord.tabs.size());
Assert.assertEquals(0, tabsRecord.lastModified);
} finally {
cursor.close();
}
}
// Not much of a test, but verifies the tabs record at least agrees with the
// disk data and doubles as a database inspector.
@Test
public void testLocalTabs() throws Exception {
final ContentProviderClient tabsClient = getTabsClient();
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
Cursor cursor = null;
try {
// Keep this in sync with the Fennec schema.
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null, BrowserContract.Tabs.CLIENT_GUID + " IS NULL", null, positionAscending);
CursorDumper.dumpCursor(cursor);
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
Assert.assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
Assert.assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
Assert.assertNotNull(tabsRecord.tabs);
Assert.assertEquals(cursor.getCount(), tabsRecord.tabs.size());
} finally {
cursor.close();
}
}
}

View file

@ -1,244 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.db;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.LocalTabsAccessor;
import org.mozilla.gecko.db.RemoteClient;
import org.mozilla.gecko.db.TabsProvider;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.internal.runtime.RuntimeAdapter;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.List;
@RunWith(TestRunner.class)
public class TestTabsProviderRemoteTabs {
private static final long ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
private static final long ONE_WEEK_IN_MILLISECONDS = 7 * ONE_DAY_IN_MILLISECONDS;
private static final long THREE_WEEKS_IN_MILLISECONDS = 3 * ONE_WEEK_IN_MILLISECONDS;
protected TabsProvider provider;
@Before
public void setUp() {
provider = new TabsProvider();
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
}
@After
public void tearDown() throws Exception {
provider.shutdown();
provider = null;
}
protected ContentProviderClient getClientsClient() {
final ShadowContentResolver cr = new ShadowContentResolver();
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
}
@Test
public void testGetClientsWithoutTabsByRecencyFromCursor() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient cpc = getClientsClient();
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
try {
// Delete all tabs to begin with.
cpc.delete(uri, null, null);
Cursor allClients = cpc.query(uri, null, null, null, null);
try {
Assert.assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
// Insert a local and remote1 client record, neither with tabs.
final long now = System.currentTimeMillis();
// Local client has GUID = null.
final ContentValues local = new ContentValues();
local.put(BrowserContract.Clients.NAME, "local");
local.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
// Remote clients have GUID != null.
final ContentValues remote1 = new ContentValues();
remote1.put(BrowserContract.Clients.GUID, "guid1");
remote1.put(BrowserContract.Clients.NAME, "remote1");
remote1.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
final ContentValues remote2 = new ContentValues();
remote2.put(BrowserContract.Clients.GUID, "guid2");
remote2.put(BrowserContract.Clients.NAME, "remote2");
remote2.put(BrowserContract.Clients.LAST_MODIFIED, now + 3);
ContentValues[] values = new ContentValues[]{local, remote1, remote2};
int inserted = cpc.bulkInsert(uri, values);
Assert.assertEquals(3, inserted);
allClients = cpc.query(BrowserContract.Clients.CONTENT_RECENCY_URI, null, null, null, null);
try {
CursorDumper.dumpCursor(allClients);
// The local client is not ignored.
Assert.assertEquals(3, allClients.getCount());
final List<RemoteClient> clients = accessor.getClientsWithoutTabsByRecencyFromCursor(allClients);
Assert.assertEquals(3, clients.size());
for (RemoteClient client : clients) {
// Each client should not have any tabs.
Assert.assertNotNull(client.tabs);
Assert.assertEquals(0, client.tabs.size());
}
// Since there are no tabs, the order should be based on last_modified.
Assert.assertEquals("guid2", clients.get(0).guid);
Assert.assertEquals("guid1", clients.get(1).guid);
Assert.assertEquals(null, clients.get(2).guid);
} finally {
allClients.close();
}
// Now let's add a few tabs to one client. The times are chosen so that one tab's
// last used is not relevant, and the other tab is the most recent used.
final ContentValues remoteTab1 = new ContentValues();
remoteTab1.put(BrowserContract.Tabs.CLIENT_GUID, "guid1");
remoteTab1.put(BrowserContract.Tabs.TITLE, "title1");
remoteTab1.put(BrowserContract.Tabs.URL, "http://test.com/test1");
remoteTab1.put(BrowserContract.Tabs.HISTORY, "[\"http://test.com/test1\"]");
remoteTab1.put(BrowserContract.Tabs.LAST_USED, now);
remoteTab1.put(BrowserContract.Tabs.POSITION, 0);
final ContentValues remoteTab2 = new ContentValues();
remoteTab2.put(BrowserContract.Tabs.CLIENT_GUID, "guid1");
remoteTab2.put(BrowserContract.Tabs.TITLE, "title2");
remoteTab2.put(BrowserContract.Tabs.URL, "http://test.com/test2");
remoteTab2.put(BrowserContract.Tabs.HISTORY, "[\"http://test.com/test2\"]");
remoteTab2.put(BrowserContract.Tabs.LAST_USED, now + 5);
remoteTab2.put(BrowserContract.Tabs.POSITION, 1);
values = new ContentValues[]{remoteTab1, remoteTab2};
inserted = cpc.bulkInsert(BrowserContract.Tabs.CONTENT_URI, values);
Assert.assertEquals(2, inserted);
allClients = cpc.query(BrowserContract.Clients.CONTENT_RECENCY_URI, null, BrowserContract.Clients.GUID + " IS NOT NULL", null, null);
try {
CursorDumper.dumpCursor(allClients);
// The local client is ignored.
Assert.assertEquals(2, allClients.getCount());
final List<RemoteClient> clients = accessor.getClientsWithoutTabsByRecencyFromCursor(allClients);
Assert.assertEquals(2, clients.size());
for (RemoteClient client : clients) {
// Each client should be remote and should not have any tabs.
Assert.assertNotNull(client.guid);
Assert.assertNotNull(client.tabs);
Assert.assertEquals(0, client.tabs.size());
}
// Since now there is a tab attached to the remote2 client more recent than the
// remote1 client modified time, it should be first.
Assert.assertEquals("guid1", clients.get(0).guid);
Assert.assertEquals("guid2", clients.get(1).guid);
} finally {
allClients.close();
}
} finally {
cpc.release();
}
}
@Test
public void testGetRecentRemoteClientsUpToOneWeekOld() throws Exception {
final Uri uri = BrowserContractHelpers.CLIENTS_CONTENT_URI;
final ContentProviderClient cpc = getClientsClient();
final LocalTabsAccessor accessor = new LocalTabsAccessor("test"); // The profile name given doesn't matter.
final Context context = RuntimeEnvironment.application.getApplicationContext();
try {
// Start Clean
cpc.delete(uri, null, null);
final Cursor allClients = cpc.query(uri, null, null, null, null);
try {
Assert.assertEquals(0, allClients.getCount());
} finally {
allClients.close();
}
// Insert a local and remote1 client record, neither with tabs.
final long now = System.currentTimeMillis();
// Local client has GUID = null.
final ContentValues local = new ContentValues();
local.put(BrowserContract.Clients.NAME, "local");
local.put(BrowserContract.Clients.LAST_MODIFIED, now + 1);
// Remote clients have GUID != null.
final ContentValues remote1 = new ContentValues();
remote1.put(BrowserContract.Clients.GUID, "guid1");
remote1.put(BrowserContract.Clients.NAME, "remote1");
remote1.put(BrowserContract.Clients.LAST_MODIFIED, now + 2);
// Insert a Remote Client that is 6 days old.
final ContentValues remote2 = new ContentValues();
remote2.put(BrowserContract.Clients.GUID, "guid2");
remote2.put(BrowserContract.Clients.NAME, "remote2");
remote2.put(BrowserContract.Clients.LAST_MODIFIED, now - ONE_WEEK_IN_MILLISECONDS + ONE_DAY_IN_MILLISECONDS);
// Insert a Remote Client with the same name as previous but with more than 3 weeks old
final ContentValues remote3 = new ContentValues();
remote3.put(BrowserContract.Clients.GUID, "guid21");
remote3.put(BrowserContract.Clients.NAME, "remote2");
remote3.put(BrowserContract.Clients.LAST_MODIFIED, now - THREE_WEEKS_IN_MILLISECONDS - ONE_DAY_IN_MILLISECONDS);
// Insert another remote client with the same name as previous but with 3 weeks - 1 day old.
final ContentValues remote4 = new ContentValues();
remote4.put(BrowserContract.Clients.GUID, "guid22");
remote4.put(BrowserContract.Clients.NAME, "remote2");
remote4.put(BrowserContract.Clients.LAST_MODIFIED, now - THREE_WEEKS_IN_MILLISECONDS + ONE_DAY_IN_MILLISECONDS);
// Insert a Remote Client that is exactly one week old.
final ContentValues remote5 = new ContentValues();
remote5.put(BrowserContract.Clients.GUID, "guid3");
remote5.put(BrowserContract.Clients.NAME, "remote3");
remote5.put(BrowserContract.Clients.LAST_MODIFIED, now - ONE_WEEK_IN_MILLISECONDS);
ContentValues[] values = new ContentValues[]{local, remote1, remote2, remote3, remote4, remote5};
int inserted = cpc.bulkInsert(uri, values);
Assert.assertEquals(values.length, inserted);
final Cursor remoteClients =
accessor.getRemoteClientsByRecencyCursor(context);
try {
CursorDumper.dumpCursor(remoteClients);
// Local client is not included.
// (remote1, guid1), (remote2, guid2), (remote3, guid3) are expected.
Assert.assertEquals(3, remoteClients.getCount());
// Check the inner data, according to recency.
List<RemoteClient> recentRemoteClientsList =
accessor.getClientsWithoutTabsByRecencyFromCursor(remoteClients);
Assert.assertEquals(3, recentRemoteClientsList.size());
Assert.assertEquals("remote1", recentRemoteClientsList.get(0).name);
Assert.assertEquals("guid1", recentRemoteClientsList.get(0).guid);
Assert.assertEquals("remote2", recentRemoteClientsList.get(1).name);
Assert.assertEquals("guid2", recentRemoteClientsList.get(1).guid);
Assert.assertEquals("remote3", recentRemoteClientsList.get(2).name);
Assert.assertEquals("guid3", recentRemoteClientsList.get(2).guid);
} finally {
remoteClients.close();
}
} finally {
cpc.release();
}
}
}

View file

@ -1,41 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.fxa.test;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.net.BaseResource;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@RunWith(TestRunner.class)
public class TestFxAccountClient20 {
protected static class MockFxAccountClient20 extends FxAccountClient20 {
public MockFxAccountClient20(String serverURI, Executor executor) {
super(serverURI, executor);
}
// Public for testing.
@Override
public BaseResource getBaseResource(final String path, final String... queryParameters) throws UnsupportedEncodingException, URISyntaxException {
return super.getBaseResource(path, queryParameters);
}
}
@Test
public void testGetCreateAccountURI() throws Exception {
final String TEST_SERVER = "https://test.com:4430/inner/v1/";
final MockFxAccountClient20 client = new MockFxAccountClient20(TEST_SERVER, Executors.newSingleThreadExecutor());
Assert.assertEquals(TEST_SERVER + "account/create", client.getBaseResource("account/create").getURIString());
Assert.assertEquals(TEST_SERVER + "account/create?service=sync&keys=true", client.getBaseResource("account/create", "service", "sync", "keys", "true").getURIString());
Assert.assertEquals(TEST_SERVER + "account/create?service=two+words", client.getBaseResource("account/create", "service", "two words").getURIString());
Assert.assertEquals(TEST_SERVER + "account/create?service=symbols%2F%3A%3F%2B", client.getBaseResource("account/create", "service", "symbols/:?+").getURIString());
}
}

View file

@ -1,131 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.fxa.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.net.SRPConstants;
import java.math.BigInteger;
/**
* Test vectors from
* <a href="https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF">https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#stretch-KDF</a>
* and
* <a href="https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol/5a9bc81e499306d769ca19b40b50fa60123df15d">https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol/5a9bc81e499306d769ca19b40b50fa60123df15d</a>.
*/
@RunWith(TestRunner.class)
public class TestFxAccountUtils {
protected static void assertEncoding(String base16String, String utf8String) throws Exception {
Assert.assertEquals(base16String, FxAccountUtils.bytes(utf8String));
}
@Test
public void testUTF8Encoding() throws Exception {
assertEncoding("616e6472c3a9406578616d706c652e6f7267", "andré@example.org");
assertEncoding("70c3a4737377c3b67264", "pässwörd");
}
@Test
public void testHexModN() {
BigInteger N = BigInteger.valueOf(14);
Assert.assertEquals(4, N.bitLength());
Assert.assertEquals(1, (N.bitLength() + 7)/8);
Assert.assertEquals("00", FxAccountUtils.hexModN(BigInteger.valueOf(0), N));
Assert.assertEquals("05", FxAccountUtils.hexModN(BigInteger.valueOf(5), N));
Assert.assertEquals("0b", FxAccountUtils.hexModN(BigInteger.valueOf(11), N));
Assert.assertEquals("00", FxAccountUtils.hexModN(BigInteger.valueOf(14), N));
Assert.assertEquals("01", FxAccountUtils.hexModN(BigInteger.valueOf(15), N));
Assert.assertEquals("02", FxAccountUtils.hexModN(BigInteger.valueOf(16), N));
Assert.assertEquals("02", FxAccountUtils.hexModN(BigInteger.valueOf(30), N));
N = BigInteger.valueOf(260);
Assert.assertEquals("00ff", FxAccountUtils.hexModN(BigInteger.valueOf(255), N));
Assert.assertEquals("0100", FxAccountUtils.hexModN(BigInteger.valueOf(256), N));
Assert.assertEquals("0101", FxAccountUtils.hexModN(BigInteger.valueOf(257), N));
Assert.assertEquals("0001", FxAccountUtils.hexModN(BigInteger.valueOf(261), N));
}
@Test
public void testSRPVerifierFunctions() throws Exception {
byte[] emailUTF8Bytes = Utils.hex2Byte("616e6472c3a9406578616d706c652e6f7267");
byte[] srpPWBytes = Utils.hex2Byte("00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d", 32);
byte[] srpSaltBytes = Utils.hex2Byte("00f1000000000000000000000000000000000000000000000000000000000179", 32);
String expectedX = "81925186909189958012481408070938147619474993903899664126296984459627523279550";
BigInteger x = FxAccountUtils.srpVerifierLowercaseX(emailUTF8Bytes, srpPWBytes, srpSaltBytes);
Assert.assertEquals(expectedX, x.toString(10));
String expectedV = "11464957230405843056840989945621595830717843959177257412217395741657995431613430369165714029818141919887853709633756255809680435884948698492811770122091692817955078535761033207000504846365974552196983218225819721112680718485091921646083608065626264424771606096544316730881455897489989950697705196721477608178869100211706638584538751009854562396937282582855620488967259498367841284829152987988548996842770025110751388952323221706639434861071834212055174768483159061566055471366772641252573641352721966728239512914666806496255304380341487975080159076396759492553066357163103546373216130193328802116982288883318596822";
BigInteger v = FxAccountUtils.srpVerifierLowercaseV(emailUTF8Bytes, srpPWBytes, srpSaltBytes, SRPConstants._2048.g, SRPConstants._2048.N);
Assert.assertEquals(expectedV, v.toString(10));
String expectedVHex = "00173ffa0263e63ccfd6791b8ee2a40f048ec94cd95aa8a3125726f9805e0c8283c658dc0b607fbb25db68e68e93f2658483049c68af7e8214c49fde2712a775b63e545160d64b00189a86708c69657da7a1678eda0cd79f86b8560ebdb1ffc221db360eab901d643a75bf1205070a5791230ae56466b8c3c1eb656e19b794f1ea0d2a077b3a755350208ea0118fec8c4b2ec344a05c66ae1449b32609ca7189451c259d65bd15b34d8729afdb5faff8af1f3437bbdc0c3d0b069a8ab2a959c90c5a43d42082c77490f3afcc10ef5648625c0605cdaace6c6fdc9e9a7e6635d619f50af7734522470502cab26a52a198f5b00a279858916507b0b4e9ef9524d6";
Assert.assertEquals(expectedVHex, FxAccountUtils.hexModN(v, SRPConstants._2048.N));
}
@Test
public void testGenerateSyncKeyBundle() throws Exception {
byte[] kB = Utils.hex2Byte("d02d8fe39f28b601159c543f2deeb8f72bdf2043e8279aa08496fbd9ebaea361");
KeyBundle bundle = FxAccountUtils.generateSyncKeyBundle(kB);
Assert.assertEquals("rsLwECkgPYeGbYl92e23FskfIbgld9TgeifEaB9ZwTI=", Base64.encodeBase64String(bundle.getEncryptionKey()));
Assert.assertEquals("fs75EseCD/VOLodlIGmwNabBjhTYBHFCe7CGIf0t8Tw=", Base64.encodeBase64String(bundle.getHMACKey()));
}
@Test
public void testGeneration() throws Exception {
byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(
Utils.hex2Byte("616e6472c3a9406578616d706c652e6f7267"),
Utils.hex2Byte("70c3a4737377c3b67264"));
Assert.assertEquals("e4e8889bd8bd61ad6de6b95c059d56e7b50dacdaf62bd84644af7e2add84345d",
Utils.byte2Hex(quickStretchedPW));
Assert.assertEquals("247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375",
Utils.byte2Hex(FxAccountUtils.generateAuthPW(quickStretchedPW)));
byte[] unwrapkB = FxAccountUtils.generateUnwrapBKey(quickStretchedPW);
Assert.assertEquals("de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28",
Utils.byte2Hex(unwrapkB));
byte[] wrapkB = Utils.hex2Byte("7effe354abecbcb234a8dfc2d7644b4ad339b525589738f2d27341bb8622ecd8");
Assert.assertEquals("a095c51c1c6e384e8d5777d97e3c487a4fc2128a00ab395a73d57fedf41631f0",
Utils.byte2Hex(FxAccountUtils.unwrapkB(unwrapkB, wrapkB)));
}
@Test
public void testClientState() throws Exception {
final String hexKB = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
final byte[] byteKB = Utils.hex2Byte(hexKB);
final String clientState = FxAccountUtils.computeClientState(byteKB);
final String expected = "6ae94683571c7a7c54dab4700aa3995f";
Assert.assertEquals(expected, clientState);
}
@Test
public void testGetAudienceForURL() throws Exception {
// Sub-domains and path components.
Assert.assertEquals("http://sub.test.com", FxAccountUtils.getAudienceForURL("http://sub.test.com"));
Assert.assertEquals("http://test.com", FxAccountUtils.getAudienceForURL("http://test.com/"));
Assert.assertEquals("http://test.com", FxAccountUtils.getAudienceForURL("http://test.com/path/component"));
Assert.assertEquals("http://test.com", FxAccountUtils.getAudienceForURL("http://test.com/path/component/"));
// No port and default port.
Assert.assertEquals("http://test.com", FxAccountUtils.getAudienceForURL("http://test.com"));
Assert.assertEquals("http://test.com:80", FxAccountUtils.getAudienceForURL("http://test.com:80"));
Assert.assertEquals("https://test.com", FxAccountUtils.getAudienceForURL("https://test.com"));
Assert.assertEquals("https://test.com:443", FxAccountUtils.getAudienceForURL("https://test.com:443"));
// Ports that are the default ports for a different scheme.
Assert.assertEquals("https://test.com:80", FxAccountUtils.getAudienceForURL("https://test.com:80"));
Assert.assertEquals("http://test.com:443", FxAccountUtils.getAudienceForURL("http://test.com:443"));
// Arbitrary ports.
Assert.assertEquals("http://test.com:8080", FxAccountUtils.getAudienceForURL("http://test.com:8080"));
Assert.assertEquals("https://test.com:4430", FxAccountUtils.getAudienceForURL("https://test.com:4430"));
}
}

View file

@ -1,34 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.test;
import ch.boye.httpclientandroidlib.HttpEntity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class EntityTestHelper {
private static final int DEFAULT_SIZE = 1024;
public static byte[] bytesFromEntity(final HttpEntity entity) throws IOException {
final InputStream is = entity.getContent();
if (is instanceof ByteArrayInputStream) {
final int size = is.available();
final byte[] buffer = new byte[size];
is.read(buffer, 0, size);
return buffer;
}
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final byte[] buffer = new byte[DEFAULT_SIZE];
int len;
while ((len = is.read(buffer, 0, DEFAULT_SIZE)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}

View file

@ -1,72 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SynchronizerConfiguration;
import org.mozilla.gecko.sync.repositories.RecordFactory;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.stage.ServerSyncStage;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* A stage that joins two Repositories with no wrapping.
*/
public abstract class BaseMockServerSyncStage extends ServerSyncStage {
public Repository local;
public Repository remote;
public String name;
public String collection;
public int version = 1;
@Override
public boolean isEnabled() {
return true;
}
@Override
protected String getCollection() {
return collection;
}
@Override
protected Repository getLocalRepository() {
return local;
}
@Override
protected Repository getRemoteRepository() throws URISyntaxException {
return remote;
}
@Override
protected String getEngineName() {
return name;
}
@Override
public Integer getStorageVersion() {
return version;
}
@Override
protected RecordFactory getRecordFactory() {
return null;
}
@Override
protected Repository wrappedServerRepo()
throws NoCollectionKeysSetException, URISyntaxException {
return getRemoteRepository();
}
public SynchronizerConfiguration leakConfig()
throws NonObjectJSONException, IOException {
return this.getConfig();
}
}

View file

@ -1,40 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.json.simple.JSONArray;
import org.mozilla.gecko.sync.CommandProcessor.Command;
public class CommandHelpers {
@SuppressWarnings("unchecked")
public static Command getCommand1() {
JSONArray args = new JSONArray();
args.add("argsA");
return new Command("displayURI", args);
}
@SuppressWarnings("unchecked")
public static Command getCommand2() {
JSONArray args = new JSONArray();
args.add("argsB");
return new Command("displayURI", args);
}
@SuppressWarnings("unchecked")
public static Command getCommand3() {
JSONArray args = new JSONArray();
args.add("argsC");
return new Command("displayURI", args);
}
@SuppressWarnings("unchecked")
public static Command getCommand4() {
JSONArray args = new JSONArray();
args.add("URI of Page");
args.add("Sender ID");
args.add("Title of Page");
return new Command("displayURI", args);
}
}

View file

@ -1,51 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import java.net.URI;
public class DefaultGlobalSessionCallback implements GlobalSessionCallback {
@Override
public void requestBackoff(long backoff) {
}
@Override
public void informUnauthorizedResponse(GlobalSession globalSession,
URI oldClusterURL) {
}
@Override
public void informUpgradeRequiredResponse(GlobalSession session) {
}
@Override
public void informMigrated(GlobalSession globalSession) {
}
@Override
public void handleAborted(GlobalSession globalSession, String reason) {
}
@Override
public void handleError(GlobalSession globalSession, Exception ex) {
}
@Override
public void handleSuccess(GlobalSession globalSession) {
}
@Override
public void handleStageCompleted(Stage currentState,
GlobalSession globalSession) {
}
@Override
public boolean shouldBackOffStorage() {
return false;
}
}

View file

@ -1,13 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.stage.AbstractNonRepositorySyncStage;
public class MockAbstractNonRepositorySyncStage extends AbstractNonRepositorySyncStage {
@Override
public void execute() {
session.advance();
}
}

View file

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
public class MockClientsDataDelegate implements ClientsDataDelegate {
private String accountGUID;
private String clientName;
private int clientsCount;
private long clientDataTimestamp = 0;
@Override
public synchronized String getAccountGUID() {
if (accountGUID == null) {
accountGUID = Utils.generateGuid();
}
return accountGUID;
}
@Override
public synchronized String getDefaultClientName() {
return "Default client";
}
@Override
public synchronized void setClientName(String clientName, long now) {
this.clientName = clientName;
this.clientDataTimestamp = now;
}
@Override
public synchronized String getClientName() {
if (clientName == null) {
setClientName(getDefaultClientName(), System.currentTimeMillis());
}
return clientName;
}
@Override
public synchronized void setClientsCount(int clientsCount) {
this.clientsCount = clientsCount;
}
@Override
public synchronized int getClientsCount() {
return clientsCount;
}
@Override
public synchronized boolean isLocalGUID(String guid) {
return getAccountGUID().equals(guid);
}
@Override
public synchronized long getLastModifiedTimestamp() {
return clientDataTimestamp;
}
@Override
public String getFormFactor() {
return "phone";
}
}

View file

@ -1,76 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.CommandProcessor.Command;
import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class MockClientsDatabaseAccessor extends ClientsDatabaseAccessor {
public boolean storedRecord = false;
public boolean dbWiped = false;
public boolean clientsTableWiped = false;
public boolean closed = false;
public boolean storedArrayList = false;
public boolean storedCommand;
@Override
public void store(ClientRecord record) {
storedRecord = true;
}
@Override
public void store(Collection<ClientRecord> records) {
storedArrayList = false;
}
@Override
public void store(String accountGUID, Command command) throws NullCursorException {
storedCommand = true;
}
@Override
public ClientRecord fetchClient(String profileID) throws NullCursorException {
return null;
}
@Override
public Map<String, ClientRecord> fetchAllClients() throws NullCursorException {
return null;
}
@Override
public List<Command> fetchCommandsForClient(String accountGUID) throws NullCursorException {
return null;
}
@Override
public int clientsCount() {
return 0;
}
@Override
public void wipeDB() {
dbWiped = true;
}
@Override
public void wipeClientsTable() {
clientsTableWiped = true;
}
@Override
public void close() {
closed = true;
}
public void resetVars() {
storedRecord = dbWiped = clientsTableWiped = closed = storedArrayList = false;
}
}

View file

@ -1,57 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.EngineSettings;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import org.mozilla.gecko.sync.stage.CompletedStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import java.io.IOException;
import java.util.HashMap;
public class MockGlobalSession extends MockPrefsGlobalSession {
public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException {
this(new SyncConfiguration(username, new BasicAuthHeaderProvider(username, password), new MockSharedPreferences(), keyBundle), callback);
}
public MockGlobalSession(SyncConfiguration config, GlobalSessionCallback callback)
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
super(config, callback, null, null);
}
@Override
public boolean isEngineRemotelyEnabled(String engine, EngineSettings engineSettings) {
return false;
}
@Override
protected void prepareStages() {
super.prepareStages();
HashMap<Stage, GlobalSyncStage> newStages = new HashMap<Stage, GlobalSyncStage>(this.stages);
for (Stage stage : this.stages.keySet()) {
newStages.put(stage, new MockServerSyncStage());
}
// This signals that the global session is complete.
newStages.put(Stage.completed, new CompletedStage());
this.stages = newStages;
}
public MockGlobalSession withStage(Stage stage, GlobalSyncStage syncStage) {
stages.put(stage, syncStage);
return this;
}
}

View file

@ -1,60 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import android.content.Context;
import android.content.SharedPreferences;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
import java.io.IOException;
/**
* GlobalSession touches the Android prefs system. Stub that out.
*/
public class MockPrefsGlobalSession extends GlobalSession {
public MockSharedPreferences prefs;
public MockPrefsGlobalSession(
SyncConfiguration config, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
super(config, callback, context, clientsDelegate);
}
public static MockPrefsGlobalSession getSession(
String username, String password,
KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
return getSession(username, new BasicAuthHeaderProvider(username, password), null,
syncKeyBundle, callback, context, clientsDelegate);
}
public static MockPrefsGlobalSession getSession(
String username, AuthHeaderProvider authHeaderProvider, String prefsPath,
KeyBundle syncKeyBundle, GlobalSessionCallback callback, Context context,
ClientsDataDelegate clientsDelegate)
throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
final SharedPreferences prefs = new MockSharedPreferences();
final SyncConfiguration config = new SyncConfiguration(username, authHeaderProvider, prefs);
config.syncKeyBundle = syncKeyBundle;
return new MockPrefsGlobalSession(config, callback, context, clientsDelegate);
}
@Override
public Context getContext() {
return null;
}
}

View file

@ -1,51 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.repositories.domain.Record;
import java.util.Random;
public class MockRecord extends Record {
private final int payloadByteCount;
public MockRecord(String guid, String collection, long lastModified, boolean deleted) {
super(guid, collection, lastModified, deleted);
// Payload used to be "foo", so let's not stray too far.
// Perhaps some tests "depend" on that payload size.
payloadByteCount = 3;
}
public MockRecord(String guid, String collection, long lastModified, boolean deleted, int payloadByteCount) {
super(guid, collection, lastModified, deleted);
this.payloadByteCount = payloadByteCount;
}
@Override
protected void populatePayload(ExtendedJSONObject payload) {
}
@Override
protected void initFromPayload(ExtendedJSONObject payload) {
}
@Override
public Record copyWithIDs(String guid, long androidID) {
MockRecord r = new MockRecord(guid, this.collection, this.lastModified, this.deleted);
r.androidID = androidID;
return r;
}
@Override
public String toJSONString() {
// Build up a randomish payload string based on the length we were asked for.
final Random random = new Random();
final char[] payloadChars = new char[payloadByteCount];
for (int i = 0; i < payloadByteCount; i++) {
payloadChars[i] = (char) (random.nextInt(26) + 'a');
}
final String payloadString = new String(payloadChars);
return "{\"id\":\"" + guid + "\", \"payload\": \"" + payloadString+ "\"}";
}
}

View file

@ -1,11 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
public class MockServerSyncStage extends BaseMockServerSyncStage {
@Override
public void execute() {
session.advance();
}
}

View file

@ -1,137 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import android.content.SharedPreferences;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* A programmable mock content provider.
*/
public class MockSharedPreferences implements SharedPreferences, SharedPreferences.Editor {
private HashMap<String, Object> mValues;
private HashMap<String, Object> mTempValues;
public MockSharedPreferences() {
mValues = new HashMap<String, Object>();
mTempValues = new HashMap<String, Object>();
}
public Editor edit() {
return this;
}
public boolean contains(String key) {
return mValues.containsKey(key);
}
public Map<String, ?> getAll() {
return new HashMap<String, Object>(mValues);
}
public boolean getBoolean(String key, boolean defValue) {
if (mValues.containsKey(key)) {
return ((Boolean)mValues.get(key)).booleanValue();
}
return defValue;
}
public float getFloat(String key, float defValue) {
if (mValues.containsKey(key)) {
return ((Float)mValues.get(key)).floatValue();
}
return defValue;
}
public int getInt(String key, int defValue) {
if (mValues.containsKey(key)) {
return ((Integer)mValues.get(key)).intValue();
}
return defValue;
}
public long getLong(String key, long defValue) {
if (mValues.containsKey(key)) {
return ((Long)mValues.get(key)).longValue();
}
return defValue;
}
public String getString(String key, String defValue) {
if (mValues.containsKey(key))
return (String)mValues.get(key);
return defValue;
}
@SuppressWarnings("unchecked")
public Set<String> getStringSet(String key, Set<String> defValues) {
if (mValues.containsKey(key)) {
return (Set<String>) mValues.get(key);
}
return defValues;
}
public void registerOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException();
}
public void unregisterOnSharedPreferenceChangeListener(
OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException();
}
public Editor putBoolean(String key, boolean value) {
mTempValues.put(key, Boolean.valueOf(value));
return this;
}
public Editor putFloat(String key, float value) {
mTempValues.put(key, value);
return this;
}
public Editor putInt(String key, int value) {
mTempValues.put(key, value);
return this;
}
public Editor putLong(String key, long value) {
mTempValues.put(key, value);
return this;
}
public Editor putString(String key, String value) {
mTempValues.put(key, value);
return this;
}
public Editor putStringSet(String key, Set<String> values) {
mTempValues.put(key, values);
return this;
}
public Editor remove(String key) {
mTempValues.remove(key);
return this;
}
public Editor clear() {
mTempValues.clear();
return this;
}
@SuppressWarnings("unchecked")
public boolean commit() {
mValues = (HashMap<String, Object>)mTempValues.clone();
return true;
}
public void apply() {
commit();
}
}

View file

@ -1,125 +0,0 @@
/**
* The MIT License
*
* Copyright (c) 2010 Xtreme Labs and Pivotal Labs
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.mozilla.gecko.background.testhelpers;
import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.FileFsFile;
import org.robolectric.res.FsFile;
import org.robolectric.util.Logger;
import org.robolectric.util.ReflectionHelpers;
/**
* Test runner customized for running unit tests either through the Gradle CLI or
* Android Studio. The runner uses the build type and build flavor to compute the
* resource, asset, and AndroidManifest paths.
*
* This test runner requires that you set the 'constants' field on the @Config
* annotation (or the org.robolectric.Config.properties file) for your tests.
*
* This is a modified version of
* https://github.com/robolectric/robolectric/blob/8676da2daa4c140679fb5903696b8191415cec8f/robolectric/src/main/java/org/robolectric/RobolectricGradleTestRunner.java
* that uses a Gradle `buildConfigField` to find build outputs.
* See https://github.com/robolectric/robolectric/issues/1648#issuecomment-113731011.
*/
public class TestRunner extends RobolectricTestRunner {
private FsFile buildFolder;
public TestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
protected AndroidManifest getAppManifest(Config config) {
if (config.constants() == Void.class) {
Logger.error("Field 'constants' not specified in @Config annotation");
Logger.error("This is required when using RobolectricGradleTestRunner!");
throw new RuntimeException("No 'constants' field in @Config annotation!");
}
buildFolder = FileFsFile.from(getBuildDir(config)).join("intermediates");
final String type = getType(config);
final String flavor = getFlavor(config);
final String packageName = getPackageName(config);
final FsFile assets = buildFolder.join("assets", flavor, type);;
final FsFile manifest = buildFolder.join("manifests", "full", flavor, type, "AndroidManifest.xml");
final FsFile res;
if (buildFolder.join("res", "merged").exists()) {
res = buildFolder.join("res", "merged", flavor, type);
} else if(buildFolder.join("res").exists()) {
res = buildFolder.join("res", flavor, type);
} else {
throw new IllegalStateException("No resource folder found");
}
Logger.debug("Robolectric assets directory: " + assets.getPath());
Logger.debug(" Robolectric res directory: " + res.getPath());
Logger.debug(" Robolectric manifest path: " + manifest.getPath());
Logger.debug(" Robolectric package name: " + packageName);
return new AndroidManifest(manifest, res, assets, packageName);
}
private static String getType(Config config) {
try {
return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE");
} catch (Throwable e) {
return null;
}
}
private static String getFlavor(Config config) {
try {
return ReflectionHelpers.getStaticField(config.constants(), "FLAVOR");
} catch (Throwable e) {
return null;
}
}
private static String getPackageName(Config config) {
try {
final String packageName = config.packageName();
if (packageName != null && !packageName.isEmpty()) {
return packageName;
} else {
return ReflectionHelpers.getStaticField(config.constants(), "APPLICATION_ID");
}
} catch (Throwable e) {
return null;
}
}
private String getBuildDir(Config config) {
try {
return ReflectionHelpers.getStaticField(config.constants(), "BUILD_DIR");
} catch (Throwable e) {
return null;
}
}
}

View file

@ -1,230 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import android.content.Context;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
import org.mozilla.gecko.sync.repositories.RecordFilter;
import org.mozilla.gecko.sync.repositories.Repository;
import org.mozilla.gecko.sync.repositories.StoreTrackingRepositorySession;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
import org.mozilla.gecko.sync.repositories.domain.Record;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WBORepository extends Repository {
public class WBORepositoryStats {
public long created = -1;
public long begun = -1;
public long fetchBegan = -1;
public long fetchCompleted = -1;
public long storeBegan = -1;
public long storeCompleted = -1;
public long finished = -1;
}
public static final String LOG_TAG = "WBORepository";
// Access to stats is not guarded.
public WBORepositoryStats stats;
// Whether or not to increment the timestamp of stored records.
public final boolean bumpTimestamps;
public class WBORepositorySession extends StoreTrackingRepositorySession {
protected WBORepository wboRepository;
protected ExecutorService delegateExecutor = Executors.newSingleThreadExecutor();
public ConcurrentHashMap<String, Record> wbos;
public WBORepositorySession(WBORepository repository) {
super(repository);
wboRepository = repository;
wbos = new ConcurrentHashMap<String, Record>();
stats = new WBORepositoryStats();
stats.created = now();
}
@Override
protected synchronized void trackGUID(String guid) {
if (wboRepository.shouldTrack()) {
super.trackGUID(guid);
}
}
@Override
public void guidsSince(long timestamp,
RepositorySessionGuidsSinceDelegate delegate) {
throw new RuntimeException("guidsSince not implemented.");
}
@Override
public void fetchSince(long timestamp,
RepositorySessionFetchRecordsDelegate delegate) {
long fetchBegan = now();
stats.fetchBegan = fetchBegan;
RecordFilter filter = storeTracker.getFilter();
for (Entry<String, Record> entry : wbos.entrySet()) {
Record record = entry.getValue();
if (record.lastModified >= timestamp) {
if (filter != null &&
filter.excludeRecord(record)) {
Logger.debug(LOG_TAG, "Excluding record " + record.guid);
continue;
}
delegate.deferredFetchDelegate(delegateExecutor).onFetchedRecord(record);
}
}
long fetchCompleted = now();
stats.fetchCompleted = fetchCompleted;
delegate.deferredFetchDelegate(delegateExecutor).onFetchCompleted(fetchCompleted);
}
@Override
public void fetch(final String[] guids,
final RepositorySessionFetchRecordsDelegate delegate) {
long fetchBegan = now();
stats.fetchBegan = fetchBegan;
for (String guid : guids) {
if (wbos.containsKey(guid)) {
delegate.deferredFetchDelegate(delegateExecutor).onFetchedRecord(wbos.get(guid));
}
}
long fetchCompleted = now();
stats.fetchCompleted = fetchCompleted;
delegate.deferredFetchDelegate(delegateExecutor).onFetchCompleted(fetchCompleted);
}
@Override
public void fetchAll(final RepositorySessionFetchRecordsDelegate delegate) {
long fetchBegan = now();
stats.fetchBegan = fetchBegan;
for (Entry<String, Record> entry : wbos.entrySet()) {
Record record = entry.getValue();
delegate.deferredFetchDelegate(delegateExecutor).onFetchedRecord(record);
}
long fetchCompleted = now();
stats.fetchCompleted = fetchCompleted;
delegate.deferredFetchDelegate(delegateExecutor).onFetchCompleted(fetchCompleted);
}
@Override
public void store(final Record record) throws NoStoreDelegateException {
if (delegate == null) {
throw new NoStoreDelegateException();
}
final long now = now();
if (stats.storeBegan < 0) {
stats.storeBegan = now;
}
Record existing = wbos.get(record.guid);
Logger.debug(LOG_TAG, "Existing record is " + (existing == null ? "<null>" : (existing.guid + ", " + existing)));
if (existing != null &&
existing.lastModified > record.lastModified) {
Logger.debug(LOG_TAG, "Local record is newer. Not storing.");
delegate.deferredStoreDelegate(delegateExecutor).onRecordStoreSucceeded(record.guid);
return;
}
if (existing != null) {
Logger.debug(LOG_TAG, "Replacing local record.");
}
// Store a copy of the record with an updated modified time.
Record toStore = record.copyWithIDs(record.guid, record.androidID);
if (bumpTimestamps) {
toStore.lastModified = now;
}
wbos.put(record.guid, toStore);
trackRecord(toStore);
delegate.deferredStoreDelegate(delegateExecutor).onRecordStoreSucceeded(record.guid);
}
@Override
public void wipe(final RepositorySessionWipeDelegate delegate) {
if (!isActive()) {
delegate.onWipeFailed(new InactiveSessionException(null));
return;
}
Logger.info(LOG_TAG, "Wiping WBORepositorySession.");
this.wbos = new ConcurrentHashMap<String, Record>();
// Wipe immediately for the convenience of test code.
wboRepository.wbos = new ConcurrentHashMap<String, Record>();
delegate.deferredWipeDelegate(delegateExecutor).onWipeSucceeded();
}
@Override
public void finish(RepositorySessionFinishDelegate delegate) throws InactiveSessionException {
Logger.info(LOG_TAG, "Finishing WBORepositorySession: handing back " + this.wbos.size() + " WBOs.");
wboRepository.wbos = this.wbos;
stats.finished = now();
super.finish(delegate);
}
@Override
public void begin(RepositorySessionBeginDelegate delegate) throws InvalidSessionTransitionException {
this.wbos = wboRepository.cloneWBOs();
stats.begun = now();
super.begin(delegate);
}
@Override
public void storeDone(long end) {
// TODO: this is not guaranteed to be called after all of the record
// store callbacks have completed!
if (stats.storeBegan < 0) {
stats.storeBegan = end;
}
stats.storeCompleted = end;
delegate.deferredStoreDelegate(delegateExecutor).onStoreCompleted(end);
}
}
public ConcurrentHashMap<String, Record> wbos;
public WBORepository(boolean bumpTimestamps) {
super();
this.bumpTimestamps = bumpTimestamps;
this.wbos = new ConcurrentHashMap<String, Record>();
}
public WBORepository() {
this(false);
}
public synchronized boolean shouldTrack() {
return false;
}
@Override
public void createSession(RepositorySessionCreationDelegate delegate,
Context context) {
delegate.deferredCreationDelegate().onSessionCreated(new WBORepositorySession(this));
}
public ConcurrentHashMap<String, Record> cloneWBOs() {
ConcurrentHashMap<String, Record> out = new ConcurrentHashMap<String, Record>();
for (Entry<String, Record> entry : wbos.entrySet()) {
out.put(entry.getKey(), entry.getValue()); // Assume that records are
// immutable.
}
return out;
}
}

View file

@ -1,172 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.background.testhelpers;
import org.mozilla.gecko.background.common.log.Logger;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
* 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();
}
}

View file

@ -1,45 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.browserid.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.browserid.ASNUtils;
import org.mozilla.gecko.sync.Utils;
import java.math.BigInteger;
@RunWith(TestRunner.class)
public class TestASNUtils {
public void doTestEncodeDecodeArrays(int length1, int length2) {
if (4 + length1 + length2 > 127) {
throw new IllegalArgumentException("Total length must be < 128 - 4.");
}
byte[] first = Utils.generateRandomBytes(length1);
byte[] second = Utils.generateRandomBytes(length2);
byte[] encoded = ASNUtils.encodeTwoArraysToASN1(first, second);
byte[][] arrays = ASNUtils.decodeTwoArraysFromASN1(encoded);
Assert.assertArrayEquals(first, arrays[0]);
Assert.assertArrayEquals(second, arrays[1]);
}
@Test
public void testEncodeDecodeArrays() {
doTestEncodeDecodeArrays(0, 0);
doTestEncodeDecodeArrays(0, 10);
doTestEncodeDecodeArrays(10, 0);
doTestEncodeDecodeArrays(10, 10);
}
@Test
public void testEncodeDecodeRandomSizeArrays() {
for (int i = 0; i < 10; i++) {
int length1 = Utils.generateBigIntegerLessThan(BigInteger.valueOf(50)).intValue() + 10;
int length2 = Utils.generateBigIntegerLessThan(BigInteger.valueOf(50)).intValue() + 10;
doTestEncodeDecodeArrays(length1, length2);
}
}
}

View file

@ -1,60 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.browserid.test;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.DSACryptoImplementation;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import java.math.BigInteger;
@RunWith(TestRunner.class)
public class TestDSACryptoImplementation {
@Test
public void testToJSONObject() throws Exception {
BigInteger p = new BigInteger("fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17", 16);
BigInteger q = new BigInteger("962eddcc369cba8ebb260ee6b6a126d9346e38c5", 16);
BigInteger g = new BigInteger("678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4", 16);
BigInteger x = new BigInteger("9516d860392003db5a4f168444903265467614db", 16);
BigInteger y = new BigInteger("455152a0e499f5c9d11f9f1868c8b868b1443ca853843226a5a9552dd909b4bdba879acc504acb690df0348d60e63ea37e8c7f075302e0df5bcdc76a383888a0", 16);
BrowserIDKeyPair keyPair = new BrowserIDKeyPair(
DSACryptoImplementation.createPrivateKey(x, p, q, g),
DSACryptoImplementation.createPublicKey(y, p, q, g));
ExtendedJSONObject o = new ExtendedJSONObject("{\"publicKey\":{\"g\":\"678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4\",\"q\":\"962eddcc369cba8ebb260ee6b6a126d9346e38c5\",\"p\":\"fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17\",\"y\":\"455152a0e499f5c9d11f9f1868c8b868b1443ca853843226a5a9552dd909b4bdba879acc504acb690df0348d60e63ea37e8c7f075302e0df5bcdc76a383888a0\",\"algorithm\":\"DS\"},\"privateKey\":{\"g\":\"678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4\",\"q\":\"962eddcc369cba8ebb260ee6b6a126d9346e38c5\",\"p\":\"fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17\",\"x\":\"9516d860392003db5a4f168444903265467614db\",\"algorithm\":\"DS\"}}");
Assert.assertEquals(o.getObject("privateKey"), keyPair.toJSONObject().getObject("privateKey"));
Assert.assertEquals(o.getObject("publicKey"), keyPair.toJSONObject().getObject("publicKey"));
}
@Test
public void testFromJSONObject() throws Exception {
BigInteger p = new BigInteger("fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17", 16);
BigInteger q = new BigInteger("962eddcc369cba8ebb260ee6b6a126d9346e38c5", 16);
BigInteger g = new BigInteger("678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4", 16);
BigInteger x = new BigInteger("9516d860392003db5a4f168444903265467614db", 16);
BigInteger y = new BigInteger("455152a0e499f5c9d11f9f1868c8b868b1443ca853843226a5a9552dd909b4bdba879acc504acb690df0348d60e63ea37e8c7f075302e0df5bcdc76a383888a0", 16);
BrowserIDKeyPair keyPair = new BrowserIDKeyPair(
DSACryptoImplementation.createPrivateKey(x, p, q, g),
DSACryptoImplementation.createPublicKey(y, p, q, g));
ExtendedJSONObject o = new ExtendedJSONObject("{\"publicKey\":{\"g\":\"678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4\",\"q\":\"962eddcc369cba8ebb260ee6b6a126d9346e38c5\",\"p\":\"fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17\",\"y\":\"455152a0e499f5c9d11f9f1868c8b868b1443ca853843226a5a9552dd909b4bdba879acc504acb690df0348d60e63ea37e8c7f075302e0df5bcdc76a383888a0\",\"algorithm\":\"DS\"},\"privateKey\":{\"g\":\"678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4\",\"q\":\"962eddcc369cba8ebb260ee6b6a126d9346e38c5\",\"p\":\"fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17\",\"x\":\"9516d860392003db5a4f168444903265467614db\",\"algorithm\":\"DS\"}}");
Assert.assertEquals(keyPair.getPublic().toJSONObject(), DSACryptoImplementation.createPublicKey(o.getObject("publicKey")).toJSONObject());
Assert.assertEquals(keyPair.getPrivate().toJSONObject(), DSACryptoImplementation.createPrivateKey(o.getObject("privateKey")).toJSONObject());
}
@Test
public void testRoundTrip() throws Exception {
BrowserIDKeyPair keyPair = DSACryptoImplementation.generateKeyPair(512);
ExtendedJSONObject o = keyPair.toJSONObject();
BrowserIDKeyPair keyPair2 = DSACryptoImplementation.fromJSONObject(o);
Assert.assertEquals(o, keyPair2.toJSONObject());
}
}

View file

@ -1,151 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.browserid.test;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.DSACryptoImplementation;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.browserid.RSACryptoImplementation;
import org.mozilla.gecko.browserid.SigningPrivateKey;
import org.mozilla.gecko.browserid.VerifyingPublicKey;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
@RunWith(TestRunner.class)
public class TestJSONWebTokenUtils {
public void doTestEncodeDecode(BrowserIDKeyPair keyPair) throws Exception {
SigningPrivateKey privateKey = keyPair.getPrivate();
VerifyingPublicKey publicKey = keyPair.getPublic();
ExtendedJSONObject o = new ExtendedJSONObject();
o.put("key", "value");
String token = JSONWebTokenUtils.encode(o.toJSONString(), privateKey);
Assert.assertNotNull(token);
String payload = JSONWebTokenUtils.decode(token, publicKey);
Assert.assertEquals(o.toJSONString(), payload);
try {
JSONWebTokenUtils.decode(token + "x", publicKey);
Assert.fail("Expected exception.");
} catch (GeneralSecurityException e) {
// Do nothing.
}
}
@Test
public void testEncodeDecodeSuccessRSA() throws Exception {
doTestEncodeDecode(RSACryptoImplementation.generateKeyPair(1024));
doTestEncodeDecode(RSACryptoImplementation.generateKeyPair(2048));
}
@Test
public void testEncodeDecodeSuccessDSA() throws Exception {
doTestEncodeDecode(DSACryptoImplementation.generateKeyPair(512));
doTestEncodeDecode(DSACryptoImplementation.generateKeyPair(1024));
}
public static String TEST_ASSERTION_ISSUER = "127.0.0.1";
public static String TEST_AUDIENCE = "http://localhost:8080";
@Test
public void testRSAGeneration() throws Exception {
// This test uses (now out-dated) MockMyID RSA data but doesn't rely on this
// data actually being MockMyID's data.
final BigInteger MOCKMYID_MODULUS = new BigInteger("15498874758090276039465094105837231567265546373975960480941122651107772824121527483107402353899846252489837024870191707394743196399582959425513904762996756672089693541009892030848825079649783086005554442490232900875792851786203948088457942416978976455297428077460890650409549242124655536986141363719589882160081480785048965686285142002320767066674879737238012064156675899512503143225481933864507793118457805792064445502834162315532113963746801770187685650408560424682654937744713813773896962263709692724630650952159596951348264005004375017610441835956073275708740239518011400991972811669493356682993446554779893834303");
final BigInteger MOCKMYID_PUBLIC_EXPONENT = new BigInteger("65537");
final BigInteger MOCKMYID_PRIVATE_EXPONENT = new BigInteger("6539906961872354450087244036236367269804254381890095841127085551577495913426869112377010004955160417265879626558436936025363204803913318582680951558904318308893730033158178650549970379367915856087364428530828396795995781364659413467784853435450762392157026962694408807947047846891301466649598749901605789115278274397848888140105306063608217776127549926721544215720872305194645129403056801987422794114703255989202755511523434098625000826968430077091984351410839837395828971692109391386427709263149504336916566097901771762648090880994773325283207496645630792248007805177873532441314470502254528486411726581424522838833");
BigInteger n = new BigInteger("20332459213245328760269530796942625317006933400814022542511832260333163206808672913301254872114045771215470352093046136365629411384688395020388553744886954869033696089099714200452682590914843971683468562019706059388121176435204818734091361033445697933682779095713376909412972373727850278295874361806633955236862180792787906413536305117030045164276955491725646610368132167655556353974515423042221261732084368978523747789654468953860772774078384556028728800902433401131226904244661160767916883680495122225202542023841606998867411022088440946301191503335932960267228470933599974787151449279465703844493353175088719018221");
BigInteger e = new BigInteger("65537");
BigInteger d = new BigInteger("9362542596354998418106014928820888151984912891492829581578681873633736656469965533631464203894863562319612803232737938923691416707617473868582415657005943574434271946791143554652502483003923911339605326222297167404896789026986450703532494518628015811567189641735787240372075015553947628033216297520493759267733018808392882741098489889488442349031883643894014316243251108104684754879103107764521172490019661792943030921873284592436328217485953770574054344056638447333651425231219150676837203185544359148474983670261712939626697233692596362322419559401320065488125670905499610998631622562652935873085671353890279911361");
long iat = 1352995809210L;
long dur = 60 * 60 * 1000;
long exp = iat + dur;
VerifyingPublicKey mockMyIdPublicKey = RSACryptoImplementation.createPublicKey(MOCKMYID_MODULUS, MOCKMYID_PUBLIC_EXPONENT);;
SigningPrivateKey mockMyIdPrivateKey = RSACryptoImplementation.createPrivateKey(MOCKMYID_MODULUS, MOCKMYID_PRIVATE_EXPONENT);
VerifyingPublicKey publicKeyToSign = RSACryptoImplementation.createPublicKey(n, e);
SigningPrivateKey privateKeyToSignWith = RSACryptoImplementation.createPrivateKey(n, d);
String certificate = JSONWebTokenUtils.createCertificate(publicKeyToSign, "test@mockmyid.com", "mockmyid.com", iat, exp, mockMyIdPrivateKey);
String assertion = JSONWebTokenUtils.createAssertion(privateKeyToSignWith, certificate, TEST_AUDIENCE, TEST_ASSERTION_ISSUER, iat, exp);
String payload = JSONWebTokenUtils.decode(certificate, mockMyIdPublicKey);
String EXPECTED_PAYLOAD = "{\"exp\":1352999409210,\"iat\":1352995809210,\"iss\":\"mockmyid.com\",\"principal\":{\"email\":\"test@mockmyid.com\"},\"public-key\":{\"e\":\"65537\",\"n\":\"20332459213245328760269530796942625317006933400814022542511832260333163206808672913301254872114045771215470352093046136365629411384688395020388553744886954869033696089099714200452682590914843971683468562019706059388121176435204818734091361033445697933682779095713376909412972373727850278295874361806633955236862180792787906413536305117030045164276955491725646610368132167655556353974515423042221261732084368978523747789654468953860772774078384556028728800902433401131226904244661160767916883680495122225202542023841606998867411022088440946301191503335932960267228470933599974787151449279465703844493353175088719018221\",\"algorithm\":\"RS\"}}";
Assert.assertEquals(EXPECTED_PAYLOAD, payload);
// Really(!) brittle tests below. The RSA signature algorithm is deterministic, so we can test the actual signature.
String EXPECTED_CERTIFICATE = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjEzNTI5OTk0MDkyMTAsImlhdCI6MTM1Mjk5NTgwOTIxMCwiaXNzIjoibW9ja215aWQuY29tIiwicHJpbmNpcGFsIjp7ImVtYWlsIjoidGVzdEBtb2NrbXlpZC5jb20ifSwicHVibGljLWtleSI6eyJlIjoiNjU1MzciLCJuIjoiMjAzMzI0NTkyMTMyNDUzMjg3NjAyNjk1MzA3OTY5NDI2MjUzMTcwMDY5MzM0MDA4MTQwMjI1NDI1MTE4MzIyNjAzMzMxNjMyMDY4MDg2NzI5MTMzMDEyNTQ4NzIxMTQwNDU3NzEyMTU0NzAzNTIwOTMwNDYxMzYzNjU2Mjk0MTEzODQ2ODgzOTUwMjAzODg1NTM3NDQ4ODY5NTQ4NjkwMzM2OTYwODkwOTk3MTQyMDA0NTI2ODI1OTA5MTQ4NDM5NzE2ODM0Njg1NjIwMTk3MDYwNTkzODgxMjExNzY0MzUyMDQ4MTg3MzQwOTEzNjEwMzM0NDU2OTc5MzM2ODI3NzkwOTU3MTMzNzY5MDk0MTI5NzIzNzM3Mjc4NTAyNzgyOTU4NzQzNjE4MDY2MzM5NTUyMzY4NjIxODA3OTI3ODc5MDY0MTM1MzYzMDUxMTcwMzAwNDUxNjQyNzY5NTU0OTE3MjU2NDY2MTAzNjgxMzIxNjc2NTU1NTYzNTM5NzQ1MTU0MjMwNDIyMjEyNjE3MzIwODQzNjg5Nzg1MjM3NDc3ODk2NTQ0Njg5NTM4NjA3NzI3NzQwNzgzODQ1NTYwMjg3Mjg4MDA5MDI0MzM0MDExMzEyMjY5MDQyNDQ2NjExNjA3Njc5MTY4ODM2ODA0OTUxMjIyMjUyMDI1NDIwMjM4NDE2MDY5OTg4Njc0MTEwMjIwODg0NDA5NDYzMDExOTE1MDMzMzU5MzI5NjAyNjcyMjg0NzA5MzM1OTk5NzQ3ODcxNTE0NDkyNzk0NjU3MDM4NDQ0OTMzNTMxNzUwODg3MTkwMTgyMjEiLCJhbGdvcml0aG0iOiJSUyJ9fQ.ZgT0ezITaE6rRQCxEA6OHkjwAsFdE-R8943UEmiCvKKpsbxlSlI1Iya1Oho2wrhet5bjBGM77EffzC2YwzD5qa7SrVpNwSCIW6AwnlJ6YePoNblkn0y7NQ_qThvLoaP4Vlk_XM0LbK_QPHqaWU7ldm8LF5Zp4oHgayMP4YhiyKYS2TwWWcvswT2g9IhU6YdYcF0TwT2YkJ4t3h7_sVn-OmQQu4k1KKGFLpT6HOj2EGaKmw-mzayHL0r7L3-5g_7Q83RMBe_k_4YeLG8InxO3M3GreqcaImv4XO5D-C__txfFuaLJjTzKBLrIIosckaNwp4JmN1Nf8x9t5RXHLCsrjw";
Assert.assertEquals(EXPECTED_CERTIFICATE, certificate);
String EXPECTED_ASSERTION = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjEzNTI5OTk0MDkyMTAsImlhdCI6MTM1Mjk5NTgwOTIxMCwiaXNzIjoibW9ja215aWQuY29tIiwicHJpbmNpcGFsIjp7ImVtYWlsIjoidGVzdEBtb2NrbXlpZC5jb20ifSwicHVibGljLWtleSI6eyJlIjoiNjU1MzciLCJuIjoiMjAzMzI0NTkyMTMyNDUzMjg3NjAyNjk1MzA3OTY5NDI2MjUzMTcwMDY5MzM0MDA4MTQwMjI1NDI1MTE4MzIyNjAzMzMxNjMyMDY4MDg2NzI5MTMzMDEyNTQ4NzIxMTQwNDU3NzEyMTU0NzAzNTIwOTMwNDYxMzYzNjU2Mjk0MTEzODQ2ODgzOTUwMjAzODg1NTM3NDQ4ODY5NTQ4NjkwMzM2OTYwODkwOTk3MTQyMDA0NTI2ODI1OTA5MTQ4NDM5NzE2ODM0Njg1NjIwMTk3MDYwNTkzODgxMjExNzY0MzUyMDQ4MTg3MzQwOTEzNjEwMzM0NDU2OTc5MzM2ODI3NzkwOTU3MTMzNzY5MDk0MTI5NzIzNzM3Mjc4NTAyNzgyOTU4NzQzNjE4MDY2MzM5NTUyMzY4NjIxODA3OTI3ODc5MDY0MTM1MzYzMDUxMTcwMzAwNDUxNjQyNzY5NTU0OTE3MjU2NDY2MTAzNjgxMzIxNjc2NTU1NTYzNTM5NzQ1MTU0MjMwNDIyMjEyNjE3MzIwODQzNjg5Nzg1MjM3NDc3ODk2NTQ0Njg5NTM4NjA3NzI3NzQwNzgzODQ1NTYwMjg3Mjg4MDA5MDI0MzM0MDExMzEyMjY5MDQyNDQ2NjExNjA3Njc5MTY4ODM2ODA0OTUxMjIyMjUyMDI1NDIwMjM4NDE2MDY5OTg4Njc0MTEwMjIwODg0NDA5NDYzMDExOTE1MDMzMzU5MzI5NjAyNjcyMjg0NzA5MzM1OTk5NzQ3ODcxNTE0NDkyNzk0NjU3MDM4NDQ0OTMzNTMxNzUwODg3MTkwMTgyMjEiLCJhbGdvcml0aG0iOiJSUyJ9fQ.ZgT0ezITaE6rRQCxEA6OHkjwAsFdE-R8943UEmiCvKKpsbxlSlI1Iya1Oho2wrhet5bjBGM77EffzC2YwzD5qa7SrVpNwSCIW6AwnlJ6YePoNblkn0y7NQ_qThvLoaP4Vlk_XM0LbK_QPHqaWU7ldm8LF5Zp4oHgayMP4YhiyKYS2TwWWcvswT2g9IhU6YdYcF0TwT2YkJ4t3h7_sVn-OmQQu4k1KKGFLpT6HOj2EGaKmw-mzayHL0r7L3-5g_7Q83RMBe_k_4YeLG8InxO3M3GreqcaImv4XO5D-C__txfFuaLJjTzKBLrIIosckaNwp4JmN1Nf8x9t5RXHLCsrjw~eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImV4cCI6MTM1Mjk5OTQwOTIxMCwiaWF0IjoxMzUyOTk1ODA5MjEwLCJpc3MiOiIxMjcuMC4wLjEifQ.gj5Q9KXR_mPEltn3SXKAjIHMOpQq0FP6NdPOB-Zu149LKhQrfXS90woVJYg8WpaasmiS6gjBFni3urq3adPktzw4RoMm1qVMvSRXXIRZzgsV_vHlSenIY0KlAk4140pAlAPcdJhB2bvKUPPDq0TLzlWHgQpheAAFMGPY1OGgwgHtsCQC_vyE2wFi8M58IGYQ-05KmWc6Zo33CJG6LjVvkTPvPTEzQKFYKwDQGc4NTkqZbCNZE6iRq4mlX9LGFddzEDiSUDmS53SwR4nfFzPQE6Q1xnU4a_BLhfNpdfOc-uHGoJGbm0ZJpLdKf7zadp34ImFA9IUBhjegingZhm2i5g";
Assert.assertEquals(EXPECTED_ASSERTION, assertion);
}
@Test
public void testDSAGeneration() throws Exception {
// This test uses MockMyID DSA data but doesn't rely on this data actually
// being MockMyID's data.
final BigInteger MOCKMYID_x = new BigInteger("385cb3509f086e110c5e24bdd395a84b335a09ae", 16);
final BigInteger MOCKMYID_y = new BigInteger("738ec929b559b604a232a9b55a5295afc368063bb9c20fac4e53a74970a4db7956d48e4c7ed523405f629b4cc83062f13029c4d615bbacb8b97f5e56f0c7ac9bc1d4e23809889fa061425c984061fca1826040c399715ce7ed385c4dd0d402256912451e03452d3c961614eb458f188e3e8d2782916c43dbe2e571251ce38262", 16);
final BigInteger MOCKMYID_p = new BigInteger("ff600483db6abfc5b45eab78594b3533d550d9f1bf2a992a7a8daa6dc34f8045ad4e6e0c429d334eeeaaefd7e23d4810be00e4cc1492cba325ba81ff2d5a5b305a8d17eb3bf4a06a349d392e00d329744a5179380344e82a18c47933438f891e22aeef812d69c8f75e326cb70ea000c3f776dfdbd604638c2ef717fc26d02e17", 16);
final BigInteger MOCKMYID_q = new BigInteger("e21e04f911d1ed7991008ecaab3bf775984309c3", 16);
final BigInteger MOCKMYID_g = new BigInteger("c52a4a0ff3b7e61fdf1867ce84138369a6154f4afa92966e3c827e25cfa6cf508b90e5de419e1337e07a2e9e2a3cd5dea704d175f8ebf6af397d69e110b96afb17c7a03259329e4829b0d03bbc7896b15b4ade53e130858cc34d96269aa89041f409136c7242a38895c9d5bccad4f389af1d7a4bd1398bd072dffa896233397a", 16);
BigInteger g = new BigInteger("f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16);
BigInteger q = new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16);
BigInteger p = new BigInteger("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16);
BigInteger x = new BigInteger("b137fc5b8faaa53b170563eb03c18b46b657bb6", 16);
BigInteger y = new BigInteger("ea809be508bc94485553efac8ef2a8debdcdb3545ce433e8bd5889ec9d0880a13b2a8af35451161e58229d1e2be69e74a7251465a394913e8e64b0c33fde39a637b6047d7370178cf4404c0a7b4c2ed31d9cfe03ab79dbcc64667e6e7bc244eb1c127c28d725db94aff29b858bdb636f1307bdf48b3c91f387c2ab588086b6c8", 16);
long iat = 1380070362995L;
long dur = 60 * 60 * 1000;
long exp = iat + dur;
VerifyingPublicKey mockMyIdPublicKey = DSACryptoImplementation.createPublicKey(MOCKMYID_y, MOCKMYID_p, MOCKMYID_q, MOCKMYID_g);
SigningPrivateKey mockMyIdPrivateKey = DSACryptoImplementation.createPrivateKey(MOCKMYID_x, MOCKMYID_p, MOCKMYID_q, MOCKMYID_g);
VerifyingPublicKey publicKeyToSign = DSACryptoImplementation.createPublicKey(y, p, q, g);
SigningPrivateKey privateKeyToSignWith = DSACryptoImplementation.createPrivateKey(x, p, q, g);
String certificate = JSONWebTokenUtils.createCertificate(publicKeyToSign, "test@mockmyid.com", "mockmyid.com", iat, exp, mockMyIdPrivateKey);
String assertion = JSONWebTokenUtils.createAssertion(privateKeyToSignWith, certificate, TEST_AUDIENCE, TEST_ASSERTION_ISSUER, iat, exp);
String payload = JSONWebTokenUtils.decode(certificate, mockMyIdPublicKey);
String EXPECTED_PAYLOAD = "{\"exp\":1380073962995,\"iat\":1380070362995,\"iss\":\"mockmyid.com\",\"principal\":{\"email\":\"test@mockmyid.com\"},\"public-key\":{\"g\":\"f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a\",\"q\":\"9760508f15230bccb292b982a2eb840bf0581cf5\",\"p\":\"fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7\",\"y\":\"ea809be508bc94485553efac8ef2a8debdcdb3545ce433e8bd5889ec9d0880a13b2a8af35451161e58229d1e2be69e74a7251465a394913e8e64b0c33fde39a637b6047d7370178cf4404c0a7b4c2ed31d9cfe03ab79dbcc64667e6e7bc244eb1c127c28d725db94aff29b858bdb636f1307bdf48b3c91f387c2ab588086b6c8\",\"algorithm\":\"DS\"}}";
Assert.assertEquals(EXPECTED_PAYLOAD, payload);
// Really(!) brittle tests below. The DSA signature algorithm is not deterministic, so we can't test the actual signature.
String EXPECTED_CERTIFICATE_PREFIX = "eyJhbGciOiJEUzEyOCJ9.eyJleHAiOjEzODAwNzM5NjI5OTUsImlhdCI6MTM4MDA3MDM2Mjk5NSwiaXNzIjoibW9ja215aWQuY29tIiwicHJpbmNpcGFsIjp7ImVtYWlsIjoidGVzdEBtb2NrbXlpZC5jb20ifSwicHVibGljLWtleSI6eyJnIjoiZjdlMWEwODVkNjliM2RkZWNiYmNhYjVjMzZiODU3Yjk3OTk0YWZiYmZhM2FlYTgyZjk1NzRjMGIzZDA3ODI2NzUxNTk1NzhlYmFkNDU5NGZlNjcxMDcxMDgxODBiNDQ5MTY3MTIzZTg0YzI4MTYxM2I3Y2YwOTMyOGNjOGE2ZTEzYzE2N2E4YjU0N2M4ZDI4ZTBhM2FlMWUyYmIzYTY3NTkxNmVhMzdmMGJmYTIxMzU2MmYxZmI2MjdhMDEyNDNiY2NhNGYxYmVhODUxOTA4OWE4ODNkZmUxNWFlNTlmMDY5MjhiNjY1ZTgwN2I1NTI1NjQwMTRjM2JmZWNmNDkyYSIsInEiOiI5NzYwNTA4ZjE1MjMwYmNjYjI5MmI5ODJhMmViODQwYmYwNTgxY2Y1IiwicCI6ImZkN2Y1MzgxMWQ3NTEyMjk1MmRmNGE5YzJlZWNlNGU3ZjYxMWI3NTIzY2VmNDQwMGMzMWUzZjgwYjY1MTI2Njk0NTVkNDAyMjUxZmI1OTNkOGQ1OGZhYmZjNWY1YmEzMGY2Y2I5YjU1NmNkNzgxM2I4MDFkMzQ2ZmYyNjY2MGI3NmI5OTUwYTVhNDlmOWZlODA0N2IxMDIyYzI0ZmJiYTlkN2ZlYjdjNjFiZjgzYjU3ZTdjNmE4YTYxNTBmMDRmYjgzZjZkM2M1MWVjMzAyMzU1NDEzNWExNjkxMzJmNjc1ZjNhZTJiNjFkNzJhZWZmMjIyMDMxOTlkZDE0ODAxYzciLCJ5IjoiZWE4MDliZTUwOGJjOTQ0ODU1NTNlZmFjOGVmMmE4ZGViZGNkYjM1NDVjZTQzM2U4YmQ1ODg5ZWM5ZDA4ODBhMTNiMmE4YWYzNTQ1MTE2MWU1ODIyOWQxZTJiZTY5ZTc0YTcyNTE0NjVhMzk0OTEzZThlNjRiMGMzM2ZkZTM5YTYzN2I2MDQ3ZDczNzAxNzhjZjQ0MDRjMGE3YjRjMmVkMzFkOWNmZTAzYWI3OWRiY2M2NDY2N2U2ZTdiYzI0NGViMWMxMjdjMjhkNzI1ZGI5NGFmZjI5Yjg1OGJkYjYzNmYxMzA3YmRmNDhiM2M5MWYzODdjMmFiNTg4MDg2YjZjOCIsImFsZ29yaXRobSI6IkRTIn19";
String[] expectedCertificateParts = EXPECTED_CERTIFICATE_PREFIX.split("\\.");
String[] certificateParts = certificate.split("\\.");
Assert.assertEquals(expectedCertificateParts[0], certificateParts[0]);
Assert.assertEquals(expectedCertificateParts[1], certificateParts[1]);
String EXPECTED_ASSERTION_FRAGMENT = "eyJhbGciOiJEUzEyOCJ9.eyJhdWQiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODA4MCIsImV4cCI6MTM4MDA3Mzk2Mjk5NSwiaWF0IjoxMzgwMDcwMzYyOTk1LCJpc3MiOiIxMjcuMC4wLjEifQ";
String[] expectedAssertionParts = EXPECTED_ASSERTION_FRAGMENT.split("\\.");
String[] assertionParts = assertion.split("~")[1].split("\\.");
Assert.assertEquals(expectedAssertionParts[0], assertionParts[0]);
Assert.assertEquals(expectedAssertionParts[1], assertionParts[1]);
}
@Test
public void testGetPayloadString() throws Exception {
String s;
s = JSONWebTokenUtils.getPayloadString("{}", "audience", "issuer", 1L, 2L);
Assert.assertEquals("{\"aud\":\"audience\",\"exp\":2,\"iat\":1,\"iss\":\"issuer\"}", s);
// Make sure we don't include null issuedAt.
s = JSONWebTokenUtils.getPayloadString("{}", "audience", "issuer", null, 3L);
Assert.assertEquals("{\"aud\":\"audience\",\"exp\":3,\"iss\":\"issuer\"}", s);
}
}

View file

@ -1,56 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.browserid.test;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.RSACryptoImplementation;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import java.math.BigInteger;
@RunWith(TestRunner.class)
public class TestRSACryptoImplementation {
@Test
public void testToJSONObject() throws Exception {
BigInteger n = new BigInteger("7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577");
BigInteger e = new BigInteger("65537");
BigInteger d = new BigInteger("2050102629239206449128199335463237235732683202345308155771672920433658970744825199440426256856862541525088288448769859770132714705204296375901885294992205");
BrowserIDKeyPair keyPair = new BrowserIDKeyPair(
RSACryptoImplementation.createPrivateKey(n, d),
RSACryptoImplementation.createPublicKey(n, e));
ExtendedJSONObject o = new ExtendedJSONObject("{\"publicKey\":{\"e\":\"65537\",\"n\":\"7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577\",\"algorithm\":\"RS\"},\"privateKey\":{\"d\":\"2050102629239206449128199335463237235732683202345308155771672920433658970744825199440426256856862541525088288448769859770132714705204296375901885294992205\",\"n\":\"7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577\",\"algorithm\":\"RS\"}}");
Assert.assertEquals(o.getObject("privateKey"), keyPair.toJSONObject().getObject("privateKey"));
Assert.assertEquals(o.getObject("publicKey"), keyPair.toJSONObject().getObject("publicKey"));
}
@Test
public void testFromJSONObject() throws Exception {
BigInteger n = new BigInteger("7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577");
BigInteger e = new BigInteger("65537");
BigInteger d = new BigInteger("2050102629239206449128199335463237235732683202345308155771672920433658970744825199440426256856862541525088288448769859770132714705204296375901885294992205");
BrowserIDKeyPair keyPair = new BrowserIDKeyPair(
RSACryptoImplementation.createPrivateKey(n, d),
RSACryptoImplementation.createPublicKey(n, e));
ExtendedJSONObject o = new ExtendedJSONObject("{\"publicKey\":{\"e\":\"65537\",\"n\":\"7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577\",\"algorithm\":\"RS\"},\"privateKey\":{\"d\":\"2050102629239206449128199335463237235732683202345308155771672920433658970744825199440426256856862541525088288448769859770132714705204296375901885294992205\",\"n\":\"7042170764319402120473546823641395184140303948430445023576085129538272863656735924617881022040465877164076593767104512065359975488480629290310209335113577\",\"algorithm\":\"RS\"}}");
Assert.assertEquals(keyPair.getPublic().toJSONObject(), RSACryptoImplementation.createPublicKey(o.getObject("publicKey")).toJSONObject());
Assert.assertEquals(keyPair.getPrivate().toJSONObject(), RSACryptoImplementation.createPrivateKey(o.getObject("privateKey")).toJSONObject());
}
@Test
public void testRoundTrip() throws Exception {
BrowserIDKeyPair keyPair = RSACryptoImplementation.generateKeyPair(512);
ExtendedJSONObject o = keyPair.toJSONObject();
BrowserIDKeyPair keyPair2 = RSACryptoImplementation.fromJSONObject(o);
Assert.assertEquals(o, keyPair2.toJSONObject());
}
}

View file

@ -1,92 +0,0 @@
/*
* 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.cleanup;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Tests functionality of the {@link FileCleanupController}.
*/
@RunWith(TestRunner.class)
public class TestFileCleanupController {
@Test
public void testStartIfReadyEmptySharedPrefsRunsCleanup() {
final Context context = mock(Context.class);
FileCleanupController.startIfReady(context, getSharedPreferences(), "");
verify(context).startService(any(Intent.class));
}
@Test
public void testStartIfReadyLastRunNowDoesNotRun() {
final SharedPreferences sharedPrefs = getSharedPreferences();
sharedPrefs.edit()
.putLong(FileCleanupController.PREF_LAST_CLEANUP_MILLIS, System.currentTimeMillis())
.commit(); // synchronous to finish before test runs.
final Context context = mock(Context.class);
FileCleanupController.startIfReady(context, sharedPrefs, "");
verify(context, never()).startService((any(Intent.class)));
}
/**
* Depends on {@link #testStartIfReadyEmptySharedPrefsRunsCleanup()} success
* i.e. we expect the cleanup to run with empty prefs.
*/
@Test
public void testStartIfReadyDoesNotRunTwiceInSuccession() {
final Context context = mock(Context.class);
final SharedPreferences sharedPrefs = getSharedPreferences();
FileCleanupController.startIfReady(context, sharedPrefs, "");
verify(context).startService(any(Intent.class));
// Note: the Controller relies on SharedPrefs.apply, but
// robolectric made this a synchronous call. Yay!
FileCleanupController.startIfReady(context, sharedPrefs, "");
verify(context, atMost(1)).startService(any(Intent.class));
}
@Test
public void testGetFilesToCleanupContainsProfilePath() {
final String profilePath = "/a/profile/path";
final ArrayList<String> fileList = FileCleanupController.getFilesToCleanup(profilePath);
assertNotNull("Returned file list is non-null", fileList);
boolean atLeastOneStartsWithProfilePath = false;
final String pathToCheck = profilePath + "/"; // Ensure the calling code adds a slash to divide the path.
for (final String path : fileList) {
if (path.startsWith(pathToCheck)) {
// It'd be great if we could assert these individually so
// we could display the Strings in console output.
atLeastOneStartsWithProfilePath = true;
}
}
assertTrue("At least one returned String starts with a profile path", atLeastOneStartsWithProfilePath);
}
private SharedPreferences getSharedPreferences() {
return RuntimeEnvironment.application.getSharedPreferences("TestFileCleanupController", 0);
}
}

View file

@ -1,106 +0,0 @@
/*
* 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.cleanup;
import android.content.Intent;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* Tests the methods of {@link FileCleanupService}.
*/
@RunWith(TestRunner.class)
public class TestFileCleanupService {
@Rule
public final TemporaryFolder tempFolder = new TemporaryFolder();
private void assertAllFilesExist(final List<File> fileList) {
for (final File file : fileList) {
assertTrue("File exists", file.exists());
}
}
private void assertAllFilesDoNotExist(final List<File> fileList) {
for (final File file : fileList) {
assertFalse("File does not exist", file.exists());
}
}
private void onHandleIntent(final ArrayList<String> filePaths) {
final FileCleanupService service = new FileCleanupService();
final Intent intent = new Intent(FileCleanupService.ACTION_DELETE_FILES);
intent.putExtra(FileCleanupService.EXTRA_FILE_PATHS_TO_DELETE, filePaths);
service.onHandleIntent(intent);
}
@Test
public void testOnHandleIntentDeleteSpecifiedFiles() throws Exception {
final int fileListCount = 3;
final ArrayList<File> filesToDelete = generateFileList(fileListCount);
final ArrayList<String> pathsToDelete = new ArrayList<>(fileListCount);
for (final File file : filesToDelete) {
pathsToDelete.add(file.getAbsolutePath());
}
assertAllFilesExist(filesToDelete);
onHandleIntent(pathsToDelete);
assertAllFilesDoNotExist(filesToDelete);
}
@Test
public void testOnHandleIntentDoesNotDeleteUnrelatedFiles() throws Exception {
final ArrayList<File> filesShouldNotBeDeleted = generateFileList(3);
assertAllFilesExist(filesShouldNotBeDeleted);
onHandleIntent(new ArrayList<String>());
assertAllFilesExist(filesShouldNotBeDeleted);
}
@Test
public void testOnHandleIntentDeletesEmptyDirectory() throws Exception {
final File dir = tempFolder.newFolder();
final ArrayList<String> filesToDelete = new ArrayList<>(1);
filesToDelete.add(dir.getAbsolutePath());
assertTrue("Empty directory exists", dir.exists());
onHandleIntent(filesToDelete);
assertFalse("Empty directory deleted by service", dir.exists());
}
@Test
public void testOnHandleIntentDoesNotDeleteNonEmptyDirectory() throws Exception {
final File dir = tempFolder.newFolder();
final ArrayList<String> filesCannotDelete = new ArrayList<>(1);
filesCannotDelete.add(dir.getAbsolutePath());
assertTrue("Directory exists", dir.exists());
final File fileInDir = new File(dir, "file_in_dir");
assertTrue("File in dir created", fileInDir.createNewFile());
onHandleIntent(filesCannotDelete);
assertTrue("Non-empty directory not deleted", dir.exists());
assertTrue("File in directory not deleted", fileInDir.exists());
}
private ArrayList<File> generateFileList(final int size) throws IOException {
final ArrayList<File> fileList = new ArrayList<>(size);
for (int i = 0; i < size; ++i) {
fileList.add(tempFolder.newFile());
}
return fileList;
}
}

View file

@ -1,67 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.db;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import static org.junit.Assert.*;
@RunWith(TestRunner.class)
public class BrowserContractTest {
@Test
/**
* Test that bookmark and sorting order clauses are set correctly
*/
public void testGetCombinedFrecencySortOrder() throws Exception {
String sqlNoBookmarksDesc = BrowserContract.getCombinedFrecencySortOrder(false, false);
String sqlNoBookmarksAsc = BrowserContract.getCombinedFrecencySortOrder(false, true);
String sqlBookmarksDesc = BrowserContract.getCombinedFrecencySortOrder(true, false);
String sqlBookmarksAsc = BrowserContract.getCombinedFrecencySortOrder(true, true);
assertTrue(sqlBookmarksAsc.endsWith(" ASC"));
assertTrue(sqlBookmarksDesc.endsWith(" DESC"));
assertTrue(sqlNoBookmarksAsc.endsWith(" ASC"));
assertTrue(sqlNoBookmarksDesc.endsWith(" DESC"));
assertTrue(sqlBookmarksAsc.startsWith("(CASE WHEN bookmark_id > -1 THEN 100 ELSE 0 END) + "));
assertTrue(sqlBookmarksDesc.startsWith("(CASE WHEN bookmark_id > -1 THEN 100 ELSE 0 END) + "));
}
@Test
/**
* Test that calculation string is correct for remote visits
* maxFrecency=1, scaleConst=110, correct sql params for visit count and last date
* and that time is converted to microseconds.
*/
public void testGetRemoteFrecencySQL() throws Exception {
long now = 1;
String sql = BrowserContract.getRemoteFrecencySQL(now);
String ageExpr = "(" + now * 1000 + " - remoteDateLastVisited) / 86400000000";
assertEquals(
"remoteVisitCount * MAX(1, 100 * 110 / (" + ageExpr + " * " + ageExpr + " + 110))",
sql
);
}
@Test
/**
* Test that calculation string is correct for remote visits
* maxFrecency=2, scaleConst=225, correct sql params for visit count and last date
* and that time is converted to microseconds.
*/
public void testGetLocalFrecencySQL() throws Exception {
long now = 1;
String sql = BrowserContract.getLocalFrecencySQL(now);
String ageExpr = "(" + now * 1000 + " - localDateLastVisited) / 86400000000";
String visitCountExpr = "(localVisitCount + 2) * (localVisitCount + 2)";
assertEquals(
visitCountExpr + " * MAX(2, 100 * 225 / (" + ageExpr + " * " + ageExpr + " + 225))",
sql
);
}
}

View file

@ -1,438 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.db;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.mozilla.gecko.sync.setup.Constants;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.mozilla.gecko.db.BrowserContract.PARAM_PROFILE;
/**
* Unit tests for the highlights query (Activity Stream).
*/
@RunWith(TestRunner.class)
public class BrowserProviderHighlightsTest extends BrowserProviderHistoryVisitsTestBase {
private ContentProviderClient highlightsClient;
private ContentProviderClient activityStreamBlocklistClient;
private ContentProviderClient bookmarksClient;
private Uri highlightsTestUri;
private Uri activityStreamBlocklistTestUri;
private Uri bookmarksTestUri;
private Uri expireHistoryNormalUri;
@Before
public void setUp() throws Exception {
super.setUp();
final Uri highlightsClientUri = BrowserContract.Highlights.CONTENT_URI.buildUpon()
.appendQueryParameter(PARAM_PROFILE, Constants.DEFAULT_PROFILE)
.build();
final Uri activityStreamBlocklistClientUri = BrowserContract.ActivityStreamBlocklist.CONTENT_URI.buildUpon()
.appendQueryParameter(PARAM_PROFILE, Constants.DEFAULT_PROFILE)
.build();
highlightsClient = contentResolver.acquireContentProviderClient(highlightsClientUri);
activityStreamBlocklistClient = contentResolver.acquireContentProviderClient(activityStreamBlocklistClientUri);
bookmarksClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
highlightsTestUri = testUri(BrowserContract.Highlights.CONTENT_URI);
activityStreamBlocklistTestUri = testUri(BrowserContract.ActivityStreamBlocklist.CONTENT_URI);
bookmarksTestUri = testUri(BrowserContract.Bookmarks.CONTENT_URI);
expireHistoryNormalUri = testUri(BrowserContract.History.CONTENT_OLD_URI).buildUpon()
.appendQueryParameter(
BrowserContract.PARAM_EXPIRE_PRIORITY,
BrowserContract.ExpirePriority.NORMAL.toString()
).build();
}
@After
public void tearDown() {
highlightsClient.release();
activityStreamBlocklistClient.release();
bookmarksClient.release();
super.tearDown();
}
/**
* Scenario: Empty database, no history, no bookmarks.
*
* Assert that:
* - Empty cursor (not null) is returned.
*/
@Test
public void testEmptyDatabase() throws Exception {
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
/**
* Scenario: The database only contains very recent history (now, 5 minutes ago, 20 minutes).
*
* Assert that:
* - No highlight is returned from recent history.
*/
@Test
public void testOnlyRecentHistory() throws Exception {
final long now = System.currentTimeMillis();
final long fiveMinutesAgo = now - 1000 * 60 * 5;
final long twentyMinutes = now - 1000 * 60 * 20;
insertHistoryItem(createUniqueUrl(), createGUID(), now, 1, createUniqueTitle());
insertHistoryItem(createUniqueUrl(), createGUID(), fiveMinutesAgo, 1, createUniqueTitle());
insertHistoryItem(createUniqueUrl(), createGUID(), twentyMinutes, 1, createUniqueTitle());
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
/**
* Scenario: The database contains recent (but not too fresh) history (1 hour, 5 days).
*
* Assert that:
* - Highlights are returned from history.
*/
@Test
public void testHighlightsArePickedFromHistory() throws Exception {
final String url1 = createUniqueUrl();
final String url2 = createUniqueUrl();
final String title1 = createUniqueTitle();
final String title2 = createUniqueTitle();
final long oneHourAgo = System.currentTimeMillis() - 1000 * 60 * 60;
final long fiveDaysAgo = System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 5;
insertHistoryItem(url1, createGUID(), oneHourAgo, 1, title1);
insertHistoryItem(url2, createGUID(), fiveDaysAgo, 1, title2);
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(2, cursor.getCount());
assertCursorContainsEntry(cursor, url1, title1);
assertCursorContainsEntry(cursor, url2, title2);
cursor.close();
}
/**
* Scenario: The database contains history that is visited frequently and rarely.
*
* Assert that:
* - Highlights are picked from rarely visited websites.
* - Highlights are not picked from frequently visited websites.
*/
@Test
public void testOftenVisitedPagesAreNotPicked() throws Exception {
final String url1 = createUniqueUrl();
final String title1 = createUniqueTitle();
final long oneHourAgo = System.currentTimeMillis() - 1000 * 60 * 60;
final long fiveDaysAgo = System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 5;
insertHistoryItem(url1, createGUID(), oneHourAgo, 2, title1);
insertHistoryItem(createUniqueUrl(), createGUID(), fiveDaysAgo, 25, createUniqueTitle());
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
// Verify that only the first URL (with one visit) is picked and the second URL with 25 visits is ignored.
Assert.assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertCursor(cursor, url1, title1);
cursor.close();
}
/**
* Scenario: The database contains history with and without titles.
*
* Assert that:
* - History without titles is not picked for highlights.
*/
@Test
public void testHistoryWithoutTitlesIsNotPicked() throws Exception {
final String url1 = createUniqueUrl();
final String url2 = createUniqueUrl();
final String title1 = "";
final String title2 = createUniqueTitle();
final long oneHourAgo = System.currentTimeMillis() - 1000 * 60 * 60;
final long fiveDaysAgo = System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 5;
insertHistoryItem(url1, createGUID(), oneHourAgo, 1, title1);
insertHistoryItem(url2, createGUID(), fiveDaysAgo, 1, title2);
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
// Only one bookmark will be picked for highlights
Assert.assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertCursor(cursor, url2, title2);
cursor.close();
}
/**
* Scenario: Database contains two bookmarks (unvisited).
*
* Assert that:
* - One bookmark is picked for highlights.
*/
@Test
public void testPickingBookmarkForHighlights() throws Exception {
final long oneHourAgo = System.currentTimeMillis() - 1000 * 60 * 60;
final long fiveDaysAgo = System.currentTimeMillis() - 1000 * 60 * 60 * 24 * 5;
final String url1 = createUniqueUrl();
final String url2 = createUniqueUrl();
final String title1 = createUniqueTitle();
final String title2 = createUniqueTitle();
insertBookmarkItem(url1, title1, oneHourAgo);
insertBookmarkItem(url2, title2, fiveDaysAgo);
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertCursor(cursor, url1, title1);
cursor.close();
}
/**
* Scenario: Database contains an often visited bookmark.
*
* Assert that:
* - Bookmark is not selected for highlights.
*/
@Test
public void testOftenVisitedBookmarksWillNotBePicked() throws Exception {
final String url = createUniqueUrl();
final long oneHourAgo = System.currentTimeMillis() - 1000 * 60 * 60;
insertBookmarkItem(url, createUniqueTitle(), oneHourAgo);
insertHistoryItem(url, createGUID(), oneHourAgo, 25, createUniqueTitle());
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
/**
* Scenario: Database contains URL as bookmark and in history (not visited often).
*
* Assert that:
* - URL is not picked twice (as bookmark and from history)
*/
@Test
public void testSameUrlIsNotPickedFromHistoryAndBookmarks() throws Exception {
final String url = createUniqueUrl();
final long oneHourAgo = System.currentTimeMillis() - 1000 * 60 * 60;
// Insert bookmark that is picked for highlights
insertBookmarkItem(url, createUniqueTitle(), oneHourAgo);
// Insert history for same URL that would be picked for highlights too
insertHistoryItem(url, createGUID(), oneHourAgo, 2, createUniqueTitle());
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(1, cursor.getCount());
cursor.close();
}
/**
* Scenario: Database contains only old bookmarks.
*
* Assert that:
* - Old bookmarks are not selected as highlight.
*/
@Test
public void testVeryOldBookmarksAreNotSelected() throws Exception {
final long oneWeekAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7);
final long oneMonthAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31);
final long oneYearAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(365);
insertBookmarkItem(createUniqueUrl(), createUniqueTitle(), oneWeekAgo);
insertBookmarkItem(createUniqueUrl(), createUniqueTitle(), oneMonthAgo);
insertBookmarkItem(createUniqueUrl(), createUniqueTitle(), oneYearAgo);
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
@Test
public void testBlocklistItemsAreNotSelected() throws Exception {
final long oneDayAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
final String blockURL = createUniqueUrl();
insertBookmarkItem(blockURL, createUniqueTitle(), oneDayAgo);
Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(1, cursor.getCount());
cursor.close();
insertBlocklistItem(blockURL);
cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
@Test
public void testBlocklistItemsExpire() throws Exception {
final long oneDayAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
final String blockURL = createUniqueUrl();
final String blockTitle = createUniqueTitle();
insertBookmarkItem(blockURL, blockTitle, oneDayAgo);
insertBlocklistItem(blockURL);
{
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
// Add (2000 / 10) items in the loop -> 201 items total
int itemsNeeded = BrowserProvider.DEFAULT_EXPIRY_RETAIN_COUNT / BrowserProvider.ACTIVITYSTREAM_BLOCKLIST_EXPIRY_FACTOR;
for (int i = 0; i < itemsNeeded; i++) {
insertBlocklistItem(createUniqueUrl());
}
// We still have zero highlights: the item is still blocked
{
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(0, cursor.getCount());
cursor.close();
}
// expire the original blocked URL - only most recent 200 items are retained
historyClient.delete(expireHistoryNormalUri, null, null);
// And the original URL is now in highlights again (note: this shouldn't happen in real life,
// since the URL will no longer be eligible for highlights by the time we expire it)
{
final Cursor cursor = highlightsClient.query(highlightsTestUri, null, null, null, null);
Assert.assertNotNull(cursor);
Assert.assertEquals(1, cursor.getCount());
cursor.moveToFirst();
assertCursor(cursor, blockURL, blockTitle);
cursor.close();
}
}
private void insertBookmarkItem(String url, String title, long createdAt) throws RemoteException {
ContentValues values = new ContentValues();
values.put(BrowserContract.Bookmarks.URL, url);
values.put(BrowserContract.Bookmarks.TITLE, title);
values.put(BrowserContract.Bookmarks.PARENT, 0);
values.put(BrowserContract.Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_BOOKMARK);
values.put(BrowserContract.Bookmarks.DATE_CREATED, createdAt);
bookmarksClient.insert(bookmarksTestUri, values);
}
private void insertBlocklistItem(String url) throws RemoteException {
final ContentValues values = new ContentValues();
values.put(BrowserContract.ActivityStreamBlocklist.URL, url);
activityStreamBlocklistClient.insert(activityStreamBlocklistTestUri, values);
}
private void assertCursor(Cursor cursor, String url, String title) {
final String actualTitle = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
Assert.assertEquals(title, actualTitle);
final String actualUrl = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
Assert.assertEquals(url, actualUrl);
}
private void assertCursorContainsEntry(Cursor cursor, String url, String title) {
cursor.moveToFirst();
do {
final String actualTitle = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.TITLE));
final String actualUrl = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Combined.URL));
if (actualTitle.equals(title) && actualUrl.equals(url)) {
return;
}
} while (cursor.moveToNext());
Assert.fail("Could not find entry title=" + title + ", url=" + url);
}
private String createUniqueUrl() {
return new Uri.Builder()
.scheme("https")
.authority(UUID.randomUUID().toString() + ".example.org")
.appendPath(UUID.randomUUID().toString())
.appendPath(UUID.randomUUID().toString())
.build()
.toString();
}
private String createUniqueTitle() {
return "Title " + UUID.randomUUID().toString();
}
private String createGUID() {
return UUID.randomUUID().toString();
}
}

View file

@ -1,341 +0,0 @@
/* 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.db;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.shadows.ShadowContentResolver;
import static org.junit.Assert.*;
/**
* Testing functionality exposed by BrowserProvider ContentProvider (history, bookmarks, etc).
* This is WIP junit4 port of robocop tests at org.mozilla.gecko.tests.testBrowserProvider.
* See Bug 1269492
*/
@RunWith(TestRunner.class)
public class BrowserProviderHistoryTest extends BrowserProviderHistoryVisitsTestBase {
private ContentProviderClient thumbnailClient;
private Uri thumbnailTestUri;
private Uri expireHistoryNormalUri;
private Uri expireHistoryAggressiveUri;
private static final long THREE_MONTHS = 1000L * 60L * 60L * 24L * 30L * 3L;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
final ShadowContentResolver cr = new ShadowContentResolver();
thumbnailClient = cr.acquireContentProviderClient(BrowserContract.Thumbnails.CONTENT_URI);
thumbnailTestUri = testUri(BrowserContract.Thumbnails.CONTENT_URI);
expireHistoryNormalUri = testUri(BrowserContract.History.CONTENT_OLD_URI).buildUpon()
.appendQueryParameter(
BrowserContract.PARAM_EXPIRE_PRIORITY,
BrowserContract.ExpirePriority.NORMAL.toString()
).build();
expireHistoryAggressiveUri = testUri(BrowserContract.History.CONTENT_OLD_URI).buildUpon()
.appendQueryParameter(
BrowserContract.PARAM_EXPIRE_PRIORITY,
BrowserContract.ExpirePriority.AGGRESSIVE.toString()
).build();
}
@After
@Override
public void tearDown() {
thumbnailClient.release();
super.tearDown();
}
/**
* Test aggressive expiration on new (recent) history items
*/
@Test
public void testHistoryExpirationAggressiveNew() throws Exception {
final int historyItemsCount = 3000;
insertHistory(historyItemsCount, System.currentTimeMillis());
historyClient.delete(expireHistoryAggressiveUri, null, null);
/**
* Aggressive expiration should leave 500 history items
* See {@link BrowserProvider.AGGRESSIVE_EXPIRY_RETAIN_COUNT}
*/
assertRowCount(historyClient, historyTestUri, 500);
/**
* Aggressive expiration should leave 15 thumbnails
* See {@link BrowserProvider.DEFAULT_EXPIRY_THUMBNAIL_COUNT}
*/
assertRowCount(thumbnailClient, thumbnailTestUri, 15);
}
/**
* Test normal expiration on new (recent) history items
*/
@Test
public void testHistoryExpirationNormalNew() throws Exception {
final int historyItemsCount = 3000;
insertHistory(historyItemsCount, System.currentTimeMillis());
historyClient.delete(expireHistoryNormalUri, null, null);
// Normal expiration shouldn't expire new items
assertRowCount(historyClient, historyTestUri, 3000);
/**
* Normal expiration should leave 15 thumbnails
* See {@link BrowserProvider.DEFAULT_EXPIRY_THUMBNAIL_COUNT}
*/
assertRowCount(thumbnailClient, thumbnailTestUri, 15);
}
/**
* Test aggressive expiration on old history items
*/
@Test
public void testHistoryExpirationAggressiveOld() throws Exception {
final int historyItemsCount = 3000;
insertHistory(historyItemsCount, System.currentTimeMillis() - THREE_MONTHS);
historyClient.delete(expireHistoryAggressiveUri, null, null);
/**
* Aggressive expiration should leave 500 history items
* See {@link BrowserProvider.AGGRESSIVE_EXPIRY_RETAIN_COUNT}
*/
assertRowCount(historyClient, historyTestUri, 500);
/**
* Aggressive expiration should leave 15 thumbnails
* See {@link BrowserProvider.DEFAULT_EXPIRY_THUMBNAIL_COUNT}
*/
assertRowCount(thumbnailClient, thumbnailTestUri, 15);
}
/**
* Test normal expiration on old history items
*/
@Test
public void testHistoryExpirationNormalOld() throws Exception {
final int historyItemsCount = 3000;
insertHistory(historyItemsCount, System.currentTimeMillis() - THREE_MONTHS);
historyClient.delete(expireHistoryNormalUri, null, null);
/**
* Normal expiration of old items should retain at most 2000 items
* See {@link BrowserProvider.DEFAULT_EXPIRY_RETAIN_COUNT}
*/
assertRowCount(historyClient, historyTestUri, 2000);
/**
* Normal expiration should leave 15 thumbnails
* See {@link BrowserProvider.DEFAULT_EXPIRY_THUMBNAIL_COUNT}
*/
assertRowCount(thumbnailClient, thumbnailTestUri, 15);
}
/**
* Test that we update aggregates at the appropriate times. Local visit aggregates are only updated
* when updating history record with PARAM_INCREMENT_VISITS=true. Remote aggregate values are updated
* only if set directly. Aggregate values are not set when inserting a new history record via insertHistory.
* Local aggregate values are set when inserting a new history record via update.
* @throws Exception
*/
@Test
public void testHistoryVisitAggregates() throws Exception {
final long baseDate = System.currentTimeMillis();
final String url = "https://www.mozilla.org";
final Uri historyIncrementVisitsUri = historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build();
// Test default values
insertHistoryItem(url, null, baseDate, null);
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
0, 0, 0, 0, 0);
// Test setting visit count on new history item creation
final String url2 = "https://www.eff.org";
insertHistoryItem(url2, null, baseDate, 17);
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url2},
17, 0, 0, 0, 0);
// Test setting visit count on new history item creation via .update
final String url3 = "https://www.torproject.org";
final ContentValues cv = new ContentValues();
cv.put(BrowserContract.History.URL, url3);
cv.put(BrowserContract.History.VISITS, 13);
cv.put(BrowserContract.History.DATE_LAST_VISITED, baseDate);
historyClient.update(historyIncrementVisitsUri, cv, BrowserContract.History.URL + " = ?", new String[] {url3});
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url3},
13, 13, baseDate, 0, 0);
// Test that updating meta doesn't touch aggregates
cv.clear();
cv.put(BrowserContract.History.TITLE, "New title");
historyClient.update(historyTestUri, cv, BrowserContract.History.URL + " = ?", new String[] {url});
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
0, 0, 0, 0, 0);
// Test that incrementing visits without specifying visit count updates local aggregate values
final long lastVisited = System.currentTimeMillis();
cv.clear();
cv.put(BrowserContract.History.DATE_LAST_VISITED, lastVisited);
historyClient.update(historyIncrementVisitsUri,
cv, BrowserContract.History.URL + " = ?", new String[] {url});
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
1, 1, lastVisited, 0, 0);
// Test that incrementing visits by a specified visit count updates local aggregate values
// We don't support bumping visit count by more than 1. This doesn't make sense when we keep
// detailed information about our individual visits.
final long lastVisited2 = System.currentTimeMillis();
cv.clear();
cv.put(BrowserContract.History.DATE_LAST_VISITED, lastVisited2);
cv.put(BrowserContract.History.VISITS, 10);
historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
cv, BrowserContract.History.URL + " = ?", new String[] {url});
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
2, 2, lastVisited2, 0, 0);
// Test that we can directly update aggregate values
// NB: visits is unchanged (2)
final long lastVisited3 = System.currentTimeMillis();
cv.clear();
cv.put(BrowserContract.History.LOCAL_DATE_LAST_VISITED, lastVisited3);
cv.put(BrowserContract.History.LOCAL_VISITS, 19);
cv.put(BrowserContract.History.REMOTE_DATE_LAST_VISITED, lastVisited3 - 100);
cv.put(BrowserContract.History.REMOTE_VISITS, 3);
historyClient.update(historyTestUri, cv, BrowserContract.History.URL + " = ?", new String[] {url});
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
2, 19, lastVisited3, 3, lastVisited3 - 100);
// Test that we can set remote aggregate count to a specific value
cv.clear();
cv.put(BrowserContract.History.REMOTE_VISITS, 5);
historyClient.update(historyTestUri, cv, BrowserContract.History.URL + " = ?", new String[] {url});
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
2, 19, lastVisited3, 5, lastVisited3 - 100);
// Test that we can increment remote aggregate value by setting a query param in the URI
final Uri historyIncrementRemoteAggregateUri = historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_REMOTE_AGGREGATES, "true")
.build();
cv.clear();
cv.put(BrowserContract.History.REMOTE_DATE_LAST_VISITED, lastVisited3);
cv.put(BrowserContract.History.REMOTE_VISITS, 3);
historyClient.update(historyIncrementRemoteAggregateUri, cv, BrowserContract.History.URL + " = ?", new String[] {url});
// NB: remoteVisits=8. Previous value was 5, and we're incrementing by 3.
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
2, 19, lastVisited3, 8, lastVisited3);
// Test that we throw when trying to increment REMOTE_VISITS without passing in "increment by" value
cv.clear();
try {
historyClient.update(historyIncrementRemoteAggregateUri, cv, BrowserContract.History.URL + " = ?", new String[]{url});
assertTrue("Expected to throw IllegalArgumentException", false);
} catch (IllegalArgumentException e) {
assertTrue(true);
// NB: same values as above, to ensure throwing update didn't actually change anything.
assertHistoryAggregates(BrowserContract.History.URL + " = ?", new String[] {url},
2, 19, lastVisited3, 8, lastVisited3);
}
}
private void assertHistoryAggregates(String selection, String[] selectionArg, int visits, int localVisits, long localLastVisited, int remoteVisits, long remoteLastVisited) throws Exception {
final Cursor c = historyClient.query(historyTestUri, new String[] {
BrowserContract.History.VISITS,
BrowserContract.History.LOCAL_VISITS,
BrowserContract.History.REMOTE_VISITS,
BrowserContract.History.LOCAL_DATE_LAST_VISITED,
BrowserContract.History.REMOTE_DATE_LAST_VISITED
}, selection, selectionArg, null);
assertNotNull(c);
try {
assertTrue(c.moveToFirst());
final int visitsCol = c.getColumnIndexOrThrow(BrowserContract.History.VISITS);
final int localVisitsCol = c.getColumnIndexOrThrow(BrowserContract.History.LOCAL_VISITS);
final int remoteVisitsCol = c.getColumnIndexOrThrow(BrowserContract.History.REMOTE_VISITS);
final int localDateLastVisitedCol = c.getColumnIndexOrThrow(BrowserContract.History.LOCAL_DATE_LAST_VISITED);
final int remoteDateLastVisitedCol = c.getColumnIndexOrThrow(BrowserContract.History.REMOTE_DATE_LAST_VISITED);
assertEquals(visits, c.getInt(visitsCol));
assertEquals(localVisits, c.getInt(localVisitsCol));
assertEquals(localLastVisited, c.getLong(localDateLastVisitedCol));
assertEquals(remoteVisits, c.getInt(remoteVisitsCol));
assertEquals(remoteLastVisited, c.getLong(remoteDateLastVisitedCol));
} finally {
c.close();
}
}
/**
* Insert <code>count</code> history records with thumbnails, and for a third of records insert a visit.
* Inserting visits only for some of the history records is in order to ensure we're correctly JOIN-ing
* History and Visits tables in the Combined view.
* Will ensure that date_created and date_modified for new records are the same as last visited date.
*
* @param count number of history records to insert
* @param baseTime timestamp which will be used as a basis for last visited date
* @throws RemoteException
*/
private void insertHistory(int count, long baseTime) throws RemoteException {
Uri incrementUri = historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build();
for (int i = 0; i < count; i++) {
final String url = "https://www.mozilla" + i + ".org";
insertHistoryItem(url, "testGUID" + i, baseTime - i, null);
if (i % 3 == 0) {
assertEquals(1, historyClient.update(incrementUri, new ContentValues(), BrowserContract.History.URL + " = ?", new String[]{url}));
}
// inserting a new entry sets the date created and modified automatically, so let's reset them
ContentValues cv = new ContentValues();
cv.put(BrowserContract.History.DATE_CREATED, baseTime - i);
cv.put(BrowserContract.History.DATE_MODIFIED, baseTime - i);
assertEquals(1, historyClient.update(historyTestUri, cv, BrowserContract.History.URL + " = ?",
new String[] { "https://www.mozilla" + i + ".org" }));
}
// insert thumbnails for history items
ContentValues[] thumbs = new ContentValues[count];
for (int i = 0; i < count; i++) {
thumbs[i] = new ContentValues();
thumbs[i].put(BrowserContract.Thumbnails.DATA, i);
thumbs[i].put(BrowserContract.Thumbnails.URL, "https://www.mozilla" + i + ".org");
}
assertEquals(count, thumbnailClient.bulkInsert(thumbnailTestUri, thumbs));
}
private void assertRowCount(final ContentProviderClient client, final Uri uri, final int count) throws RemoteException {
final Cursor c = client.query(uri, null, null, null, null);
assertNotNull(c);
try {
assertEquals(count, c.getCount());
} finally {
c.close();
}
}
}

View file

@ -1,338 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.db;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract.History;
import static org.junit.Assert.*;
@RunWith(TestRunner.class)
/**
* Testing insertion/deletion of visits as by-product of updating history records through BrowserProvider
*/
public class BrowserProviderHistoryVisitsTest extends BrowserProviderHistoryVisitsTestBase {
@Test
/**
* Testing updating history records without affecting visits
*/
public void testUpdateNoVisit() throws Exception {
insertHistoryItem("https://www.mozilla.org", "testGUID");
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
ContentValues historyUpdate = new ContentValues();
historyUpdate.put(History.TITLE, "Mozilla!");
assertEquals(1,
historyClient.update(
historyTestUri, historyUpdate, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
)
);
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
ContentValues historyToInsert = new ContentValues();
historyToInsert.put(History.URL, "https://www.eff.org");
assertEquals(1,
historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
historyToInsert, null, null
)
);
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
}
@Test
/**
* Testing INCREMENT_VISITS flag for multiple history records at once
*/
public void testUpdateMultipleHistoryIncrementVisit() throws Exception {
insertHistoryItem("https://www.mozilla.org", "testGUID");
insertHistoryItem("https://www.mozilla.org", "testGUID2");
// test that visits get inserted when updating existing history records
assertEquals(2, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
Cursor cursor = visitsClient.query(
visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
assertNotNull(cursor);
assertEquals(2, cursor.getCount());
assertTrue(cursor.moveToFirst());
String guid1 = cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID));
cursor.moveToNext();
String guid2 = cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID));
cursor.close();
assertNotEquals(guid1, guid2);
assertTrue(guid1.equals("testGUID") || guid1.equals("testGUID2"));
}
@Test
/**
* Testing INCREMENT_VISITS flag and its interplay with INSERT_IF_NEEDED
*/
public void testUpdateHistoryIncrementVisit() throws Exception {
insertHistoryItem("https://www.mozilla.org", "testGUID");
// test that visit gets inserted when updating an existing histor record
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
Cursor cursor = visitsClient.query(
visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals(
"testGUID",
cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID))
);
cursor.close();
// test that visit gets inserted when updatingOrInserting a new history record
ContentValues historyItem = new ContentValues();
historyItem.put(History.URL, "https://www.eff.org");
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
historyItem, null, null
));
cursor = historyClient.query(
historyTestUri,
new String[] {History.GUID}, History.URL + " = ?", new String[] {"https://www.eff.org"}, null
);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
String insertedGUID = cursor.getString(cursor.getColumnIndex(History.GUID));
cursor.close();
cursor = visitsClient.query(
visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
assertNotNull(cursor);
assertEquals(2, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals(insertedGUID,
cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID))
);
cursor.close();
}
@Test
/**
* Test that for locally generated visits, we store their timestamps in microseconds, and not in
* milliseconds like history does.
*/
public void testTimestampConversionOnInsertion() throws Exception {
insertHistoryItem("https://www.mozilla.org", "testGUID");
Long lastVisited = System.currentTimeMillis();
ContentValues updatedVisitedTime = new ContentValues();
updatedVisitedTime.put(History.DATE_LAST_VISITED, lastVisited);
// test with last visited date passed in
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
updatedVisitedTime, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
Cursor cursor = visitsClient.query(visitsTestUri, new String[] {BrowserContract.Visits.DATE_VISITED}, null, null, null);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals(lastVisited * 1000, cursor.getLong(cursor.getColumnIndex(BrowserContract.Visits.DATE_VISITED)));
cursor.close();
// test without last visited date
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
cursor = visitsClient.query(visitsTestUri, new String[] {BrowserContract.Visits.DATE_VISITED}, null, null, null);
assertNotNull(cursor);
assertEquals(2, cursor.getCount());
assertTrue(cursor.moveToFirst());
// CP should generate time off of current time upon insertion and convert to microseconds.
// This also tests correct ordering (DESC on date).
assertTrue(lastVisited * 1000 < cursor.getLong(cursor.getColumnIndex(BrowserContract.Visits.DATE_VISITED)));
cursor.close();
}
@Test
/**
* This should perform `DELETE FROM visits WHERE history_guid in IN (?, ?, ?, ..., ?)` sort of statement
* SQLite has a variable count limit (999 by default), so we're testing here that our deletion
* code does the right thing and chunks deletes to account for this limitation.
*/
public void testDeletingLotsOfHistory() throws Exception {
Uri incrementUri = historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build();
// insert bunch of history records, and for each insert a visit
for (int i = 0; i < 2100; i++) {
final String url = "https://www.mozilla" + i + ".org";
insertHistoryItem(url, "testGUID" + i);
assertEquals(1, historyClient.update(incrementUri, new ContentValues(), History.URL + " = ?", new String[] {url}));
}
// sanity check
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(2100, cursor.getCount());
cursor.close();
// delete all of the history items - this will trigger chunked deletion of visits as well
assertEquals(2100,
historyClient.delete(historyTestUri, null, null)
);
// check that all visits where deleted
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
}
@Test
/**
* Test visit deletion as by-product of history deletion - both explicit (from outside of Sync),
* and implicit (cascaded, from Sync).
*/
public void testDeletingHistory() throws Exception {
insertHistoryItem("https://www.mozilla.org", "testGUID");
insertHistoryItem("https://www.eff.org", "testGUID2");
// insert some visits
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.eff.org"}
));
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(3, cursor.getCount());
cursor.close();
// test that corresponding visit records are deleted if Sync isn't involved
assertEquals(1,
historyClient.delete(historyTestUri, History.URL + " = ?", new String[] {"https://www.mozilla.org"})
);
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
cursor.close();
// test that corresponding visit records are deleted if Sync is involved
// insert some more visits
ContentValues moz = new ContentValues();
moz.put(History.URL, "https://www.mozilla.org");
moz.put(History.GUID, "testGUID3");
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
moz, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true")
.appendQueryParameter(BrowserContract.PARAM_INSERT_IF_NEEDED, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.eff.org"}
));
assertEquals(1,
historyClient.delete(
historyTestUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_IS_SYNC, "true").build(),
History.URL + " = ?", new String[] {"https://www.eff.org"})
);
cursor = visitsClient.query(visitsTestUri, new String[] {BrowserContract.Visits.HISTORY_GUID}, null, null, null);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals("testGUID3", cursor.getString(cursor.getColumnIndex(BrowserContract.Visits.HISTORY_GUID)));
cursor.close();
}
@Test
/**
* Test that changes to History GUID are cascaded to individual visits.
* See UPDATE CASCADED on Visit's HISTORY_GUID foreign key.
*/
public void testHistoryGUIDUpdate() throws Exception {
insertHistoryItem("https://www.mozilla.org", "testGUID");
insertHistoryItem("https://www.eff.org", "testGUID2");
// insert some visits
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
assertEquals(1, historyClient.update(
historyTestUri.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_INCREMENT_VISITS, "true").build(),
new ContentValues(), History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
// change testGUID -> testGUIDNew
ContentValues newGuid = new ContentValues();
newGuid.put(History.GUID, "testGUIDNew");
assertEquals(1, historyClient.update(
historyTestUri, newGuid, History.URL + " = ?", new String[] {"https://www.mozilla.org"}
));
Cursor cursor = visitsClient.query(visitsTestUri, null, BrowserContract.Visits.HISTORY_GUID + " = ?", new String[] {"testGUIDNew"}, null);
assertNotNull(cursor);
assertEquals(2, cursor.getCount());
cursor.close();
}
}

View file

@ -1,77 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.db;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.net.Uri;
import android.os.RemoteException;
import org.junit.After;
import org.junit.Before;
import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.UUID;
public class BrowserProviderHistoryVisitsTestBase {
/* package-private */ ShadowContentResolver contentResolver;
/* package-private */ ContentProviderClient historyClient;
/* package-private */ ContentProviderClient visitsClient;
/* package-private */ Uri historyTestUri;
/* package-private */ Uri visitsTestUri;
private BrowserProvider provider;
@Before
public void setUp() throws Exception {
provider = new BrowserProvider();
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
contentResolver = new ShadowContentResolver();
historyClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.HISTORY_CONTENT_URI);
visitsClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
historyTestUri = testUri(BrowserContract.History.CONTENT_URI);
visitsTestUri = testUri(BrowserContract.Visits.CONTENT_URI);
}
@After
public void tearDown() {
historyClient.release();
visitsClient.release();
provider.shutdown();
}
/* package-private */ Uri testUri(Uri baseUri) {
return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_IS_TEST, "1").build();
}
/* package-private */ Uri insertHistoryItem(String url, String guid) throws RemoteException {
return insertHistoryItem(url, guid, System.currentTimeMillis(), null, null);
}
/* package-private */ Uri insertHistoryItem(String url, String guid, Long lastVisited, Integer visitCount) throws RemoteException {
return insertHistoryItem(url, guid, lastVisited, visitCount, null);
}
/* package-private */ Uri insertHistoryItem(String url, String guid, Long lastVisited, Integer visitCount, String title) throws RemoteException {
ContentValues historyItem = new ContentValues();
historyItem.put(BrowserContract.History.URL, url);
if (guid != null) {
historyItem.put(BrowserContract.History.GUID, guid);
}
if (visitCount != null) {
historyItem.put(BrowserContract.History.VISITS, visitCount);
}
historyItem.put(BrowserContract.History.DATE_LAST_VISITED, lastVisited);
if (title != null) {
historyItem.put(BrowserContract.History.TITLE, title);
}
return historyClient.insert(historyTestUri, historyItem);
}
}

View file

@ -1,301 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.db;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract.Visits;
@RunWith(TestRunner.class)
/**
* Testing direct interactions with visits through BrowserProvider
*/
public class BrowserProviderVisitsTest extends BrowserProviderHistoryVisitsTestBase {
@Test
/**
* Test that default visit parameters are set on insert.
*/
public void testDefaultVisit() throws RemoteException {
String url = "https://www.mozilla.org";
String guid = "testGuid";
assertNotNull(insertHistoryItem(url, guid));
ContentValues visitItem = new ContentValues();
Long visitedDate = System.currentTimeMillis();
visitItem.put(Visits.HISTORY_GUID, guid);
visitItem.put(Visits.DATE_VISITED, visitedDate);
Uri insertedVisitUri = visitsClient.insert(visitsTestUri, visitItem);
assertNotNull(insertedVisitUri);
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
try {
assertTrue(cursor.moveToFirst());
String insertedGuid = cursor.getString(cursor.getColumnIndex(Visits.HISTORY_GUID));
assertEquals(guid, insertedGuid);
Long insertedDate = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(visitedDate, insertedDate);
Integer insertedType = cursor.getInt(cursor.getColumnIndex(Visits.VISIT_TYPE));
assertEquals(insertedType, Integer.valueOf(1));
Integer insertedIsLocal = cursor.getInt(cursor.getColumnIndex(Visits.IS_LOCAL));
assertEquals(insertedIsLocal, Integer.valueOf(1));
} finally {
cursor.close();
}
}
@Test
/**
* Test that we can't insert visit for non-existing GUID.
*/
public void testMissingHistoryGuid() throws RemoteException {
ContentValues visitItem = new ContentValues();
visitItem.put(Visits.HISTORY_GUID, "blah");
visitItem.put(Visits.DATE_VISITED, System.currentTimeMillis());
assertNull(visitsClient.insert(visitsTestUri, visitItem));
}
@Test
/**
* Test that visit insert uses non-conflict insert.
*/
public void testNonConflictInsert() throws RemoteException {
String url = "https://www.mozilla.org";
String guid = "testGuid";
assertNotNull(insertHistoryItem(url, guid));
ContentValues visitItem = new ContentValues();
Long visitedDate = System.currentTimeMillis();
visitItem.put(Visits.HISTORY_GUID, guid);
visitItem.put(Visits.DATE_VISITED, visitedDate);
Uri insertedVisitUri = visitsClient.insert(visitsTestUri, visitItem);
assertNotNull(insertedVisitUri);
Uri insertedVisitUri2 = visitsClient.insert(visitsTestUri, visitItem);
assertEquals(insertedVisitUri, insertedVisitUri2);
}
@Test
/**
* Test that non-default visit parameters won't get overridden.
*/
public void testNonDefaultInsert() throws RemoteException {
assertNotNull(insertHistoryItem("https://www.mozilla.org", "testGuid"));
Integer typeToInsert = 5;
Integer isLocalToInsert = 0;
ContentValues visitItem = new ContentValues();
visitItem.put(Visits.HISTORY_GUID, "testGuid");
visitItem.put(Visits.DATE_VISITED, System.currentTimeMillis());
visitItem.put(Visits.VISIT_TYPE, typeToInsert);
visitItem.put(Visits.IS_LOCAL, isLocalToInsert);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
try {
assertTrue(cursor.moveToFirst());
Integer insertedVisitType = cursor.getInt(cursor.getColumnIndex(Visits.VISIT_TYPE));
assertEquals(typeToInsert, insertedVisitType);
Integer insertedIsLocal = cursor.getInt(cursor.getColumnIndex(Visits.IS_LOCAL));
assertEquals(isLocalToInsert, insertedIsLocal);
} finally {
cursor.close();
}
}
@Test
/**
* Test that default sorting order (DATE_VISITED DESC) is set if we don't specify any sorting params
*/
public void testDefaultSortingOrder() throws RemoteException {
assertNotNull(insertHistoryItem("https://www.mozilla.org", "testGuid"));
Long time1 = System.currentTimeMillis();
Long time2 = time1 + 100;
Long time3 = time1 + 200;
ContentValues visitItem = new ContentValues();
visitItem.put(Visits.DATE_VISITED, time1);
visitItem.put(Visits.HISTORY_GUID, "testGuid");
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem.put(Visits.DATE_VISITED, time3);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem.put(Visits.DATE_VISITED, time2);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
try {
assertEquals(3, cursor.getCount());
assertTrue(cursor.moveToFirst());
Long timeInserted = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(time3, timeInserted);
cursor.moveToNext();
timeInserted = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(time2, timeInserted);
cursor.moveToNext();
timeInserted = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(time1, timeInserted);
} finally {
cursor.close();
}
}
@Test
/**
* Test that if we pass sorting params, they're not overridden
*/
public void testNonDefaultSortingOrder() throws RemoteException {
assertNotNull(insertHistoryItem("https://www.mozilla.org", "testGuid"));
Long time1 = System.currentTimeMillis();
Long time2 = time1 + 100;
Long time3 = time1 + 200;
ContentValues visitItem = new ContentValues();
visitItem.put(Visits.DATE_VISITED, time1);
visitItem.put(Visits.HISTORY_GUID, "testGuid");
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem.put(Visits.DATE_VISITED, time3);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem.put(Visits.DATE_VISITED, time2);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, Visits.DATE_VISITED + " ASC");
assertNotNull(cursor);
assertEquals(3, cursor.getCount());
assertTrue(cursor.moveToFirst());
Long timeInserted = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(time1, timeInserted);
cursor.moveToNext();
timeInserted = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(time2, timeInserted);
cursor.moveToNext();
timeInserted = cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED));
assertEquals(time3, timeInserted);
cursor.close();
}
@Test
/**
* Tests deletion of all visits, and by some selection (GUID, IS_LOCAL)
*/
public void testVisitDeletion() throws RemoteException {
assertNotNull(insertHistoryItem("https://www.mozilla.org", "testGuid"));
assertNotNull(insertHistoryItem("https://www.eff.org", "testGuid2"));
Long time1 = System.currentTimeMillis();
ContentValues visitItem = new ContentValues();
visitItem.put(Visits.DATE_VISITED, time1);
visitItem.put(Visits.HISTORY_GUID, "testGuid");
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem = new ContentValues();
visitItem.put(Visits.DATE_VISITED, time1 + 100);
visitItem.put(Visits.HISTORY_GUID, "testGuid");
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
ContentValues visitItem2 = new ContentValues();
visitItem2.put(Visits.DATE_VISITED, time1);
visitItem2.put(Visits.HISTORY_GUID, "testGuid2");
assertNotNull(visitsClient.insert(visitsTestUri, visitItem2));
Cursor cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(3, cursor.getCount());
cursor.close();
assertEquals(3, visitsClient.delete(visitsTestUri, null, null));
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(0, cursor.getCount());
cursor.close();
// test selective deletion - by IS_LOCAL
visitItem = new ContentValues();
visitItem.put(Visits.DATE_VISITED, time1);
visitItem.put(Visits.HISTORY_GUID, "testGuid");
visitItem.put(Visits.IS_LOCAL, 0);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem = new ContentValues();
visitItem.put(Visits.DATE_VISITED, time1 + 100);
visitItem.put(Visits.HISTORY_GUID, "testGuid");
visitItem.put(Visits.IS_LOCAL, 1);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem));
visitItem2 = new ContentValues();
visitItem2.put(Visits.DATE_VISITED, time1);
visitItem2.put(Visits.HISTORY_GUID, "testGuid2");
visitItem2.put(Visits.IS_LOCAL, 0);
assertNotNull(visitsClient.insert(visitsTestUri, visitItem2));
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(3, cursor.getCount());
cursor.close();
assertEquals(2,
visitsClient.delete(visitsTestUri, Visits.IS_LOCAL + " = ?", new String[]{"0"}));
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals(time1 + 100, cursor.getLong(cursor.getColumnIndex(Visits.DATE_VISITED)));
assertEquals("testGuid", cursor.getString(cursor.getColumnIndex(Visits.HISTORY_GUID)));
assertEquals(1, cursor.getInt(cursor.getColumnIndex(Visits.IS_LOCAL)));
cursor.close();
// test selective deletion - by HISTORY_GUID
assertNotNull(visitsClient.insert(visitsTestUri, visitItem2));
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(2, cursor.getCount());
cursor.close();
assertEquals(1,
visitsClient.delete(visitsTestUri, Visits.HISTORY_GUID + " = ?", new String[]{"testGuid"}));
cursor = visitsClient.query(visitsTestUri, null, null, null, null);
assertNotNull(cursor);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToFirst());
assertEquals("testGuid2", cursor.getString(cursor.getColumnIndex(Visits.HISTORY_GUID)));
cursor.close();
}
}

View file

@ -1,33 +0,0 @@
/* 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.distribution;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
@RunWith(TestRunner.class)
public class TestReferrerDescriptor {
@Test
public void testReferrerDescriptor() {
String referrerString1 = "utm_source%3Dsource%26utm_content%3Dcontent%26utm_campaign%3Dcampaign%26utm_medium%3Dmedium%26utm_term%3Dterm";
String referrerString2 = "utm_source=source&utm_content=content&utm_campaign=campaign&utm_medium=medium&utm_term=term";
ReferrerDescriptor referrer1 = new ReferrerDescriptor(referrerString1);
Assert.assertNotNull(referrer1);
Assert.assertEquals(referrer1.source, "source");
Assert.assertEquals(referrer1.content, "content");
Assert.assertEquals(referrer1.campaign, "campaign");
Assert.assertEquals(referrer1.medium, "medium");
Assert.assertEquals(referrer1.term, "term");
ReferrerDescriptor referrer2 = new ReferrerDescriptor(referrerString2);
Assert.assertNotNull(referrer2);
Assert.assertEquals(referrer2.source, "source");
Assert.assertEquals(referrer2.content, "content");
Assert.assertEquals(referrer2.campaign, "campaign");
Assert.assertEquals(referrer2.medium, "medium");
Assert.assertEquals(referrer2.term, "term");
}
}

View file

@ -1,607 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.dlc;
import android.content.Context;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.dlc.catalog.DownloadContent;
import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
import org.robolectric.RuntimeEnvironment;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.Collections;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
/**
* DownloadAction: Download content that has been scheduled during "study" or "verify".
*/
@RunWith(TestRunner.class)
public class TestDownloadAction {
private static final String TEST_URL = "http://example.org";
private static final int STATUS_OK = 200;
private static final int STATUS_PARTIAL_CONTENT = 206;
/**
* Scenario: The current network is metered.
*
* Verify that:
* * No download is performed on a metered network
*/
@Test
public void testNothingIsDoneOnMeteredNetwork() throws Exception {
DownloadAction action = spy(new DownloadAction(null));
doReturn(true).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
action.perform(RuntimeEnvironment.application, null);
verify(action, never()).buildHttpURLConnection(anyString());
verify(action, never()).download(anyString(), any(File.class));
}
/**
* Scenario: No (connected) network is available.
*
* Verify that:
* * No download is performed
*/
@Test
public void testNothingIsDoneIfNoNetworkIsAvailable() throws Exception {
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
action.perform(RuntimeEnvironment.application, null);
verify(action, never()).isActiveNetworkMetered(any(Context.class));
verify(action, never()).buildHttpURLConnection(anyString());
verify(action, never()).download(anyString(), any(File.class));
}
/**
* Scenario: Content is scheduled for download but already exists locally (with correct checksum).
*
* Verify that:
* * No download is performed for existing file
* * Content is marked as downloaded in the catalog
*/
@Test
public void testExistingAndVerifiedFilesAreNotDownloadedAgain() throws Exception {
DownloadContent content = new DownloadContentBuilder().build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
File file = mock(File.class);
doReturn(true).when(file).exists();
doReturn(file).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).verify(eq(file), anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(action, never()).download(anyString(), any(File.class));
verify(catalog).markAsDownloaded(content);
}
/**
* Scenario: Server returns a server error (HTTP 500).
*
* Verify that:
* * Situation is treated as recoverable (RecoverableDownloadContentException)
*/
@Test(expected=BaseAction.RecoverableDownloadContentException.class)
public void testServerErrorsAreRecoverable() throws Exception {
HttpURLConnection connection = mockHttpURLConnection(500, "");
File temporaryFile = mock(File.class);
doReturn(false).when(temporaryFile).exists();
DownloadAction action = spy(new DownloadAction(null));
doReturn(connection).when(action).buildHttpURLConnection(anyString());
action.download(TEST_URL, temporaryFile);
verify(connection).getInputStream();
}
/**
* Scenario: Server returns a client error (HTTP 404).
*
* Verify that:
* * Situation is treated as unrecoverable (UnrecoverableDownloadContentException)
*/
@Test(expected=BaseAction.UnrecoverableDownloadContentException.class)
public void testClientErrorsAreUnrecoverable() throws Exception {
HttpURLConnection connection = mockHttpURLConnection(404, "");
File temporaryFile = mock(File.class);
doReturn(false).when(temporaryFile).exists();
DownloadAction action = spy(new DownloadAction(null));
doReturn(connection).when(action).buildHttpURLConnection(anyString());
action.download(TEST_URL, temporaryFile);
verify(connection).getInputStream();
}
/**
* Scenario: A successful download has been performed.
*
* Verify that:
* * The content will be extracted to the destination
* * The content is marked as downloaded in the catalog
*/
@Test
public void testSuccessfulDownloadsAreMarkedAsDownloaded() throws Exception {
DownloadContent content = new DownloadContentBuilder()
.setKind(DownloadContent.KIND_FONT)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
File file = mockNotExistingFile();
doReturn(file).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(false).when(action).verify(eq(file), anyString());
doNothing().when(action).download(anyString(), eq(file));
doReturn(true).when(action).verify(eq(file), anyString());
doNothing().when(action).extract(eq(file), eq(file), anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(action).download(anyString(), eq(file));
verify(action).extract(eq(file), eq(file), anyString());
verify(catalog).markAsDownloaded(content);
}
/**
* Scenario: Pretend a partially downloaded file already exists.
*
* Verify that:
* * Range header is set in request
* * Content will be appended to existing file
* * Content will be marked as downloaded in catalog
*/
@Test
public void testResumingDownloadFromExistingFile() throws Exception {
DownloadContent content = new DownloadContentBuilder()
.setKind(DownloadContent.KIND_FONT)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setSize(4223)
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
File temporaryFile = mockFileWithSize(1337L);
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
doReturn(outputStream).when(action).openFile(eq(temporaryFile), anyBoolean());
HttpURLConnection connection = mockHttpURLConnection(STATUS_PARTIAL_CONTENT, "HelloWorld");
doReturn(connection).when(action).buildHttpURLConnection(anyString());
File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).verify(eq(temporaryFile), anyString());
doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(connection).getInputStream();
verify(connection).setRequestProperty("Range", "bytes=1337-");
Assert.assertEquals("HelloWorld", new String(outputStream.toByteArray(), "UTF-8"));
verify(action).openFile(eq(temporaryFile), eq(true));
verify(catalog).markAsDownloaded(content);
verify(temporaryFile).delete();
}
/**
* Scenario: Download fails with IOException.
*
* Verify that:
* * Partially downloaded file will not be deleted
* * Content will not be marked as downloaded in catalog
*/
@Test
public void testTemporaryFileIsNotDeletedAfterDownloadAborted() throws Exception {
DownloadContent content = new DownloadContentBuilder()
.setKind(DownloadContent.KIND_FONT)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setSize(4223)
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
File temporaryFile = mockFileWithSize(1337L);
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
ByteArrayOutputStream outputStream = spy(new ByteArrayOutputStream());
doReturn(outputStream).when(action).openFile(eq(temporaryFile), anyBoolean());
doThrow(IOException.class).when(outputStream).write(any(byte[].class), anyInt(), anyInt());
HttpURLConnection connection = mockHttpURLConnection(STATUS_PARTIAL_CONTENT, "HelloWorld");
doReturn(connection).when(action).buildHttpURLConnection(anyString());
doReturn(mockNotExistingFile()).when(action).getDestinationFile(RuntimeEnvironment.application, content);
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog, never()).markAsDownloaded(content);
verify(action, never()).verify(any(File.class), anyString());
verify(temporaryFile, never()).delete();
}
/**
* Scenario: Partially downloaded file is already complete.
*
* Verify that:
* * No download request is made
* * File is treated as completed and will be verified and extracted
* * Content is marked as downloaded in catalog
*/
@Test
public void testNoRequestIsSentIfFileIsAlreadyComplete() throws Exception {
DownloadContent content = new DownloadContentBuilder()
.setKind(DownloadContent.KIND_FONT)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setSize(1337L)
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
File temporaryFile = mockFileWithSize(1337L);
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).verify(eq(temporaryFile), anyString());
doNothing().when(action).extract(eq(temporaryFile), eq(destinationFile), anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(action, never()).download(anyString(), eq(temporaryFile));
verify(action).verify(eq(temporaryFile), anyString());
verify(action).extract(eq(temporaryFile), eq(destinationFile), anyString());
verify(catalog).markAsDownloaded(content);
}
/**
* Scenario: Download is completed but verification (checksum) failed.
*
* Verify that:
* * Downloaded file is deleted
* * File will not be extracted
* * Content is not marked as downloaded in the catalog
*/
@Test
public void testTemporaryFileWillBeDeletedIfVerificationFails() throws Exception {
DownloadContent content = new DownloadContentBuilder()
.setKind(DownloadContent.KIND_FONT)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setSize(1337L)
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Collections.singletonList(content)).when(catalog).getScheduledDownloads();
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
doNothing().when(action).download(anyString(), any(File.class));
doReturn(false).when(action).verify(any(File.class), anyString());
File temporaryFile = mockNotExistingFile();
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
action.perform(RuntimeEnvironment.application, catalog);
verify(temporaryFile).delete();
verify(action, never()).extract(any(File.class), any(File.class), anyString());
verify(catalog, never()).markAsDownloaded(content);
}
/**
* Scenario: Not enough storage space for content is available.
*
* Verify that:
* * No download will per performed
*/
@Test
public void testNoDownloadIsPerformedIfNotEnoughStorageIsAvailable() throws Exception {
DownloadContent content = createFontWithSize(1337L);
DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content);
DownloadAction action = spy(new DownloadAction(null));
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
File temporaryFile = mockNotExistingFile();
doReturn(temporaryFile).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
File destinationFile = mockNotExistingFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).hasEnoughDiskSpace(content, destinationFile, temporaryFile);
verify(action, never()).buildHttpURLConnection(anyString());
verify(action, never()).download(anyString(), any(File.class));
verify(action, never()).verify(any(File.class), anyString());
verify(catalog, never()).markAsDownloaded(content);
}
/**
* Scenario: Not enough storage space for temporary file available.
*
* Verify that:
* * hasEnoughDiskSpace() returns false
*/
@Test
public void testWithNotEnoughSpaceForTemporaryFile() throws Exception{
DownloadContent content = createFontWithSize(2048);
File destinationFile = mockNotExistingFile();
File temporaryFile = mockNotExistingFileWithUsableSpace(1024);
DownloadAction action = new DownloadAction(null);
Assert.assertFalse(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
}
/**
* Scenario: Not enough storage space for destination file available.
*
* Verify that:
* * hasEnoughDiskSpace() returns false
*/
@Test
public void testWithNotEnoughSpaceForDestinationFile() throws Exception {
DownloadContent content = createFontWithSize(2048);
File destinationFile = mockNotExistingFileWithUsableSpace(1024);
File temporaryFile = mockNotExistingFile();
DownloadAction action = new DownloadAction(null);
Assert.assertFalse(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
}
/**
* Scenario: Enough storage space for temporary and destination file available.
*
* Verify that:
* * hasEnoughDiskSpace() returns true
*/
@Test
public void testWithEnoughSpaceForEverything() throws Exception {
DownloadContent content = createFontWithSize(2048);
File destinationFile = mockNotExistingFileWithUsableSpace(4096);
File temporaryFile = mockNotExistingFileWithUsableSpace(4096);
DownloadAction action = new DownloadAction(null);
Assert.assertTrue(action.hasEnoughDiskSpace(content, destinationFile, temporaryFile));
}
/**
* Scenario: Download failed with network I/O error.
*
* Verify that:
* * Error is not counted as failure
*/
@Test
public void testNetworkErrorIsNotCountedAsFailure() throws Exception {
DownloadContent content = createFont();
DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content);
DownloadAction action = spy(new DownloadAction(null));
doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
doReturn(mockNotExistingFile()).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
doReturn(mockNotExistingFile()).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).hasEnoughDiskSpace(eq(content), any(File.class), any(File.class));
HttpURLConnection connection = mockHttpURLConnection(STATUS_OK, "");
doThrow(IOException.class).when(connection).getInputStream();
doReturn(connection).when(action).buildHttpURLConnection(anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog, never()).rememberFailure(eq(content), anyInt());
verify(catalog, never()).markAsDownloaded(content);
}
/**
* Scenario: Disk IO Error when extracting file.
*
* Verify that:
* * Error is counted as failure
* * After multiple errors the content is marked as permanently failed
*/
@Test
public void testDiskIOErrorIsCountedAsFailure() throws Exception {
DownloadContent content = createFont();
DownloadContentCatalog catalog = mockCatalogWithScheduledDownloads(content);
doCallRealMethod().when(catalog).rememberFailure(eq(content), anyInt());
doCallRealMethod().when(catalog).markAsPermanentlyFailed(content);
Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
DownloadAction action = spy(new DownloadAction(null));
doReturn(true).when(action).isConnectedToNetwork(RuntimeEnvironment.application);
doReturn(false).when(action).isActiveNetworkMetered(RuntimeEnvironment.application);
doReturn(mockNotExistingFile()).when(action).createTemporaryFile(RuntimeEnvironment.application, content);
doReturn(mockNotExistingFile()).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).hasEnoughDiskSpace(eq(content), any(File.class), any(File.class));
doNothing().when(action).download(anyString(), any(File.class));
doReturn(true).when(action).verify(any(File.class), anyString());
File destinationFile = mock(File.class);
doReturn(false).when(destinationFile).exists();
File parentFile = mock(File.class);
doReturn(false).when(parentFile).mkdirs();
doReturn(false).when(parentFile).exists();
doReturn(parentFile).when(destinationFile).getParentFile();
doReturn(destinationFile).when(action).getDestinationFile(RuntimeEnvironment.application, content);
for (int i = 0; i < 10; i++) {
action.perform(RuntimeEnvironment.application, catalog);
Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
}
action.perform(RuntimeEnvironment.application, catalog);
Assert.assertEquals(DownloadContent.STATE_FAILED, content.getState());
verify(catalog, times(11)).rememberFailure(eq(content), anyInt());
}
/**
* Scenario: If the file to be downloaded is of kind - "hyphenation"
*
* Verify that:
* * isHyphenationDictionary returns true for a download content with kind "hyphenation"
* * isHyphenationDictionary returns false for a download content with unknown/different kind like "Font"
*/
@Test
public void testIsHyphenationDictionary() throws Exception {
DownloadContent hyphenationContent = createHyphenationDictionary();
Assert.assertTrue(hyphenationContent.isHyphenationDictionary());
DownloadContent fontContent = createFont();
Assert.assertFalse(fontContent.isHyphenationDictionary());
DownloadContent unknownContent = createUnknownContent(1024L);
Assert.assertFalse(unknownContent.isHyphenationDictionary());
}
/**
* Scenario: If the content to be downloaded is known
*
* Verify that:
* * isKnownContent returns true for a downloadable content with a known kind and type.
* * isKnownContent returns false for a downloadable content with unknown kind and type.
*/
@Test
public void testIsKnownContent() throws Exception {
DownloadContent fontContent = createFontWithSize(1024L);
DownloadContent hyphenationContent = createHyphenationDictionaryWithSize(1024L);
DownloadContent unknownContent = createUnknownContent(1024L);
DownloadContent contentWithUnknownType = createContentWithoutType(1024L);
Assert.assertTrue(fontContent.isKnownContent());
Assert.assertTrue(hyphenationContent.isKnownContent());
Assert.assertFalse(unknownContent.isKnownContent());
Assert.assertFalse(contentWithUnknownType.isKnownContent());
}
private DownloadContent createUnknownContent(long size) {
return new DownloadContentBuilder()
.setSize(size)
.build();
}
private DownloadContent createContentWithoutType(long size) {
return new DownloadContentBuilder()
.setKind(DownloadContent.KIND_HYPHENATION_DICTIONARY)
.setSize(size)
.build();
}
private DownloadContent createFont() {
return createFontWithSize(102400L);
}
private DownloadContent createFontWithSize(long size) {
return new DownloadContentBuilder()
.setKind(DownloadContent.KIND_FONT)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setSize(size)
.build();
}
private DownloadContent createHyphenationDictionary() {
return createHyphenationDictionaryWithSize(102400L);
}
private DownloadContent createHyphenationDictionaryWithSize(long size) {
return new DownloadContentBuilder()
.setKind(DownloadContent.KIND_HYPHENATION_DICTIONARY)
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setSize(size)
.build();
}
private DownloadContentCatalog mockCatalogWithScheduledDownloads(DownloadContent... content) {
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
doReturn(Arrays.asList(content)).when(catalog).getScheduledDownloads();
return catalog;
}
private static File mockNotExistingFile() {
return mockFileWithUsableSpace(false, 0, Long.MAX_VALUE);
}
private static File mockNotExistingFileWithUsableSpace(long usableSpace) {
return mockFileWithUsableSpace(false, 0, usableSpace);
}
private static File mockFileWithSize(long length) {
return mockFileWithUsableSpace(true, length, Long.MAX_VALUE);
}
private static File mockFileWithUsableSpace(boolean exists, long length, long usableSpace) {
File file = mock(File.class);
doReturn(exists).when(file).exists();
doReturn(length).when(file).length();
File parentFile = mock(File.class);
doReturn(usableSpace).when(parentFile).getUsableSpace();
doReturn(parentFile).when(file).getParentFile();
return file;
}
private static HttpURLConnection mockHttpURLConnection(int statusCode, String content) throws Exception {
HttpURLConnection connection = mock(HttpURLConnection.class);
doReturn(statusCode).when(connection).getResponseCode();
doReturn(new ByteArrayInputStream(content.getBytes("UTF-8"))).when(connection).getInputStream();
return connection;
}
}

View file

@ -1,119 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.dlc;
import android.content.Context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.dlc.catalog.DownloadContent;
import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* StudyAction: Scan the catalog for "new" content available for download.
*/
@RunWith(TestRunner.class)
public class TestStudyAction {
/**
* Scenario: Catalog is empty.
*
* Verify that:
* * No download is scheduled
* * Download action is not started
*/
@Test
public void testPerformWithEmptyCatalog() {
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.getContentToStudy()).thenReturn(new ArrayList<DownloadContent>());
StudyAction action = spy(new StudyAction());
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog).getContentToStudy();
verify(catalog, never()).markAsDownloaded(any(DownloadContent.class));
verify(action, never()).startDownloads(any(Context.class));
}
/**
* Scenario: Catalog contains two items that have not been downloaded yet.
*
* Verify that:
* * Both items are scheduled to be downloaded
*/
@Test
public void testPerformWithNewContent() {
DownloadContent content1 = new DownloadContentBuilder()
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setKind(DownloadContent.KIND_FONT)
.build();
DownloadContent content2 = new DownloadContentBuilder()
.setType(DownloadContent.TYPE_ASSET_ARCHIVE)
.setKind(DownloadContent.KIND_FONT)
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.getContentToStudy()).thenReturn(Arrays.asList(content1, content2));
StudyAction action = spy(new StudyAction());
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog).scheduleDownload(content1);
verify(catalog).scheduleDownload(content2);
}
/**
* Scenario: Catalog contains item that are scheduled for download.
*
* Verify that:
* * Download action is started
*/
@Test
public void testStartingDownloadsAfterScheduling() {
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.hasScheduledDownloads()).thenReturn(true);
StudyAction action = spy(new StudyAction());
action.perform(RuntimeEnvironment.application, catalog);
verify(action).startDownloads(any(Context.class));
}
/**
* Scenario: Catalog contains unknown content.
*
* Verify that:
* * Unknown content is not scheduled for download.
*/
@Test
public void testPerformWithUnknownContent() {
DownloadContent content = new DownloadContentBuilder()
.setType("Unknown-Type")
.setKind("Unknown-Kind")
.build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.getContentToStudy()).thenReturn(Collections.singletonList(content));
StudyAction action = spy(new StudyAction());
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog, never()).scheduleDownload(content);
}
}

View file

@ -1,276 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.dlc;
import android.content.Context;
import android.support.v4.util.ArrayMap;
import android.support.v4.util.AtomicFile;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.dlc.catalog.DownloadContent;
import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
import org.mozilla.gecko.util.IOUtils;
import org.robolectric.RuntimeEnvironment;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* SyncAction: Synchronize catalog from a (mocked) Kinto instance.
*/
@RunWith(TestRunner.class)
public class TestSyncAction {
/**
* Scenario: The server returns an empty record set.
*/
@Test
public void testEmptyResult() throws Exception {
SyncAction action = spy(new SyncAction());
doReturn(true).when(action).isSyncEnabledForClient(RuntimeEnvironment.application);
doReturn(new JSONArray()).when(action).fetchRawCatalog(anyLong());
action.perform(RuntimeEnvironment.application, mockCatalog());
verify(action, never()).createContent(anyCatalog(), anyJSONObject());
verify(action, never()).updateContent(anyCatalog(), anyJSONObject(), anyContent());
verify(action, never()).deleteContent(anyCatalog(), anyString());
verify(action, never()).startStudyAction(anyContext());
}
/**
* Scenario: The server returns an item that is not in the catalog yet.
*/
@Test
public void testAddingNewContent() throws Exception {
SyncAction action = spy(new SyncAction());
doReturn(true).when(action).isSyncEnabledForClient(RuntimeEnvironment.application);
doReturn(fromFile("dlc_sync_single_font.json")).when(action).fetchRawCatalog(anyLong());
DownloadContentCatalog catalog = mockCatalog();
action.perform(RuntimeEnvironment.application, catalog);
// A new content item has been created
verify(action).createContent(anyCatalog(), anyJSONObject());
// No content item has been updated or deleted
verify(action, never()).updateContent(anyCatalog(), anyJSONObject(), anyContent());
verify(action, never()).deleteContent(anyCatalog(), anyString());
// A new item has been added to the catalog
ArgumentCaptor<DownloadContent> captor = ArgumentCaptor.forClass(DownloadContent.class);
verify(catalog).add(captor.capture());
// The item matches the values from the server response
DownloadContent content = captor.getValue();
Assert.assertEquals("c906275c-3747-fe27-426f-6187526a6f06", content.getId());
Assert.assertEquals("4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067", content.getChecksum());
Assert.assertEquals("960be4fc5a92c1dc488582b215d5d75429fd4ffbee463105d29992cd792a912e", content.getDownloadChecksum());
Assert.assertEquals("CharisSILCompact-R.ttf", content.getFilename());
Assert.assertEquals(DownloadContent.KIND_FONT, content.getKind());
Assert.assertEquals("/attachments/0d28a72d-a51f-46f8-9e5a-f95c61de904e.gz", content.getLocation());
Assert.assertEquals(DownloadContent.TYPE_ASSET_ARCHIVE, content.getType());
Assert.assertEquals(1455710632607L, content.getLastModified());
Assert.assertEquals(1727656L, content.getSize());
Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
}
/**
* Scenario: The catalog is using the old format, we want to make sure we abort cleanly.
*/
@Test
public void testUpdatingWithOldCatalog() throws Exception{
SyncAction action = spy(new SyncAction());
doReturn(true).when(action).isSyncEnabledForClient(RuntimeEnvironment.application);
doReturn(fromFile("dlc_sync_old_format.json")).when(action).fetchRawCatalog(anyLong());
DownloadContent existingContent = createTestContent("c906275c-3747-fe27-426f-6187526a6f06");
DownloadContentCatalog catalog = spy(new MockedContentCatalog(existingContent));
action.perform(RuntimeEnvironment.application, catalog);
// make sure nothing was done
verify(action, never()).createContent(anyCatalog(), anyJSONObject());
verify(action, never()).updateContent(anyCatalog(), anyJSONObject(), anyContent());
verify(action, never()).deleteContent(anyCatalog(), anyString());
verify(action, never()).startStudyAction(anyContext());
}
/**
* Scenario: The catalog contains one item and the server returns a new version.
*/
@Test
public void testUpdatingExistingContent() throws Exception{
SyncAction action = spy(new SyncAction());
doReturn(true).when(action).isSyncEnabledForClient(RuntimeEnvironment.application);
doReturn(fromFile("dlc_sync_single_font.json")).when(action).fetchRawCatalog(anyLong());
DownloadContent existingContent = createTestContent("c906275c-3747-fe27-426f-6187526a6f06");
DownloadContentCatalog catalog = spy(new MockedContentCatalog(existingContent));
action.perform(RuntimeEnvironment.application, catalog);
// A content item has been updated
verify(action).updateContent(anyCatalog(), anyJSONObject(), eq(existingContent));
// No content item has been created or deleted
verify(action, never()).createContent(anyCatalog(), anyJSONObject());
verify(action, never()).deleteContent(anyCatalog(), anyString());
// An item has been updated in the catalog
ArgumentCaptor<DownloadContent> captor = ArgumentCaptor.forClass(DownloadContent.class);
verify(catalog).update(captor.capture());
// The item has the new values from the sever response
DownloadContent content = captor.getValue();
Assert.assertEquals("c906275c-3747-fe27-426f-6187526a6f06", content.getId());
Assert.assertEquals("4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067", content.getChecksum());
Assert.assertEquals("960be4fc5a92c1dc488582b215d5d75429fd4ffbee463105d29992cd792a912e", content.getDownloadChecksum());
Assert.assertEquals("CharisSILCompact-R.ttf", content.getFilename());
Assert.assertEquals(DownloadContent.KIND_FONT, content.getKind());
Assert.assertEquals("/attachments/0d28a72d-a51f-46f8-9e5a-f95c61de904e.gz", content.getLocation());
Assert.assertEquals(DownloadContent.TYPE_ASSET_ARCHIVE, content.getType());
Assert.assertEquals(1455710632607L, content.getLastModified());
Assert.assertEquals(1727656L, content.getSize());
Assert.assertEquals(DownloadContent.STATE_UPDATED, content.getState());
}
/**
* Scenario: Catalog contains one item and the server returns that it has been deleted.
*/
@Test
public void testDeletingExistingContent() throws Exception {
SyncAction action = spy(new SyncAction());
doReturn(true).when(action).isSyncEnabledForClient(RuntimeEnvironment.application);
doReturn(fromFile("dlc_sync_deleted_item.json")).when(action).fetchRawCatalog(anyLong());
final String id = "c906275c-3747-fe27-426f-6187526a6f06";
DownloadContent existingContent = createTestContent(id);
DownloadContentCatalog catalog = spy(new MockedContentCatalog(existingContent));
action.perform(RuntimeEnvironment.application, catalog);
// A content item has been deleted
verify(action).deleteContent(anyCatalog(), eq(id));
// No content item has been created or updated
verify(action, never()).createContent(anyCatalog(), anyJSONObject());
verify(action, never()).updateContent(anyCatalog(), anyJSONObject(), anyContent());
// An item has been marked for deletion in the catalog
ArgumentCaptor<DownloadContent> captor = ArgumentCaptor.forClass(DownloadContent.class);
verify(catalog).markAsDeleted(captor.capture());
DownloadContent content = captor.getValue();
Assert.assertEquals(id, content.getId());
List<DownloadContent> contentToDelete = catalog.getContentToDelete();
Assert.assertEquals(1, contentToDelete.size());
Assert.assertEquals(id, contentToDelete.get(0).getId());
}
/**
* Create a DownloadContent object with arbitrary data.
*/
private DownloadContent createTestContent(String id) {
return new DownloadContentBuilder()
.setId(id)
.setLocation("/somewhere/something")
.setFilename("some.file")
.setChecksum("Some-checksum")
.setDownloadChecksum("Some-download-checksum")
.setLastModified(4223)
.setType("Some-type")
.setKind("Some-kind")
.setSize(27)
.setState(DownloadContent.STATE_SCHEDULED)
.build();
}
/**
* Create a Kinto response from a JSON file.
*/
private JSONArray fromFile(String fileName) throws IOException, JSONException {
URL url = getClass().getResource("/" + fileName);
if (url == null) {
throw new FileNotFoundException(fileName);
}
InputStream inputStream = null;
ByteArrayOutputStream outputStream = null;
try {
inputStream = new BufferedInputStream(new FileInputStream(url.getPath()));
outputStream = new ByteArrayOutputStream();
IOUtils.copy(inputStream, outputStream);
JSONObject object = new JSONObject(outputStream.toString());
return object.getJSONArray("data");
} finally {
IOUtils.safeStreamClose(inputStream);
IOUtils.safeStreamClose(outputStream);
}
}
private static class MockedContentCatalog extends DownloadContentCatalog {
public MockedContentCatalog(DownloadContent content) {
super(mock(AtomicFile.class));
ArrayMap<String, DownloadContent> map = new ArrayMap<>();
map.put(content.getId(), content);
onCatalogLoaded(map);
}
}
private DownloadContentCatalog mockCatalog() {
return mock(DownloadContentCatalog.class);
}
private DownloadContentCatalog anyCatalog() {
return any(DownloadContentCatalog.class);
}
private JSONObject anyJSONObject() {
return any(JSONObject.class);
}
private DownloadContent anyContent() {
return any(DownloadContent.class);
}
private Context anyContext() {
return any(Context.class);
}
}

View file

@ -1,123 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.dlc;
import android.content.Context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.dlc.catalog.DownloadContent;
import org.mozilla.gecko.dlc.catalog.DownloadContentBuilder;
import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
import org.robolectric.RuntimeEnvironment;
import java.io.File;
import java.util.Collections;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* VerifyAction: Validate downloaded content. Does it still exist and does it have the correct checksum?
*/
@RunWith(TestRunner.class)
public class TestVerifyAction {
/**
* Scenario: Downloaded file does not exist anymore.
*
* Verify that:
* * Content is re-scheduled for download.
*/
@Test
public void testReschedulingIfFileDoesNotExist() throws Exception {
DownloadContent content = new DownloadContentBuilder().build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.getDownloadedContent()).thenReturn(Collections.singletonList(content));
File file = mock(File.class);
when(file.exists()).thenReturn(false);
VerifyAction action = spy(new VerifyAction());
doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog).scheduleDownload(content);
}
/**
* Scenario: Content has been scheduled for download.
*
* Verify that:
* * Download action is started
*/
@Test
public void testStartingDownloadsAfterScheduling() {
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.hasScheduledDownloads()).thenReturn(true);
VerifyAction action = spy(new VerifyAction());
action.perform(RuntimeEnvironment.application, catalog);
verify(action).startDownloads(any(Context.class));
}
/**
* Scenario: Checksum of existing file does not match expectation.
*
* Verify that:
* * Content is re-scheduled for download.
*/
@Test
public void testReschedulingIfVerificationFailed() throws Exception {
DownloadContent content = new DownloadContentBuilder().build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.getDownloadedContent()).thenReturn(Collections.singletonList(content));
File file = mock(File.class);
when(file.exists()).thenReturn(true);
VerifyAction action = spy(new VerifyAction());
doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(false).when(action).verify(eq(file), anyString());
action.perform(RuntimeEnvironment.application, catalog);
verify(catalog).scheduleDownload(content);
}
/**
* Scenario: Downloaded file exists and has the correct checksum.
*
* Verify that:
* * No download is scheduled
* * Download action is not started
*/
@Test
public void testSuccessfulVerification() throws Exception {
DownloadContent content = new DownloadContentBuilder().build();
DownloadContentCatalog catalog = mock(DownloadContentCatalog.class);
when(catalog.getDownloadedContent()).thenReturn(Collections.singletonList(content));
File file = mock(File.class);
when(file.exists()).thenReturn(true);
VerifyAction action = spy(new VerifyAction());
doReturn(file).when(action).getDestinationFile(RuntimeEnvironment.application, content);
doReturn(true).when(action).verify(eq(file), anyString());
verify(catalog, never()).scheduleDownload(content);
verify(action, never()).startDownloads(RuntimeEnvironment.application);
}
}

View file

@ -1,69 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.dlc.catalog;
import org.json.JSONException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
@RunWith(TestRunner.class)
public class TestDownloadContentBuilder {
/**
* Verify that the values passed to the builder are all set on the DownloadContent object.
*/
@Test
public void testBuilder() {
DownloadContent content = createTestContent();
Assert.assertEquals("Some-ID", content.getId());
Assert.assertEquals("/somewhere/something", content.getLocation());
Assert.assertEquals("some.file", content.getFilename());
Assert.assertEquals("Some-checksum", content.getChecksum());
Assert.assertEquals("Some-download-checksum", content.getDownloadChecksum());
Assert.assertEquals(4223, content.getLastModified());
Assert.assertEquals("Some-type", content.getType());
Assert.assertEquals("Some-kind", content.getKind());
Assert.assertEquals(27, content.getSize());
Assert.assertEquals(DownloadContent.STATE_SCHEDULED, content.getState());
}
/**
* Verify that a DownloadContent object exported to JSON and re-imported from JSON does not change.
*/
public void testJSONSerializationAndDeserialization() throws JSONException {
DownloadContent content = DownloadContentBuilder.fromJSON(DownloadContentBuilder.toJSON(createTestContent()));
Assert.assertEquals("Some-ID", content.getId());
Assert.assertEquals("/somewhere/something", content.getLocation());
Assert.assertEquals("some.file", content.getFilename());
Assert.assertEquals("Some-checksum", content.getChecksum());
Assert.assertEquals("Some-download-checksum", content.getDownloadChecksum());
Assert.assertEquals(4223, content.getLastModified());
Assert.assertEquals("Some-type", content.getType());
Assert.assertEquals("Some-kind", content.getKind());
Assert.assertEquals(27, content.getSize());
Assert.assertEquals(DownloadContent.STATE_SCHEDULED, content.getState());
}
/**
* Create a DownloadContent object with arbitrary data.
*/
private DownloadContent createTestContent() {
return new DownloadContentBuilder()
.setId("Some-ID")
.setLocation("/somewhere/something")
.setFilename("some.file")
.setChecksum("Some-checksum")
.setDownloadChecksum("Some-download-checksum")
.setLastModified(4223)
.setType("Some-type")
.setKind("Some-kind")
.setSize(27)
.setState(DownloadContent.STATE_SCHEDULED)
.build();
}
}

View file

@ -1,262 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.dlc.catalog;
import android.support.v4.util.ArrayMap;
import android.support.v4.util.AtomicFile;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@RunWith(TestRunner.class)
public class TestDownloadContentCatalog {
/**
* Scenario: Create a new, fresh catalog.
*
* Verify that:
* * Catalog has not changed
* * Unchanged catalog will not be saved to disk
*/
@Test
public void testUntouchedCatalogHasNotChangedAndWillNotBePersisted() throws Exception {
AtomicFile file = mock(AtomicFile.class);
doReturn("{content:[]}".getBytes("UTF-8")).when(file).readFully();
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
catalog.loadFromDisk();
Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
catalog.writeToDisk();
Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
verify(file, never()).startWrite();
}
/**
* Scenario: Create a new, fresh catalog.
*
* Verify that:
* * Catalog is bootstrapped with items.
*/
@Test
public void testCatalogIsBootstrappedIfFileDoesNotExist() throws Exception {
// The catalog is only bootstrapped if fonts are excluded from the build. If this is a build
// with fonts included then ignore this test.
Assume.assumeTrue("Fonts are excluded from build", AppConstants.MOZ_ANDROID_EXCLUDE_FONTS);
AtomicFile file = mock(AtomicFile.class);
doThrow(FileNotFoundException.class).when(file).readFully();
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
catalog.loadFromDisk();
Assert.assertTrue("Catalog is not empty", catalog.getContentToStudy().size() > 0);
}
/**
* Scenario: Schedule downloading an item from the catalog.
*
* Verify that:
* * Catalog has changed
*/
@Test
public void testCatalogHasChangedWhenDownloadIsScheduled() throws Exception {
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
DownloadContent content = new DownloadContentBuilder().build();
catalog.onCatalogLoaded(createMapOfContent(content));
Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
catalog.scheduleDownload(content);
Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
}
/**
* Scenario: Mark an item in the catalog as downloaded.
*
* Verify that:
* * Catalog has changed
*/
@Test
public void testCatalogHasChangedWhenContentIsDownloaded() throws Exception {
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
DownloadContent content = new DownloadContentBuilder().build();
catalog.onCatalogLoaded(createMapOfContent(content));
Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
catalog.markAsDownloaded(content);
Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
}
/**
* Scenario: Mark an item in the catalog as permanently failed.
*
* Verify that:
* * Catalog has changed
*/
@Test
public void testCatalogHasChangedIfDownloadHasFailedPermanently() throws Exception {
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
DownloadContent content = new DownloadContentBuilder().build();
catalog.onCatalogLoaded(createMapOfContent(content));
Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
catalog.markAsPermanentlyFailed(content);
Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
}
/**
* Scenario: A changed catalog is written to disk.
*
* Verify that:
* * Before write: Catalog has changed
* * After write: Catalog has not changed.
*/
@Test
public void testCatalogHasNotChangedAfterWritingToDisk() throws Exception {
AtomicFile file = mock(AtomicFile.class);
doReturn(mock(FileOutputStream.class)).when(file).startWrite();
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(file));
DownloadContent content = new DownloadContentBuilder().build();
catalog.onCatalogLoaded(createMapOfContent(content));
catalog.scheduleDownload(content);
Assert.assertTrue("Catalog has changed", catalog.hasCatalogChanged());
catalog.writeToDisk();
Assert.assertFalse("Catalog has not changed", catalog.hasCatalogChanged());
}
/**
* Scenario: A catalog with multiple items in different states.
*
* Verify that:
* * getContentWithoutState(), getDownloadedContent() and getScheduledDownloads() returns
* the correct items depenending on their state.
*/
@Test
public void testContentClassification() {
DownloadContentCatalog catalog = spy(new DownloadContentCatalog(mock(AtomicFile.class)));
DownloadContent content1 = new DownloadContentBuilder().setId("A").setState(DownloadContent.STATE_NONE).build();
DownloadContent content2 = new DownloadContentBuilder().setId("B").setState(DownloadContent.STATE_NONE).build();
DownloadContent content3 = new DownloadContentBuilder().setId("C").setState(DownloadContent.STATE_SCHEDULED).build();
DownloadContent content4 = new DownloadContentBuilder().setId("D").setState(DownloadContent.STATE_SCHEDULED).build();
DownloadContent content5 = new DownloadContentBuilder().setId("E").setState(DownloadContent.STATE_SCHEDULED).build();
DownloadContent content6 = new DownloadContentBuilder().setId("F").setState(DownloadContent.STATE_DOWNLOADED).build();
DownloadContent content7 = new DownloadContentBuilder().setId("G").setState(DownloadContent.STATE_FAILED).build();
DownloadContent content8 = new DownloadContentBuilder().setId("H").setState(DownloadContent.STATE_UPDATED).build();
DownloadContent content9 = new DownloadContentBuilder().setId("I").setState(DownloadContent.STATE_DELETED).build();
DownloadContent content10 = new DownloadContentBuilder().setId("J").setState(DownloadContent.STATE_DELETED).build();
catalog.onCatalogLoaded(createMapOfContent(content1, content2, content3, content4, content5, content6,
content7, content8, content9, content10));
Assert.assertTrue(catalog.hasScheduledDownloads());
Assert.assertEquals(3, catalog.getContentToStudy().size());
Assert.assertEquals(1, catalog.getDownloadedContent().size());
Assert.assertEquals(3, catalog.getScheduledDownloads().size());
Assert.assertEquals(2, catalog.getContentToDelete().size());
Assert.assertTrue(catalog.getContentToStudy().contains(content1));
Assert.assertTrue(catalog.getContentToStudy().contains(content2));
Assert.assertTrue(catalog.getContentToStudy().contains(content8));
Assert.assertTrue(catalog.getDownloadedContent().contains(content6));
Assert.assertTrue(catalog.getScheduledDownloads().contains(content3));
Assert.assertTrue(catalog.getScheduledDownloads().contains(content4));
Assert.assertTrue(catalog.getScheduledDownloads().contains(content5));
Assert.assertTrue(catalog.getContentToDelete().contains(content9));
Assert.assertTrue(catalog.getContentToDelete().contains(content10));
}
/**
* Scenario: Calling rememberFailure() on a catalog with varying values
*/
@Test
public void testRememberingFailures() {
DownloadContentCatalog catalog = new DownloadContentCatalog(mock(AtomicFile.class));
Assert.assertFalse(catalog.hasCatalogChanged());
DownloadContent content = new DownloadContentBuilder().build();
Assert.assertEquals(0, content.getFailures());
catalog.rememberFailure(content, 42);
Assert.assertEquals(1, content.getFailures());
Assert.assertTrue(catalog.hasCatalogChanged());
catalog.rememberFailure(content, 42);
Assert.assertEquals(2, content.getFailures());
// Failure counter is reset if different failure has been reported
catalog.rememberFailure(content, 23);
Assert.assertEquals(1, content.getFailures());
// Failure counter is reset after successful download
catalog.markAsDownloaded(content);
Assert.assertEquals(0, content.getFailures());
}
/**
* Scenario: Content has failed multiple times with the same failure type.
*
* Verify that:
* * Content is marked as permanently failed
*/
@Test
public void testContentWillBeMarkedAsPermanentlyFailedAfterMultipleFailures() {
DownloadContentCatalog catalog = new DownloadContentCatalog(mock(AtomicFile.class));
DownloadContent content = new DownloadContentBuilder().build();
Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
for (int i = 0; i < 10; i++) {
catalog.rememberFailure(content, 42);
Assert.assertEquals(i + 1, content.getFailures());
Assert.assertEquals(DownloadContent.STATE_NONE, content.getState());
}
catalog.rememberFailure(content, 42);
Assert.assertEquals(10, content.getFailures());
Assert.assertEquals(DownloadContent.STATE_FAILED, content.getState());
}
private ArrayMap<String, DownloadContent> createMapOfContent(DownloadContent... content) {
ArrayMap<String, DownloadContent> map = new ArrayMap<>();
for (DownloadContent currentContent : content) {
map.put(currentContent.getId(), currentContent);
}
return map;
}
}

View file

@ -1,74 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.feeds.knownsites;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.helpers.AssertUtil;
@RunWith(TestRunner.class)
public class TestKnownSiteBlogger {
/**
* Test that the search string is a substring of some known URLs.
*/
@Test
public void testURLSearchString() {
final KnownSite blogger = new KnownSiteBlogger();
final String searchString = blogger.getURLSearchString();
AssertUtil.assertContains(
"http://mykzilla.blogspot.com/",
searchString);
AssertUtil.assertContains(
"http://example.blogspot.com",
searchString);
AssertUtil.assertContains(
"https://mykzilla.blogspot.com/2015/06/introducing-pluotsorbet.html",
searchString);
AssertUtil.assertContains(
"http://android-developers.blogspot.com/2016/02/android-support-library-232.html",
searchString);
AssertUtil.assertContainsNot(
"http://www.mozilla.org",
searchString);
}
/**
* Test that we get a feed URL for valid Blogger URLs.
*/
@Test
public void testGettingFeedFromURL() {
final KnownSite blogger = new KnownSiteBlogger();
Assert.assertEquals(
"https://mykzilla.blogspot.com/feeds/posts/default",
blogger.getFeedFromURL("http://mykzilla.blogspot.com/"));
Assert.assertEquals(
"https://example.blogspot.com/feeds/posts/default",
blogger.getFeedFromURL("http://example.blogspot.com"));
Assert.assertEquals(
"https://mykzilla.blogspot.com/feeds/posts/default",
blogger.getFeedFromURL("https://mykzilla.blogspot.com/2015/06/introducing-pluotsorbet.html"));
Assert.assertEquals(
"https://android-developers.blogspot.com/feeds/posts/default",
blogger.getFeedFromURL("http://android-developers.blogspot.com/2016/02/android-support-library-232.html"));
Assert.assertEquals(
"https://example.blogspot.com/feeds/posts/default",
blogger.getFeedFromURL("http://example.blogspot.com/2016/03/i-moved-to-example.blogspot.com"));
Assert.assertNull(blogger.getFeedFromURL("http://www.mozilla.org"));
}
}

View file

@ -1,66 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.feeds.knownsites;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.helpers.AssertUtil;
@RunWith(TestRunner.class)
public class TestKnownSiteMedium {
/**
* Test that the search string is a substring of some known URLs.
*/
@Test
public void testURLSearchString() {
final KnownSite medium = new KnownSiteMedium();
final String searchString = medium.getURLSearchString();
AssertUtil.assertContains(
"https://medium.com/@Antlam/",
searchString);
AssertUtil.assertContains(
"https://medium.com/google-developers",
searchString);
AssertUtil.assertContains(
"http://medium.com/@brandonshin/how-slackbot-forced-us-to-workout-7b4741a2de73",
searchString
);
AssertUtil.assertContainsNot(
"http://www.mozilla.org",
searchString);
}
/**
* Test that we get a feed URL for valid Medium URLs.
*/
@Test
public void testGettingFeedFromURL() {
final KnownSite medium = new KnownSiteMedium();
Assert.assertEquals(
"https://medium.com/feed/@Antlam",
medium.getFeedFromURL("https://medium.com/@Antlam/")
);
Assert.assertEquals(
"https://medium.com/feed/google-developers",
medium.getFeedFromURL("https://medium.com/google-developers")
);
Assert.assertEquals(
"https://medium.com/feed/@brandonshin",
medium.getFeedFromURL("http://medium.com/@brandonshin/how-slackbot-forced-us-to-workout-7b4741a2de73")
);
Assert.assertNull(medium.getFeedFromURL("http://www.mozilla.org"));
}
}

View file

@ -1,62 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.feeds.knownsites;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.helpers.AssertUtil;
@RunWith(TestRunner.class)
public class TestKnownSiteTumblr {
/**
* Test that the search string is a substring of some known URLs.
*/
@Test
public void testURLSearchString() {
final KnownSite tumblr = new KnownSiteTumblr();
final String searchString = tumblr.getURLSearchString();
AssertUtil.assertContains(
"http://contentnotifications.tumblr.com/",
searchString);
AssertUtil.assertContains(
"https://contentnotifications.tumblr.com",
searchString);
AssertUtil.assertContains(
"http://contentnotifications.tumblr.com/post/142684202402/content-notification-firefox-for-android-480",
searchString);
AssertUtil.assertContainsNot(
"http://www.mozilla.org",
searchString);
}
/**
* Test that we get a feed URL for valid Medium URLs.
*/
@Test
public void testGettingFeedFromURL() {
final KnownSite tumblr = new KnownSiteTumblr();
Assert.assertEquals(
"http://contentnotifications.tumblr.com/rss",
tumblr.getFeedFromURL("http://contentnotifications.tumblr.com/")
);
Assert.assertEquals(
"http://staff.tumblr.com/rss",
tumblr.getFeedFromURL("https://staff.tumblr.com/post/141928246566/replies-are-back-and-the-sun-is-shining-on-the")
);
Assert.assertNull(tumblr.getFeedFromURL("https://www.tumblr.com"));
Assert.assertNull(tumblr.getFeedFromURL("http://www.mozilla.org"));
}
}

View file

@ -1,323 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.feeds.parser;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Locale;
@RunWith(TestRunner.class)
public class TestSimpleFeedParser {
/**
* Parse and verify the RSS example from Wikipedia:
* https://en.wikipedia.org/wiki/RSS#Example
*/
@Test
public void testRSSExample() throws Exception {
InputStream stream = openFeed("feed_rss_wikipedia.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("RSS Title", feed.getTitle());
Assert.assertEquals("http://www.example.com/main.html", feed.getWebsiteURL());
Assert.assertNull(feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Example entry", item.getTitle());
Assert.assertEquals("http://www.example.com/blog/post/1", item.getURL());
Assert.assertEquals(1252254000000L, item.getTimestamp());
}
/**
* Parse and verify the ATOM example from Wikipedia:
* https://en.wikipedia.org/wiki/Atom_%28standard%29#Example_of_an_Atom_1.0_feed
*/
@Test
public void testATOMExample() throws Exception {
InputStream stream = openFeed("feed_atom_wikipedia.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Example Feed", feed.getTitle());
Assert.assertEquals("http://example.org/", feed.getWebsiteURL());
Assert.assertEquals("http://example.org/feed/", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Atom-Powered Robots Run Amok", item.getTitle());
Assert.assertEquals("http://example.org/2003/12/13/atom03.html", item.getURL());
Assert.assertEquals(1071340202000L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of a Medium feed.
*/
@Test
public void testMediumFeed() throws Exception {
InputStream stream = openFeed("feed_rss_medium.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Anthony Lam on Medium", feed.getTitle());
Assert.assertEquals("https://medium.com/@antlam?source=rss-59f49b9e4b19------2", feed.getWebsiteURL());
Assert.assertEquals("https://medium.com/feed/@antlam", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("UX thoughts for 2016", item.getTitle());
Assert.assertEquals("https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8?source=rss-59f49b9e4b19------2", item.getURL());
Assert.assertEquals(1452537838000L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of planet.mozilla.org ATOM feed.
*/
@Test
public void testPlanetMozillaATOMFeed() throws Exception {
InputStream stream = openFeed("feed_atom_planetmozilla.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Planet Mozilla", feed.getTitle());
Assert.assertEquals("http://planet.mozilla.org/", feed.getWebsiteURL());
Assert.assertEquals("http://planet.mozilla.org/atom.xml", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Firefox 45.0 Beta 3 Testday, February 5th", item.getTitle());
Assert.assertEquals("https://quality.mozilla.org/2016/01/firefox-45-0-beta-3-testday-february-5th/", item.getURL());
Assert.assertEquals(1453819255000L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of planet.mozilla.org RSS 2.0 feed.
*/
@Test
public void testPlanetMozillaRSS20Feed() throws Exception {
InputStream stream = openFeed("feed_rss20_planetmozilla.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Planet Mozilla", feed.getTitle());
Assert.assertEquals("http://planet.mozilla.org/", feed.getWebsiteURL());
Assert.assertEquals("http://planet.mozilla.org/rss20.xml", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Aaron Klotz: Announcing Mozdbgext", item.getTitle());
Assert.assertEquals("http://dblohm7.ca/blog/2016/01/26/announcing-mozdbgext/", item.getURL());
Assert.assertEquals(1453837500000L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of planet.mozilla.org RSS 1.0 feed.
*/
@Test
public void testPlanetMozillaRSS10Feed() throws Exception {
InputStream stream = openFeed("feed_rss10_planetmozilla.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Planet Mozilla", feed.getTitle());
Assert.assertEquals("http://planet.mozilla.org/", feed.getWebsiteURL());
Assert.assertEquals("http://planet.mozilla.org/rss10.xml", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Aaron Klotz: Announcing Mozdbgext", item.getTitle());
Assert.assertEquals("http://dblohm7.ca/blog/2016/01/26/announcing-mozdbgext/", item.getURL());
Assert.assertEquals(1453837500000L, item.getTimestamp());
}
/**
* Parse an verify a snapshot of a feedburner ATOM feed.
*/
@Test
public void testFeedburnerAtomFeed() throws Exception {
InputStream stream = openFeed("feed_atom_feedburner.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Android Zeitgeist", feed.getTitle());
Assert.assertEquals("http://www.androidzeitgeist.com/", feed.getWebsiteURL());
Assert.assertEquals("http://feeds.feedburner.com/AndroidZeitgeist", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Support for restricted profiles in Firefox 42", item.getTitle());
Assert.assertEquals("http://feedproxy.google.com/~r/AndroidZeitgeist/~3/xaSicfGuwOU/support-restricted-profiles-firefox.html", item.getURL());
Assert.assertEquals(1442511968239L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of a Tumblr RSS feed.
*/
@Test
public void testTumblrRssFeed() throws Exception {
InputStream stream = openFeed("feed_rss_tumblr.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("Tumblr Staff", feed.getTitle());
Assert.assertEquals("http://staff.tumblr.com/", feed.getWebsiteURL());
Assert.assertNull(feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("hardyboyscovers: Can Nancy Drew see things through and solve...", item.getTitle());
Assert.assertEquals("http://staff.tumblr.com/post/138124026275", item.getURL());
Assert.assertEquals(1453861812000L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of a Spiegel (German news magazine) RSS feed.
*/
@Test
public void testSpiegelRssFeed() throws Exception {
InputStream stream = openFeed("feed_rss_spon.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("SPIEGEL ONLINE - Schlagzeilen", feed.getTitle());
Assert.assertEquals("http://www.spiegel.de", feed.getWebsiteURL());
Assert.assertNull(feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Angebliche Vergewaltigung einer 13-Jährigen: Steinmeier kanzelt russischen Minister Lawrow ab", item.getTitle());
Assert.assertEquals("http://www.spiegel.de/politik/ausland/steinmeier-kanzelt-lawrow-ab-aerger-um-angebliche-vergewaltigung-a-1074292.html#ref=rss", item.getURL());
Assert.assertEquals(1453914976000L, item.getTimestamp());
}
/**
* Parse and verify a snapshot of a Heise (German tech news) RSS feed.
*/
@Test
public void testHeiseRssFeed() throws Exception {
InputStream stream = openFeed("feed_rss_heise.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("heise online News", feed.getTitle());
Assert.assertEquals("http://www.heise.de/newsticker/", feed.getWebsiteURL());
Assert.assertNull(feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Google: “Dramatische Verbesserungen” für Chrome in iOS", item.getTitle());
Assert.assertEquals("http://www.heise.de/newsticker/meldung/Google-Dramatische-Verbesserungen-fuer-Chrome-in-iOS-3085808.html?wt_mc=rss.ho.beitrag.atom", item.getURL());
Assert.assertEquals(1453915920000L, item.getTimestamp());
}
@Test
public void testWordpressFeed() throws Exception {
InputStream stream = openFeed("feed_rss_wordpress.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("justasimpletest2016", feed.getTitle());
Assert.assertEquals("https://justasimpletest2016.wordpress.com", feed.getWebsiteURL());
Assert.assertEquals("https://justasimpletest2016.wordpress.com/feed/", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("Hello World!", item.getTitle());
Assert.assertEquals("https://justasimpletest2016.wordpress.com/2016/02/26/hello-world/", item.getURL());
Assert.assertEquals(1456524466000L, item.getTimestamp());
}
/**
* Parse and test a snapshot of mykzilla.blogspot.com
*/
@Test
public void testBloggerFeed() throws Exception {
InputStream stream = openFeed("feed_atom_blogger.xml");
SimpleFeedParser parser = new SimpleFeedParser();
Feed feed = parser.parse(stream);
Assert.assertNotNull(feed);
Assert.assertEquals("mykzilla", feed.getTitle());
Assert.assertEquals("http://mykzilla.blogspot.com/", feed.getWebsiteURL());
Assert.assertEquals("http://www.blogger.com/feeds/18929277/posts/default", feed.getFeedURL());
Assert.assertTrue(feed.isSufficientlyComplete());
Item item = feed.getLastItem();
Assert.assertNotNull(item);
Assert.assertEquals("URL Has Been Changed", item.getTitle());
Assert.assertEquals("http://mykzilla.blogspot.com/2016/01/url-has-been-changed.html", item.getURL());
Assert.assertEquals(1452531451366L, item.getTimestamp());
}
private InputStream openFeed(String fileName) throws URISyntaxException, FileNotFoundException, UnsupportedEncodingException {
URL url = getClass().getResource("/" + fileName);
if (url == null) {
throw new FileNotFoundException(fileName);
}
return new BufferedInputStream(new FileInputStream(url.getPath()));
}
}

View file

@ -1,70 +0,0 @@
/* 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 ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.fxa.SkewHandler;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.sync.net.BaseResource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@RunWith(TestRunner.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);
}
}

View file

@ -1,226 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.fxa.login;
import android.text.TextUtils;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.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.fxa.FxAccountDevice;
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 java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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 final Map<String, FxAccountDevice> devices;
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);
this.devices = new HashMap<String, FxAccountDevice>();
}
}
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 accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate) {
boolean userFound = false;
for (User user : users.values()) {
if (user.uid.equals(uid)) {
userFound = true;
break;
}
}
requestDelegate.handleSuccess(new AccountStatusResponse(userFound));
}
@Override
public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> 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 RecoveryEmailStatusResponse(email, user.verified));
}
@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 registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice deviceToRegister, RequestDelegate<FxAccountDevice> 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 {
String deviceId = deviceToRegister.id;
if (TextUtils.isEmpty(deviceId)) { // Create
deviceId = UUID.randomUUID().toString();
FxAccountDevice device = new FxAccountDevice(deviceToRegister.name, deviceId, deviceToRegister.type, null, null, null, null);
requestDelegate.handleSuccess(device);
} else { // Update
FxAccountDevice existingDevice = user.devices.get(deviceId);
if (existingDevice != null) {
String deviceName = existingDevice.name;
if (!TextUtils.isEmpty(deviceToRegister.name)) {
deviceName = deviceToRegister.name;
} // We could also update the other fields..
FxAccountDevice device = new FxAccountDevice(deviceName, existingDevice.id, existingDevice.type,
existingDevice.isCurrentDevice, existingDevice.pushCallback, existingDevice.pushPublicKey,existingDevice.pushAuthKey);
requestDelegate.handleSuccess(device);
} else { // Device unknown
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown");
return;
}
}
} catch (Exception e) {
requestDelegate.handleError(e);
}
}
@Override
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> 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;
}
Collection<FxAccountDevice> devices = user.devices.values();
FxAccountDevice[] devicesArray = devices.toArray(new FxAccountDevice[devices.size()]);
requestDelegate.handleSuccess(devicesArray);
}
@Override
public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate) {
requestDelegate.handleSuccess(new ExtendedJSONObject());
}
}

View file

@ -1,205 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.fxa.login;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.background.testhelpers.TestRunner;
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 java.security.NoSuchAlgorithmException;
import java.util.LinkedList;
@RunWith(TestRunner.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]");
}
}

View file

@ -1,91 +0,0 @@
/* 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.background.testhelpers.TestRunner;
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;
@RunWith(TestRunner.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());
}
}

View file

@ -1,29 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.helpers;
import org.junit.Assert;
/**
* Some additional assert methods on top of org.junit.Assert.
*/
public class AssertUtil {
/**
* Asserts that the String {@code text} contains the String {@code sequence}. If it doesn't then
* an {@link AssertionError} will be thrown.
*/
public static void assertContains(String text, String sequence) {
Assert.assertTrue(text.contains(sequence));
}
/**
* Asserts that the String {@code text} contains not the String {@code sequence}. If it does
* then an {@link AssertionError} will be thrown.
*/
public static void assertContainsNot(String text, String sequence) {
Assert.assertFalse(text.contains(sequence));
}
}

View file

@ -1,264 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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.home;
import android.content.Context;
import android.util.Pair;
import android.util.SparseArray;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomeConfig.PanelType;
import org.robolectric.RuntimeEnvironment;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.*;
@RunWith(TestRunner.class)
public class TestHomeConfigPrefsBackendMigration {
// Each Pair consists of a list of panels that exist going into a given migration, and a list containing
// the expected default output panel corresponding to each given input panel in the list of existing panels.
// E.g. if a given N->N+1 migration starts with panels Foo and Bar, and removes Bar, the two lists would
// be {Foo, Bar} and {Foo, Foo}.
// Note: the index where each pair is inserted corresponds to the HomeConfig version before the migration.
// The final item in this list denotes the current HomeCOnfig version, and therefore only needs to contain
// the list of panel types that are expected by default (but no list for after the non-existent migration).
final SparseArray<Pair<PanelType[], PanelType[]>> migrationConstellations = new SparseArray<>();
{
// 6->7: the recent tabs panel was merged into the combined history panel
migrationConstellations.put(6, new Pair<>(
/* Panels that are expected to exist before this migration happens */
new PanelType[] {
PanelType.TOP_SITES,
PanelType.BOOKMARKS,
PanelType.COMBINED_HISTORY,
PanelType.DEPRECATED_RECENT_TABS
},
/* The expected default panel that is expected after the migration */
new PanelType[] {
PanelType.TOP_SITES, /* TOP_SITES remains the default if it was previously the default */
PanelType.BOOKMARKS, /* same as TOP_SITES */
PanelType.COMBINED_HISTORY, /* same as TOP_SITES */
PanelType.COMBINED_HISTORY /* DEPRECATED_RECENT_TABS is replaced by COMBINED_HISTORY during this migration and is therefore the new default */
}
));
// 7->8: no changes, this was a fixup migration since 6->7 was previously botched
migrationConstellations.put(7, new Pair<>(
new PanelType[] {
PanelType.TOP_SITES,
PanelType.BOOKMARKS,
PanelType.COMBINED_HISTORY,
},
new PanelType[] {
PanelType.TOP_SITES,
PanelType.BOOKMARKS,
PanelType.COMBINED_HISTORY,
}
));
migrationConstellations.put(8, new Pair<>(
new PanelType[] {
PanelType.TOP_SITES,
PanelType.BOOKMARKS,
PanelType.COMBINED_HISTORY,
},
new PanelType[] {
// Last version: no migration exists yet, we only need to define a list
// of expected panels.
}
));
}
private JSONArray createDisabledConfigsForList(Context context,
PanelType[] panels) throws JSONException {
final JSONArray jsonPanels = new JSONArray();
for (int i = 0; i < panels.length; i++) {
final PanelType panel = panels[i];
jsonPanels.put(HomeConfig.createBuiltinPanelConfig(context, panel,
EnumSet.of(PanelConfig.Flags.DISABLED_PANEL)).toJSON());
}
return jsonPanels;
}
private JSONArray createConfigsForList(Context context, PanelType[] panels,
int defaultIndex) throws JSONException {
if (defaultIndex < 0 || defaultIndex >= panels.length) {
throw new IllegalArgumentException("defaultIndex must point to panel in the array");
}
final JSONArray jsonPanels = new JSONArray();
for (int i = 0; i < panels.length; i++) {
final PanelType panel = panels[i];
final PanelConfig config;
if (i == defaultIndex) {
config = HomeConfig.createBuiltinPanelConfig(context, panel,
EnumSet.of(PanelConfig.Flags.DEFAULT_PANEL));
} else {
config = HomeConfig.createBuiltinPanelConfig(context, panel);
}
jsonPanels.put(config.toJSON());
}
return jsonPanels;
}
private PanelType getDefaultPanel(final JSONArray jsonPanels) throws JSONException {
assertTrue("panel list must not be empty", jsonPanels.length() > 0);
for (int i = 0; i < jsonPanels.length(); i++) {
final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
if (panelConfig.isDefault()) {
return panelConfig.getType();
}
}
return null;
}
private void checkAllPanelsAreDisabled(JSONArray jsonPanels) throws JSONException {
for (int i = 0; i < jsonPanels.length(); i++) {
final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
final PanelConfig config = new PanelConfig(jsonPanelConfig);
assertTrue("Non disabled panel \"" + config.getType().name() + "\" found in list, excpected all panels to be disabled", config.isDisabled());
}
}
private void checkListContainsExpectedPanels(JSONArray jsonPanels,
PanelType[] expected) throws JSONException {
// Given the short lists we have here an ArraySet might be more appropriate, but it requires API >= 23.
final Set<PanelType> expectedSet = new HashSet<>();
for (PanelType panelType : expected) {
expectedSet.add(panelType);
}
for (int i = 0; i < jsonPanels.length(); i++) {
final JSONObject jsonPanelConfig = jsonPanels.getJSONObject(i);
final PanelType panelType = new PanelConfig(jsonPanelConfig).getType();
assertTrue("Unexpected panel of type " + panelType.name() + " found in list",
expectedSet.contains(panelType));
expectedSet.remove(panelType);
}
assertEquals("Expected panels not contained in list",
0, expectedSet.size());
}
@Test
public void testMigrationRetainsDefaultAfter6() throws JSONException {
final Context context = RuntimeEnvironment.application;
final Pair<PanelType[], PanelType[]> finalConstellation = migrationConstellations.get(HomeConfigPrefsBackend.VERSION);
assertNotNull("It looks like you added a HomeConfig migration, please add an appropriate entry to migrationConstellations",
finalConstellation);
// We want to calculate the number of iterations here to make sure we cover all provided constellations.
// Iterating over the array and manually checking for each version could result in constellations
// being skipped if there are any gaps in the array
final int firstTestedVersion = HomeConfigPrefsBackend.VERSION - (migrationConstellations.size() - 1);
// The last constellation is only used for the counts / expected outputs, hence we start
// with the second-last constellation
for (int testVersion = HomeConfigPrefsBackend.VERSION - 1; testVersion >= firstTestedVersion; testVersion--) {
final Pair<PanelType[], PanelType[]> currentConstellation = migrationConstellations.get(testVersion);
assertNotNull("No constellation for version " + testVersion + " - you must provide a constellation for every version upgrade in the list",
currentConstellation);
final PanelType[] inputList = currentConstellation.first;
final PanelType[] expectedDefaults = currentConstellation.second;
for (int i = 0; i < inputList.length; i++) {
JSONArray jsonPanels = createConfigsForList(context, inputList, i);
// Verify that we still have a default panel, and that it is the expected default panel
// No need to pass in the prefsEditor since that is only used for the 0->1 migration
jsonPanels = HomeConfigPrefsBackend.migratePrefsFromVersionToVersion(context, testVersion, testVersion + 1, jsonPanels, null);
final PanelType oldDefaultPanelType = inputList[i];
final PanelType expectedNewDefaultPanelType = expectedDefaults[i];
final PanelType newDefaultPanelType = getDefaultPanel(jsonPanels);
assertNotNull("No default panel set when migrating from " + testVersion + " to " + testVersion + 1 + ", with previous default as " + oldDefaultPanelType.name(),
newDefaultPanelType);
assertEquals("Migration changed to unexpected default panel - migrating from " + oldDefaultPanelType.name() + ", expected " + expectedNewDefaultPanelType.name() + " but got " + newDefaultPanelType.name(),
newDefaultPanelType, expectedNewDefaultPanelType);
// Verify that the panels remaining after the migration correspond to the input panels
// for the next migration
final PanelType[] expectedOutputList = migrationConstellations.get(testVersion + 1).first;
assertEquals("Number of panels after migration doesn't match expected count",
jsonPanels.length(), expectedOutputList.length);
checkListContainsExpectedPanels(jsonPanels, expectedOutputList);
}
}
}
// Test that if all panels are disabled, the migration retains all panels as being disabled
// (in addition to correctly removing panels as necessary).
@Test
public void testMigrationRetainsAllPanelsHiddenAfter6() throws JSONException {
final Context context = RuntimeEnvironment.application;
final Pair<PanelType[], PanelType[]> finalConstellation = migrationConstellations.get(HomeConfigPrefsBackend.VERSION);
assertNotNull("It looks like you added a HomeConfig migration, please add an appropriate entry to migrationConstellations",
finalConstellation);
final int firstTestedVersion = HomeConfigPrefsBackend.VERSION - (migrationConstellations.size() - 1);
for (int testVersion = HomeConfigPrefsBackend.VERSION - 1; testVersion >= firstTestedVersion; testVersion--) {
final Pair<PanelType[], PanelType[]> currentConstellation = migrationConstellations.get(testVersion);
assertNotNull("No constellation for version " + testVersion + " - you must provide a constellation for every version upgrade in the list",
currentConstellation);
final PanelType[] inputList = currentConstellation.first;
JSONArray jsonPanels = createDisabledConfigsForList(context, inputList);
jsonPanels = HomeConfigPrefsBackend.migratePrefsFromVersionToVersion(context, testVersion, testVersion + 1, jsonPanels, null);
// All panels should remain disabled after the migration
checkAllPanelsAreDisabled(jsonPanels);
// Duplicated from previous test:
// Verify that the panels remaining after the migration correspond to the input panels
// for the next migration
final PanelType[] expectedOutputList = migrationConstellations.get(testVersion + 1).first;
assertEquals("Number of panels after migration doesn't match expected count",
jsonPanels.length(), expectedOutputList.length);
checkListContainsExpectedPanels(jsonPanels, expectedOutputList);
}
}
}

View file

@ -1,56 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.icons;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
@RunWith(TestRunner.class)
public class TestIconDescriptor {
private static final String ICON_URL = "https://www.mozilla.org/favicon.ico";
private static final String MIME_TYPE = "image/png";
private static final int ICON_SIZE = 64;
@Test
public void testGenericIconDescriptor() {
final IconDescriptor descriptor = IconDescriptor.createGenericIcon(ICON_URL);
Assert.assertEquals(ICON_URL, descriptor.getUrl());
Assert.assertNull(descriptor.getMimeType());
Assert.assertEquals(0, descriptor.getSize());
Assert.assertEquals(IconDescriptor.TYPE_GENERIC, descriptor.getType());
}
@Test
public void testFaviconIconDescriptor() {
final IconDescriptor descriptor = IconDescriptor.createFavicon(ICON_URL, ICON_SIZE, MIME_TYPE);
Assert.assertEquals(ICON_URL, descriptor.getUrl());
Assert.assertEquals(MIME_TYPE, descriptor.getMimeType());
Assert.assertEquals(ICON_SIZE, descriptor.getSize());
Assert.assertEquals(IconDescriptor.TYPE_FAVICON, descriptor.getType());
}
@Test
public void testTouchIconDescriptor() {
final IconDescriptor descriptor = IconDescriptor.createTouchicon(ICON_URL, ICON_SIZE, MIME_TYPE);
Assert.assertEquals(ICON_URL, descriptor.getUrl());
Assert.assertEquals(MIME_TYPE, descriptor.getMimeType());
Assert.assertEquals(ICON_SIZE, descriptor.getSize());
Assert.assertEquals(IconDescriptor.TYPE_TOUCHICON, descriptor.getType());
}
@Test
public void testLookupIconDescriptor() {
final IconDescriptor descriptor = IconDescriptor.createLookupIcon(ICON_URL);
Assert.assertEquals(ICON_URL, descriptor.getUrl());
Assert.assertNull(descriptor.getMimeType());
Assert.assertEquals(0, descriptor.getSize());
Assert.assertEquals(IconDescriptor.TYPE_LOOKUP, descriptor.getType());
}
}

View file

@ -1,152 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.icons;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import java.util.TreeSet;
@RunWith(TestRunner.class)
public class TestIconDescriptorComparator {
private static final String TEST_ICON_URL_1 = "http://www.mozilla.org/favicon.ico";
private static final String TEST_ICON_URL_2 = "http://www.example.org/favicon.ico";
private static final String TEST_ICON_URL_3 = "http://www.example.com/favicon.ico";
private static final String TEST_MIME_TYPE = "image/png";
private static final int TEST_SIZE = 32;
@Test
public void testIconsWithTheSameUrlAreTreatedAsEqual() {
final IconDescriptor descriptor1 = IconDescriptor.createGenericIcon(TEST_ICON_URL_1);
final IconDescriptor descriptor2 = IconDescriptor.createGenericIcon(TEST_ICON_URL_1);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(0, comparator.compare(descriptor1, descriptor2));
Assert.assertEquals(0, comparator.compare(descriptor2, descriptor1));
}
@Test
public void testTouchIconsAreRankedHigherThanFavicons() {
final IconDescriptor faviconDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_1, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptor touchIconDescriptor = IconDescriptor.createTouchicon(TEST_ICON_URL_2, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(1, comparator.compare(faviconDescriptor, touchIconDescriptor));
Assert.assertEquals(-1, comparator.compare(touchIconDescriptor, faviconDescriptor));
}
@Test
public void testFaviconsAndTouchIconsAreRankedHigherThanGenericIcons() {
final IconDescriptor genericDescriptor = IconDescriptor.createGenericIcon(TEST_ICON_URL_1);
final IconDescriptor faviconDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_2, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptor touchIconDescriptor = IconDescriptor.createTouchicon(TEST_ICON_URL_3, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(1, comparator.compare(genericDescriptor, faviconDescriptor));
Assert.assertEquals(-1, comparator.compare(faviconDescriptor, genericDescriptor));
Assert.assertEquals(1, comparator.compare(genericDescriptor, touchIconDescriptor));
Assert.assertEquals(-1, comparator.compare(touchIconDescriptor, genericDescriptor));
}
@Test
public void testLookupIconsAreRankedHigherThanGenericIcons() {
final IconDescriptor genericDescriptor = IconDescriptor.createGenericIcon(TEST_ICON_URL_1);
final IconDescriptor lookupDescriptor = IconDescriptor.createLookupIcon(TEST_ICON_URL_2);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(1, comparator.compare(genericDescriptor, lookupDescriptor));
Assert.assertEquals(-1, comparator.compare(lookupDescriptor, genericDescriptor));
}
@Test
public void testFaviconsAndTouchIconsAreRankedHigherThanLookupIcons() {
final IconDescriptor lookupDescriptor = IconDescriptor.createLookupIcon(TEST_ICON_URL_1);
final IconDescriptor faviconDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_2, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptor touchIconDescriptor = IconDescriptor.createTouchicon(TEST_ICON_URL_3, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(1, comparator.compare(lookupDescriptor, faviconDescriptor));
Assert.assertEquals(-1, comparator.compare(faviconDescriptor, lookupDescriptor));
Assert.assertEquals(1, comparator.compare(lookupDescriptor, touchIconDescriptor));
Assert.assertEquals(-1, comparator.compare(touchIconDescriptor, lookupDescriptor));
}
@Test
public void testLargestIconOfSameTypeIsSelected() {
final IconDescriptor smallDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_1, 16, TEST_MIME_TYPE);
final IconDescriptor largeDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_2, 128, TEST_MIME_TYPE);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(1, comparator.compare(smallDescriptor, largeDescriptor));
Assert.assertEquals(-1, comparator.compare(largeDescriptor, smallDescriptor));
}
@Test
public void testContainerTypesArePreferred() {
final IconDescriptor containerDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_1, TEST_SIZE, "image/x-icon");
final IconDescriptor faviconDescriptor = IconDescriptor.createFavicon(TEST_ICON_URL_2, TEST_SIZE, "image/png");
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(1, comparator.compare(faviconDescriptor, containerDescriptor));
Assert.assertEquals(-1, comparator.compare(containerDescriptor, faviconDescriptor));
}
@Test
public void testWithNoDifferences() {
final IconDescriptor descriptor1 = IconDescriptor.createFavicon(TEST_ICON_URL_1, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptor descriptor2 = IconDescriptor.createFavicon(TEST_ICON_URL_2, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertNotEquals(0, comparator.compare(descriptor1, descriptor2));
Assert.assertNotEquals(0, comparator.compare(descriptor2, descriptor1));
}
@Test
public void testWithSameObject() {
final IconDescriptor descriptor = IconDescriptor.createTouchicon(TEST_ICON_URL_1, TEST_SIZE, TEST_MIME_TYPE);
final IconDescriptorComparator comparator = new IconDescriptorComparator();
Assert.assertEquals(0, comparator.compare(descriptor, descriptor));
}
/**
* This test reconstructs the scenario from bug 1331808. A comparator implementation that does
* not return a consistent order can break the implementation of remove() of the TreeSet class.
*/
@Test
public void testBug1331808() {
TreeSet<IconDescriptor> set = new TreeSet<>(new IconDescriptorComparator());
set.add(IconDescriptor.createFavicon("http://example.org/new-logo32.jpg", 0, ""));
set.add(IconDescriptor.createTouchicon("http://example.org/new-logo57.jpg", 0, ""));
set.add(IconDescriptor.createTouchicon("http://example.org/new-logo76.jpg", 76, ""));
set.add(IconDescriptor.createTouchicon("http://example.org/new-logo120.jpg", 120, ""));
set.add(IconDescriptor.createTouchicon("http://example.org/new-logo152.jpg", 114, ""));
set.add(IconDescriptor.createFavicon("http://example.org/02.png", 32, ""));
set.add(IconDescriptor.createFavicon("http://example.org/01.png", 192, ""));
set.add(IconDescriptor.createTouchicon("http://example.org/03.png", 0, ""));
for (int i = 8; i > 0; i--) {
Assert.assertEquals("items in set before deleting: " + i, i, set.size());
Assert.assertTrue("item removed successfully: " + i, set.remove(set.first()));
Assert.assertEquals("items in set after deleting: " + i, i - 1, set.size());
}
}
}

View file

@ -1,81 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.icons;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
import java.util.TreeSet;
import static org.hamcrest.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@RunWith(TestRunner.class)
public class TestIconRequest {
private static final String TEST_PAGE_URL = "http://www.mozilla.org";
private static final String TEST_ICON_URL_1 = "http://www.mozilla.org/favicon.ico";
private static final String TEST_ICON_URL_2 = "http://www.example.org/favicon.ico";
@Test
public void testIconHandling() {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL)
.build();
Assert.assertEquals(0, request.getIconCount());
Assert.assertFalse(request.hasIconDescriptors());
request.modify()
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_1))
.deferBuild();
Assert.assertEquals(1, request.getIconCount());
Assert.assertTrue(request.hasIconDescriptors());
request.modify()
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_2))
.deferBuild();
Assert.assertEquals(2, request.getIconCount());
Assert.assertTrue(request.hasIconDescriptors());
Assert.assertEquals(TEST_ICON_URL_2, request.getBestIcon().getUrl());
request.moveToNextIcon();
Assert.assertEquals(1, request.getIconCount());
Assert.assertTrue(request.hasIconDescriptors());
Assert.assertEquals(TEST_ICON_URL_1, request.getBestIcon().getUrl());
request.moveToNextIcon();
Assert.assertEquals(0, request.getIconCount());
Assert.assertFalse(request.hasIconDescriptors());
}
/**
* If removing an icon from the internal set failed then we want to throw an exception.
*/
@Test(expected = IllegalStateException.class)
public void testMoveToNextIconThrowsException() {
final IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL)
.build();
//noinspection unchecked - Creating a mock of a generic type
request.icons = (TreeSet<IconDescriptor>) mock(TreeSet.class);
//noinspection SuspiciousMethodCalls
doReturn(false).when(request.icons).remove(anyObject());
request.moveToNextIcon();
}
}

View file

@ -1,159 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.icons;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(TestRunner.class)
public class TestIconRequestBuilder {
private static final String TEST_PAGE_URL_1 = "http://www.mozilla.org";
private static final String TEST_PAGE_URL_2 = "http://www.example.org";
private static final String TEST_ICON_URL_1 = "http://www.mozilla.org/favicon.ico";
private static final String TEST_ICON_URL_2 = "http://www.example.org/favicon.ico";
@Test
public void testPrivileged() {
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertFalse(request.isPrivileged());
request.modify()
.privileged(true)
.deferBuild();
Assert.assertTrue(request.isPrivileged());
}
@Test
public void testPageUrl() {
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertEquals(TEST_PAGE_URL_1, request.getPageUrl());
request.modify()
.pageUrl(TEST_PAGE_URL_2)
.deferBuild();
Assert.assertEquals(TEST_PAGE_URL_2, request.getPageUrl());
}
@Test
public void testIcons() {
// Initially a request is empty.
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertEquals(0, request.getIconCount());
// Adding one icon URL.
request.modify()
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_1))
.deferBuild();
Assert.assertEquals(1, request.getIconCount());
// Adding the same icon URL again is ignored.
request.modify()
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_1))
.deferBuild();
Assert.assertEquals(1, request.getIconCount());
// Adding another new icon URL.
request.modify()
.icon(IconDescriptor.createGenericIcon(TEST_ICON_URL_2))
.deferBuild();
Assert.assertEquals(2, request.getIconCount());
}
@Test
public void testSkipNetwork() {
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertFalse(request.shouldSkipNetwork());
request.modify()
.skipNetwork()
.deferBuild();
Assert.assertTrue(request.shouldSkipNetwork());
}
@Test
public void testSkipDisk() {
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertFalse(request.shouldSkipDisk());
request.modify()
.skipDisk()
.deferBuild();
Assert.assertTrue(request.shouldSkipDisk());
}
@Test
public void testSkipMemory() {
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertFalse(request.shouldSkipMemory());
request.modify()
.skipMemory()
.deferBuild();
Assert.assertTrue(request.shouldSkipMemory());
}
@Test
public void testExecutionOnBackgroundThread() {
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertFalse(request.shouldRunOnBackgroundThread());
request.modify()
.executeCallbackOnBackgroundThread()
.deferBuild();
Assert.assertTrue(request.shouldRunOnBackgroundThread());
}
@Test
public void testForLauncherIcon() {
// This code will call into GeckoAppShell to determine the launcher icon size for this configuration
GeckoAppShell.setApplicationContext(RuntimeEnvironment.application);
IconRequest request = Icons.with(RuntimeEnvironment.application)
.pageUrl(TEST_PAGE_URL_1)
.build();
Assert.assertEquals(32, request.getTargetSize());
request.modify()
.forLauncherIcon()
.deferBuild();
Assert.assertEquals(48, request.getTargetSize());
}
}

View file

@ -1,148 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.icons;
import android.graphics.Bitmap;
import android.graphics.Color;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import static org.mockito.Mockito.mock;
@RunWith(TestRunner.class)
public class TestIconResponse {
private static final String ICON_URL = "http://www.mozilla.org/favicon.ico";
@Test
public void testDefaultResponse() {
final Bitmap bitmap = mock(Bitmap.class);
final IconResponse response = IconResponse.create(bitmap);
Assert.assertEquals(bitmap, response.getBitmap());
Assert.assertFalse(response.hasUrl());
Assert.assertNull(response.getUrl());
Assert.assertFalse(response.hasColor());
Assert.assertEquals(0, response.getColor());
Assert.assertFalse(response.isGenerated());
Assert.assertFalse(response.isFromNetwork());
Assert.assertFalse(response.isFromDisk());
Assert.assertFalse(response.isFromMemory());
}
@Test
public void testNetworkResponse() {
final Bitmap bitmap = mock(Bitmap.class);
final IconResponse response = IconResponse.createFromNetwork(bitmap, ICON_URL);
Assert.assertEquals(bitmap, response.getBitmap());
Assert.assertTrue(response.hasUrl());
Assert.assertEquals(ICON_URL, response.getUrl());
Assert.assertFalse(response.hasColor());
Assert.assertEquals(0, response.getColor());
Assert.assertFalse(response.isGenerated());
Assert.assertTrue(response.isFromNetwork());
Assert.assertFalse(response.isFromDisk());
Assert.assertFalse(response.isFromMemory());
}
@Test
public void testGeneratedResponse() {
final Bitmap bitmap = mock(Bitmap.class);
final IconResponse response = IconResponse.createGenerated(bitmap, Color.CYAN);
Assert.assertEquals(bitmap, response.getBitmap());
Assert.assertFalse(response.hasUrl());
Assert.assertNull(response.getUrl());
Assert.assertTrue(response.hasColor());
Assert.assertEquals(Color.CYAN, response.getColor());
Assert.assertTrue(response.isGenerated());
Assert.assertFalse(response.isFromNetwork());
Assert.assertFalse(response.isFromDisk());
Assert.assertFalse(response.isFromMemory());
}
@Test
public void testMemoryResponse() {
final Bitmap bitmap = mock(Bitmap.class);
final IconResponse response = IconResponse.createFromMemory(bitmap, ICON_URL, Color.CYAN);
Assert.assertEquals(bitmap, response.getBitmap());
Assert.assertTrue(response.hasUrl());
Assert.assertEquals(ICON_URL, response.getUrl());
Assert.assertTrue(response.hasColor());
Assert.assertEquals(Color.CYAN, response.getColor());
Assert.assertFalse(response.isGenerated());
Assert.assertFalse(response.isFromNetwork());
Assert.assertFalse(response.isFromDisk());
Assert.assertTrue(response.isFromMemory());
}
@Test
public void testDiskResponse() {
final Bitmap bitmap = mock(Bitmap.class);
final IconResponse response = IconResponse.createFromDisk(bitmap, ICON_URL);
Assert.assertEquals(bitmap, response.getBitmap());
Assert.assertTrue(response.hasUrl());
Assert.assertEquals(ICON_URL, response.getUrl());
Assert.assertFalse(response.hasColor());
Assert.assertEquals(0, response.getColor());
Assert.assertFalse(response.isGenerated());
Assert.assertFalse(response.isFromNetwork());
Assert.assertTrue(response.isFromDisk());
Assert.assertFalse(response.isFromMemory());
}
@Test
public void testUpdatingColor() {
final IconResponse response = IconResponse.create(mock(Bitmap.class));
Assert.assertFalse(response.hasColor());
Assert.assertEquals(0, response.getColor());
response.updateColor(Color.YELLOW);
Assert.assertTrue(response.hasColor());
Assert.assertEquals(Color.YELLOW, response.getColor());
response.updateColor(Color.MAGENTA);
Assert.assertTrue(response.hasColor());
Assert.assertEquals(Color.MAGENTA, response.getColor());
}
@Test
public void testUpdatingBitmap() {
final Bitmap originalBitmap = mock(Bitmap.class);
final Bitmap updatedBitmap = mock(Bitmap.class);
final IconResponse response = IconResponse.create(originalBitmap);
Assert.assertEquals(originalBitmap, response.getBitmap());
Assert.assertNotEquals(updatedBitmap, response.getBitmap());
response.updateBitmap(updatedBitmap);
Assert.assertNotEquals(originalBitmap, response.getBitmap());
Assert.assertEquals(updatedBitmap, response.getBitmap());
}
}

Some files were not shown because too many files have changed in this diff Show more