mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-22 00:17:32 +09:00
Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android
This commit is contained in:
parent
85045bf6be
commit
c9b411d6cd
3891 changed files with 0 additions and 428084 deletions
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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+ "\"}";
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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]");
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue