mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-15 11:47:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
|
|
@ -0,0 +1,23 @@
|
|||
#filter substitution
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.mozilla.gecko.background.tests"
|
||||
sharedUserId="@MOZ_ANDROID_SHARED_ID@"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0" >
|
||||
|
||||
<uses-sdk android:minSdkVersion="8"
|
||||
android:targetSdkVersion="@ANDROID_TARGET_SDK@" />
|
||||
|
||||
<application
|
||||
android:debuggable="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@ANDROID_BACKGROUND_APP_DISPLAYNAME@">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
android:label="@string/app_name"
|
||||
android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="@ANDROID_BACKGROUND_TARGET_PACKAGE_NAME@" />
|
||||
</manifest>
|
||||
13
mobile/android/tests/background/junit3/Makefile.in
Normal file
13
mobile/android/tests/background/junit3/Makefile.in
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# 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/.
|
||||
|
||||
ANDROID_EXTRA_JARS := \
|
||||
background-junit3.jar \
|
||||
$(NULL)
|
||||
|
||||
ANDROID_MANIFEST_FILE := $(CURDIR)/AndroidManifest.xml
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
tools:: $(ANDROID_APK_NAME).apk
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
background_junit3_sources = [
|
||||
'src/org/mozilla/gecko/background/common/TestAndroidLogWriters.java',
|
||||
'src/org/mozilla/gecko/background/common/TestUtils.java',
|
||||
'src/org/mozilla/gecko/background/common/TestWaitHelper.java',
|
||||
'src/org/mozilla/gecko/background/db/AndroidBrowserRepositoryTestCase.java',
|
||||
'src/org/mozilla/gecko/background/db/TestAndroidBrowserBookmarksRepository.java',
|
||||
'src/org/mozilla/gecko/background/db/TestAndroidBrowserHistoryRepository.java',
|
||||
'src/org/mozilla/gecko/background/db/TestBookmarks.java',
|
||||
'src/org/mozilla/gecko/background/db/TestClientsDatabase.java',
|
||||
'src/org/mozilla/gecko/background/db/TestClientsDatabaseAccessor.java',
|
||||
'src/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java',
|
||||
'src/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java',
|
||||
'src/org/mozilla/gecko/background/db/TestPasswordsRepository.java',
|
||||
'src/org/mozilla/gecko/background/db/TestTopSites.java',
|
||||
'src/org/mozilla/gecko/background/fxa/authenticator/TestAccountPickler.java',
|
||||
'src/org/mozilla/gecko/background/fxa/TestAccountLoader.java',
|
||||
'src/org/mozilla/gecko/background/fxa/TestBrowserIDKeyPairGeneration.java',
|
||||
'src/org/mozilla/gecko/background/helpers/AndroidSyncTestCase.java',
|
||||
'src/org/mozilla/gecko/background/helpers/DBHelpers.java',
|
||||
'src/org/mozilla/gecko/background/helpers/DBProviderTestCase.java',
|
||||
'src/org/mozilla/gecko/background/nativecode/test/TestNativeCrypto.java',
|
||||
'src/org/mozilla/gecko/background/sync/AndroidSyncTestCaseWithAccounts.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/BookmarkHelpers.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultBeginDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultCleanDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultFetchDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultFinishDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultGuidsSinceDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultSessionCreationDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/DefaultStoreDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectBeginDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectBeginFailDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectFetchDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectFetchSinceDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectFinishDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectFinishFailDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectGuidsSinceDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectInvalidRequestFetchDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectInvalidTypeStoreDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectManyStoredDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectNoGUIDsSinceDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectNoStoreDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectStoreCompletedDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/ExpectStoredDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/HistoryHelpers.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/PasswordHelpers.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/SessionTestHelper.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessBeginDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessCreationDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessFetchDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessFinishDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/helpers/SimpleSuccessStoreDelegate.java',
|
||||
'src/org/mozilla/gecko/background/sync/TestClientsStage.java',
|
||||
'src/org/mozilla/gecko/background/sync/TestResetting.java',
|
||||
'src/org/mozilla/gecko/background/sync/TestStoreTracking.java',
|
||||
'src/org/mozilla/gecko/background/sync/TestSyncConfiguration.java',
|
||||
'src/org/mozilla/gecko/background/sync/TestWebURLFinder.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/BaseMockServerSyncStage.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/CommandHelpers.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/DefaultGlobalSessionCallback.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockAbstractNonRepositorySyncStage.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockClientsDatabaseAccessor.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockClientsDataDelegate.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockRecord.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockServerSyncStage.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/MockSharedPreferences.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/WaitHelper.java',
|
||||
'src/org/mozilla/gecko/background/testhelpers/WBORepository.java',
|
||||
]
|
||||
22
mobile/android/tests/background/junit3/instrumentation.ini
Normal file
22
mobile/android/tests/background/junit3/instrumentation.ini
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
[DEFAULT]
|
||||
subsuite = background
|
||||
|
||||
[src/org/mozilla/gecko/background/common/TestAndroidLogWriters.java]
|
||||
[src/org/mozilla/gecko/background/common/TestUtils.java]
|
||||
[src/org/mozilla/gecko/background/common/TestWaitHelper.java]
|
||||
[src/org/mozilla/gecko/background/db/TestAndroidBrowserBookmarksRepository.java]
|
||||
[src/org/mozilla/gecko/background/db/TestAndroidBrowserHistoryRepository.java]
|
||||
[src/org/mozilla/gecko/background/db/TestBookmarks.java]
|
||||
[src/org/mozilla/gecko/background/db/TestClientsDatabase.java]
|
||||
[src/org/mozilla/gecko/background/db/TestClientsDatabaseAccessor.java]
|
||||
[src/org/mozilla/gecko/background/db/TestFennecTabsRepositorySession.java]
|
||||
[src/org/mozilla/gecko/background/db/TestFormHistoryRepositorySession.java]
|
||||
[src/org/mozilla/gecko/background/db/TestPasswordsRepository.java]
|
||||
[src/org/mozilla/gecko/background/fxa/TestBrowserIDKeyPairGeneration.java]
|
||||
[src/org/mozilla/gecko/background/fxa/authenticator/TestAccountPickler.java]
|
||||
[src/org/mozilla/gecko/background/nativecode/test/TestNativeCrypto.java]
|
||||
[src/org/mozilla/gecko/background/sync/TestClientsStage.java]
|
||||
[src/org/mozilla/gecko/background/sync/TestResetting.java]
|
||||
[src/org/mozilla/gecko/background/sync/TestStoreTracking.java]
|
||||
[src/org/mozilla/gecko/background/sync/TestSyncConfiguration.java]
|
||||
[src/org/mozilla/gecko/background/sync/TestWebURLFinder.java]
|
||||
42
mobile/android/tests/background/junit3/moz.build
Normal file
42
mobile/android/tests/background/junit3/moz.build
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
|
||||
|
||||
ANDROID_APK_NAME = 'background-junit3-debug'
|
||||
ANDROID_APK_PACKAGE = 'org.mozilla.gecko.background.tests'
|
||||
|
||||
include('background_junit3_sources.mozbuild')
|
||||
|
||||
jar = add_java_jar('background-junit3')
|
||||
jar.sources += background_junit3_sources
|
||||
jar.extra_jars += [
|
||||
CONFIG['ANDROID_SUPPORT_V4_AAR_LIB'],
|
||||
CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_LIB'],
|
||||
TOPOBJDIR + '/mobile/android/base/constants.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/gecko-R.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/gecko-browser.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/gecko-mozglue.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/gecko-thirdparty.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/gecko-util.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/gecko-view.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/services.jar',
|
||||
TOPOBJDIR + '/mobile/android/base/sync-thirdparty.jar',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_ANDROID_MLS_STUMBLER']:
|
||||
jar.extra_jars += [
|
||||
TOPOBJDIR + '/mobile/android/stumbler/stumbler.jar',
|
||||
]
|
||||
|
||||
ANDROID_INSTRUMENTATION_MANIFESTS += ['instrumentation.ini']
|
||||
|
||||
DEFINES['ANDROID_BACKGROUND_TARGET_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
|
||||
DEFINES['ANDROID_BACKGROUND_APP_DISPLAYNAME'] = '%s Background Tests' % CONFIG['MOZ_APP_DISPLAYNAME']
|
||||
DEFINES['MOZ_ANDROID_SHARED_ID'] = CONFIG['MOZ_ANDROID_SHARED_ID']
|
||||
OBJDIR_PP_FILES.mobile.android.tests.background.junit3 += [
|
||||
'AndroidManifest.xml.in',
|
||||
]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
12
mobile/android/tests/background/junit3/res/layout/main.xml
Normal file
12
mobile/android/tests/background/junit3/res/layout/main.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Gecko Background Tests</string>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.common;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.common.log.writers.AndroidLevelCachingLogWriter;
|
||||
import org.mozilla.gecko.background.common.log.writers.AndroidLogWriter;
|
||||
import org.mozilla.gecko.background.common.log.writers.LogWriter;
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
|
||||
public class TestAndroidLogWriters extends AndroidSyncTestCase {
|
||||
public static final String TEST_LOG_TAG = "TestAndroidLogWriters";
|
||||
|
||||
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";
|
||||
|
||||
public void setUp() {
|
||||
Logger.stopLoggingToAll();
|
||||
}
|
||||
|
||||
public void tearDown() {
|
||||
Logger.resetLogging();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify these *all* appear in the Android log by using
|
||||
* <code>adb logcat | grep TestAndroidLogWriters</code> after executing
|
||||
* <code>adb shell setprop log.tag.TestAndroidLogWriters ERROR</code>.
|
||||
* <p>
|
||||
* This writer does not use the Android log levels!
|
||||
*/
|
||||
public void testAndroidLogWriter() {
|
||||
LogWriter lw = new AndroidLogWriter();
|
||||
|
||||
Logger.error(TEST_LOG_TAG, TEST_MESSAGE_1, new RuntimeException());
|
||||
Logger.startLoggingTo(lw);
|
||||
Logger.error(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.warn(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.info(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.debug(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.trace(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.stopLoggingTo(lw);
|
||||
Logger.error(TEST_LOG_TAG, TEST_MESSAGE_3, new RuntimeException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify only *some* of these appear in the Android log by using
|
||||
* <code>adb logcat | grep TestAndroidLogWriters</code> after executing
|
||||
* <code>adb shell setprop log.tag.TestAndroidLogWriters INFO</code>.
|
||||
* <p>
|
||||
* This writer should use the Android log levels!
|
||||
*/
|
||||
public void testAndroidLevelCachingLogWriter() throws Exception {
|
||||
LogWriter lw = new AndroidLevelCachingLogWriter(new AndroidLogWriter());
|
||||
|
||||
Logger.error(TEST_LOG_TAG, TEST_MESSAGE_1, new RuntimeException());
|
||||
Logger.startLoggingTo(lw);
|
||||
Logger.error(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.warn(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.info(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.debug(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.trace(TEST_LOG_TAG, TEST_MESSAGE_2);
|
||||
Logger.stopLoggingTo(lw);
|
||||
Logger.error(TEST_LOG_TAG, TEST_MESSAGE_3, new RuntimeException());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.common;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
public class TestUtils extends AndroidSyncTestCase {
|
||||
protected static void assertStages(String[] all, String[] sync, String[] skip, String[] expected) {
|
||||
final Set<String> sAll = new HashSet<String>();
|
||||
for (String s : all) {
|
||||
sAll.add(s);
|
||||
}
|
||||
List<String> sSync = null;
|
||||
if (sync != null) {
|
||||
sSync = new ArrayList<String>();
|
||||
for (String s : sync) {
|
||||
sSync.add(s);
|
||||
}
|
||||
}
|
||||
List<String> sSkip = null;
|
||||
if (skip != null) {
|
||||
sSkip = new ArrayList<String>();
|
||||
for (String s : skip) {
|
||||
sSkip.add(s);
|
||||
}
|
||||
}
|
||||
List<String> stages = new ArrayList<String>(Utils.getStagesToSync(sAll, sSync, sSkip));
|
||||
Collections.sort(stages);
|
||||
List<String> exp = new ArrayList<String>();
|
||||
for (String e : expected) {
|
||||
exp.add(e);
|
||||
}
|
||||
assertEquals(exp, stages);
|
||||
}
|
||||
|
||||
public void testGetStagesToSync() {
|
||||
final String[] all = new String[] { "other1", "other2", "skip1", "skip2", "sync1", "sync2" };
|
||||
assertStages(all, null, null, all);
|
||||
assertStages(all, new String[] { "sync1" }, null, new String[] { "sync1" });
|
||||
assertStages(all, null, new String[] { "skip1", "skip2" }, new String[] { "other1", "other2", "sync1", "sync2" });
|
||||
assertStages(all, new String[] { "sync1", "sync2" }, new String[] { "skip1", "skip2" }, new String[] { "sync1", "sync2" });
|
||||
}
|
||||
|
||||
protected static void assertStagesFromBundle(String[] all, String[] sync, String[] skip, String[] expected) {
|
||||
final Set<String> sAll = new HashSet<String>();
|
||||
for (String s : all) {
|
||||
sAll.add(s);
|
||||
}
|
||||
final Bundle bundle = new Bundle();
|
||||
Utils.putStageNamesToSync(bundle, sync, skip);
|
||||
|
||||
Collection<String> ss = Utils.getStagesToSyncFromBundle(sAll, bundle);
|
||||
List<String> stages = new ArrayList<String>(ss);
|
||||
Collections.sort(stages);
|
||||
List<String> exp = new ArrayList<String>();
|
||||
for (String e : expected) {
|
||||
exp.add(e);
|
||||
}
|
||||
assertEquals(exp, stages);
|
||||
}
|
||||
|
||||
public void testGetStagesToSyncFromBundle() {
|
||||
final String[] all = new String[] { "other1", "other2", "skip1", "skip2", "sync1", "sync2" };
|
||||
assertStagesFromBundle(all, null, null, all);
|
||||
assertStagesFromBundle(all, new String[] { "sync1" }, null, new String[] { "sync1" });
|
||||
assertStagesFromBundle(all, null, new String[] { "skip1", "skip2" }, new String[] { "other1", "other2", "sync1", "sync2" });
|
||||
assertStagesFromBundle(all, new String[] { "sync1", "sync2" }, new String[] { "skip1", "skip2" }, new String[] { "sync1", "sync2" });
|
||||
}
|
||||
|
||||
public static void deleteDirectoryRecursively(final File dir) throws IOException {
|
||||
if (!dir.isDirectory()) {
|
||||
throw new IllegalStateException("Given directory, " + dir + ", is not a directory!");
|
||||
}
|
||||
|
||||
for (File f : dir.listFiles()) {
|
||||
if (f.isDirectory()) {
|
||||
deleteDirectoryRecursively(f);
|
||||
} else if (!f.delete()) {
|
||||
// Since this method is for testing, we assume we should be able to do this.
|
||||
throw new IOException("Could not delete file, " + f.getAbsolutePath() + ". Permissions?");
|
||||
}
|
||||
}
|
||||
|
||||
if (!dir.delete()) {
|
||||
throw new IOException("Could not delete dir, " + dir.getAbsolutePath() + ".");
|
||||
}
|
||||
}
|
||||
|
||||
public void testDeleteDirectoryRecursively() throws Exception {
|
||||
final String TEST_DIR = getApplicationContext().getCacheDir().getAbsolutePath() +
|
||||
"-testDeleteDirectory-" + System.currentTimeMillis();
|
||||
|
||||
// Non-existent directory.
|
||||
final File nonexistent = new File("nonexistentDirectory"); // Hopefully. ;)
|
||||
assertFalse(nonexistent.exists());
|
||||
try {
|
||||
deleteDirectoryRecursively(nonexistent);
|
||||
fail("deleteDirectoryRecursively on a nonexistent directory should throw Exception");
|
||||
} catch (IllegalStateException e) { }
|
||||
|
||||
// Empty dir.
|
||||
File dir = mkdir(TEST_DIR);
|
||||
deleteDirectoryRecursively(dir);
|
||||
assertFalse(dir.exists());
|
||||
|
||||
// Filled dir.
|
||||
dir = mkdir(TEST_DIR);
|
||||
populateDir(dir);
|
||||
deleteDirectoryRecursively(dir);
|
||||
assertFalse(dir.exists());
|
||||
|
||||
// Filled dir with empty dir.
|
||||
dir = mkdir(TEST_DIR);
|
||||
populateDir(dir);
|
||||
File subDir = new File(TEST_DIR + File.separator + "subDir");
|
||||
assertTrue(subDir.mkdir());
|
||||
deleteDirectoryRecursively(dir);
|
||||
assertFalse(subDir.exists()); // For short-circuiting errors.
|
||||
assertFalse(dir.exists());
|
||||
|
||||
// Filled dir with filled dir.
|
||||
dir = mkdir(TEST_DIR);
|
||||
populateDir(dir);
|
||||
subDir = new File(TEST_DIR + File.separator + "subDir");
|
||||
assertTrue(subDir.mkdir());
|
||||
populateDir(subDir);
|
||||
deleteDirectoryRecursively(dir);
|
||||
assertFalse(subDir.exists()); // For short-circuiting errors.
|
||||
assertFalse(dir.exists());
|
||||
}
|
||||
|
||||
private File mkdir(final String name) {
|
||||
final File dir = new File(name);
|
||||
assertTrue(dir.mkdir());
|
||||
return dir;
|
||||
}
|
||||
|
||||
private void populateDir(final File dir) throws IOException {
|
||||
assertTrue(dir.isDirectory());
|
||||
final String dirPath = dir.getAbsolutePath();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final File f = new File(dirPath + File.separator + i);
|
||||
assertTrue(f.createNewFile()); // Throws IOException if file could not be created.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.common;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper.InnerError;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper.TimeoutError;
|
||||
import org.mozilla.gecko.sync.ThreadPool;
|
||||
|
||||
public class TestWaitHelper extends AndroidSyncTestCase {
|
||||
private static final String ERROR_UNIQUE_IDENTIFIER = "error unique identifier";
|
||||
|
||||
public static int NO_WAIT = 1; // Milliseconds.
|
||||
public static int SHORT_WAIT = 100; // Milliseconds.
|
||||
public static int LONG_WAIT = 3 * SHORT_WAIT;
|
||||
|
||||
private Object notifyMonitor = new Object();
|
||||
// Guarded by notifyMonitor.
|
||||
private boolean performNotifyCalled = false;
|
||||
private boolean performNotifyErrorCalled = false;
|
||||
private void setPerformNotifyCalled() {
|
||||
synchronized (notifyMonitor) {
|
||||
performNotifyCalled = true;
|
||||
}
|
||||
}
|
||||
private void setPerformNotifyErrorCalled() {
|
||||
synchronized (notifyMonitor) {
|
||||
performNotifyErrorCalled = true;
|
||||
}
|
||||
}
|
||||
private void resetNotifyCalled() {
|
||||
synchronized (notifyMonitor) {
|
||||
performNotifyCalled = false;
|
||||
performNotifyErrorCalled = false;
|
||||
}
|
||||
}
|
||||
private void assertBothCalled() {
|
||||
synchronized (notifyMonitor) {
|
||||
assertTrue(performNotifyCalled);
|
||||
assertTrue(performNotifyErrorCalled);
|
||||
}
|
||||
}
|
||||
private void assertErrorCalled() {
|
||||
synchronized (notifyMonitor) {
|
||||
assertFalse(performNotifyCalled);
|
||||
assertTrue(performNotifyErrorCalled);
|
||||
}
|
||||
}
|
||||
private void assertCalled() {
|
||||
synchronized (notifyMonitor) {
|
||||
assertTrue(performNotifyCalled);
|
||||
assertFalse(performNotifyErrorCalled);
|
||||
}
|
||||
}
|
||||
|
||||
public WaitHelper waitHelper;
|
||||
|
||||
public TestWaitHelper() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setUp() {
|
||||
WaitHelper.resetTestWaiter();
|
||||
waitHelper = WaitHelper.getTestWaiter();
|
||||
resetNotifyCalled();
|
||||
}
|
||||
|
||||
public void tearDown() {
|
||||
assertTrue(waitHelper.isIdle());
|
||||
}
|
||||
|
||||
public Runnable performNothingRunnable() {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable performNotifyRunnable() {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable performNotifyAfterDelayRunnable(final int delayInMillis) {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(delayInMillis);
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted.");
|
||||
}
|
||||
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable performNotifyErrorRunnable() {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify(new AssertionFailedError(ERROR_UNIQUE_IDENTIFIER));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable inThreadPool(final Runnable runnable) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ThreadPool.run(runnable);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable inThread(final Runnable runnable) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(runnable).start();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void expectAssertionFailedError(Runnable runnable) {
|
||||
try {
|
||||
waitHelper.performWait(runnable);
|
||||
} catch (InnerError e) {
|
||||
AssertionFailedError inner = (AssertionFailedError)e.innerError;
|
||||
setPerformNotifyErrorCalled();
|
||||
String message = inner.getMessage();
|
||||
assertTrue("Expected '" + message + "' to contain '" + ERROR_UNIQUE_IDENTIFIER + "'",
|
||||
message.contains(ERROR_UNIQUE_IDENTIFIER));
|
||||
}
|
||||
}
|
||||
|
||||
protected void expectAssertionFailedErrorAfterDelay(int wait, Runnable runnable) {
|
||||
try {
|
||||
waitHelper.performWait(wait, runnable);
|
||||
} catch (InnerError e) {
|
||||
AssertionFailedError inner = (AssertionFailedError)e.innerError;
|
||||
setPerformNotifyErrorCalled();
|
||||
String message = inner.getMessage();
|
||||
assertTrue("Expected '" + message + "' to contain '" + ERROR_UNIQUE_IDENTIFIER + "'",
|
||||
message.contains(ERROR_UNIQUE_IDENTIFIER));
|
||||
}
|
||||
}
|
||||
|
||||
public void testPerformWait() {
|
||||
waitHelper.performWait(performNotifyRunnable());
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public void testPerformWaitInThread() {
|
||||
waitHelper.performWait(inThread(performNotifyRunnable()));
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public void testPerformWaitInThreadPool() {
|
||||
waitHelper.performWait(inThreadPool(performNotifyRunnable()));
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public void testPerformTimeoutWait() {
|
||||
waitHelper.performWait(SHORT_WAIT, performNotifyRunnable());
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public void testPerformTimeoutWaitInThread() {
|
||||
waitHelper.performWait(SHORT_WAIT, inThread(performNotifyRunnable()));
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public void testPerformTimeoutWaitInThreadPool() {
|
||||
waitHelper.performWait(SHORT_WAIT, inThreadPool(performNotifyRunnable()));
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public void testPerformErrorWaitInThread() {
|
||||
expectAssertionFailedError(inThread(performNotifyErrorRunnable()));
|
||||
assertBothCalled();
|
||||
}
|
||||
|
||||
public void testPerformErrorWaitInThreadPool() {
|
||||
expectAssertionFailedError(inThreadPool(performNotifyErrorRunnable()));
|
||||
assertBothCalled();
|
||||
}
|
||||
|
||||
public void testPerformErrorTimeoutWaitInThread() {
|
||||
expectAssertionFailedErrorAfterDelay(SHORT_WAIT, inThread(performNotifyErrorRunnable()));
|
||||
assertBothCalled();
|
||||
}
|
||||
|
||||
public void testPerformErrorTimeoutWaitInThreadPool() {
|
||||
expectAssertionFailedErrorAfterDelay(SHORT_WAIT, inThreadPool(performNotifyErrorRunnable()));
|
||||
assertBothCalled();
|
||||
}
|
||||
|
||||
public void testTimeout() {
|
||||
try {
|
||||
waitHelper.performWait(SHORT_WAIT, performNothingRunnable());
|
||||
} catch (TimeoutError e) {
|
||||
setPerformNotifyErrorCalled();
|
||||
assertEquals(SHORT_WAIT, e.waitTimeInMillis);
|
||||
}
|
||||
assertErrorCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* This will pass. The sequence in the main thread is:
|
||||
* - A short delay.
|
||||
* - performNotify is called.
|
||||
* - performWait is called and immediately finds that performNotify was called before.
|
||||
*/
|
||||
public void testDelay() {
|
||||
try {
|
||||
waitHelper.performWait(1, performNotifyAfterDelayRunnable(SHORT_WAIT));
|
||||
} catch (AssertionFailedError e) {
|
||||
setPerformNotifyErrorCalled();
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("TIMEOUT"));
|
||||
}
|
||||
assertCalled();
|
||||
}
|
||||
|
||||
public Runnable performNotifyMultipleTimesRunnable() {
|
||||
return new Runnable() {
|
||||
public void run() {
|
||||
waitHelper.performNotify();
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testPerformNotifyMultipleTimesFails() {
|
||||
try {
|
||||
waitHelper.performWait(NO_WAIT, performNotifyMultipleTimesRunnable()); // Not run on thread, so runnable executes before performWait looks for notifications.
|
||||
} catch (WaitHelper.MultipleNotificationsError e) {
|
||||
setPerformNotifyErrorCalled();
|
||||
}
|
||||
assertBothCalled();
|
||||
assertFalse(waitHelper.isIdle()); // First perform notify should be hanging around.
|
||||
waitHelper.performWait(NO_WAIT, performNothingRunnable());
|
||||
}
|
||||
|
||||
public void testNestedWaitsAndNotifies() {
|
||||
waitHelper.performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
waitHelper.performWait(new Runnable() {
|
||||
public void run() {
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
});
|
||||
setPerformNotifyErrorCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
});
|
||||
assertBothCalled();
|
||||
}
|
||||
|
||||
public void testAssertIsReported() {
|
||||
try {
|
||||
waitHelper.performWait(1, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
assertTrue("unique identifier", false);
|
||||
}
|
||||
});
|
||||
} catch (AssertionFailedError e) {
|
||||
setPerformNotifyErrorCalled();
|
||||
assertTrue(e.getMessage(), e.getMessage().contains("unique identifier"));
|
||||
}
|
||||
assertErrorCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* The inner wait will timeout, but the outer wait will succeed. The sequence in the helper thread is:
|
||||
* - A short delay.
|
||||
* - performNotify is called.
|
||||
*
|
||||
* The sequence in the main thread is:
|
||||
* - performWait is called and times out because the helper thread does not call
|
||||
* performNotify quickly enough.
|
||||
*/
|
||||
public void testDelayInThread() throws InterruptedException {
|
||||
waitHelper.performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
waitHelper.performWait(NO_WAIT, inThread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(SHORT_WAIT);
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted.");
|
||||
}
|
||||
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
}));
|
||||
} catch (WaitHelper.TimeoutError e) {
|
||||
setPerformNotifyErrorCalled();
|
||||
assertEquals(NO_WAIT, e.waitTimeInMillis);
|
||||
}
|
||||
}
|
||||
});
|
||||
assertBothCalled();
|
||||
}
|
||||
|
||||
/**
|
||||
* The inner wait will timeout, but the outer wait will succeed. The sequence in the helper thread is:
|
||||
* - A short delay.
|
||||
* - performNotify is called.
|
||||
*
|
||||
* The sequence in the main thread is:
|
||||
* - performWait is called and times out because the helper thread does not call
|
||||
* performNotify quickly enough.
|
||||
*/
|
||||
public void testDelayInThreadPool() throws InterruptedException {
|
||||
waitHelper.performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
waitHelper.performWait(NO_WAIT, inThreadPool(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(SHORT_WAIT);
|
||||
} catch (InterruptedException e) {
|
||||
fail("Interrupted.");
|
||||
}
|
||||
|
||||
setPerformNotifyCalled();
|
||||
waitHelper.performNotify();
|
||||
}
|
||||
}));
|
||||
} catch (WaitHelper.TimeoutError e) {
|
||||
setPerformNotifyErrorCalled();
|
||||
assertEquals(NO_WAIT, e.waitTimeInMillis);
|
||||
}
|
||||
}
|
||||
});
|
||||
assertBothCalled();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,818 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.sync.helpers.DefaultBeginDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.DefaultCleanDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.DefaultFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.DefaultFinishDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.DefaultSessionCreationDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.DefaultStoreDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectBeginDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectBeginFailDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFinishFailDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectInvalidRequestFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectManyStoredDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectStoreCompletedDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
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.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor;
|
||||
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 android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
||||
public abstract class AndroidBrowserRepositoryTestCase extends AndroidSyncTestCase {
|
||||
protected static String LOG_TAG = "BrowserRepositoryTest";
|
||||
|
||||
protected static void wipe(AndroidBrowserRepositoryDataAccessor helper) {
|
||||
Logger.debug(LOG_TAG, "Wiping.");
|
||||
try {
|
||||
helper.wipe();
|
||||
} catch (NullPointerException e) {
|
||||
// This will be handled in begin, here we can just ignore
|
||||
// the error if it actually occurs since this is just test
|
||||
// code. We will throw a ProfileDatabaseException. This
|
||||
// error shouldn't occur in the future, but results from
|
||||
// trying to access content providers before Fennec has
|
||||
// been run at least once.
|
||||
Logger.error(LOG_TAG, "ProfileDatabaseException seen in wipe. Begin should fail");
|
||||
fail("NullPointerException in wipe.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
|
||||
wipe(helper);
|
||||
assertTrue(WaitHelper.getTestWaiter().isIdle());
|
||||
closeDataAccessor(helper);
|
||||
}
|
||||
|
||||
public void tearDown() {
|
||||
assertTrue(WaitHelper.getTestWaiter().isIdle());
|
||||
}
|
||||
|
||||
protected RepositorySession createSession() {
|
||||
return SessionTestHelper.createSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
protected RepositorySession createAndBeginSession() {
|
||||
return SessionTestHelper.createAndBeginSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
protected static void dispose(RepositorySession session) {
|
||||
if (session != null) {
|
||||
session.abort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored.
|
||||
*/
|
||||
public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) {
|
||||
return new ExpectFetchDelegate(expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored.
|
||||
*/
|
||||
public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) {
|
||||
return new ExpectGuidsSinceDelegate(expected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any).
|
||||
*/
|
||||
public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() {
|
||||
return new ExpectGuidsSinceDelegate(new String[] {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored.
|
||||
*/
|
||||
public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) {
|
||||
return new ExpectFetchSinceDelegate(timestamp, expected);
|
||||
}
|
||||
|
||||
public static Runnable storeRunnable(final RepositorySession session, final Record record, final DefaultStoreDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.setStoreDelegate(delegate);
|
||||
try {
|
||||
session.store(record);
|
||||
session.storeDone();
|
||||
} catch (NoStoreDelegateException e) {
|
||||
fail("NoStoreDelegateException should not occur.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable storeRunnable(final RepositorySession session, final Record record) {
|
||||
return storeRunnable(session, record, new ExpectStoredDelegate(record.guid));
|
||||
}
|
||||
|
||||
public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records, final DefaultStoreDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.setStoreDelegate(delegate);
|
||||
try {
|
||||
for (Record record : records) {
|
||||
session.store(record);
|
||||
}
|
||||
session.storeDone();
|
||||
} catch (NoStoreDelegateException e) {
|
||||
fail("NoStoreDelegateException should not occur.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records) {
|
||||
return storeManyRunnable(session, records, new ExpectManyStoredDelegate(records));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a record and don't expect a store callback until we're done.
|
||||
*
|
||||
* @param session
|
||||
* @param record
|
||||
* @return Runnable.
|
||||
*/
|
||||
public static Runnable quietStoreRunnable(final RepositorySession session, final Record record) {
|
||||
return storeRunnable(session, record, new ExpectStoreCompletedDelegate());
|
||||
}
|
||||
|
||||
public static Runnable beginRunnable(final RepositorySession session, final DefaultBeginDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session.begin(delegate);
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable finishRunnable(final RepositorySession session, final DefaultFinishDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session.finish(delegate);
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable fetchAllRunnable(final RepositorySession session, final ExpectFetchDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchAll(delegate);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) {
|
||||
return fetchAllRunnable(session, preparedExpectFetchDelegate(expectedRecords));
|
||||
}
|
||||
|
||||
public Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.guidsSince(timestamp, preparedExpectGuidsSinceDelegate(expected));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchSince(timestamp, preparedExpectFetchSinceDelegate(timestamp, expected));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final DefaultFetchDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session.fetch(guids, delegate);
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
public Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) {
|
||||
return fetchRunnable(session, guids, preparedExpectFetchDelegate(expected));
|
||||
}
|
||||
|
||||
public static Runnable cleanRunnable(final Repository repository, final boolean success, final Context context, final DefaultCleanDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
repository.clean(success, delegate, context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract Repository getRepository();
|
||||
protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor();
|
||||
|
||||
protected static void doStore(RepositorySession session, Record[] records) {
|
||||
performWait(storeManyRunnable(session, records));
|
||||
}
|
||||
|
||||
// Tests to implement
|
||||
public abstract void testFetchAll();
|
||||
public abstract void testGuidsSinceReturnMultipleRecords();
|
||||
public abstract void testGuidsSinceReturnNoRecords();
|
||||
public abstract void testFetchSinceOneRecord();
|
||||
public abstract void testFetchSinceReturnNoRecords();
|
||||
public abstract void testFetchOneRecordByGuid();
|
||||
public abstract void testFetchMultipleRecordsByGuids();
|
||||
public abstract void testFetchNoRecordByGuid();
|
||||
public abstract void testWipe();
|
||||
public abstract void testStore();
|
||||
public abstract void testRemoteNewerTimeStamp();
|
||||
public abstract void testLocalNewerTimeStamp();
|
||||
public abstract void testDeleteRemoteNewer();
|
||||
public abstract void testDeleteLocalNewer();
|
||||
public abstract void testDeleteRemoteLocalNonexistent();
|
||||
public abstract void testStoreIdenticalExceptGuid();
|
||||
public abstract void testCleanMultipleRecords();
|
||||
|
||||
|
||||
/*
|
||||
* Test abstractions
|
||||
*/
|
||||
protected void basicStoreTest(Record record) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(storeRunnable(session, record));
|
||||
}
|
||||
|
||||
protected void basicFetchAllTest(Record[] expected) {
|
||||
Logger.debug("rnewman", "Starting testFetchAll.");
|
||||
RepositorySession session = createAndBeginSession();
|
||||
Logger.debug("rnewman", "Prepared.");
|
||||
|
||||
AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
|
||||
helper.dumpDB();
|
||||
performWait(storeManyRunnable(session, expected));
|
||||
|
||||
helper.dumpDB();
|
||||
performWait(fetchAllRunnable(session, expected));
|
||||
|
||||
closeDataAccessor(helper);
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests for clean
|
||||
*/
|
||||
// Input: 4 records; 2 which are to be cleaned, 2 which should remain after the clean
|
||||
protected void cleanMultipleRecords(Record delete0, Record delete1, Record keep0, Record keep1, Record keep2) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
doStore(session, new Record[] {
|
||||
delete0, delete1, keep0, keep1, keep2
|
||||
});
|
||||
|
||||
// Force two records to appear deleted.
|
||||
AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
|
||||
db.updateByGuid(delete0.guid, cv);
|
||||
db.updateByGuid(delete1.guid, cv);
|
||||
|
||||
final DefaultCleanDelegate delegate = new DefaultCleanDelegate() {
|
||||
public void onCleaned(Repository repo) {
|
||||
performNotify();
|
||||
}
|
||||
};
|
||||
|
||||
final Runnable cleanRunnable = cleanRunnable(
|
||||
getRepository(),
|
||||
true,
|
||||
getApplicationContext(),
|
||||
delegate);
|
||||
|
||||
performWait(cleanRunnable);
|
||||
performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[] { keep0, keep1, keep2})));
|
||||
closeDataAccessor(db);
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests for guidsSince
|
||||
*/
|
||||
protected void guidsSinceReturnMultipleRecords(Record record0, Record record1) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
String[] expected = new String[2];
|
||||
expected[0] = record0.guid;
|
||||
expected[1] = record1.guid;
|
||||
|
||||
Logger.debug(getName(), "Storing two records...");
|
||||
performWait(storeManyRunnable(session, new Record[] { record0, record1 }));
|
||||
Logger.debug(getName(), "Getting guids since " + timestamp + "; expecting " + expected.length);
|
||||
performWait(guidsSinceRunnable(session, timestamp, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
protected void guidsSinceReturnNoRecords(Record record0) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
// Store 1 record in the past.
|
||||
performWait(storeRunnable(session, record0));
|
||||
|
||||
String[] expected = {};
|
||||
performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests for fetchSince
|
||||
*/
|
||||
protected void fetchSinceOneRecord(Record record0, Record record1) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
performWait(storeRunnable(session, record0));
|
||||
long timestamp = System.currentTimeMillis();
|
||||
Logger.debug("fetchSinceOneRecord", "Entering synchronized section. Timestamp " + timestamp);
|
||||
synchronized(this) {
|
||||
try {
|
||||
wait(1000);
|
||||
} catch (InterruptedException e) {
|
||||
Logger.warn("fetchSinceOneRecord", "Interrupted.", e);
|
||||
}
|
||||
}
|
||||
Logger.debug("fetchSinceOneRecord", "Storing.");
|
||||
performWait(storeRunnable(session, record1));
|
||||
|
||||
Logger.debug("fetchSinceOneRecord", "Fetching record 1.");
|
||||
String[] expectedOne = new String[] { record1.guid };
|
||||
performWait(fetchSinceRunnable(session, timestamp + 10, expectedOne));
|
||||
|
||||
Logger.debug("fetchSinceOneRecord", "Fetching both, relying on inclusiveness.");
|
||||
String[] expectedBoth = new String[] { record0.guid, record1.guid };
|
||||
performWait(fetchSinceRunnable(session, timestamp - 3000, expectedBoth));
|
||||
|
||||
Logger.debug("fetchSinceOneRecord", "Done.");
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
protected void fetchSinceReturnNoRecords(Record record) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
performWait(storeRunnable(session, record));
|
||||
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {}));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
protected void fetchOneRecordByGuid(Record record0, Record record1) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
Record[] store = new Record[] { record0, record1 };
|
||||
performWait(storeManyRunnable(session, store));
|
||||
|
||||
String[] guids = new String[] { record0.guid };
|
||||
Record[] expected = new Record[] { record0 };
|
||||
performWait(fetchRunnable(session, guids, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
protected void fetchMultipleRecordsByGuids(Record record0,
|
||||
Record record1, Record record2) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
Record[] store = new Record[] { record0, record1, record2 };
|
||||
performWait(storeManyRunnable(session, store));
|
||||
|
||||
String[] guids = new String[] { record0.guid, record2.guid };
|
||||
Record[] expected = new Record[] { record0, record2 };
|
||||
performWait(fetchRunnable(session, guids, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
protected void fetchNoRecordByGuid(Record record) {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
performWait(storeRunnable(session, record));
|
||||
performWait(fetchRunnable(session,
|
||||
new String[] { Utils.generateGuid() },
|
||||
new Record[] {}));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test wipe
|
||||
*/
|
||||
protected void doWipe(final Record record0, final Record record1) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
final Runnable run = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.wipe(new RepositorySessionWipeDelegate() {
|
||||
public void onWipeSucceeded() {
|
||||
performNotify();
|
||||
}
|
||||
public void onWipeFailed(Exception ex) {
|
||||
fail("wipe should have succeeded");
|
||||
performNotify();
|
||||
}
|
||||
@Override
|
||||
public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) {
|
||||
final RepositorySessionWipeDelegate self = this;
|
||||
return new RepositorySessionWipeDelegate() {
|
||||
|
||||
@Override
|
||||
public void onWipeSucceeded() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onWipeSucceeded();
|
||||
}}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWipeFailed(final Exception ex) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onWipeFailed(ex);
|
||||
}}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService newExecutor) {
|
||||
if (newExecutor == executor) {
|
||||
return this;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't re-defer this delegate.");
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Store 2 records.
|
||||
Record[] records = new Record[] { record0, record1 };
|
||||
performWait(storeManyRunnable(session, records));
|
||||
performWait(fetchAllRunnable(session, records));
|
||||
|
||||
// Wipe.
|
||||
performWait(run);
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO adding or subtracting from lastModified timestamps does NOTHING
|
||||
* since it gets overwritten when we store stuff. See other tests
|
||||
* for ways to do this properly.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Record being stored has newer timestamp than existing local record, local
|
||||
* record has not been modified since last sync.
|
||||
*/
|
||||
protected void remoteNewerTimeStamp(Record local, Record remote) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
// Record existing and hasn't changed since before lastSync.
|
||||
// Automatically will be assigned lastModified = current time.
|
||||
performWait(storeRunnable(session, local));
|
||||
|
||||
remote.guid = local.guid;
|
||||
|
||||
// Get the timestamp and make remote newer than it
|
||||
ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local });
|
||||
performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate));
|
||||
remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000;
|
||||
performWait(storeRunnable(session, remote));
|
||||
|
||||
Record[] expected = new Record[] { remote };
|
||||
ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
|
||||
performWait(fetchAllRunnable(session, delegate));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Local record has a newer timestamp than the record being stored. For now,
|
||||
* we just take newer (local) record)
|
||||
*/
|
||||
protected void localNewerTimeStamp(Record local, Record remote) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
performWait(storeRunnable(session, local));
|
||||
|
||||
remote.guid = local.guid;
|
||||
|
||||
// Get the timestamp and make remote older than it
|
||||
ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local });
|
||||
performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate));
|
||||
remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000;
|
||||
performWait(storeRunnable(session, remote));
|
||||
|
||||
// Do a fetch and make sure that we get back the local record.
|
||||
Record[] expected = new Record[] { local };
|
||||
performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected)));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a record that is marked as deleted, remote has newer timestamp
|
||||
*/
|
||||
protected void deleteRemoteNewer(Record local, Record remote) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
// Record existing and hasn't changed since before lastSync.
|
||||
// Automatically will be assigned lastModified = current time.
|
||||
performWait(storeRunnable(session, local));
|
||||
|
||||
// Pass the same record to store, but mark it deleted and modified
|
||||
// more recently
|
||||
ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local });
|
||||
performWait(fetchRunnable(session, new String[] { local.guid }, timestampDelegate));
|
||||
remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000;
|
||||
remote.deleted = true;
|
||||
remote.guid = local.guid;
|
||||
performWait(storeRunnable(session, remote));
|
||||
|
||||
performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[]{})));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
// Store two records that are identical (this has different meanings based on the
|
||||
// type of record) other than their guids. The record existing locally already
|
||||
// should have its guid replaced (the assumption is that the record existed locally
|
||||
// and then sync was enabled and this record existed on another sync'd device).
|
||||
public void storeIdenticalExceptGuid(Record record0) {
|
||||
Logger.debug("storeIdenticalExceptGuid", "Started.");
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
Logger.debug("storeIdenticalExceptGuid", "Session is " + session);
|
||||
performWait(storeRunnable(session, record0));
|
||||
Logger.debug("storeIdenticalExceptGuid", "Stored record0.");
|
||||
DefaultFetchDelegate timestampDelegate = getTimestampDelegate(record0.guid);
|
||||
|
||||
performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate));
|
||||
Logger.debug("storeIdenticalExceptGuid", "fetchRunnable done.");
|
||||
record0.lastModified = timestampDelegate.records.get(0).lastModified + 3000;
|
||||
record0.guid = Utils.generateGuid();
|
||||
Logger.debug("storeIdenticalExceptGuid", "Storing modified...");
|
||||
performWait(storeRunnable(session, record0));
|
||||
Logger.debug("storeIdenticalExceptGuid", "Stored modified.");
|
||||
|
||||
Record[] expected = new Record[] { record0 };
|
||||
performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected)));
|
||||
Logger.debug("storeIdenticalExceptGuid", "Fetched all. Returning.");
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
// Special delegate so that we don't verify parenting is correct since
|
||||
// at some points it won't be since parent folder hasn't been stored.
|
||||
private DefaultFetchDelegate getTimestampDelegate(final String guid) {
|
||||
return new DefaultFetchDelegate() {
|
||||
@Override
|
||||
public void onFetchCompleted(final long fetchEnd) {
|
||||
assertEquals(guid, this.records.get(0).guid);
|
||||
performNotify();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a record that is marked as deleted, local has newer timestamp
|
||||
* and was not marked deleted (so keep it)
|
||||
*/
|
||||
protected void deleteLocalNewer(Record local, Record remote) {
|
||||
Logger.debug("deleteLocalNewer", "Begin.");
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
Logger.debug("deleteLocalNewer", "Storing local...");
|
||||
performWait(storeRunnable(session, local));
|
||||
|
||||
// Create an older version of a record with the same GUID.
|
||||
remote.guid = local.guid;
|
||||
|
||||
Logger.debug("deleteLocalNewer", "Fetching...");
|
||||
|
||||
// Get the timestamp and make remote older than it
|
||||
Record[] expected = new Record[] { local };
|
||||
ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(expected);
|
||||
performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate));
|
||||
|
||||
Logger.debug("deleteLocalNewer", "Fetched.");
|
||||
remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000;
|
||||
|
||||
Logger.debug("deleteLocalNewer", "Last modified is " + remote.lastModified);
|
||||
remote.deleted = true;
|
||||
Logger.debug("deleteLocalNewer", "Storing deleted...");
|
||||
performWait(quietStoreRunnable(session, remote)); // This appears to do a lot of work...?!
|
||||
|
||||
// Do a fetch and make sure that we get back the first (local) record.
|
||||
performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected)));
|
||||
Logger.debug("deleteLocalNewer", "Fetched and done!");
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a record that is marked as deleted, record never existed locally
|
||||
*/
|
||||
protected void deleteRemoteLocalNonexistent(Record remote) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
long timestamp = 1000000000;
|
||||
|
||||
// Pass a record marked deleted to store, doesn't exist locally
|
||||
remote.lastModified = timestamp;
|
||||
remote.deleted = true;
|
||||
performWait(quietStoreRunnable(session, remote));
|
||||
|
||||
ExpectFetchDelegate delegate = preparedExpectFetchDelegate(new Record[]{});
|
||||
performWait(fetchAllRunnable(session, delegate));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests that don't require specific records based on type of repository.
|
||||
* These tests don't need to be overriden in subclasses, they will just work.
|
||||
*/
|
||||
public void testCreateSessionNullContext() {
|
||||
Logger.debug(LOG_TAG, "In testCreateSessionNullContext.");
|
||||
Repository repo = getRepository();
|
||||
try {
|
||||
repo.createSession(new DefaultSessionCreationDelegate(), null);
|
||||
fail("Should throw.");
|
||||
} catch (Exception ex) {
|
||||
assertNotNull(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void testStoreNullRecord() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
try {
|
||||
session.setStoreDelegate(new DefaultStoreDelegate());
|
||||
session.store(null);
|
||||
fail("Should throw.");
|
||||
} catch (Exception ex) {
|
||||
assertNotNull(ex);
|
||||
}
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchNoGuids() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(fetchRunnable(session, new String[] {}, new ExpectInvalidRequestFetchDelegate()));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchNullGuids() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(fetchRunnable(session, null, new ExpectInvalidRequestFetchDelegate()));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testBeginOnNewSession() {
|
||||
final RepositorySession session = createSession();
|
||||
performWait(beginRunnable(session, new ExpectBeginDelegate()));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testBeginOnRunningSession() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
try {
|
||||
session.begin(new ExpectBeginFailDelegate());
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
dispose(session);
|
||||
return;
|
||||
}
|
||||
fail("Should have caught InvalidSessionTransitionException.");
|
||||
}
|
||||
|
||||
public void testBeginOnFinishedSession() throws InactiveSessionException {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(finishRunnable(session, new ExpectFinishDelegate()));
|
||||
try {
|
||||
session.begin(new ExpectBeginFailDelegate());
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
Logger.debug(getName(), "Yay! Got an exception.", e);
|
||||
dispose(session);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
Logger.debug(getName(), "Yay! Got an exception.", e);
|
||||
dispose(session);
|
||||
return;
|
||||
}
|
||||
fail("Should have caught InvalidSessionTransitionException.");
|
||||
}
|
||||
|
||||
public void testFinishOnFinishedSession() throws InactiveSessionException {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(finishRunnable(session, new ExpectFinishDelegate()));
|
||||
try {
|
||||
session.finish(new ExpectFinishFailDelegate());
|
||||
} catch (InactiveSessionException e) {
|
||||
dispose(session);
|
||||
return;
|
||||
}
|
||||
fail("Should have caught InactiveSessionException.");
|
||||
}
|
||||
|
||||
public void testFetchOnInactiveSession() throws InactiveSessionException {
|
||||
final RepositorySession session = createSession();
|
||||
try {
|
||||
session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate());
|
||||
} catch (InactiveSessionException e) {
|
||||
// Yay.
|
||||
dispose(session);
|
||||
return;
|
||||
};
|
||||
fail("Should have caught InactiveSessionException.");
|
||||
}
|
||||
|
||||
public void testFetchOnFinishedSession() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
Logger.debug(getName(), "Finishing...");
|
||||
performWait(finishRunnable(session, new ExpectFinishDelegate()));
|
||||
try {
|
||||
session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate());
|
||||
} catch (InactiveSessionException e) {
|
||||
// Yay.
|
||||
dispose(session);
|
||||
return;
|
||||
};
|
||||
fail("Should have caught InactiveSessionException.");
|
||||
}
|
||||
|
||||
public void testGuidsSinceOnUnstartedSession() {
|
||||
final RepositorySession session = createSession();
|
||||
Runnable run = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.guidsSince(System.currentTimeMillis(),
|
||||
new RepositorySessionGuidsSinceDelegate() {
|
||||
public void onGuidsSinceSucceeded(String[] guids) {
|
||||
fail("Session inactive, should fail");
|
||||
performNotify();
|
||||
}
|
||||
|
||||
public void onGuidsSinceFailed(Exception ex) {
|
||||
verifyInactiveException(ex);
|
||||
performNotify();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
performWait(run);
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
private static void verifyInactiveException(Exception ex) {
|
||||
if (!(ex instanceof InactiveSessionException)) {
|
||||
fail("Wrong exception type");
|
||||
}
|
||||
}
|
||||
|
||||
protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,636 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectInvalidTypeStoreDelegate;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
|
||||
import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepository;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
public class TestAndroidBrowserBookmarksRepository extends AndroidBrowserRepositoryTestCase {
|
||||
|
||||
@Override
|
||||
protected AndroidBrowserRepository getRepository() {
|
||||
|
||||
/**
|
||||
* Override this chain in order to avoid our test code having to create two
|
||||
* sessions all the time.
|
||||
*/
|
||||
return new AndroidBrowserBookmarksRepository() {
|
||||
@Override
|
||||
protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
|
||||
AndroidBrowserBookmarksRepositorySession session;
|
||||
session = new AndroidBrowserBookmarksRepositorySession(this, context) {
|
||||
@Override
|
||||
protected synchronized void trackGUID(String guid) {
|
||||
System.out.println("Ignoring trackGUID call: this is a test!");
|
||||
}
|
||||
};
|
||||
delegate.deferredCreationDelegate().onSessionCreated(session);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AndroidBrowserRepositoryDataAccessor getDataAccessor() {
|
||||
return new AndroidBrowserBookmarksDataAccessor(getApplicationContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored.
|
||||
*/
|
||||
@Override
|
||||
public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) {
|
||||
ExpectFetchDelegate delegate = new ExpectFetchDelegate(expected);
|
||||
delegate.ignore.addAll(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet());
|
||||
return delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any).
|
||||
*/
|
||||
public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() {
|
||||
ExpectGuidsSinceDelegate delegate = new ExpectGuidsSinceDelegate(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet().toArray(new String[] {}));
|
||||
return delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored.
|
||||
*/
|
||||
@Override
|
||||
public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) {
|
||||
ExpectGuidsSinceDelegate delegate = new ExpectGuidsSinceDelegate(expected);
|
||||
delegate.ignore.addAll(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet());
|
||||
return delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored.
|
||||
*/
|
||||
public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) {
|
||||
ExpectFetchSinceDelegate delegate = new ExpectFetchSinceDelegate(timestamp, expected);
|
||||
delegate.ignore.addAll(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet());
|
||||
return delegate;
|
||||
}
|
||||
|
||||
// NOTE NOTE NOTE
|
||||
// Must store folder before records if we we are checking that the
|
||||
// records returned are the same as those sent in. If the folder isn't stored
|
||||
// first, the returned records won't be identical to those stored because we
|
||||
// aren't able to find the parent name/guid when we do a fetch. If you don't want
|
||||
// to store a folder first, store your record in "mobile" or one of the folders
|
||||
// that always exists.
|
||||
|
||||
public void testFetchOneWithChildren() {
|
||||
BookmarkRecord folder = BookmarkHelpers.createFolder1();
|
||||
BookmarkRecord bookmark1 = BookmarkHelpers.createBookmark1();
|
||||
BookmarkRecord bookmark2 = BookmarkHelpers.createBookmark2();
|
||||
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
Record[] records = new Record[] { folder, bookmark1, bookmark2 };
|
||||
performWait(storeManyRunnable(session, records));
|
||||
|
||||
AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
|
||||
helper.dumpDB();
|
||||
closeDataAccessor(helper);
|
||||
|
||||
String[] guids = new String[] { folder.guid };
|
||||
Record[] expected = new Record[] { folder };
|
||||
performWait(fetchRunnable(session, guids, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchAll() {
|
||||
Record[] expected = new Record[3];
|
||||
expected[0] = BookmarkHelpers.createFolder1();
|
||||
expected[1] = BookmarkHelpers.createBookmark1();
|
||||
expected[2] = BookmarkHelpers.createBookmark2();
|
||||
basicFetchAllTest(expected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testGuidsSinceReturnMultipleRecords() {
|
||||
BookmarkRecord record0 = BookmarkHelpers.createBookmark1();
|
||||
BookmarkRecord record1 = BookmarkHelpers.createBookmark2();
|
||||
guidsSinceReturnMultipleRecords(record0, record1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testGuidsSinceReturnNoRecords() {
|
||||
guidsSinceReturnNoRecords(BookmarkHelpers.createBookmarkInMobileFolder1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchSinceOneRecord() {
|
||||
fetchSinceOneRecord(BookmarkHelpers.createBookmarkInMobileFolder1(),
|
||||
BookmarkHelpers.createBookmarkInMobileFolder2());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchSinceReturnNoRecords() {
|
||||
fetchSinceReturnNoRecords(BookmarkHelpers.createBookmark1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchOneRecordByGuid() {
|
||||
fetchOneRecordByGuid(BookmarkHelpers.createBookmarkInMobileFolder1(),
|
||||
BookmarkHelpers.createBookmarkInMobileFolder2());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchMultipleRecordsByGuids() {
|
||||
BookmarkRecord record0 = BookmarkHelpers.createFolder1();
|
||||
BookmarkRecord record1 = BookmarkHelpers.createBookmark1();
|
||||
BookmarkRecord record2 = BookmarkHelpers.createBookmark2();
|
||||
fetchMultipleRecordsByGuids(record0, record1, record2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchNoRecordByGuid() {
|
||||
fetchNoRecordByGuid(BookmarkHelpers.createBookmark1());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void testWipe() {
|
||||
doWipe(BookmarkHelpers.createBookmarkInMobileFolder1(),
|
||||
BookmarkHelpers.createBookmarkInMobileFolder2());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testStore() {
|
||||
basicStoreTest(BookmarkHelpers.createBookmark1());
|
||||
}
|
||||
|
||||
|
||||
public void testStoreFolder() {
|
||||
basicStoreTest(BookmarkHelpers.createFolder1());
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: 2011-12-24, tests disabled because we no longer fail
|
||||
* a store call if we get an unknown record type.
|
||||
*/
|
||||
/*
|
||||
* Test storing each different type of Bookmark record.
|
||||
* We expect any records with type other than "bookmark"
|
||||
* or "folder" to fail. For now we throw these away.
|
||||
*/
|
||||
/*
|
||||
public void testStoreMicrosummary() {
|
||||
basicStoreFailTest(BookmarkHelpers.createMicrosummary());
|
||||
}
|
||||
|
||||
public void testStoreQuery() {
|
||||
basicStoreFailTest(BookmarkHelpers.createQuery());
|
||||
}
|
||||
|
||||
public void testStoreLivemark() {
|
||||
basicStoreFailTest(BookmarkHelpers.createLivemark());
|
||||
}
|
||||
|
||||
public void testStoreSeparator() {
|
||||
basicStoreFailTest(BookmarkHelpers.createSeparator());
|
||||
}
|
||||
*/
|
||||
|
||||
protected void basicStoreFailTest(Record record) {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(storeRunnable(session, record, new ExpectInvalidTypeStoreDelegate()));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-parenting tests
|
||||
*/
|
||||
// Insert two records missing parent, then insert their parent.
|
||||
// Make sure they end up with the correct parent on fetch.
|
||||
public void testBasicReparenting() throws InactiveSessionException {
|
||||
Record[] expected = new Record[] {
|
||||
BookmarkHelpers.createBookmark1(),
|
||||
BookmarkHelpers.createBookmark2(),
|
||||
BookmarkHelpers.createFolder1()
|
||||
};
|
||||
doMultipleFolderReparentingTest(expected);
|
||||
}
|
||||
|
||||
// Insert 3 folders and 4 bookmarks in different orders
|
||||
// and make sure they come out parented correctly
|
||||
public void testMultipleFolderReparenting1() throws InactiveSessionException {
|
||||
Record[] expected = new Record[] {
|
||||
BookmarkHelpers.createBookmark1(),
|
||||
BookmarkHelpers.createBookmark2(),
|
||||
BookmarkHelpers.createBookmark3(),
|
||||
BookmarkHelpers.createFolder1(),
|
||||
BookmarkHelpers.createBookmark4(),
|
||||
BookmarkHelpers.createFolder3(),
|
||||
BookmarkHelpers.createFolder2(),
|
||||
};
|
||||
doMultipleFolderReparentingTest(expected);
|
||||
}
|
||||
|
||||
public void testMultipleFolderReparenting2() throws InactiveSessionException {
|
||||
Record[] expected = new Record[] {
|
||||
BookmarkHelpers.createBookmark1(),
|
||||
BookmarkHelpers.createBookmark2(),
|
||||
BookmarkHelpers.createBookmark3(),
|
||||
BookmarkHelpers.createFolder1(),
|
||||
BookmarkHelpers.createBookmark4(),
|
||||
BookmarkHelpers.createFolder3(),
|
||||
BookmarkHelpers.createFolder2(),
|
||||
};
|
||||
doMultipleFolderReparentingTest(expected);
|
||||
}
|
||||
|
||||
public void testMultipleFolderReparenting3() throws InactiveSessionException {
|
||||
Record[] expected = new Record[] {
|
||||
BookmarkHelpers.createBookmark1(),
|
||||
BookmarkHelpers.createBookmark2(),
|
||||
BookmarkHelpers.createBookmark3(),
|
||||
BookmarkHelpers.createFolder1(),
|
||||
BookmarkHelpers.createBookmark4(),
|
||||
BookmarkHelpers.createFolder3(),
|
||||
BookmarkHelpers.createFolder2(),
|
||||
};
|
||||
doMultipleFolderReparentingTest(expected);
|
||||
}
|
||||
|
||||
private void doMultipleFolderReparentingTest(Record[] expected) throws InactiveSessionException {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
doStore(session, expected);
|
||||
ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
|
||||
performWait(fetchAllRunnable(session, delegate));
|
||||
performWait(finishRunnable(session, new ExpectFinishDelegate()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test storing identical records with different guids.
|
||||
* For bookmarks identical is defined by the following fields
|
||||
* being the same: title, uri, type, parentName
|
||||
*/
|
||||
@Override
|
||||
public void testStoreIdenticalExceptGuid() {
|
||||
storeIdenticalExceptGuid(BookmarkHelpers.createBookmarkInMobileFolder1());
|
||||
}
|
||||
|
||||
/*
|
||||
* More complicated situation in which we insert a folder
|
||||
* followed by a couple of its children. We then insert
|
||||
* the folder again but with a different guid. Children
|
||||
* must still get correct parent when they are fetched.
|
||||
* Store a record after with the new guid as the parent
|
||||
* and make sure it works as well.
|
||||
*/
|
||||
public void testStoreIdenticalFoldersWithChildren() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
Record record0 = BookmarkHelpers.createFolder1();
|
||||
|
||||
// Get timestamp so that the conflicting folder that we store below is newer.
|
||||
// Children won't come back on this fetch since they haven't been stored, so remove them
|
||||
// before our delegate throws a failure.
|
||||
BookmarkRecord rec0 = (BookmarkRecord) record0;
|
||||
rec0.children = new JSONArray();
|
||||
performWait(storeRunnable(session, record0));
|
||||
|
||||
ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { rec0 });
|
||||
performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate));
|
||||
|
||||
AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
|
||||
helper.dumpDB();
|
||||
closeDataAccessor(helper);
|
||||
|
||||
Record record1 = BookmarkHelpers.createBookmark1();
|
||||
Record record2 = BookmarkHelpers.createBookmark2();
|
||||
Record record3 = BookmarkHelpers.createFolder1();
|
||||
BookmarkRecord bmk3 = (BookmarkRecord) record3;
|
||||
record3.guid = Utils.generateGuid();
|
||||
record3.lastModified = timestampDelegate.records.get(0).lastModified + 3000;
|
||||
assertFalse(record0.guid.equals(record3.guid));
|
||||
|
||||
// Store an additional record after inserting the duplicate folder
|
||||
// with new GUID. Make sure it comes back as well.
|
||||
Record record4 = BookmarkHelpers.createBookmark3();
|
||||
BookmarkRecord bmk4 = (BookmarkRecord) record4;
|
||||
bmk4.parentID = bmk3.guid;
|
||||
bmk4.parentName = bmk3.parentName;
|
||||
|
||||
doStore(session, new Record[] {
|
||||
record1, record2, record3, bmk4
|
||||
});
|
||||
BookmarkRecord bmk1 = (BookmarkRecord) record1;
|
||||
bmk1.parentID = record3.guid;
|
||||
BookmarkRecord bmk2 = (BookmarkRecord) record2;
|
||||
bmk2.parentID = record3.guid;
|
||||
Record[] expect = new Record[] {
|
||||
bmk1, bmk2, record3
|
||||
};
|
||||
fetchAllRunnable(session, preparedExpectFetchDelegate(expect));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testRemoteNewerTimeStamp() {
|
||||
BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
|
||||
BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
|
||||
remoteNewerTimeStamp(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testLocalNewerTimeStamp() {
|
||||
BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
|
||||
BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
|
||||
localNewerTimeStamp(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testDeleteRemoteNewer() {
|
||||
BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
|
||||
BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
|
||||
deleteRemoteNewer(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testDeleteLocalNewer() {
|
||||
BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
|
||||
BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
|
||||
deleteLocalNewer(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testDeleteRemoteLocalNonexistent() {
|
||||
BookmarkRecord remote = BookmarkHelpers.createBookmark2();
|
||||
deleteRemoteLocalNonexistent(remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testCleanMultipleRecords() {
|
||||
cleanMultipleRecords(
|
||||
BookmarkHelpers.createBookmarkInMobileFolder1(),
|
||||
BookmarkHelpers.createBookmarkInMobileFolder2(),
|
||||
BookmarkHelpers.createBookmark1(),
|
||||
BookmarkHelpers.createBookmark2(),
|
||||
BookmarkHelpers.createFolder1());
|
||||
}
|
||||
|
||||
public void testBasicPositioning() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
Record[] expected = new Record[] {
|
||||
BookmarkHelpers.createBookmark1(),
|
||||
BookmarkHelpers.createFolder1(),
|
||||
BookmarkHelpers.createBookmark2()
|
||||
};
|
||||
System.out.println("TEST: Inserting " + expected[0].guid + ", "
|
||||
+ expected[1].guid + ", "
|
||||
+ expected[2].guid);
|
||||
doStore(session, expected);
|
||||
|
||||
ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
|
||||
performWait(fetchAllRunnable(session, delegate));
|
||||
|
||||
int found = 0;
|
||||
boolean foundFolder = false;
|
||||
for (int i = 0; i < delegate.records.size(); i++) {
|
||||
BookmarkRecord rec = (BookmarkRecord) delegate.records.get(i);
|
||||
if (rec.guid.equals(expected[0].guid)) {
|
||||
assertEquals(0, ((BookmarkRecord) delegate.records.get(i)).androidPosition);
|
||||
found++;
|
||||
} else if (rec.guid.equals(expected[2].guid)) {
|
||||
assertEquals(1, ((BookmarkRecord) delegate.records.get(i)).androidPosition);
|
||||
found++;
|
||||
} else if (rec.guid.equals(expected[1].guid)) {
|
||||
foundFolder = true;
|
||||
} else {
|
||||
System.out.println("TEST: found " + rec.guid);
|
||||
}
|
||||
}
|
||||
assertTrue(foundFolder);
|
||||
assertEquals(2, found);
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testSqlInjectPurgeDeleteAndUpdateByGuid() {
|
||||
// Some setup.
|
||||
RepositorySession session = createAndBeginSession();
|
||||
AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
|
||||
|
||||
// Create and insert 2 bookmarks, 2nd one is evil (attempts injection).
|
||||
BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
|
||||
BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
|
||||
bmk2.guid = "' or '1'='1";
|
||||
|
||||
db.insert(bmk1);
|
||||
db.insert(bmk2);
|
||||
|
||||
// Test 1 - updateByGuid() handles evil bookmarks correctly.
|
||||
db.updateByGuid(bmk2.guid, cv);
|
||||
|
||||
// Query bookmarks table.
|
||||
Cursor cur = getAllBookmarks();
|
||||
int numBookmarks = cur.getCount();
|
||||
|
||||
// Ensure only the evil bookmark is marked for deletion.
|
||||
try {
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
|
||||
boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
|
||||
|
||||
if (guid.equals(bmk2.guid)) {
|
||||
assertTrue(deleted);
|
||||
} else {
|
||||
assertFalse(deleted);
|
||||
}
|
||||
cur.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
|
||||
// Test 2 - Ensure purgeDelete()'s call to delete() deletes only 1 record.
|
||||
try {
|
||||
db.purgeDeleted();
|
||||
} catch (NullCursorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
cur = getAllBookmarks();
|
||||
int numBookmarksAfterDeletion = cur.getCount();
|
||||
|
||||
// Ensure we have only 1 deleted row.
|
||||
assertEquals(numBookmarksAfterDeletion, numBookmarks - 1);
|
||||
|
||||
// Ensure only the evil bookmark is deleted.
|
||||
try {
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
|
||||
boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
|
||||
|
||||
if (guid.equals(bmk2.guid)) {
|
||||
fail("Evil guid was not deleted!");
|
||||
} else {
|
||||
assertFalse(deleted);
|
||||
}
|
||||
cur.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
protected Cursor getAllBookmarks() {
|
||||
Context context = getApplicationContext();
|
||||
Cursor cur = context.getContentResolver().query(BrowserContractHelpers.BOOKMARKS_CONTENT_URI,
|
||||
BrowserContractHelpers.BookmarkColumns, null, null, null);
|
||||
return cur;
|
||||
}
|
||||
|
||||
public void testSqlInjectFetch() {
|
||||
// Some setup.
|
||||
RepositorySession session = createAndBeginSession();
|
||||
AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
|
||||
|
||||
// Create and insert 4 bookmarks, last one is evil (attempts injection).
|
||||
BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
|
||||
BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
|
||||
BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3();
|
||||
BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4();
|
||||
bmk4.guid = "' or '1'='1";
|
||||
|
||||
db.insert(bmk1);
|
||||
db.insert(bmk2);
|
||||
db.insert(bmk3);
|
||||
db.insert(bmk4);
|
||||
|
||||
// Perform a fetch.
|
||||
Cursor cur = null;
|
||||
try {
|
||||
cur = db.fetch(new String[] { bmk3.guid, bmk4.guid });
|
||||
} catch (NullCursorException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
// Ensure the correct number (2) of records were fetched and with the correct guids.
|
||||
if (cur == null) {
|
||||
fail("No records were fetched.");
|
||||
}
|
||||
|
||||
try {
|
||||
if (cur.getCount() != 2) {
|
||||
fail("Wrong number of guids fetched!");
|
||||
}
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
|
||||
if (!guid.equals(bmk3.guid) && !guid.equals(bmk4.guid)) {
|
||||
fail("Wrong guids were fetched!");
|
||||
}
|
||||
cur.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testSqlInjectDelete() {
|
||||
// Some setup.
|
||||
RepositorySession session = createAndBeginSession();
|
||||
AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
|
||||
|
||||
// Create and insert 2 bookmarks, 2nd one is evil (attempts injection).
|
||||
BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
|
||||
BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
|
||||
bmk2.guid = "' or '1'='1";
|
||||
|
||||
db.insert(bmk1);
|
||||
db.insert(bmk2);
|
||||
|
||||
// Note size of table before delete.
|
||||
Cursor cur = getAllBookmarks();
|
||||
int numBookmarks = cur.getCount();
|
||||
|
||||
db.purgeGuid(bmk2.guid);
|
||||
|
||||
// Note size of table after delete.
|
||||
cur = getAllBookmarks();
|
||||
int numBookmarksAfterDelete = cur.getCount();
|
||||
|
||||
// Ensure size of table after delete is *only* 1 less.
|
||||
assertEquals(numBookmarksAfterDelete, numBookmarks - 1);
|
||||
|
||||
try {
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
|
||||
if (guid.equals(bmk2.guid)) {
|
||||
fail("Guid was not deleted!");
|
||||
}
|
||||
cur.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that data accessor's bulkInsert actually inserts.
|
||||
* @throws NullCursorException
|
||||
*/
|
||||
public void testBulkInsert() throws NullCursorException {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
|
||||
|
||||
// Have to set androidID of parent manually.
|
||||
Cursor cur = db.fetch(new String[] { "mobile" } );
|
||||
assertEquals(1, cur.getCount());
|
||||
cur.moveToFirst();
|
||||
int mobileAndroidID = RepoUtils.getIntFromCursor(cur, BrowserContract.Bookmarks._ID);
|
||||
|
||||
BookmarkRecord bookmark1 = BookmarkHelpers.createBookmarkInMobileFolder1();
|
||||
BookmarkRecord bookmark2 = BookmarkHelpers.createBookmarkInMobileFolder2();
|
||||
bookmark1.androidParentID = mobileAndroidID;
|
||||
bookmark2.androidParentID = mobileAndroidID;
|
||||
ArrayList<Record> recordList = new ArrayList<Record>();
|
||||
recordList.add(bookmark1);
|
||||
recordList.add(bookmark2);
|
||||
db.bulkInsert(recordList);
|
||||
|
||||
String[] guids = new String[] { bookmark1.guid, bookmark2.guid };
|
||||
Record[] expected = new Record[] { bookmark1, bookmark2 };
|
||||
performWait(fetchRunnable(session, guids, expected));
|
||||
dispose(session);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,450 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.HistoryHelpers;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryDataAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepository;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserHistoryRepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepository;
|
||||
import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
public class TestAndroidBrowserHistoryRepository extends AndroidBrowserRepositoryTestCase {
|
||||
|
||||
@Override
|
||||
protected AndroidBrowserRepository getRepository() {
|
||||
|
||||
/**
|
||||
* Override this chain in order to avoid our test code having to create two
|
||||
* sessions all the time.
|
||||
*/
|
||||
return new AndroidBrowserHistoryRepository() {
|
||||
@Override
|
||||
protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
|
||||
AndroidBrowserHistoryRepositorySession session;
|
||||
session = new AndroidBrowserHistoryRepositorySession(this, context) {
|
||||
@Override
|
||||
protected synchronized void trackGUID(String guid) {
|
||||
System.out.println("Ignoring trackGUID call: this is a test!");
|
||||
}
|
||||
};
|
||||
delegate.onSessionCreated(session);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AndroidBrowserRepositoryDataAccessor getDataAccessor() {
|
||||
return new AndroidBrowserHistoryDataAccessor(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchAll() {
|
||||
Record[] expected = new Record[2];
|
||||
expected[0] = HistoryHelpers.createHistory3();
|
||||
expected[1] = HistoryHelpers.createHistory2();
|
||||
basicFetchAllTest(expected);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test storing identical records with different guids.
|
||||
* For bookmarks identical is defined by the following fields
|
||||
* being the same: title, uri, type, parentName
|
||||
*/
|
||||
@Override
|
||||
public void testStoreIdenticalExceptGuid() {
|
||||
storeIdenticalExceptGuid(HistoryHelpers.createHistory1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testCleanMultipleRecords() {
|
||||
cleanMultipleRecords(
|
||||
HistoryHelpers.createHistory1(),
|
||||
HistoryHelpers.createHistory2(),
|
||||
HistoryHelpers.createHistory3(),
|
||||
HistoryHelpers.createHistory4(),
|
||||
HistoryHelpers.createHistory5()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testGuidsSinceReturnMultipleRecords() {
|
||||
HistoryRecord record0 = HistoryHelpers.createHistory1();
|
||||
HistoryRecord record1 = HistoryHelpers.createHistory2();
|
||||
guidsSinceReturnMultipleRecords(record0, record1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testGuidsSinceReturnNoRecords() {
|
||||
guidsSinceReturnNoRecords(HistoryHelpers.createHistory3());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchSinceOneRecord() {
|
||||
fetchSinceOneRecord(HistoryHelpers.createHistory1(),
|
||||
HistoryHelpers.createHistory2());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchSinceReturnNoRecords() {
|
||||
fetchSinceReturnNoRecords(HistoryHelpers.createHistory3());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchOneRecordByGuid() {
|
||||
fetchOneRecordByGuid(HistoryHelpers.createHistory1(),
|
||||
HistoryHelpers.createHistory2());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchMultipleRecordsByGuids() {
|
||||
HistoryRecord record0 = HistoryHelpers.createHistory1();
|
||||
HistoryRecord record1 = HistoryHelpers.createHistory2();
|
||||
HistoryRecord record2 = HistoryHelpers.createHistory3();
|
||||
fetchMultipleRecordsByGuids(record0, record1, record2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testFetchNoRecordByGuid() {
|
||||
fetchNoRecordByGuid(HistoryHelpers.createHistory1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testWipe() {
|
||||
doWipe(HistoryHelpers.createHistory2(), HistoryHelpers.createHistory3());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testStore() {
|
||||
basicStoreTest(HistoryHelpers.createHistory1());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testRemoteNewerTimeStamp() {
|
||||
HistoryRecord local = HistoryHelpers.createHistory1();
|
||||
HistoryRecord remote = HistoryHelpers.createHistory2();
|
||||
remoteNewerTimeStamp(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testLocalNewerTimeStamp() {
|
||||
HistoryRecord local = HistoryHelpers.createHistory1();
|
||||
HistoryRecord remote = HistoryHelpers.createHistory2();
|
||||
localNewerTimeStamp(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testDeleteRemoteNewer() {
|
||||
HistoryRecord local = HistoryHelpers.createHistory1();
|
||||
HistoryRecord remote = HistoryHelpers.createHistory2();
|
||||
deleteRemoteNewer(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testDeleteLocalNewer() {
|
||||
HistoryRecord local = HistoryHelpers.createHistory1();
|
||||
HistoryRecord remote = HistoryHelpers.createHistory2();
|
||||
deleteLocalNewer(local, remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testDeleteRemoteLocalNonexistent() {
|
||||
deleteRemoteLocalNonexistent(HistoryHelpers.createHistory2());
|
||||
}
|
||||
|
||||
/**
|
||||
* Exists to provide access to record string logic.
|
||||
*/
|
||||
protected class HelperHistorySession extends AndroidBrowserHistoryRepositorySession {
|
||||
public HelperHistorySession(Repository repository, Context context) {
|
||||
super(repository, context);
|
||||
}
|
||||
|
||||
public boolean sameRecordString(HistoryRecord r1, HistoryRecord r2) {
|
||||
return buildRecordString(r1).equals(buildRecordString(r2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that two history records with the same URI but different
|
||||
* titles will be reconciled locally.
|
||||
*/
|
||||
public void testRecordStringCollisionAndEquality() {
|
||||
final AndroidBrowserHistoryRepository repo = new AndroidBrowserHistoryRepository();
|
||||
final HelperHistorySession testSession = new HelperHistorySession(repo, getApplicationContext());
|
||||
|
||||
final long now = RepositorySession.now();
|
||||
|
||||
final HistoryRecord record0 = new HistoryRecord(null, "history", now + 1, false);
|
||||
final HistoryRecord record1 = new HistoryRecord(null, "history", now + 2, false);
|
||||
final HistoryRecord record2 = new HistoryRecord(null, "history", now + 3, false);
|
||||
|
||||
record0.histURI = "http://example.com/foo";
|
||||
record1.histURI = "http://example.com/foo";
|
||||
record2.histURI = "http://example.com/bar";
|
||||
record0.title = "Foo 0";
|
||||
record1.title = "Foo 1";
|
||||
record2.title = "Foo 2";
|
||||
|
||||
// Ensure that two records with the same URI produce the same record string,
|
||||
// and two records with different URIs do not.
|
||||
assertTrue(testSession.sameRecordString(record0, record1));
|
||||
assertFalse(testSession.sameRecordString(record0, record2));
|
||||
|
||||
// Two records are congruent if they have the same URI and their
|
||||
// identifiers match (which is why these all have null GUIDs).
|
||||
assertTrue(record0.congruentWith(record0));
|
||||
assertTrue(record0.congruentWith(record1));
|
||||
assertTrue(record1.congruentWith(record0));
|
||||
assertFalse(record0.congruentWith(record2));
|
||||
assertFalse(record1.congruentWith(record2));
|
||||
assertFalse(record2.congruentWith(record1));
|
||||
assertFalse(record2.congruentWith(record0));
|
||||
|
||||
// None of these records are equal, because they have different titles.
|
||||
// (Except for being equal to themselves, of course.)
|
||||
assertTrue(record0.equalPayloads(record0));
|
||||
assertTrue(record1.equalPayloads(record1));
|
||||
assertTrue(record2.equalPayloads(record2));
|
||||
assertFalse(record0.equalPayloads(record1));
|
||||
assertFalse(record1.equalPayloads(record0));
|
||||
assertFalse(record1.equalPayloads(record2));
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests for adding some visits to a history record
|
||||
* and doing a fetch.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAddOneVisit() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
HistoryRecord record0 = HistoryHelpers.createHistory3();
|
||||
performWait(storeRunnable(session, record0));
|
||||
|
||||
// Add one visit to the count and put in a new
|
||||
// last visited date.
|
||||
ContentValues cv = new ContentValues();
|
||||
int visits = record0.visits.size() + 1;
|
||||
long newVisitTime = System.currentTimeMillis();
|
||||
cv.put(BrowserContract.History.VISITS, visits);
|
||||
cv.put(BrowserContract.History.DATE_LAST_VISITED, newVisitTime);
|
||||
final AndroidBrowserRepositoryDataAccessor dataAccessor = getDataAccessor();
|
||||
dataAccessor.updateByGuid(record0.guid, cv);
|
||||
|
||||
// Add expected visit to record for verification.
|
||||
JSONObject expectedVisit = new JSONObject();
|
||||
expectedVisit.put("date", newVisitTime * 1000); // Microseconds.
|
||||
expectedVisit.put("type", 1L);
|
||||
record0.visits.add(expectedVisit);
|
||||
|
||||
performWait(fetchRunnable(session, new String[] { record0.guid }, new ExpectFetchDelegate(new Record[] { record0 })));
|
||||
closeDataAccessor(dataAccessor);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAddMultipleVisits() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
HistoryRecord record0 = HistoryHelpers.createHistory4();
|
||||
performWait(storeRunnable(session, record0));
|
||||
|
||||
// Add three visits to the count and put in a new
|
||||
// last visited date.
|
||||
ContentValues cv = new ContentValues();
|
||||
int visits = record0.visits.size() + 3;
|
||||
long newVisitTime = System.currentTimeMillis();
|
||||
cv.put(BrowserContract.History.VISITS, visits);
|
||||
cv.put(BrowserContract.History.DATE_LAST_VISITED, newVisitTime);
|
||||
final AndroidBrowserRepositoryDataAccessor dataAccessor = getDataAccessor();
|
||||
dataAccessor.updateByGuid(record0.guid, cv);
|
||||
|
||||
// Now shift to microsecond timing for visits.
|
||||
long newMicroVisitTime = newVisitTime * 1000;
|
||||
|
||||
// Add expected visits to record for verification
|
||||
JSONObject expectedVisit = new JSONObject();
|
||||
expectedVisit.put("date", newMicroVisitTime);
|
||||
expectedVisit.put("type", 1L);
|
||||
record0.visits.add(expectedVisit);
|
||||
expectedVisit = new JSONObject();
|
||||
expectedVisit.put("date", newMicroVisitTime - 1000);
|
||||
expectedVisit.put("type", 1L);
|
||||
record0.visits.add(expectedVisit);
|
||||
expectedVisit = new JSONObject();
|
||||
expectedVisit.put("date", newMicroVisitTime - 2000);
|
||||
expectedVisit.put("type", 1L);
|
||||
record0.visits.add(expectedVisit);
|
||||
|
||||
ExpectFetchDelegate delegate = new ExpectFetchDelegate(new Record[] { record0 });
|
||||
performWait(fetchRunnable(session, new String[] { record0.guid }, delegate));
|
||||
|
||||
Record fetched = delegate.records.get(0);
|
||||
assertTrue(record0.equalPayloads(fetched));
|
||||
closeDataAccessor(dataAccessor);
|
||||
}
|
||||
|
||||
public void testInvalidHistoryItemIsSkipped() throws NullCursorException {
|
||||
final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession();
|
||||
final AndroidBrowserRepositoryDataAccessor dbHelper = session.getDBHelper();
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
final HistoryRecord emptyURL = new HistoryRecord(Utils.generateGuid(), "history", now, false);
|
||||
final HistoryRecord noVisits = new HistoryRecord(Utils.generateGuid(), "history", now, false);
|
||||
final HistoryRecord aboutURL = new HistoryRecord(Utils.generateGuid(), "history", now, false);
|
||||
|
||||
emptyURL.fennecDateVisited = now;
|
||||
emptyURL.fennecVisitCount = 1;
|
||||
emptyURL.histURI = "";
|
||||
emptyURL.title = "Something";
|
||||
|
||||
noVisits.fennecDateVisited = now;
|
||||
noVisits.fennecVisitCount = 0;
|
||||
noVisits.histURI = "http://example.org/novisits";
|
||||
noVisits.title = "Something Else";
|
||||
|
||||
aboutURL.fennecDateVisited = now;
|
||||
aboutURL.fennecVisitCount = 1;
|
||||
aboutURL.histURI = "about:home";
|
||||
aboutURL.title = "Fennec Home";
|
||||
|
||||
Uri one = dbHelper.insert(emptyURL);
|
||||
Uri two = dbHelper.insert(noVisits);
|
||||
Uri tre = dbHelper.insert(aboutURL);
|
||||
assertNotNull(one);
|
||||
assertNotNull(two);
|
||||
assertNotNull(tre);
|
||||
|
||||
// The records are in the DB.
|
||||
final Cursor all = dbHelper.fetchAll();
|
||||
assertEquals(3, all.getCount());
|
||||
all.close();
|
||||
|
||||
// But aren't returned by fetching.
|
||||
performWait(fetchAllRunnable(session, new Record[] {}));
|
||||
|
||||
// And we'd ignore about:home if we downloaded it.
|
||||
assertTrue(session.shouldIgnore(aboutURL));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testSqlInjectPurgeDelete() {
|
||||
// Some setup.
|
||||
RepositorySession session = createAndBeginSession();
|
||||
final AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
|
||||
|
||||
try {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
|
||||
|
||||
// Create and insert 2 history entries, 2nd one is evil (attempts injection).
|
||||
HistoryRecord h1 = HistoryHelpers.createHistory1();
|
||||
HistoryRecord h2 = HistoryHelpers.createHistory2();
|
||||
h2.guid = "' or '1'='1";
|
||||
|
||||
db.insert(h1);
|
||||
db.insert(h2);
|
||||
|
||||
// Test 1 - updateByGuid() handles evil history entries correctly.
|
||||
db.updateByGuid(h2.guid, cv);
|
||||
|
||||
// Query history table.
|
||||
Cursor cur = getAllHistory();
|
||||
int numHistory = cur.getCount();
|
||||
|
||||
// Ensure only the evil history entry is marked for deletion.
|
||||
try {
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
|
||||
boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
|
||||
|
||||
if (guid.equals(h2.guid)) {
|
||||
assertTrue(deleted);
|
||||
} else {
|
||||
assertFalse(deleted);
|
||||
}
|
||||
cur.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
|
||||
// Test 2 - Ensure purgeDelete()'s call to delete() deletes only 1 record.
|
||||
try {
|
||||
db.purgeDeleted();
|
||||
} catch (NullCursorException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
cur = getAllHistory();
|
||||
int numHistoryAfterDeletion = cur.getCount();
|
||||
|
||||
// Ensure we have only 1 deleted row.
|
||||
assertEquals(numHistoryAfterDeletion, numHistory - 1);
|
||||
|
||||
// Ensure only the evil history is deleted.
|
||||
try {
|
||||
cur.moveToFirst();
|
||||
while (!cur.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
|
||||
boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
|
||||
|
||||
if (guid.equals(h2.guid)) {
|
||||
fail("Evil guid was not deleted!");
|
||||
} else {
|
||||
assertFalse(deleted);
|
||||
}
|
||||
cur.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
cur.close();
|
||||
}
|
||||
} finally {
|
||||
closeDataAccessor(db);
|
||||
session.abort();
|
||||
}
|
||||
}
|
||||
|
||||
protected Cursor getAllHistory() {
|
||||
Context context = getApplicationContext();
|
||||
Cursor cur = context.getContentResolver().query(BrowserContractHelpers.HISTORY_CONTENT_URI,
|
||||
BrowserContractHelpers.HistoryColumns, null, null, null);
|
||||
return cur;
|
||||
}
|
||||
|
||||
public void testDataAccessorBulkInsert() throws NullCursorException {
|
||||
final AndroidBrowserHistoryRepositorySession session = (AndroidBrowserHistoryRepositorySession) createAndBeginSession();
|
||||
AndroidBrowserHistoryDataAccessor db = (AndroidBrowserHistoryDataAccessor) session.getDBHelper();
|
||||
|
||||
ArrayList<HistoryRecord> records = new ArrayList<HistoryRecord>();
|
||||
records.add(HistoryHelpers.createHistory1());
|
||||
records.add(HistoryHelpers.createHistory2());
|
||||
records.add(HistoryHelpers.createHistory3());
|
||||
db.bulkInsert(records);
|
||||
|
||||
performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(records.toArray(new Record[records.size()]))));
|
||||
session.abort();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,200 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabase;
|
||||
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class TestClientsDatabase extends AndroidTestCase {
|
||||
|
||||
protected ClientsDatabase db;
|
||||
|
||||
public void setUp() {
|
||||
db = new ClientsDatabase(mContext);
|
||||
db.wipeDB();
|
||||
}
|
||||
|
||||
public void testStoreAndFetch() {
|
||||
ClientRecord record = new ClientRecord();
|
||||
String profileConst = Constants.DEFAULT_PROFILE;
|
||||
db.store(profileConst, record);
|
||||
|
||||
Cursor cur = null;
|
||||
try {
|
||||
// Test stored item gets fetched correctly.
|
||||
cur = db.fetchClientsCursor(record.guid, profileConst);
|
||||
assertTrue(cur.moveToFirst());
|
||||
assertEquals(1, cur.getCount());
|
||||
|
||||
String guid = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
|
||||
String profileId = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_PROFILE);
|
||||
String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME);
|
||||
String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE);
|
||||
|
||||
assertEquals(record.guid, guid);
|
||||
assertEquals(profileConst, profileId);
|
||||
assertEquals(record.name, clientName);
|
||||
assertEquals(record.type, clientType);
|
||||
} catch (NullCursorException e) {
|
||||
fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testStoreAndFetchSpecificCommands() {
|
||||
String accountGUID = Utils.generateGuid();
|
||||
ArrayList<String> args = new ArrayList<String>();
|
||||
args.add("URI of Page");
|
||||
args.add("Sender GUID");
|
||||
args.add("Title of Page");
|
||||
String jsonArgs = JSONArray.toJSONString(args);
|
||||
|
||||
Cursor cur = null;
|
||||
try {
|
||||
db.store(accountGUID, "displayURI", jsonArgs);
|
||||
|
||||
// This row should not show up in the fetch.
|
||||
args.add("Another arg.");
|
||||
db.store(accountGUID, "displayURI", JSONArray.toJSONString(args));
|
||||
|
||||
// Test stored item gets fetched correctly.
|
||||
cur = db.fetchSpecificCommand(accountGUID, "displayURI", jsonArgs);
|
||||
assertTrue(cur.moveToFirst());
|
||||
assertEquals(1, cur.getCount());
|
||||
|
||||
String guid = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
|
||||
String commandType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_COMMAND);
|
||||
String fetchedArgs = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ARGS);
|
||||
|
||||
assertEquals(accountGUID, guid);
|
||||
assertEquals("displayURI", commandType);
|
||||
assertEquals(jsonArgs, fetchedArgs);
|
||||
} catch (NullCursorException e) {
|
||||
fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testFetchCommandsForClient() {
|
||||
String accountGUID = Utils.generateGuid();
|
||||
ArrayList<String> args = new ArrayList<String>();
|
||||
args.add("URI of Page");
|
||||
args.add("Sender GUID");
|
||||
args.add("Title of Page");
|
||||
String jsonArgs = JSONArray.toJSONString(args);
|
||||
|
||||
Cursor cur = null;
|
||||
try {
|
||||
db.store(accountGUID, "displayURI", jsonArgs);
|
||||
|
||||
// This row should ALSO show up in the fetch.
|
||||
args.add("Another arg.");
|
||||
db.store(accountGUID, "displayURI", JSONArray.toJSONString(args));
|
||||
|
||||
// Test both stored items with the same GUID but different command are fetched.
|
||||
cur = db.fetchCommandsForClient(accountGUID);
|
||||
assertTrue(cur.moveToFirst());
|
||||
assertEquals(2, cur.getCount());
|
||||
} catch (NullCursorException e) {
|
||||
fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public void testDelete() {
|
||||
ClientRecord record1 = new ClientRecord();
|
||||
ClientRecord record2 = new ClientRecord();
|
||||
String profileConst = Constants.DEFAULT_PROFILE;
|
||||
|
||||
db.store(profileConst, record1);
|
||||
db.store(profileConst, record2);
|
||||
|
||||
Cursor cur = null;
|
||||
try {
|
||||
// Test record doesn't exist after delete.
|
||||
db.deleteClient(record1.guid, profileConst);
|
||||
cur = db.fetchClientsCursor(record1.guid, profileConst);
|
||||
assertFalse(cur.moveToFirst());
|
||||
assertEquals(0, cur.getCount());
|
||||
|
||||
// Test record2 still there after deleting record1.
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
assertTrue(cur.moveToFirst());
|
||||
assertEquals(1, cur.getCount());
|
||||
|
||||
String guid = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
|
||||
String profileId = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_PROFILE);
|
||||
String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME);
|
||||
String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE);
|
||||
|
||||
assertEquals(record2.guid, guid);
|
||||
assertEquals(profileConst, profileId);
|
||||
assertEquals(record2.name, clientName);
|
||||
assertEquals(record2.type, clientType);
|
||||
} catch (NullCursorException e) {
|
||||
fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
public void testWipe() {
|
||||
ClientRecord record1 = new ClientRecord();
|
||||
ClientRecord record2 = new ClientRecord();
|
||||
String profileConst = Constants.DEFAULT_PROFILE;
|
||||
|
||||
db.store(profileConst, record1);
|
||||
db.store(profileConst, record2);
|
||||
|
||||
|
||||
Cursor cur = null;
|
||||
try {
|
||||
// Test before wipe the records are there.
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
assertTrue(cur.moveToFirst());
|
||||
assertEquals(1, cur.getCount());
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
assertTrue(cur.moveToFirst());
|
||||
assertEquals(1, cur.getCount());
|
||||
|
||||
// Test after wipe neither record exists.
|
||||
db.wipeClientsTable();
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
assertFalse(cur.moveToFirst());
|
||||
assertEquals(0, cur.getCount());
|
||||
cur = db.fetchClientsCursor(record1.guid, profileConst);
|
||||
assertFalse(cur.moveToFirst());
|
||||
assertEquals(0, cur.getCount());
|
||||
} catch (NullCursorException e) {
|
||||
fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mozilla.gecko.background.testhelpers.CommandHelpers;
|
||||
import org.mozilla.gecko.sync.CommandProcessor.Command;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
public class TestClientsDatabaseAccessor extends AndroidTestCase {
|
||||
|
||||
public class StubbedClientsDatabaseAccessor extends ClientsDatabaseAccessor {
|
||||
public StubbedClientsDatabaseAccessor(Context mContext) {
|
||||
super(mContext);
|
||||
}
|
||||
}
|
||||
|
||||
StubbedClientsDatabaseAccessor db;
|
||||
|
||||
public void setUp() {
|
||||
db = new StubbedClientsDatabaseAccessor(mContext);
|
||||
db.wipeDB();
|
||||
}
|
||||
|
||||
public void tearDown() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
public void testStoreArrayListAndFetch() throws NullCursorException {
|
||||
ArrayList<ClientRecord> list = new ArrayList<ClientRecord>();
|
||||
ClientRecord record1 = new ClientRecord(Utils.generateGuid());
|
||||
ClientRecord record2 = new ClientRecord(Utils.generateGuid());
|
||||
ClientRecord record3 = new ClientRecord(Utils.generateGuid());
|
||||
|
||||
list.add(record1);
|
||||
list.add(record2);
|
||||
db.store(list);
|
||||
|
||||
ClientRecord r1 = db.fetchClient(record1.guid);
|
||||
ClientRecord r2 = db.fetchClient(record2.guid);
|
||||
ClientRecord r3 = db.fetchClient(record3.guid);
|
||||
|
||||
assertNotNull(r1);
|
||||
assertNotNull(r2);
|
||||
assertNull(r3);
|
||||
assertTrue(record1.equals(r1));
|
||||
assertTrue(record2.equals(r2));
|
||||
assertFalse(record3.equals(r3));
|
||||
}
|
||||
|
||||
public void testStoreAndFetchCommandsForClient() {
|
||||
String accountGUID1 = Utils.generateGuid();
|
||||
String accountGUID2 = Utils.generateGuid();
|
||||
|
||||
Command command1 = CommandHelpers.getCommand1();
|
||||
Command command2 = CommandHelpers.getCommand2();
|
||||
Command command3 = CommandHelpers.getCommand3();
|
||||
|
||||
Cursor cur = null;
|
||||
try {
|
||||
db.store(accountGUID1, command1);
|
||||
db.store(accountGUID1, command2);
|
||||
db.store(accountGUID2, command3);
|
||||
|
||||
List<Command> commands = db.fetchCommandsForClient(accountGUID1);
|
||||
assertEquals(2, commands.size());
|
||||
assertEquals(1, commands.get(0).args.size());
|
||||
assertEquals(1, commands.get(1).args.size());
|
||||
} catch (NullCursorException e) {
|
||||
fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testNumClients() {
|
||||
final int COUNT = 5;
|
||||
ArrayList<ClientRecord> list = new ArrayList<ClientRecord>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
list.add(new ClientRecord());
|
||||
}
|
||||
db.store(list);
|
||||
assertEquals(COUNT, db.clientsCount());
|
||||
}
|
||||
|
||||
public void testFetchAll() throws NullCursorException {
|
||||
ArrayList<ClientRecord> list = new ArrayList<ClientRecord>();
|
||||
ClientRecord record1 = new ClientRecord(Utils.generateGuid());
|
||||
ClientRecord record2 = new ClientRecord(Utils.generateGuid());
|
||||
|
||||
list.add(record1);
|
||||
list.add(record2);
|
||||
|
||||
boolean thrown = false;
|
||||
try {
|
||||
Map<String, ClientRecord> records = db.fetchAllClients();
|
||||
|
||||
assertNotNull(records);
|
||||
assertEquals(0, records.size());
|
||||
|
||||
db.store(list);
|
||||
records = db.fetchAllClients();
|
||||
assertNotNull(records);
|
||||
assertEquals(2, records.size());
|
||||
assertTrue(record1.equals(records.get(record1.guid)));
|
||||
assertTrue(record2.equals(records.get(record2.guid)));
|
||||
|
||||
// put() should throw an exception since records is immutable.
|
||||
records.put(null, null);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
thrown = true;
|
||||
}
|
||||
assertTrue(thrown);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
|
||||
import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserContract.Clients;
|
||||
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
|
||||
import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository.FennecTabsRepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class TestFennecTabsRepositorySession extends AndroidSyncTestCase {
|
||||
public static final MockClientsDataDelegate clientsDataDelegate = new MockClientsDataDelegate();
|
||||
public static final String TEST_CLIENT_GUID = clientsDataDelegate.getAccountGUID();
|
||||
public static final String TEST_CLIENT_NAME = clientsDataDelegate.getClientName();
|
||||
public static final String TEST_CLIENT_DEVICE_TYPE = "phablet";
|
||||
|
||||
// Override these to test against data that is not live.
|
||||
public static final String TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION = BrowserContract.Tabs.CLIENT_GUID + " IS ?";
|
||||
public static final String[] TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION_ARGS = new String[] { TEST_CLIENT_GUID };
|
||||
|
||||
public static final String TEST_CLIENTS_GUID_IS_LOCAL_SELECTION = BrowserContract.Clients.GUID + " IS ?";
|
||||
public static final String[] TEST_CLIENTS_GUID_IS_LOCAL_SELECTION_ARGS = new String[] { TEST_CLIENT_GUID };
|
||||
|
||||
protected ContentProviderClient tabsClient = null;
|
||||
protected ContentProviderClient clientsClient = null;
|
||||
|
||||
protected ContentProviderClient getTabsClient() {
|
||||
final ContentResolver cr = getApplicationContext().getContentResolver();
|
||||
return cr.acquireContentProviderClient(BrowserContractHelpers.TABS_CONTENT_URI);
|
||||
}
|
||||
|
||||
protected ContentProviderClient getClientsClient() {
|
||||
final ContentResolver cr = getApplicationContext().getContentResolver();
|
||||
return cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
|
||||
}
|
||||
|
||||
public TestFennecTabsRepositorySession() throws NoContentProviderException {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
if (tabsClient == null) {
|
||||
tabsClient = getTabsClient();
|
||||
}
|
||||
if (clientsClient == null) {
|
||||
clientsClient = getClientsClient();
|
||||
}
|
||||
}
|
||||
|
||||
protected int deleteTestClient(final ContentProviderClient clientsClient) throws RemoteException {
|
||||
if (clientsClient == null) {
|
||||
return -1;
|
||||
}
|
||||
return clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, TEST_CLIENTS_GUID_IS_LOCAL_SELECTION, TEST_CLIENTS_GUID_IS_LOCAL_SELECTION_ARGS);
|
||||
}
|
||||
|
||||
protected int deleteAllTestTabs(final ContentProviderClient tabsClient) throws RemoteException {
|
||||
if (tabsClient == null) {
|
||||
return -1;
|
||||
}
|
||||
return tabsClient.delete(BrowserContractHelpers.TABS_CONTENT_URI,
|
||||
TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION, TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION_ARGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
if (tabsClient != null) {
|
||||
deleteAllTestTabs(tabsClient);
|
||||
|
||||
tabsClient.release();
|
||||
tabsClient = null;
|
||||
}
|
||||
|
||||
if (clientsClient != null) {
|
||||
deleteTestClient(clientsClient);
|
||||
|
||||
clientsClient.release();
|
||||
clientsClient = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected FennecTabsRepository getRepository() {
|
||||
/**
|
||||
* Override this chain in order to avoid our test code having to create two
|
||||
* sessions all the time.
|
||||
*/
|
||||
return new FennecTabsRepository(clientsDataDelegate) {
|
||||
@Override
|
||||
public void createSession(RepositorySessionCreationDelegate delegate,
|
||||
Context context) {
|
||||
try {
|
||||
final FennecTabsRepositorySession session = new FennecTabsRepositorySession(this, context) {
|
||||
@Override
|
||||
protected synchronized void trackGUID(String guid) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String localClientSelection() {
|
||||
return TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] localClientSelectionArgs() {
|
||||
return TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION_ARGS;
|
||||
}
|
||||
};
|
||||
delegate.onSessionCreated(session);
|
||||
} catch (Exception e) {
|
||||
delegate.onSessionCreateFailed(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected FennecTabsRepositorySession createSession() {
|
||||
return (FennecTabsRepositorySession) SessionTestHelper.createSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
protected FennecTabsRepositorySession createAndBeginSession() {
|
||||
return (FennecTabsRepositorySession) SessionTestHelper.createAndBeginSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
protected Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final Record[] expectedRecords) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchSince(timestamp, new ExpectFetchDelegate(expectedRecords));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchAll(new ExpectFetchDelegate(expectedRecords));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected Tab testTab1;
|
||||
protected Tab testTab2;
|
||||
protected Tab testTab3;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private 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));
|
||||
}
|
||||
|
||||
protected TabsRecord insertTestTabsAndExtractTabsRecord() throws RemoteException {
|
||||
insertSomeTestTabs(tabsClient);
|
||||
|
||||
final String positionAscending = BrowserContract.Tabs.POSITION + " ASC";
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = tabsClient.query(BrowserContractHelpers.TABS_CONTENT_URI, null,
|
||||
TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION, TEST_TABS_CLIENT_GUID_IS_LOCAL_SELECTION_ARGS, positionAscending);
|
||||
CursorDumper.dumpCursor(cursor);
|
||||
|
||||
final TabsRecord tabsRecord = FennecTabsRepository.tabsRecordFromCursor(cursor, TEST_CLIENT_GUID, TEST_CLIENT_NAME);
|
||||
|
||||
assertEquals(TEST_CLIENT_GUID, tabsRecord.guid);
|
||||
assertEquals(TEST_CLIENT_NAME, tabsRecord.clientName);
|
||||
|
||||
assertNotNull(tabsRecord.tabs);
|
||||
assertEquals(cursor.getCount(), tabsRecord.tabs.size());
|
||||
|
||||
return tabsRecord;
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void testFetchAll() throws NoContentProviderException, RemoteException {
|
||||
final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
|
||||
|
||||
final FennecTabsRepositorySession session = createAndBeginSession();
|
||||
performWait(fetchAllRunnable(session, new Record[] { tabsRecord }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testFetchSince() throws NoContentProviderException, RemoteException {
|
||||
final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
|
||||
|
||||
final FennecTabsRepositorySession session = createAndBeginSession();
|
||||
|
||||
// Not all tabs are modified after this, but the record should contain them all.
|
||||
performWait(fetchSinceRunnable(session, 1000, new Record[] { tabsRecord }));
|
||||
|
||||
// No tabs are modified after this, but our client name has changed in the interim.
|
||||
performWait(fetchSinceRunnable(session, 4000, new Record[] { tabsRecord }));
|
||||
|
||||
// No tabs are modified after this, and our client name hasn't changed, so
|
||||
// we shouldn't get a record at all. Note: this runs after our static
|
||||
// initializer that sets the client data timestamp.
|
||||
final long now = System.currentTimeMillis();
|
||||
performWait(fetchSinceRunnable(session, now, new Record[] { }));
|
||||
|
||||
// No tabs are modified after this, but our client name has changed, so
|
||||
// again we get a record.
|
||||
clientsDataDelegate.setClientName("new client name", System.currentTimeMillis());
|
||||
performWait(fetchSinceRunnable(session, now, new Record[] { tabsRecord }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
// Verify that storing a tabs record writes a clients record with the correct
|
||||
// device type to the Fennec clients provider.
|
||||
public void testStore() throws NoContentProviderException, RemoteException {
|
||||
// Get a valid tabsRecord to write.
|
||||
final TabsRecord tabsRecord = insertTestTabsAndExtractTabsRecord();
|
||||
deleteAllTestTabs(tabsClient);
|
||||
deleteTestClient(clientsClient);
|
||||
|
||||
final ContentResolver cr = getApplicationContext().getContentResolver();
|
||||
final ContentProviderClient clientsClient = cr.acquireContentProviderClient(BrowserContractHelpers.CLIENTS_CONTENT_URI);
|
||||
|
||||
try {
|
||||
// This clients DB is not the Fennec DB; it's Sync's own clients DB.
|
||||
final ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(getApplicationContext());
|
||||
try {
|
||||
ClientRecord clientRecord = new ClientRecord(TEST_CLIENT_GUID);
|
||||
clientRecord.name = TEST_CLIENT_NAME;
|
||||
clientRecord.type = TEST_CLIENT_DEVICE_TYPE;
|
||||
db.store(clientRecord);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
|
||||
final FennecTabsRepositorySession session = createAndBeginSession();
|
||||
performWait(AndroidBrowserRepositoryTestCase.storeRunnable(session, tabsRecord));
|
||||
|
||||
session.abort();
|
||||
|
||||
// This store should write Sync's idea of the client's device_type to Fennec's clients CP.
|
||||
final Cursor cursor = clientsClient.query(BrowserContractHelpers.CLIENTS_CONTENT_URI, null,
|
||||
TEST_CLIENTS_GUID_IS_LOCAL_SELECTION, TEST_CLIENTS_GUID_IS_LOCAL_SELECTION_ARGS, null);
|
||||
assertNotNull(cursor);
|
||||
|
||||
try {
|
||||
assertTrue(cursor.moveToFirst());
|
||||
assertEquals(TEST_CLIENT_GUID, cursor.getString(cursor.getColumnIndex(Clients.GUID)));
|
||||
assertEquals(TEST_CLIENT_NAME, cursor.getString(cursor.getColumnIndex(Clients.NAME)));
|
||||
assertEquals(TEST_CLIENT_DEVICE_TYPE, cursor.getString(cursor.getColumnIndex(Clients.DEVICE_TYPE)));
|
||||
assertTrue(cursor.isLast());
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
} finally {
|
||||
// We can't delete only our test client due to a Fennec CP issue with guid vs. client_guid.
|
||||
clientsClient.delete(BrowserContractHelpers.CLIENTS_CONTENT_URI, null, null);
|
||||
clientsClient.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectNoStoreDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
|
||||
import org.mozilla.gecko.sync.repositories.NoContentProviderException;
|
||||
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.mozilla.gecko.sync.repositories.android.FormHistoryRepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.FormHistoryRecord;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class TestFormHistoryRepositorySession extends AndroidSyncTestCase {
|
||||
protected ContentProviderClient formsProvider = null;
|
||||
|
||||
public TestFormHistoryRepositorySession() throws NoContentProviderException {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setUp() {
|
||||
if (formsProvider == null) {
|
||||
try {
|
||||
formsProvider = FormHistoryRepositorySession.acquireContentProvider(getApplicationContext());
|
||||
} catch (NoContentProviderException e) {
|
||||
fail("Failed to acquireContentProvider: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
FormHistoryRepositorySession.purgeDatabases(formsProvider);
|
||||
} catch (RemoteException e) {
|
||||
fail("Failed to purgeDatabases: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
public void tearDown() {
|
||||
if (formsProvider != null) {
|
||||
formsProvider.release();
|
||||
formsProvider = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected FormHistoryRepositorySession.FormHistoryRepository getRepository() {
|
||||
/**
|
||||
* Override this chain in order to avoid our test code having to create two
|
||||
* sessions all the time.
|
||||
*/
|
||||
return new FormHistoryRepositorySession.FormHistoryRepository() {
|
||||
@Override
|
||||
public void createSession(RepositorySessionCreationDelegate delegate,
|
||||
Context context) {
|
||||
try {
|
||||
final FormHistoryRepositorySession session = new FormHistoryRepositorySession(this, context) {
|
||||
@Override
|
||||
protected synchronized void trackGUID(String guid) {
|
||||
}
|
||||
};
|
||||
delegate.onSessionCreated(session);
|
||||
} catch (Exception e) {
|
||||
delegate.onSessionCreateFailed(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
protected FormHistoryRepositorySession createSession() {
|
||||
return (FormHistoryRepositorySession) SessionTestHelper.createSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
protected FormHistoryRepositorySession createAndBeginSession() {
|
||||
return (FormHistoryRepositorySession) SessionTestHelper.createAndBeginSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
public void testAcquire() throws NoContentProviderException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
assertNotNull(session.getFormsProvider());
|
||||
session.abort();
|
||||
}
|
||||
|
||||
protected int numRecords(FormHistoryRepositorySession session, Uri uri) throws RemoteException {
|
||||
Cursor cur = null;
|
||||
try {
|
||||
cur = session.getFormsProvider().query(uri, null, null, null, null);
|
||||
return cur.getCount();
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected long after0;
|
||||
protected long after1;
|
||||
protected long after2;
|
||||
protected long after3;
|
||||
protected long after4;
|
||||
protected FormHistoryRecord regular1;
|
||||
protected FormHistoryRecord regular2;
|
||||
protected FormHistoryRecord deleted1;
|
||||
protected FormHistoryRecord deleted2;
|
||||
|
||||
public void insertTwoRecords(FormHistoryRepositorySession session) throws RemoteException {
|
||||
Uri regularUri = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
|
||||
Uri deletedUri = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
|
||||
after0 = System.currentTimeMillis();
|
||||
|
||||
regular1 = new FormHistoryRecord("guid1", "forms", System.currentTimeMillis(), false);
|
||||
regular1.fieldName = "fieldName1";
|
||||
regular1.fieldValue = "value1";
|
||||
final ContentValues cv1 = new ContentValues();
|
||||
cv1.put(BrowserContract.FormHistory.GUID, regular1.guid);
|
||||
cv1.put(BrowserContract.FormHistory.FIELD_NAME, regular1.fieldName);
|
||||
cv1.put(BrowserContract.FormHistory.VALUE, regular1.fieldValue);
|
||||
cv1.put(BrowserContract.FormHistory.FIRST_USED, 1000 * regular1.lastModified); // Microseconds.
|
||||
|
||||
int regularInserted = session.getFormsProvider().bulkInsert(regularUri, new ContentValues[] { cv1 });
|
||||
assertEquals(1, regularInserted);
|
||||
after1 = System.currentTimeMillis();
|
||||
|
||||
deleted1 = new FormHistoryRecord("guid3", "forms", -1, true);
|
||||
final ContentValues cv3 = new ContentValues();
|
||||
cv3.put(BrowserContract.FormHistory.GUID, deleted1.guid);
|
||||
// cv3.put(BrowserContract.DeletedFormHistory.TIME_DELETED, record3.lastModified); // Set by CP.
|
||||
|
||||
int deletedInserted = session.getFormsProvider().bulkInsert(deletedUri, new ContentValues[] { cv3 });
|
||||
assertEquals(1, deletedInserted);
|
||||
after2 = System.currentTimeMillis();
|
||||
|
||||
regular2 = null;
|
||||
deleted2 = null;
|
||||
}
|
||||
|
||||
public void insertFourRecords(FormHistoryRepositorySession session) throws RemoteException {
|
||||
Uri regularUri = BrowserContractHelpers.FORM_HISTORY_CONTENT_URI;
|
||||
Uri deletedUri = BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI;
|
||||
|
||||
insertTwoRecords(session);
|
||||
|
||||
regular2 = new FormHistoryRecord("guid2", "forms", System.currentTimeMillis(), false);
|
||||
regular2.fieldName = "fieldName2";
|
||||
regular2.fieldValue = "value2";
|
||||
final ContentValues cv2 = new ContentValues();
|
||||
cv2.put(BrowserContract.FormHistory.GUID, regular2.guid);
|
||||
cv2.put(BrowserContract.FormHistory.FIELD_NAME, regular2.fieldName);
|
||||
cv2.put(BrowserContract.FormHistory.VALUE, regular2.fieldValue);
|
||||
cv2.put(BrowserContract.FormHistory.FIRST_USED, 1000 * regular2.lastModified); // Microseconds.
|
||||
|
||||
int regularInserted = session.getFormsProvider().bulkInsert(regularUri, new ContentValues[] { cv2 });
|
||||
assertEquals(1, regularInserted);
|
||||
after3 = System.currentTimeMillis();
|
||||
|
||||
deleted2 = new FormHistoryRecord("guid4", "forms", -1, true);
|
||||
final ContentValues cv4 = new ContentValues();
|
||||
cv4.put(BrowserContract.FormHistory.GUID, deleted2.guid);
|
||||
// cv4.put(BrowserContract.DeletedFormHistory.TIME_DELETED, record4.lastModified); // Set by CP.
|
||||
|
||||
int deletedInserted = session.getFormsProvider().bulkInsert(deletedUri, new ContentValues[] { cv4 });
|
||||
assertEquals(1, deletedInserted);
|
||||
after4 = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void testWipe() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertTwoRecords(session);
|
||||
assertTrue(numRecords(session, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI) > 0);
|
||||
assertTrue(numRecords(session, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI) > 0);
|
||||
|
||||
performWait(WaitHelper.onThreadRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.wipe(new RepositorySessionWipeDelegate() {
|
||||
public void onWipeSucceeded() {
|
||||
performNotify();
|
||||
}
|
||||
public void onWipeFailed(Exception ex) {
|
||||
performNotify("Wipe should have succeeded", ex);
|
||||
}
|
||||
@Override
|
||||
public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
assertEquals(0, numRecords(session, BrowserContractHelpers.FORM_HISTORY_CONTENT_URI));
|
||||
assertEquals(0, numRecords(session, BrowserContractHelpers.DELETED_FORM_HISTORY_CONTENT_URI));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
protected Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expectedGuids) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchSince(timestamp, new ExpectFetchSinceDelegate(timestamp, expectedGuids));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchAll(new ExpectFetchDelegate(expectedRecords));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expectedRecords) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session.fetch(guids, new ExpectFetchDelegate(expectedRecords));
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testFetchAll() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertTwoRecords(session);
|
||||
|
||||
performWait(fetchAllRunnable(session, new Record[] { regular1, deleted1 }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testFetchByGuid() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertTwoRecords(session);
|
||||
|
||||
performWait(fetchRunnable(session,
|
||||
new String[] { regular1.guid, deleted1.guid },
|
||||
new Record[] { regular1, deleted1 }));
|
||||
performWait(fetchRunnable(session,
|
||||
new String[] { regular1.guid },
|
||||
new Record[] { regular1 }));
|
||||
performWait(fetchRunnable(session,
|
||||
new String[] { deleted1.guid, "NON_EXISTENT_GUID?" },
|
||||
new Record[] { deleted1 }));
|
||||
performWait(fetchRunnable(session,
|
||||
new String[] { "FIRST_NON_EXISTENT_GUID", "SECOND_NON_EXISTENT_GUID?" },
|
||||
new Record[] { }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testFetchSince() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertFourRecords(session);
|
||||
|
||||
performWait(fetchSinceRunnable(session,
|
||||
after0, new String[] { regular1.guid, deleted1.guid, regular2.guid, deleted2.guid }));
|
||||
performWait(fetchSinceRunnable(session,
|
||||
after1, new String[] { deleted1.guid, regular2.guid, deleted2.guid }));
|
||||
performWait(fetchSinceRunnable(session,
|
||||
after2, new String[] { regular2.guid, deleted2.guid }));
|
||||
performWait(fetchSinceRunnable(session,
|
||||
after3, new String[] { deleted2.guid }));
|
||||
performWait(fetchSinceRunnable(session,
|
||||
after4, new String[] { }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
protected Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expectedGuids) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.guidsSince(timestamp, new ExpectGuidsSinceDelegate(expectedGuids));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testGuidsSince() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertTwoRecords(session);
|
||||
|
||||
performWait(guidsSinceRunnable(session,
|
||||
after0, new String[] { regular1.guid, deleted1.guid }));
|
||||
performWait(guidsSinceRunnable(session,
|
||||
after1, new String[] { deleted1.guid}));
|
||||
performWait(guidsSinceRunnable(session,
|
||||
after2, new String[] { }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
protected Runnable storeRunnable(final RepositorySession session, final Record record, final RepositorySessionStoreDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.setStoreDelegate(delegate);
|
||||
try {
|
||||
session.store(record);
|
||||
session.storeDone();
|
||||
} catch (NoStoreDelegateException e) {
|
||||
performNotify("NoStoreDelegateException should not occur.", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testStoreRemoteNew() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertTwoRecords(session);
|
||||
|
||||
FormHistoryRecord rec;
|
||||
|
||||
// remote regular, local missing => should store.
|
||||
rec = new FormHistoryRecord("new1", "forms", System.currentTimeMillis(), false);
|
||||
rec.fieldName = "fieldName1";
|
||||
rec.fieldValue = "fieldValue1";
|
||||
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
|
||||
performWait(fetchRunnable(session, new String[] { rec.guid }, new Record[] { rec }));
|
||||
|
||||
// remote deleted, local missing => should delete, but at the moment we ignore.
|
||||
rec = new FormHistoryRecord("new2", "forms", System.currentTimeMillis(), true);
|
||||
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
|
||||
performWait(fetchRunnable(session, new String[] { rec.guid }, new Record[] { }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testStoreRemoteNewer() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertFourRecords(session);
|
||||
long newTimestamp = System.currentTimeMillis();
|
||||
|
||||
FormHistoryRecord rec;
|
||||
|
||||
// remote regular, local regular, remote newer => should update.
|
||||
rec = new FormHistoryRecord(regular1.guid, regular1.collection, newTimestamp, false);
|
||||
rec.fieldName = regular1.fieldName;
|
||||
rec.fieldValue = regular1.fieldValue + "NEW";
|
||||
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
|
||||
performWait(fetchRunnable(session, new String[] { regular1.guid }, new Record[] { rec }));
|
||||
|
||||
// remote deleted, local regular, remote newer => should delete everything.
|
||||
rec = new FormHistoryRecord(regular2.guid, regular2.collection, newTimestamp, true);
|
||||
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
|
||||
performWait(fetchRunnable(session, new String[] { regular2.guid }, new Record[] { }));
|
||||
|
||||
// remote regular, local deleted, remote newer => should update.
|
||||
rec = new FormHistoryRecord(deleted1.guid, deleted1.collection, newTimestamp, false);
|
||||
rec.fieldName = regular1.fieldName;
|
||||
rec.fieldValue = regular1.fieldValue + "NEW";
|
||||
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
|
||||
performWait(fetchRunnable(session, new String[] { deleted1.guid }, new Record[] { rec }));
|
||||
|
||||
// remote deleted, local deleted, remote newer => should delete everything.
|
||||
rec = new FormHistoryRecord(deleted2.guid, deleted2.collection, newTimestamp, true);
|
||||
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
|
||||
performWait(fetchRunnable(session, new String[] { deleted2.guid }, new Record[] { }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testStoreRemoteOlder() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
long oldTimestamp = System.currentTimeMillis() - 100;
|
||||
insertFourRecords(session);
|
||||
|
||||
FormHistoryRecord rec;
|
||||
|
||||
// remote regular, local regular, remote older => should ignore.
|
||||
rec = new FormHistoryRecord(regular1.guid, regular1.collection, oldTimestamp, false);
|
||||
rec.fieldName = regular1.fieldName;
|
||||
rec.fieldValue = regular1.fieldValue + "NEW";
|
||||
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
|
||||
|
||||
// remote deleted, local regular, remote older => should ignore.
|
||||
rec = new FormHistoryRecord(regular2.guid, regular2.collection, oldTimestamp, true);
|
||||
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
|
||||
|
||||
// remote regular, local deleted, remote older => should ignore.
|
||||
rec = new FormHistoryRecord(deleted1.guid, deleted1.collection, oldTimestamp, false);
|
||||
rec.fieldName = regular1.fieldName;
|
||||
rec.fieldValue = regular1.fieldValue + "NEW";
|
||||
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
|
||||
|
||||
// remote deleted, local deleted, remote older => should ignore.
|
||||
rec = new FormHistoryRecord(deleted2.guid, deleted2.collection, oldTimestamp, true);
|
||||
performWait(storeRunnable(session, rec, new ExpectNoStoreDelegate()));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
|
||||
public void testStoreDifferentGuid() throws NoContentProviderException, RemoteException {
|
||||
final FormHistoryRepositorySession session = createAndBeginSession();
|
||||
|
||||
insertTwoRecords(session);
|
||||
|
||||
FormHistoryRecord rec = (FormHistoryRecord) regular1.copyWithIDs("distinct", 999);
|
||||
performWait(storeRunnable(session, rec, new ExpectStoredDelegate(rec.guid)));
|
||||
// Existing record should take remote record's GUID.
|
||||
performWait(fetchAllRunnable(session, new Record[] { rec, deleted1 }));
|
||||
|
||||
session.abort();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,482 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectNoStoreDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.PasswordHelpers;
|
||||
import org.mozilla.gecko.background.sync.helpers.SessionTestHelper;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.repositories.InactiveSessionException;
|
||||
import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
|
||||
import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.os.RemoteException;
|
||||
|
||||
public class TestPasswordsRepository extends AndroidSyncTestCase {
|
||||
private final String NEW_PASSWORD1 = "password";
|
||||
private final String NEW_PASSWORD2 = "drowssap";
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
wipe();
|
||||
assertTrue(WaitHelper.getTestWaiter().isIdle());
|
||||
}
|
||||
|
||||
public void testFetchAll() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
Record[] expected = new Record[] { PasswordHelpers.createPassword1(),
|
||||
PasswordHelpers.createPassword2() };
|
||||
|
||||
performWait(storeRunnable(session, expected[0]));
|
||||
performWait(storeRunnable(session, expected[1]));
|
||||
|
||||
performWait(fetchAllRunnable(session, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testGuidsSinceReturnMultipleRecords() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
PasswordRecord record1 = PasswordHelpers.createPassword1();
|
||||
PasswordRecord record2 = PasswordHelpers.createPassword2();
|
||||
|
||||
updatePassword(NEW_PASSWORD1, record1);
|
||||
long timestamp = updatePassword(NEW_PASSWORD2, record2);
|
||||
|
||||
String[] expected = new String[] { record1.guid, record2.guid };
|
||||
|
||||
performWait(storeRunnable(session, record1));
|
||||
performWait(storeRunnable(session, record2));
|
||||
|
||||
performWait(guidsSinceRunnable(session, timestamp, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testGuidsSinceReturnNoRecords() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
// Store 1 record in the past.
|
||||
performWait(storeRunnable(session, PasswordHelpers.createPassword1()));
|
||||
|
||||
String[] expected = {};
|
||||
performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchSinceOneRecord() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
// Passwords fetchSince checks timePasswordChanged, not insertion time.
|
||||
PasswordRecord record1 = PasswordHelpers.createPassword1();
|
||||
long timeModified1 = updatePassword(NEW_PASSWORD1, record1);
|
||||
performWait(storeRunnable(session, record1));
|
||||
|
||||
PasswordRecord record2 = PasswordHelpers.createPassword2();
|
||||
long timeModified2 = updatePassword(NEW_PASSWORD2, record2);
|
||||
performWait(storeRunnable(session, record2));
|
||||
|
||||
String[] expectedOne = new String[] { record2.guid };
|
||||
performWait(fetchSinceRunnable(session, timeModified2 - 10, expectedOne));
|
||||
|
||||
String[] expectedBoth = new String[] { record1.guid, record2.guid };
|
||||
performWait(fetchSinceRunnable(session, timeModified1 - 10, expectedBoth));
|
||||
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchSinceReturnNoRecords() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
|
||||
performWait(storeRunnable(session, PasswordHelpers.createPassword2()));
|
||||
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {}));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchOneRecordByGuid() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
Record record = PasswordHelpers.createPassword1();
|
||||
performWait(storeRunnable(session, record));
|
||||
performWait(storeRunnable(session, PasswordHelpers.createPassword2()));
|
||||
|
||||
String[] guids = new String[] { record.guid };
|
||||
Record[] expected = new Record[] { record };
|
||||
performWait(fetchRunnable(session, guids, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchMultipleRecordsByGuids() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
PasswordRecord record1 = PasswordHelpers.createPassword1();
|
||||
PasswordRecord record2 = PasswordHelpers.createPassword2();
|
||||
PasswordRecord record3 = PasswordHelpers.createPassword3();
|
||||
|
||||
performWait(storeRunnable(session, record1));
|
||||
performWait(storeRunnable(session, record2));
|
||||
performWait(storeRunnable(session, record3));
|
||||
|
||||
String[] guids = new String[] { record1.guid, record2.guid };
|
||||
Record[] expected = new Record[] { record1, record2 };
|
||||
performWait(fetchRunnable(session, guids, expected));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testFetchNoRecordByGuid() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
Record record = PasswordHelpers.createPassword1();
|
||||
|
||||
performWait(storeRunnable(session, record));
|
||||
performWait(fetchRunnable(session,
|
||||
new String[] { Utils.generateGuid() },
|
||||
new Record[] {}));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testStore() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
performWait(storeRunnable(session, PasswordHelpers.createPassword1()));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testRemoteNewerTimeStamp() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
|
||||
// Store updated local record.
|
||||
PasswordRecord local = PasswordHelpers.createPassword1();
|
||||
updatePassword(NEW_PASSWORD1, local, System.currentTimeMillis() - 1000);
|
||||
performWait(storeRunnable(session, local));
|
||||
|
||||
// Sync a remote record version that is newer.
|
||||
PasswordRecord remote = PasswordHelpers.createPassword2();
|
||||
remote.guid = local.guid;
|
||||
updatePassword(NEW_PASSWORD2, remote);
|
||||
performWait(storeRunnable(session, remote));
|
||||
|
||||
// Make a fetch, expecting only the newer (remote) record.
|
||||
performWait(fetchAllRunnable(session, new Record[] { remote }));
|
||||
|
||||
// Store an older local record.
|
||||
PasswordRecord local2 = PasswordHelpers.createPassword3();
|
||||
updatePassword(NEW_PASSWORD2, local2, System.currentTimeMillis() - 1000);
|
||||
performWait(storeRunnable(session, local2));
|
||||
|
||||
// Sync a remote record version that is newer and is deleted.
|
||||
PasswordRecord remote2 = PasswordHelpers.createPassword3();
|
||||
remote2.guid = local2.guid;
|
||||
remote2.deleted = true;
|
||||
updatePassword(NEW_PASSWORD2, remote2);
|
||||
performWait(storeRunnable(session, remote2));
|
||||
|
||||
// Make a fetch, expecting the local record to be deleted.
|
||||
performWait(fetchRunnable(session, new String[] { remote2.guid }, new Record[] {}));
|
||||
|
||||
// Store an older deleted local record.
|
||||
PasswordRecord local3 = PasswordHelpers.createPassword4();
|
||||
updatePassword(NEW_PASSWORD2, local3, System.currentTimeMillis() - 1000);
|
||||
local3.deleted = true;
|
||||
storeLocalDeletedRecord(local3, System.currentTimeMillis() - 1000);
|
||||
|
||||
// Sync a remote record version that is newer and is deleted.
|
||||
PasswordRecord remote3 = PasswordHelpers.createPassword5();
|
||||
remote3.guid = local3.guid;
|
||||
remote3.deleted = true;
|
||||
updatePassword(NEW_PASSWORD2, remote3);
|
||||
performWait(storeRunnable(session, remote3));
|
||||
|
||||
// Make a fetch, expecting the local record to be deleted.
|
||||
performWait(fetchRunnable(session, new String[] { remote3.guid }, new Record[] {}));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testLocalNewerTimeStamp() {
|
||||
final RepositorySession session = createAndBeginSession();
|
||||
// Remote record updated before local record.
|
||||
PasswordRecord remote = PasswordHelpers.createPassword1();
|
||||
updatePassword(NEW_PASSWORD1, remote, System.currentTimeMillis() - 1000);
|
||||
|
||||
// Store updated local record.
|
||||
PasswordRecord local = PasswordHelpers.createPassword2();
|
||||
updatePassword(NEW_PASSWORD2, local);
|
||||
performWait(storeRunnable(session, local));
|
||||
|
||||
// Sync a remote record version that is older.
|
||||
remote.guid = local.guid;
|
||||
performWait(storeRunnable(session, remote));
|
||||
|
||||
// Make a fetch, expecting only the newer (local) record.
|
||||
performWait(fetchAllRunnable(session, new Record[] { local }));
|
||||
|
||||
// Remote record updated before local record.
|
||||
PasswordRecord remote2 = PasswordHelpers.createPassword3();
|
||||
updatePassword(NEW_PASSWORD1, remote2, System.currentTimeMillis() - 1000);
|
||||
|
||||
// Store updated local record that is deleted.
|
||||
PasswordRecord local2 = PasswordHelpers.createPassword3();
|
||||
updatePassword(NEW_PASSWORD2, local2);
|
||||
local2.deleted = true;
|
||||
storeLocalDeletedRecord(local2, System.currentTimeMillis());
|
||||
|
||||
// Sync a remote record version that is older.
|
||||
remote2.guid = local2.guid;
|
||||
performWait(storeRunnable(session, remote2, new ExpectNoStoreDelegate()));
|
||||
|
||||
// Make a fetch, expecting only the deleted newer (local) record.
|
||||
performWait(fetchRunnable(session, new String[] { local2.guid }, new Record[] { local2 }));
|
||||
|
||||
// Remote record updated before local record.
|
||||
PasswordRecord remote3 = PasswordHelpers.createPassword4();
|
||||
updatePassword(NEW_PASSWORD1, remote3, System.currentTimeMillis() - 1000);
|
||||
|
||||
// Store updated local record that is deleted.
|
||||
PasswordRecord local3 = PasswordHelpers.createPassword4();
|
||||
updatePassword(NEW_PASSWORD2, local3);
|
||||
local3.deleted = true;
|
||||
storeLocalDeletedRecord(local3, System.currentTimeMillis());
|
||||
|
||||
// Sync a remote record version that is older and is deleted.
|
||||
remote3.guid = local3.guid;
|
||||
remote3.deleted = true;
|
||||
performWait(storeRunnable(session, remote3));
|
||||
|
||||
// Make a fetch, expecting the local record to be deleted.
|
||||
performWait(fetchRunnable(session, new String[] { local3.guid }, new Record[] {}));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store two records that are identical except for guid. Expect to find the
|
||||
* remote one after reconciling.
|
||||
*/
|
||||
public void testStoreIdenticalExceptGuid() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
PasswordRecord record = PasswordHelpers.createPassword1();
|
||||
record.guid = "before1";
|
||||
// Store record.
|
||||
performWait(storeRunnable(session, record));
|
||||
|
||||
// Store same record, but with different guid.
|
||||
record.guid = Utils.generateGuid();
|
||||
performWait(storeRunnable(session, record));
|
||||
|
||||
performWait(fetchAllRunnable(session, new Record[] { record }));
|
||||
dispose(session);
|
||||
|
||||
session = createAndBeginSession();
|
||||
|
||||
PasswordRecord record2 = PasswordHelpers.createPassword2();
|
||||
record2.guid = "before2";
|
||||
// Store record.
|
||||
performWait(storeRunnable(session, record2));
|
||||
|
||||
// Store same record, but with different guid.
|
||||
record2.guid = Utils.generateGuid();
|
||||
performWait(storeRunnable(session, record2));
|
||||
|
||||
performWait(fetchAllRunnable(session, new Record[] { record, record2 }));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store two records that are identical except for guid when they both point
|
||||
* to the same site and there are multiple records for that site. Expect to
|
||||
* find the remote one after reconciling.
|
||||
*/
|
||||
public void testStoreIdenticalExceptGuidOnSameSite() {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
PasswordRecord record1 = PasswordHelpers.createPassword1();
|
||||
record1.encryptedUsername = "original";
|
||||
record1.guid = "before1";
|
||||
PasswordRecord record2 = PasswordHelpers.createPassword1();
|
||||
record2.encryptedUsername = "different";
|
||||
record1.guid = "before2";
|
||||
// Store records.
|
||||
performWait(storeRunnable(session, record1));
|
||||
performWait(storeRunnable(session, record2));
|
||||
performWait(fetchAllRunnable(session, new Record[] { record1, record2 }));
|
||||
|
||||
dispose(session);
|
||||
session = createAndBeginSession();
|
||||
|
||||
// Store same records, but with different guids.
|
||||
record1.guid = Utils.generateGuid();
|
||||
performWait(storeRunnable(session, record1));
|
||||
performWait(fetchAllRunnable(session, new Record[] { record1, record2 }));
|
||||
|
||||
record2.guid = Utils.generateGuid();
|
||||
performWait(storeRunnable(session, record2));
|
||||
performWait(fetchAllRunnable(session, new Record[] { record1, record2 }));
|
||||
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
public void testRawFetch() throws RemoteException {
|
||||
RepositorySession session = createAndBeginSession();
|
||||
Record[] expected = new Record[] { PasswordHelpers.createPassword1(),
|
||||
PasswordHelpers.createPassword2() };
|
||||
|
||||
performWait(storeRunnable(session, expected[0]));
|
||||
performWait(storeRunnable(session, expected[1]));
|
||||
|
||||
ContentProviderClient client = getApplicationContext().getContentResolver().acquireContentProviderClient(BrowserContract.PASSWORDS_AUTHORITY_URI);
|
||||
Cursor cursor = client.query(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null, null, null);
|
||||
assertEquals(2, cursor.getCount());
|
||||
cursor.moveToFirst();
|
||||
Set<String> guids = new HashSet<String>();
|
||||
while (!cursor.isAfterLast()) {
|
||||
String guid = RepoUtils.getStringFromCursor(cursor, BrowserContract.Passwords.GUID);
|
||||
guids.add(guid);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
cursor.close();
|
||||
assertEquals(2, guids.size());
|
||||
assertTrue(guids.contains(expected[0].guid));
|
||||
assertTrue(guids.contains(expected[1].guid));
|
||||
dispose(session);
|
||||
}
|
||||
|
||||
// Helper methods.
|
||||
private RepositorySession createAndBeginSession() {
|
||||
return SessionTestHelper.createAndBeginSession(
|
||||
getApplicationContext(),
|
||||
getRepository());
|
||||
}
|
||||
|
||||
private Repository getRepository() {
|
||||
/**
|
||||
* Override this chain in order to avoid our test code having to create two
|
||||
* sessions all the time. Don't track records, so they filtering doesn't happen.
|
||||
*/
|
||||
return new PasswordsRepositorySession.PasswordsRepository() {
|
||||
@Override
|
||||
public void createSession(RepositorySessionCreationDelegate delegate,
|
||||
Context context) {
|
||||
PasswordsRepositorySession session;
|
||||
session = new PasswordsRepositorySession(this, context) {
|
||||
@Override
|
||||
protected synchronized void trackGUID(String guid) {
|
||||
}
|
||||
};
|
||||
delegate.onSessionCreated(session);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void wipe() {
|
||||
Context context = getApplicationContext();
|
||||
context.getContentResolver().delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null);
|
||||
context.getContentResolver().delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, null, null);
|
||||
}
|
||||
|
||||
private void storeLocalDeletedRecord(Record record, long time) {
|
||||
// Wipe data-store
|
||||
wipe();
|
||||
// Store record in deleted table.
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(BrowserContract.DeletedColumns.GUID, record.guid);
|
||||
contentValues.put(BrowserContract.DeletedColumns.TIME_DELETED, time);
|
||||
contentValues.put(BrowserContract.DeletedColumns.ID, record.androidID);
|
||||
getApplicationContext().getContentResolver().insert(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, contentValues);
|
||||
}
|
||||
|
||||
private static void dispose(RepositorySession session) {
|
||||
if (session != null) {
|
||||
session.abort();
|
||||
}
|
||||
}
|
||||
|
||||
private static long updatePassword(String password, PasswordRecord record, long timestamp) {
|
||||
record.encryptedPassword = password;
|
||||
long modifiedTime = System.currentTimeMillis();
|
||||
record.timePasswordChanged = record.lastModified = modifiedTime;
|
||||
return modifiedTime;
|
||||
}
|
||||
|
||||
private static long updatePassword(String password, PasswordRecord record) {
|
||||
return updatePassword(password, record, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
// Runnable Helpers.
|
||||
private static Runnable storeRunnable(final RepositorySession session, final Record record) {
|
||||
return storeRunnable(session, record, new ExpectStoredDelegate(record.guid));
|
||||
}
|
||||
|
||||
private static Runnable storeRunnable(final RepositorySession session, final Record record, final RepositorySessionStoreDelegate delegate) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.setStoreDelegate(delegate);
|
||||
try {
|
||||
session.store(record);
|
||||
session.storeDone();
|
||||
} catch (NoStoreDelegateException e) {
|
||||
fail("NoStoreDelegateException should not occur.");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Runnable fetchAllRunnable(final RepositorySession session, final Record[] records) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchAll(new ExpectFetchDelegate(records));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.guidsSince(timestamp, new ExpectGuidsSinceDelegate(expected));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
session.fetchSince(timestamp, new ExpectFetchSinceDelegate(timestamp, expected));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
session.fetch(guids, new ExpectFetchDelegate(expected));
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.db.BrowserContract.Combined;
|
||||
import org.mozilla.gecko.db.SuggestedSites;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
/**
|
||||
* Exercise BrowserDB's getTopSites
|
||||
*
|
||||
* @author ahunt
|
||||
*
|
||||
*/
|
||||
public class TestTopSites extends ActivityInstrumentationTestCase2<Activity> {
|
||||
Context mContext;
|
||||
SuggestedSites mSuggestedSites;
|
||||
|
||||
public TestTopSites() {
|
||||
super(Activity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
mContext = getInstrumentation().getTargetContext();
|
||||
mSuggestedSites = new SuggestedSites(mContext);
|
||||
|
||||
// By default we're using StubBrowserDB which has no suggested sites available.
|
||||
BrowserDB.from(GeckoProfile.get(mContext, Constants.DEFAULT_PROFILE)).setSuggestedSites(mSuggestedSites);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() {
|
||||
BrowserDB.from(GeckoProfile.get(mContext, Constants.DEFAULT_PROFILE)).setSuggestedSites(null);
|
||||
}
|
||||
|
||||
public void testGetTopSites() {
|
||||
final int SUGGESTED_LIMIT = 6;
|
||||
final int TOTAL_LIMIT = 50;
|
||||
|
||||
ContentResolver cr = mContext.getContentResolver();
|
||||
|
||||
final Uri uri = BrowserContract.TopSites.CONTENT_URI
|
||||
.buildUpon()
|
||||
.appendQueryParameter(BrowserContract.PARAM_PROFILE,
|
||||
Constants.DEFAULT_PROFILE)
|
||||
.appendQueryParameter(BrowserContract.PARAM_LIMIT,
|
||||
String.valueOf(SUGGESTED_LIMIT))
|
||||
.appendQueryParameter(BrowserContract.PARAM_SUGGESTEDSITES_LIMIT,
|
||||
String.valueOf(TOTAL_LIMIT))
|
||||
.build();
|
||||
|
||||
final Cursor c = cr.query(uri,
|
||||
new String[] { Combined._ID,
|
||||
Combined.URL,
|
||||
Combined.TITLE,
|
||||
Combined.BOOKMARK_ID,
|
||||
Combined.HISTORY_ID },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
int suggestedCount = 0;
|
||||
try {
|
||||
while (c.moveToNext()) {
|
||||
int type = c.getInt(c.getColumnIndexOrThrow(BrowserContract.Bookmarks.TYPE));
|
||||
assertEquals(BrowserContract.TopSites.TYPE_SUGGESTED, type);
|
||||
suggestedCount++;
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
|
||||
Cursor suggestedSitesCursor = mSuggestedSites.get(SUGGESTED_LIMIT);
|
||||
|
||||
assertEquals(suggestedSitesCursor.getCount(), suggestedCount);
|
||||
|
||||
suggestedSitesCursor.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.content.Loader;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
|
||||
import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts;
|
||||
import org.mozilla.gecko.fxa.AccountLoader;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Separated;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* A version of https://android.googlesource.com/platform/frameworks/base/+/c91893511dc1b9e634648406c9ae61b15476e65d/test-runner/src/android/test/LoaderTestCase.java,
|
||||
* hacked to work with the v4 support library, and patched to work around
|
||||
* https://code.google.com/p/android/issues/detail?id=40987.
|
||||
*/
|
||||
public class TestAccountLoader extends AndroidSyncTestCaseWithAccounts {
|
||||
// Test account names must start with TEST_USERNAME in order to be recognized
|
||||
// as test accounts and deleted in tearDown.
|
||||
private static final String TEST_USERNAME = "testAccount@mozilla.com";
|
||||
private static final String TEST_ACCOUNTTYPE = FxAccountConstants.ACCOUNT_TYPE;
|
||||
|
||||
private static final String TEST_SYNCKEY = "testSyncKey";
|
||||
private static final String TEST_SYNCPASSWORD = "testSyncPassword";
|
||||
|
||||
private static final String TEST_TOKEN_SERVER_URI = "testTokenServerURI";
|
||||
private static final String TEST_PROFILE_SERVER_URI = "testProfileServerURI";
|
||||
private static final String TEST_AUTH_SERVER_URI = "testAuthServerURI";
|
||||
private static final String TEST_PROFILE = "testProfile";
|
||||
|
||||
public TestAccountLoader() {
|
||||
super(TEST_ACCOUNTTYPE, TEST_USERNAME);
|
||||
}
|
||||
|
||||
static {
|
||||
// Force class loading of AsyncTask on the main thread so that it's handlers are tied to
|
||||
// the main thread and responses from the worker thread get delivered on the main thread.
|
||||
// The tests are run on another thread, allowing them to block waiting on a response from
|
||||
// the code running on the main thread. The main thread can't block since the AsyncTask
|
||||
// results come in via the event loop.
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a Loader synchronously and returns the result of the load. The loader will
|
||||
* be started, stopped, and destroyed by this method so it cannot be reused.
|
||||
*
|
||||
* @param loader The loader to run synchronously
|
||||
* @return The result from the loader
|
||||
*/
|
||||
public <T> T getLoaderResultSynchronously(final Loader<T> loader) {
|
||||
// The test thread blocks on this queue until the loader puts it's result in
|
||||
final ArrayBlockingQueue<AtomicReference<T>> queue = new ArrayBlockingQueue<AtomicReference<T>>(1);
|
||||
|
||||
// This callback runs on the "main" thread and unblocks the test thread
|
||||
// when it puts the result into the blocking queue
|
||||
final Loader.OnLoadCompleteListener<T> listener = new Loader.OnLoadCompleteListener<T>() {
|
||||
@Override
|
||||
public void onLoadComplete(Loader<T> completedLoader, T data) {
|
||||
// Shut the loader down
|
||||
completedLoader.unregisterListener(this);
|
||||
completedLoader.stopLoading();
|
||||
completedLoader.reset();
|
||||
// Store the result, unblocking the test thread
|
||||
queue.add(new AtomicReference<T>(data));
|
||||
}
|
||||
};
|
||||
|
||||
// This handler runs on the "main" thread of the process since AsyncTask
|
||||
// is documented as needing to run on the main thread and many Loaders use
|
||||
// AsyncTask
|
||||
final Handler mainThreadHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
loader.registerListener(0, listener);
|
||||
loader.startLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// Ask the main thread to start the loading process
|
||||
mainThreadHandler.sendEmptyMessage(0);
|
||||
|
||||
// Block on the queue waiting for the result of the load to be inserted
|
||||
T result;
|
||||
while (true) {
|
||||
try {
|
||||
result = queue.take().get();
|
||||
break;
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("waiting thread interrupted", e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void testInitialLoad() throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException {
|
||||
// This is tricky. We can't mock the AccountManager easily -- see
|
||||
// https://groups.google.com/d/msg/android-mock/VXyzvKTMUGs/Y26wVPrl50sJ --
|
||||
// and we don't want to delete any existing accounts on device. So our test
|
||||
// needs to be adaptive (and therefore a little race-prone).
|
||||
|
||||
final Context context = getApplicationContext();
|
||||
final AccountLoader loader = new AccountLoader(context);
|
||||
|
||||
final boolean firefoxAccountsExist = FirefoxAccounts.firefoxAccountsExist(context);
|
||||
|
||||
if (firefoxAccountsExist) {
|
||||
assertFirefoxAccount(getLoaderResultSynchronously((Loader<Account>) loader));
|
||||
return;
|
||||
}
|
||||
|
||||
// This account will get cleaned up in tearDown.
|
||||
final State state = new Separated(TEST_USERNAME, "uid", false); // State choice is arbitrary.
|
||||
final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context,
|
||||
TEST_USERNAME, TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, TEST_PROFILE_SERVER_URI,
|
||||
state, AndroidSyncTestCaseWithAccounts.TEST_SYNC_AUTOMATICALLY_MAP_WITH_ALL_AUTHORITIES_DISABLED);
|
||||
assertNotNull(account);
|
||||
assertFirefoxAccount(getLoaderResultSynchronously((Loader<Account>) loader));
|
||||
}
|
||||
|
||||
protected void assertFirefoxAccount(Account account) {
|
||||
assertNotNull(account);
|
||||
assertEquals(FxAccountConstants.ACCOUNT_TYPE, account.type);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.fxa;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
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 org.mozilla.gecko.sync.Utils;
|
||||
|
||||
public class TestBrowserIDKeyPairGeneration extends AndroidSyncTestCase {
|
||||
public void doTestEncodeDecode(BrowserIDKeyPair keyPair) throws Exception {
|
||||
SigningPrivateKey privateKey = keyPair.getPrivate();
|
||||
VerifyingPublicKey publicKey = keyPair.getPublic();
|
||||
|
||||
ExtendedJSONObject o = new ExtendedJSONObject();
|
||||
o.put("key", Utils.generateGuid());
|
||||
|
||||
String token = JSONWebTokenUtils.encode(o.toJSONString(), privateKey);
|
||||
assertNotNull(token);
|
||||
|
||||
String payload = JSONWebTokenUtils.decode(token, publicKey);
|
||||
assertEquals(o.toJSONString(), payload);
|
||||
|
||||
try {
|
||||
JSONWebTokenUtils.decode(token + "x", publicKey);
|
||||
fail("Expected exception.");
|
||||
} catch (GeneralSecurityException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
public void testEncodeDecodeSuccessRSA() throws Exception {
|
||||
doTestEncodeDecode(RSACryptoImplementation.generateKeyPair(1024));
|
||||
doTestEncodeDecode(RSACryptoImplementation.generateKeyPair(2048));
|
||||
}
|
||||
|
||||
public void testEncodeDecodeSuccessDSA() throws Exception {
|
||||
doTestEncodeDecode(DSACryptoImplementation.generateKeyPair(512));
|
||||
doTestEncodeDecode(DSACryptoImplementation.generateKeyPair(1024));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.fxa.authenticator;
|
||||
|
||||
import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AccountPickler;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Separated;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.test.RenamingDelegatingContext;
|
||||
|
||||
public class TestAccountPickler extends AndroidSyncTestCaseWithAccounts {
|
||||
private static final String TEST_TOKEN_SERVER_URI = "tokenServerURI";
|
||||
private static final String TEST_PROFILE_SERVER_URI = "profileServerURI";
|
||||
private static final String TEST_AUTH_SERVER_URI = "serverURI";
|
||||
private static final String TEST_PROFILE = "profile";
|
||||
private final static String FILENAME_PREFIX = "TestAccountPickler-";
|
||||
private final static String PICKLE_FILENAME = "pickle";
|
||||
|
||||
private final static String TEST_ACCOUNTTYPE = FxAccountConstants.ACCOUNT_TYPE;
|
||||
|
||||
// Test account names must start with TEST_USERNAME in order to be recognized
|
||||
// as test accounts and deleted in tearDown.
|
||||
public static final String TEST_USERNAME = "testFirefoxAccount@mozilla.com";
|
||||
|
||||
public Account account;
|
||||
public RenamingDelegatingContext context;
|
||||
|
||||
public TestAccountPickler() {
|
||||
super(TEST_ACCOUNTTYPE, TEST_USERNAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
this.account = null;
|
||||
// Randomize the filename prefix in case we don't clean up correctly.
|
||||
this.context = new RenamingDelegatingContext(getApplicationContext(), FILENAME_PREFIX +
|
||||
Math.random() * 1000001 + "-");
|
||||
this.accountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() {
|
||||
super.tearDown();
|
||||
this.context.deleteFile(PICKLE_FILENAME);
|
||||
}
|
||||
|
||||
public AndroidFxAccount addTestAccount() throws Exception {
|
||||
final State state = new Separated(TEST_USERNAME, "uid", false); // State choice is arbitrary.
|
||||
final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, TEST_USERNAME,
|
||||
TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, TEST_PROFILE_SERVER_URI, state,
|
||||
AndroidSyncTestCaseWithAccounts.TEST_SYNC_AUTOMATICALLY_MAP_WITH_ALL_AUTHORITIES_DISABLED);
|
||||
assertNotNull(account);
|
||||
assertNotNull(account.getProfile());
|
||||
assertTrue(testAccountsExist()); // Sanity check.
|
||||
this.account = account.getAndroidAccount(); // To remove in tearDown() if we throw.
|
||||
return account;
|
||||
}
|
||||
|
||||
public void testPickle() throws Exception {
|
||||
final AndroidFxAccount account = addTestAccount();
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
final ExtendedJSONObject o = AccountPickler.toJSON(account, now);
|
||||
assertNotNull(o.toJSONString());
|
||||
|
||||
assertEquals(3, o.getLong(AccountPickler.KEY_PICKLE_VERSION).longValue());
|
||||
assertTrue(o.getLong(AccountPickler.KEY_PICKLE_TIMESTAMP).longValue() < System.currentTimeMillis());
|
||||
|
||||
assertEquals(AndroidFxAccount.CURRENT_ACCOUNT_VERSION, o.getIntegerSafely(AccountPickler.KEY_ACCOUNT_VERSION).intValue());
|
||||
assertEquals(FxAccountConstants.ACCOUNT_TYPE, o.getString(AccountPickler.KEY_ACCOUNT_TYPE));
|
||||
|
||||
assertEquals(TEST_USERNAME, o.getString(AccountPickler.KEY_EMAIL));
|
||||
assertEquals(TEST_PROFILE, o.getString(AccountPickler.KEY_PROFILE));
|
||||
assertEquals(TEST_AUTH_SERVER_URI, o.getString(AccountPickler.KEY_IDP_SERVER_URI));
|
||||
assertEquals(TEST_TOKEN_SERVER_URI, o.getString(AccountPickler.KEY_TOKEN_SERVER_URI));
|
||||
assertEquals(TEST_PROFILE_SERVER_URI, o.getString(AccountPickler.KEY_PROFILE_SERVER_URI));
|
||||
|
||||
assertNotNull(o.getObject(AccountPickler.KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP));
|
||||
assertNotNull(o.get(AccountPickler.KEY_BUNDLE));
|
||||
}
|
||||
|
||||
public void testPickleAndUnpickle() throws Exception {
|
||||
final AndroidFxAccount inputAccount = addTestAccount();
|
||||
|
||||
AccountPickler.pickle(inputAccount, PICKLE_FILENAME);
|
||||
final ExtendedJSONObject inputJSON = AccountPickler.toJSON(inputAccount, 0);
|
||||
final State inputState = inputAccount.getState();
|
||||
assertNotNull(inputJSON);
|
||||
assertNotNull(inputState);
|
||||
|
||||
// unpickle adds an account to the AccountManager so delete it first.
|
||||
deleteTestAccounts();
|
||||
assertFalse(testAccountsExist());
|
||||
|
||||
final AndroidFxAccount unpickledAccount = AccountPickler.unpickle(context, PICKLE_FILENAME);
|
||||
assertNotNull(unpickledAccount);
|
||||
final ExtendedJSONObject unpickledJSON = AccountPickler.toJSON(unpickledAccount, 0);
|
||||
final State unpickledState = unpickledAccount.getState();
|
||||
assertNotNull(unpickledJSON);
|
||||
assertNotNull(unpickledState);
|
||||
|
||||
assertEquals(inputJSON, unpickledJSON);
|
||||
assertStateEquals(inputState, unpickledState);
|
||||
}
|
||||
|
||||
public void testDeletePickle() throws Exception {
|
||||
final AndroidFxAccount account = addTestAccount();
|
||||
AccountPickler.pickle(account, PICKLE_FILENAME);
|
||||
|
||||
final String s = Utils.readFile(context, PICKLE_FILENAME);
|
||||
assertNotNull(s);
|
||||
assertTrue(s.length() > 0);
|
||||
|
||||
AccountPickler.deletePickle(context, PICKLE_FILENAME);
|
||||
assertFileNotPresent(context, PICKLE_FILENAME);
|
||||
}
|
||||
|
||||
private void assertStateEquals(final State expected, final State actual) throws Exception {
|
||||
// TODO: Write and use State.equals. Thus, this is only thorough for the State base class.
|
||||
assertEquals(expected.getStateLabel(), actual.getStateLabel());
|
||||
assertEquals(expected.email, actual.email);
|
||||
assertEquals(expected.uid, actual.uid);
|
||||
assertEquals(expected.verified, actual.verified);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.helpers;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
/**
|
||||
* AndroidSyncTestCase provides helper methods for testing.
|
||||
*/
|
||||
public class AndroidSyncTestCase extends ActivityInstrumentationTestCase2<Activity> {
|
||||
protected static String LOG_TAG = "AndroidSyncTestCase";
|
||||
|
||||
public AndroidSyncTestCase() {
|
||||
super(Activity.class);
|
||||
WaitHelper.resetTestWaiter();
|
||||
}
|
||||
|
||||
public Context getApplicationContext() {
|
||||
return this.getInstrumentation().getTargetContext();
|
||||
}
|
||||
|
||||
public static void performWait(Runnable runnable) {
|
||||
try {
|
||||
WaitHelper.getTestWaiter().performWait(runnable);
|
||||
} catch (WaitHelper.InnerError e) {
|
||||
AssertionFailedError inner = new AssertionFailedError("Caught error in performWait");
|
||||
inner.initCause(e.innerError);
|
||||
throw inner;
|
||||
}
|
||||
}
|
||||
|
||||
public static void performNotify() {
|
||||
WaitHelper.getTestWaiter().performNotify();
|
||||
}
|
||||
|
||||
public static void performNotify(Throwable e) {
|
||||
WaitHelper.getTestWaiter().performNotify(e);
|
||||
}
|
||||
|
||||
public static void performNotify(String reason, Throwable e) {
|
||||
AssertionFailedError er = new AssertionFailedError(reason + ": " + e.getMessage());
|
||||
er.initCause(e);
|
||||
WaitHelper.getTestWaiter().performNotify(er);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.helpers;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class DBHelpers {
|
||||
|
||||
/*
|
||||
* Works for strings and int-ish values.
|
||||
*/
|
||||
public static void assertCursorContains(Object[][] expected, Cursor actual) {
|
||||
Assert.assertEquals(expected.length, actual.getCount());
|
||||
int i = 0, j = 0;
|
||||
Object[] row;
|
||||
|
||||
do {
|
||||
row = expected[i];
|
||||
for (j = 0; j < row.length; ++j) {
|
||||
Object atIndex = row[j];
|
||||
if (atIndex == null) {
|
||||
continue;
|
||||
}
|
||||
if (atIndex instanceof String) {
|
||||
Assert.assertEquals(atIndex, actual.getString(j));
|
||||
} else {
|
||||
Assert.assertEquals(atIndex, actual.getInt(j));
|
||||
}
|
||||
}
|
||||
++i;
|
||||
} while (actual.moveToPosition(i));
|
||||
}
|
||||
|
||||
public static int getRowCount(SQLiteDatabase db, String table) {
|
||||
return getRowCount(db, table, null, null);
|
||||
}
|
||||
|
||||
public static int getRowCount(SQLiteDatabase db, String table, String selection, String[] selectionArgs) {
|
||||
final Cursor c = db.query(table, null, selection, selectionArgs, null, null, null);
|
||||
try {
|
||||
return c.getCount();
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ID that is non-existent in the given sqlite table. Assumes that a column named
|
||||
* "id" exists.
|
||||
*/
|
||||
public static int getNonExistentID(SQLiteDatabase db, String table) {
|
||||
// XXX: We should use selectionArgs to concatenate table, but sqlite throws a syntax error on
|
||||
// "?" because it wants to ensure id is a valid column in table.
|
||||
final Cursor c = db.rawQuery("SELECT MAX(id) + 1 FROM " + table, null);
|
||||
try {
|
||||
if (!c.moveToNext()) {
|
||||
return 0;
|
||||
}
|
||||
return c.getInt(0);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an ID that exists in the given sqlite table. Assumes that a column named * "id"
|
||||
* exists.
|
||||
*/
|
||||
public static long getExistentID(SQLiteDatabase db, String table) {
|
||||
final Cursor c = db.query(table, new String[] {"id"}, null, null, null, null, null, "1");
|
||||
try {
|
||||
if (!c.moveToNext()) {
|
||||
throw new IllegalStateException("Given table does not contain any entries.");
|
||||
}
|
||||
return c.getInt(0);
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.helpers;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.Context;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.mock.MockContentResolver;
|
||||
|
||||
/**
|
||||
* Because ProviderTestCase2 is unable to handle custom DB paths.
|
||||
*/
|
||||
public abstract class DBProviderTestCase<T extends ContentProvider> extends
|
||||
AndroidTestCase {
|
||||
|
||||
Class<T> providerClass;
|
||||
String providerAuthority;
|
||||
|
||||
protected File fakeProfileDirectory;
|
||||
private MockContentResolver resolver;
|
||||
private T provider;
|
||||
|
||||
public DBProviderTestCase(Class<T> providerClass, String providerAuthority) {
|
||||
this.providerClass = providerClass;
|
||||
this.providerAuthority = providerAuthority;
|
||||
}
|
||||
|
||||
public T getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
public MockContentResolver getMockContentResolver() {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
protected String getCacheSuffix() {
|
||||
return this.getClass().getName() + "-" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
File cache = getContext().getCacheDir();
|
||||
fakeProfileDirectory = new File(cache.getAbsolutePath() + getCacheSuffix());
|
||||
System.out.println("Test: Creating profile directory " + fakeProfileDirectory.getAbsolutePath());
|
||||
if (!fakeProfileDirectory.mkdir()) {
|
||||
throw new IllegalStateException("Could not create temporary directory.");
|
||||
}
|
||||
|
||||
final Context context = getContext();
|
||||
assertNotNull(context);
|
||||
resolver = new MockContentResolver();
|
||||
provider = providerClass.newInstance();
|
||||
provider.attachInfo(context, null);
|
||||
assertNotNull(provider);
|
||||
resolver.addProvider(providerAuthority, getProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
// We don't check return values.
|
||||
System.out.println("Test: Cleaning up " + fakeProfileDirectory.getAbsolutePath());
|
||||
for (File child : fakeProfileDirectory.listFiles()) {
|
||||
child.delete();
|
||||
}
|
||||
fakeProfileDirectory.delete();
|
||||
super.tearDown();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.nativecode.test;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.mozilla.gecko.background.nativecode.NativeCrypto;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
/*
|
||||
* Tests the Java wrapper over native implementations of crypto code. Test vectors from:
|
||||
* * PBKDF2SHA256:
|
||||
* - <https://github.com/ircmaxell/PHP-PasswordLib/blob/master/test/Data/Vectors/pbkdf2-draft-josefsson-sha256.test-vectors>
|
||||
* - <https://gitorious.org/scrypt/nettle-scrypt/blobs/37c0d5288e991604fe33dba2f1724986a8dddf56/testsuite/pbkdf2-test.c>
|
||||
* * SHA-1:
|
||||
* - <http://oauth.googlecode.com/svn/code/c/liboauth/src/sha1.c>
|
||||
*/
|
||||
public class TestNativeCrypto extends TestCase {
|
||||
|
||||
public final void testPBKDF2SHA256A() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
String p = "password";
|
||||
String s = "salt";
|
||||
int dkLen = 32;
|
||||
|
||||
checkPBKDF2SHA256(p, s, 1, dkLen, "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b");
|
||||
checkPBKDF2SHA256(p, s, 4096, dkLen, "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a");
|
||||
}
|
||||
|
||||
public final void testPBKDF2SHA256B() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
String p = "passwordPASSWORDpassword";
|
||||
String s = "saltSALTsaltSALTsaltSALTsaltSALTsalt";
|
||||
int dkLen = 40;
|
||||
|
||||
checkPBKDF2SHA256(p, s, 4096, dkLen, "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9");
|
||||
}
|
||||
|
||||
public final void testPBKDF2SHA256scryptA() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
String p = "passwd";
|
||||
String s = "salt";
|
||||
int dkLen = 64;
|
||||
|
||||
checkPBKDF2SHA256(p, s, 1, dkLen, "55ac046e56e3089fec1691c22544b605f94185216dde0465e68b9d57c20dacbc49ca9cccf179b645991664b39d77ef317c71b845b1e30bd509112041d3a19783");
|
||||
}
|
||||
|
||||
public final void testPBKDF2SHA256scryptB() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
String p = "Password";
|
||||
String s = "NaCl";
|
||||
int dkLen = 64;
|
||||
|
||||
checkPBKDF2SHA256(p, s, 80000, dkLen, "4ddcd8f60b98be21830cee5ef22701f9641a4418d04c0414aeff08876b34ab56a1d425a1225833549adb841b51c9b3176a272bdebba1d078478f62b397f33c8d");
|
||||
}
|
||||
|
||||
public final void testPBKDF2SHA256C() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
String p = "pass\0word";
|
||||
String s = "sa\0lt";
|
||||
int dkLen = 16;
|
||||
|
||||
checkPBKDF2SHA256(p, s, 4096, dkLen, "89b69d0516f829893c696226650a8687");
|
||||
}
|
||||
|
||||
/*
|
||||
// This test takes two or three minutes to run, so we don't.
|
||||
public final void testPBKDF2SHA256D() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
String p = "password";
|
||||
String s = "salt";
|
||||
int dkLen = 32;
|
||||
|
||||
checkPBKDF2SHA256(p, s, 16777216, dkLen, "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e89b3b0bcb17ad10e3ac6eba46");
|
||||
}
|
||||
*/
|
||||
|
||||
public final void testTimePBKDF2SHA256() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
checkPBKDF2SHA256("password", "salt", 80000, 32, null);
|
||||
}
|
||||
|
||||
public final void testPBKDF2SHA256InvalidLenArg() throws UnsupportedEncodingException, GeneralSecurityException {
|
||||
final String p = "password";
|
||||
final String s = "salt";
|
||||
final int c = 1;
|
||||
final int dkLen = -1; // Should always be positive.
|
||||
|
||||
try {
|
||||
NativeCrypto.pbkdf2SHA256(p.getBytes("US-ASCII"), s.getBytes("US-ASCII"), c, dkLen);
|
||||
fail("Expected sha256 to throw with negative dkLen argument.");
|
||||
} catch (IllegalArgumentException e) { } // Expected.
|
||||
}
|
||||
|
||||
public final void testSHA1() throws UnsupportedEncodingException {
|
||||
final String[] inputs = new String[] {
|
||||
"abc",
|
||||
"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
||||
"" // To be filled in below.
|
||||
};
|
||||
final String baseStr = "01234567";
|
||||
final int repetitions = 80;
|
||||
final StringBuilder builder = new StringBuilder(baseStr.length() * repetitions);
|
||||
for (int i = 0; i < 80; ++i) {
|
||||
builder.append(baseStr);
|
||||
}
|
||||
inputs[2] = builder.toString();
|
||||
|
||||
final String[] expecteds = new String[] {
|
||||
"a9993e364706816aba3e25717850c26c9cd0d89d",
|
||||
"84983e441c3bd26ebaae4aa1f95129e5e54670f1",
|
||||
"dea356a2cddd90c7a7ecedc5ebb563934f460452"
|
||||
};
|
||||
|
||||
for (int i = 0; i < inputs.length; ++i) {
|
||||
final byte[] input = inputs[i].getBytes("US-ASCII");
|
||||
final String expected = expecteds[i];
|
||||
|
||||
final byte[] actual = NativeCrypto.sha1(input);
|
||||
assertNotNull("Hashed value is non-null", actual);
|
||||
assertExpectedBytes(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test to ensure the output of our SHA1 algo is the same as MessageDigest's. This is important
|
||||
* because we intend to replace MessageDigest in FHR with this SHA-1 algo (bug 959652).
|
||||
*/
|
||||
public final void testSHA1AgainstMessageDigest() throws UnsupportedEncodingException,
|
||||
NoSuchAlgorithmException {
|
||||
final String[] inputs = {
|
||||
"password",
|
||||
"saranghae",
|
||||
"aoeusnthaoeusnthaoeusnth \0 12345098765432109876_!"
|
||||
};
|
||||
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
for (final String input : inputs) {
|
||||
final byte[] inputBytes = input.getBytes("US-ASCII");
|
||||
|
||||
final byte[] mdBytes = digest.digest(inputBytes);
|
||||
final byte[] ourBytes = NativeCrypto.sha1(inputBytes);
|
||||
assertTrue("MessageDigest hash is the same as NativeCrypto SHA-1 hash",
|
||||
Arrays.equals(ourBytes, mdBytes));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPBKDF2SHA256(String p, String s, int c, int dkLen,
|
||||
final String expectedStr)
|
||||
throws GeneralSecurityException, UnsupportedEncodingException {
|
||||
long start = System.currentTimeMillis();
|
||||
byte[] key = NativeCrypto.pbkdf2SHA256(p.getBytes("US-ASCII"), s.getBytes("US-ASCII"), c, dkLen);
|
||||
assertNotNull(key);
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
|
||||
System.err.println("SHA-256 " + c + " took " + (end - start) + "ms");
|
||||
if (expectedStr == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
assertEquals(dkLen, Utils.hex2Byte(expectedStr).length);
|
||||
assertExpectedBytes(expectedStr, key);
|
||||
}
|
||||
|
||||
private void assertExpectedBytes(final String expectedStr, byte[] key) {
|
||||
assertEquals(expectedStr, Utils.byte2Hex(key));
|
||||
byte[] expected = Utils.hex2Byte(expectedStr);
|
||||
|
||||
assertEquals(expected.length, key.length);
|
||||
for (int i = 0; i < key.length; i++) {
|
||||
assertEquals(expected[i], key[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.content.Context;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AndroidSyncTestCaseWithAccounts extends AndroidSyncTestCase {
|
||||
public final String testAccountType;
|
||||
public final String testAccountPrefix;
|
||||
|
||||
protected Context context;
|
||||
protected AccountManager accountManager;
|
||||
protected int numAccounts;
|
||||
|
||||
public static final Map<String, Boolean> TEST_SYNC_AUTOMATICALLY_MAP_WITH_ALL_AUTHORITIES_DISABLED;
|
||||
static {
|
||||
final Map<String, Boolean> m = new HashMap<String, Boolean>();
|
||||
for (String authority : AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP.keySet()) {
|
||||
m.put(authority, false);
|
||||
}
|
||||
TEST_SYNC_AUTOMATICALLY_MAP_WITH_ALL_AUTHORITIES_DISABLED = m;
|
||||
}
|
||||
|
||||
public AndroidSyncTestCaseWithAccounts(String accountType, String accountPrefix) {
|
||||
super();
|
||||
this.testAccountType = accountType;
|
||||
this.testAccountPrefix = accountPrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
context = getApplicationContext();
|
||||
accountManager = AccountManager.get(context);
|
||||
deleteTestAccounts(); // Always start with no test accounts.
|
||||
numAccounts = accountManager.getAccountsByType(testAccountType).length;
|
||||
}
|
||||
|
||||
public List<Account> getTestAccounts() {
|
||||
final List<Account> testAccounts = new ArrayList<Account>();
|
||||
|
||||
final Account[] accounts = accountManager.getAccountsByType(testAccountType);
|
||||
for (Account account : accounts) {
|
||||
if (account.name.startsWith(testAccountPrefix)) {
|
||||
testAccounts.add(account);
|
||||
}
|
||||
}
|
||||
|
||||
return testAccounts;
|
||||
}
|
||||
|
||||
public static void deleteAccount(final InstrumentationTestCase test, final AccountManager accountManager, final Account account) {
|
||||
performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
test.runTestOnUiThread(new Runnable() {
|
||||
final AccountManagerCallback<Boolean> callback = new AccountManagerCallback<Boolean>() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Boolean> future) {
|
||||
try {
|
||||
future.getResult(5L, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
performNotify();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
accountManager.removeAccount(account, callback, null);
|
||||
}
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteTestAccounts() {
|
||||
for (Account account : getTestAccounts()) {
|
||||
deleteAccount(this, accountManager, account);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean testAccountsExist() {
|
||||
// Note that we don't use FirefoxAccounts.firefoxAccountsExist because it unpickles.
|
||||
return !getTestAccounts().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() {
|
||||
deleteTestAccounts();
|
||||
assertEquals(numAccounts, accountManager.getAccountsByType(testAccountType).length);
|
||||
}
|
||||
|
||||
public static void assertFileNotPresent(final Context context, final String filename) throws Exception {
|
||||
// Verify file is not present.
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = context.openFileInput(filename);
|
||||
fail("Should get FileNotFoundException.");
|
||||
} catch (FileNotFoundException e) {
|
||||
// Do nothing; file should not exist.
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
fis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback;
|
||||
import org.mozilla.gecko.background.testhelpers.MockClientsDataDelegate;
|
||||
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
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 org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class TestClientsStage extends AndroidSyncTestCase {
|
||||
private static final String TEST_USERNAME = "johndoe";
|
||||
private static final String TEST_PASSWORD = "password";
|
||||
private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(getApplicationContext());
|
||||
db.wipeDB();
|
||||
db.close();
|
||||
}
|
||||
|
||||
public void testWipeClearsClients() throws Exception {
|
||||
|
||||
// Wiping clients is equivalent to a reset and dropping all local stored client records.
|
||||
// Resetting is defined as being the same as for other engines -- discard local
|
||||
// and remote timestamps, tracked failed records, and tracked records to fetch.
|
||||
|
||||
final Context context = getApplicationContext();
|
||||
final ClientsDatabaseAccessor dataAccessor = new ClientsDatabaseAccessor(context);
|
||||
final GlobalSessionCallback callback = new DefaultGlobalSessionCallback();
|
||||
final ClientsDataDelegate delegate = new MockClientsDataDelegate();
|
||||
|
||||
final KeyBundle keyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY);
|
||||
final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD);
|
||||
final SharedPreferences prefs = new MockSharedPreferences();
|
||||
final SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, authHeaderProvider, prefs);
|
||||
config.syncKeyBundle = keyBundle;
|
||||
GlobalSession session = new GlobalSession(config, callback, context, delegate);
|
||||
|
||||
SyncClientsEngineStage stage = new SyncClientsEngineStage() {
|
||||
|
||||
@Override
|
||||
public synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
|
||||
if (db == null) {
|
||||
db = dataAccessor;
|
||||
}
|
||||
return db;
|
||||
}
|
||||
};
|
||||
|
||||
final String guid = "clientabcdef";
|
||||
long lastModified = System.currentTimeMillis();
|
||||
ClientRecord record = new ClientRecord(guid, "clients", lastModified , false);
|
||||
record.name = "John's Phone";
|
||||
record.type = "mobile";
|
||||
record.device = "Some Device";
|
||||
record.os = "iOS";
|
||||
record.commands = new JSONArray();
|
||||
|
||||
dataAccessor.store(record);
|
||||
assertEquals(1, dataAccessor.clientsCount());
|
||||
|
||||
final ClientRecord stored = dataAccessor.fetchAllClients().get(guid);
|
||||
assertNotNull(stored);
|
||||
assertEquals("John's Phone", stored.name);
|
||||
assertEquals("mobile", stored.type);
|
||||
assertEquals("Some Device", stored.device);
|
||||
assertEquals("iOS", stored.os);
|
||||
|
||||
stage.wipeLocal(session);
|
||||
|
||||
try {
|
||||
assertEquals(0, dataAccessor.clientsCount());
|
||||
assertEquals(0L, session.config.getPersistedServerClientRecordTimestamp());
|
||||
assertEquals(0, session.getClientsDelegate().getClientsCount());
|
||||
} finally {
|
||||
dataAccessor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,198 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.testhelpers.BaseMockServerSyncStage;
|
||||
import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback;
|
||||
import org.mozilla.gecko.background.testhelpers.MockRecord;
|
||||
import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
|
||||
import org.mozilla.gecko.background.testhelpers.WBORepository;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.sync.EngineSettings;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.MetaGlobalException;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.SynchronizerConfiguration;
|
||||
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
||||
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
import org.mozilla.gecko.sync.stage.NoSuchStageException;
|
||||
import org.mozilla.gecko.sync.synchronizer.Synchronizer;
|
||||
|
||||
/**
|
||||
* Test the on-device side effects of reset operations on a stage.
|
||||
*
|
||||
* See also "TestResetCommands" in the unit test suite.
|
||||
*/
|
||||
public class TestResetting extends AndroidSyncTestCase {
|
||||
private static final String TEST_USERNAME = "johndoe";
|
||||
private static final String TEST_PASSWORD = "password";
|
||||
private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
assertTrue(WaitHelper.getTestWaiter().isIdle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a mock stage that synchronizes two mock repositories. Apply various
|
||||
* reset/sync/wipe permutations and check state.
|
||||
*/
|
||||
public void testResetAndWipeStage() throws Exception {
|
||||
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final GlobalSessionCallback callback = createGlobalSessionCallback();
|
||||
final GlobalSession session = createDefaultGlobalSession(callback);
|
||||
|
||||
final ExecutableMockServerSyncStage stage = new ExecutableMockServerSyncStage() {
|
||||
@Override
|
||||
public void onSynchronized(Synchronizer synchronizer) {
|
||||
try {
|
||||
assertTrue(startTime <= synchronizer.bundleA.getTimestamp());
|
||||
assertTrue(startTime <= synchronizer.bundleB.getTimestamp());
|
||||
|
||||
// Call up to allow the usual persistence etc. to happen.
|
||||
super.onSynchronized(synchronizer);
|
||||
} catch (Throwable e) {
|
||||
performNotify(e);
|
||||
return;
|
||||
}
|
||||
performNotify();
|
||||
}
|
||||
};
|
||||
|
||||
final boolean bumpTimestamps = true;
|
||||
WBORepository local = new WBORepository(bumpTimestamps);
|
||||
WBORepository remote = new WBORepository(bumpTimestamps);
|
||||
|
||||
stage.name = "mock";
|
||||
stage.collection = "mock";
|
||||
stage.local = local;
|
||||
stage.remote = remote;
|
||||
|
||||
stage.executeSynchronously(session);
|
||||
|
||||
// Verify the persisted values.
|
||||
assertConfigTimestampsGreaterThan(stage.leakConfig(), startTime, startTime);
|
||||
|
||||
// Reset.
|
||||
stage.resetLocal(session);
|
||||
|
||||
// Verify that they're gone.
|
||||
assertConfigTimestampsEqual(stage.leakConfig(), 0, 0);
|
||||
|
||||
// Now sync data, ensure that timestamps come back.
|
||||
final long afterReset = System.currentTimeMillis();
|
||||
final String recordGUID = "abcdefghijkl";
|
||||
local.wbos.put(recordGUID, new MockRecord(recordGUID, "mock", startTime, false));
|
||||
|
||||
// Sync again with data and verify timestamps and data.
|
||||
stage.executeSynchronously(session);
|
||||
|
||||
assertConfigTimestampsGreaterThan(stage.leakConfig(), afterReset, afterReset);
|
||||
assertEquals(1, remote.wbos.size());
|
||||
assertEquals(1, local.wbos.size());
|
||||
|
||||
Record remoteRecord = remote.wbos.get(recordGUID);
|
||||
assertNotNull(remoteRecord);
|
||||
assertNotNull(local.wbos.get(recordGUID));
|
||||
assertEquals(recordGUID, remoteRecord.guid);
|
||||
assertTrue(afterReset <= remoteRecord.lastModified);
|
||||
|
||||
// Reset doesn't clear data.
|
||||
stage.resetLocal(session);
|
||||
assertConfigTimestampsEqual(stage.leakConfig(), 0, 0);
|
||||
assertEquals(1, remote.wbos.size());
|
||||
assertEquals(1, local.wbos.size());
|
||||
remoteRecord = remote.wbos.get(recordGUID);
|
||||
assertNotNull(remoteRecord);
|
||||
assertNotNull(local.wbos.get(recordGUID));
|
||||
|
||||
// Wipe does. Recover from reset...
|
||||
final long beforeWipe = System.currentTimeMillis();
|
||||
stage.executeSynchronously(session);
|
||||
assertEquals(1, remote.wbos.size());
|
||||
assertEquals(1, local.wbos.size());
|
||||
assertConfigTimestampsGreaterThan(stage.leakConfig(), beforeWipe, beforeWipe);
|
||||
|
||||
// ... then wipe.
|
||||
stage.wipeLocal(session);
|
||||
assertConfigTimestampsEqual(stage.leakConfig(), 0, 0);
|
||||
assertEquals(1, remote.wbos.size()); // We don't wipe the server.
|
||||
assertEquals(0, local.wbos.size()); // We do wipe local.
|
||||
}
|
||||
|
||||
/**
|
||||
* A stage that joins two Repositories with no wrapping.
|
||||
*/
|
||||
public class ExecutableMockServerSyncStage extends BaseMockServerSyncStage {
|
||||
/**
|
||||
* Run this stage synchronously.
|
||||
*/
|
||||
public void executeSynchronously(final GlobalSession session) {
|
||||
final BaseMockServerSyncStage self = this;
|
||||
performWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
self.execute(session);
|
||||
} catch (NoSuchStageException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalSession createDefaultGlobalSession(final GlobalSessionCallback callback) throws Exception {
|
||||
final KeyBundle keyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY);
|
||||
final AuthHeaderProvider authHeaderProvider = new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD);
|
||||
final SharedPreferences prefs = new MockSharedPreferences();
|
||||
final SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, authHeaderProvider, prefs);
|
||||
config.syncKeyBundle = keyBundle;
|
||||
return new GlobalSession(config, callback, getApplicationContext(), 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.
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void assertConfigTimestampsGreaterThan(SynchronizerConfiguration config, long local, long remote) {
|
||||
assertTrue(local <= config.localBundle.getTimestamp());
|
||||
assertTrue(remote <= config.remoteBundle.getTimestamp());
|
||||
}
|
||||
|
||||
private static void assertConfigTimestampsEqual(SynchronizerConfiguration config, long local, long remote) {
|
||||
assertEquals(local, config.localBundle.getTimestamp());
|
||||
assertEquals(remote, config.remoteBundle.getTimestamp());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.background.sync.helpers.SimpleSuccessBeginDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate;
|
||||
import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate;
|
||||
import org.mozilla.gecko.background.testhelpers.WBORepository;
|
||||
import org.mozilla.gecko.sync.CryptoRecord;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
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.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
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.SynchronizerDelegate;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class TestStoreTracking extends AndroidSyncTestCase {
|
||||
public void assertEq(Object expected, Object actual) {
|
||||
try {
|
||||
assertEquals(expected, actual);
|
||||
} catch (AssertionFailedError e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
|
||||
public class TrackingWBORepository extends WBORepository {
|
||||
@Override
|
||||
public synchronized boolean shouldTrack() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestStoreRetrieveByGUID(final WBORepository repository,
|
||||
final RepositorySession session,
|
||||
final String expectedGUID,
|
||||
final Record record) {
|
||||
|
||||
final SimpleSuccessStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() {
|
||||
|
||||
@Override
|
||||
public void onRecordStoreSucceeded(String guid) {
|
||||
Logger.debug(getName(), "Stored " + guid);
|
||||
assertEq(expectedGUID, guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreCompleted(long storeEnd) {
|
||||
Logger.debug(getName(), "Store completed at " + storeEnd + ".");
|
||||
try {
|
||||
session.fetch(new String[] { expectedGUID }, new SimpleSuccessFetchDelegate() {
|
||||
@Override
|
||||
public void onFetchedRecord(Record record) {
|
||||
Logger.debug(getName(), "Hurrah! Fetched record " + record.guid);
|
||||
assertEq(expectedGUID, record.guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(final long fetchEnd) {
|
||||
Logger.debug(getName(), "Fetch completed at " + fetchEnd + ".");
|
||||
|
||||
// But fetching by time returns nothing.
|
||||
session.fetchSince(0, new SimpleSuccessFetchDelegate() {
|
||||
private AtomicBoolean fetched = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public void onFetchedRecord(Record record) {
|
||||
Logger.debug(getName(), "Fetched record " + record.guid);
|
||||
fetched.set(true);
|
||||
performNotify(new AssertionFailedError("Should have fetched no record!"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(final long fetchEnd) {
|
||||
if (fetched.get()) {
|
||||
Logger.debug(getName(), "Not finishing session: record retrieved.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
session.finish(new SimpleSuccessFinishDelegate() {
|
||||
@Override
|
||||
public void onFinishSucceeded(RepositorySession session,
|
||||
RepositorySessionBundle bundle) {
|
||||
performNotify();
|
||||
}
|
||||
});
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
session.setStoreDelegate(storeDelegate);
|
||||
try {
|
||||
Logger.debug(getName(), "Storing...");
|
||||
session.store(record);
|
||||
session.storeDone();
|
||||
} catch (NoStoreDelegateException e) {
|
||||
// Should not happen.
|
||||
}
|
||||
}
|
||||
|
||||
private void doTestNewSessionRetrieveByTime(final WBORepository repository,
|
||||
final String expectedGUID) {
|
||||
final SimpleSuccessCreationDelegate createDelegate = new SimpleSuccessCreationDelegate() {
|
||||
@Override
|
||||
public void onSessionCreated(final RepositorySession session) {
|
||||
Logger.debug(getName(), "Session created.");
|
||||
try {
|
||||
session.begin(new SimpleSuccessBeginDelegate() {
|
||||
@Override
|
||||
public void onBeginSucceeded(final RepositorySession session) {
|
||||
// Now we get a result.
|
||||
session.fetchSince(0, new SimpleSuccessFetchDelegate() {
|
||||
|
||||
@Override
|
||||
public void onFetchedRecord(Record record) {
|
||||
assertEq(expectedGUID, record.guid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(long end) {
|
||||
try {
|
||||
session.finish(new SimpleSuccessFinishDelegate() {
|
||||
@Override
|
||||
public void onFinishSucceeded(RepositorySession session,
|
||||
RepositorySessionBundle bundle) {
|
||||
// Hooray!
|
||||
performNotify();
|
||||
}
|
||||
});
|
||||
} catch (InactiveSessionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
Runnable create = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
repository.createSession(createDelegate, getApplicationContext());
|
||||
}
|
||||
};
|
||||
|
||||
performWait(create);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a record in one session. Verify that fetching by GUID returns
|
||||
* the record. Verify that fetching by timestamp fails to return records.
|
||||
* Start a new session. Verify that fetching by timestamp returns the
|
||||
* stored record.
|
||||
*
|
||||
* Invokes doTestStoreRetrieveByGUID, doTestNewSessionRetrieveByTime.
|
||||
*/
|
||||
public void testStoreRetrieveByGUID() {
|
||||
Logger.debug(getName(), "Started.");
|
||||
final WBORepository r = new TrackingWBORepository();
|
||||
final long now = System.currentTimeMillis();
|
||||
final String expectedGUID = "abcdefghijkl";
|
||||
final Record record = new BookmarkRecord(expectedGUID, "bookmarks", now , false);
|
||||
|
||||
final RepositorySessionCreationDelegate createDelegate = new SimpleSuccessCreationDelegate() {
|
||||
@Override
|
||||
public void onSessionCreated(RepositorySession session) {
|
||||
Logger.debug(getName(), "Session created: " + session);
|
||||
try {
|
||||
session.begin(new SimpleSuccessBeginDelegate() {
|
||||
@Override
|
||||
public void onBeginSucceeded(final RepositorySession session) {
|
||||
doTestStoreRetrieveByGUID(r, session, expectedGUID, record);
|
||||
}
|
||||
});
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Context applicationContext = getApplicationContext();
|
||||
|
||||
// This has to happen on a new thread so that we
|
||||
// can wait for it!
|
||||
Runnable create = onThreadRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
r.createSession(createDelegate, applicationContext);
|
||||
}
|
||||
});
|
||||
|
||||
Runnable retrieve = onThreadRunnable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
doTestNewSessionRetrieveByTime(r, expectedGUID);
|
||||
performNotify();
|
||||
}
|
||||
});
|
||||
|
||||
performWait(create);
|
||||
performWait(retrieve);
|
||||
}
|
||||
|
||||
private Runnable onThreadRunnable(final Runnable r) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(r).start();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public class CountingWBORepository extends TrackingWBORepository {
|
||||
public AtomicLong counter = new AtomicLong(0L);
|
||||
public class CountingWBORepositorySession extends WBORepositorySession {
|
||||
private static final String LOG_TAG = "CountingRepoSession";
|
||||
|
||||
public CountingWBORepositorySession(WBORepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(final Record record) throws NoStoreDelegateException {
|
||||
Logger.debug(LOG_TAG, "Counter now " + counter.incrementAndGet());
|
||||
super.store(record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createSession(RepositorySessionCreationDelegate delegate,
|
||||
Context context) {
|
||||
delegate.deferredCreationDelegate().onSessionCreated(new CountingWBORepositorySession(this));
|
||||
}
|
||||
}
|
||||
|
||||
public class TestRecord extends Record {
|
||||
public TestRecord(String guid, String collection, long lastModified,
|
||||
boolean deleted) {
|
||||
super(guid, collection, lastModified, deleted);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initFromEnvelope(CryptoRecord payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CryptoRecord getEnvelope() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populatePayload(ExtendedJSONObject payload) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initFromPayload(ExtendedJSONObject payload) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Record copyWithIDs(String guid, long androidID) {
|
||||
return new TestRecord(guid, this.collection, this.lastModified, this.deleted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create two repositories, syncing from one to the other. Ensure
|
||||
* that records stored from one aren't re-uploaded.
|
||||
*/
|
||||
public void testStoreBetweenRepositories() {
|
||||
final CountingWBORepository repoA = new CountingWBORepository(); // "Remote". First source.
|
||||
final CountingWBORepository repoB = new CountingWBORepository(); // "Local". First sink.
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
TestRecord recordA1 = new TestRecord("aacdefghiaaa", "coll", now - 30, false);
|
||||
TestRecord recordA2 = new TestRecord("aacdefghibbb", "coll", now - 20, false);
|
||||
TestRecord recordB1 = new TestRecord("aacdefghiaaa", "coll", now - 10, false);
|
||||
TestRecord recordB2 = new TestRecord("aacdefghibbb", "coll", now - 40, false);
|
||||
|
||||
TestRecord recordA3 = new TestRecord("nncdefghibbb", "coll", now, false);
|
||||
TestRecord recordB3 = new TestRecord("nncdefghiaaa", "coll", now, false);
|
||||
|
||||
// A1 and B1 are the same, but B's version is newer. We expect A1 to be downloaded
|
||||
// and B1 to be uploaded.
|
||||
// A2 and B2 are the same, but A's version is newer. We expect A2 to be downloaded
|
||||
// and B2 to not be uploaded.
|
||||
// Both A3 and B3 are new. We expect them to go in each direction.
|
||||
// Expected counts, then:
|
||||
// Repo A: B1 + B3
|
||||
// Repo B: A1 + A2 + A3
|
||||
repoB.wbos.put(recordB1.guid, recordB1);
|
||||
repoB.wbos.put(recordB2.guid, recordB2);
|
||||
repoB.wbos.put(recordB3.guid, recordB3);
|
||||
repoA.wbos.put(recordA1.guid, recordA1);
|
||||
repoA.wbos.put(recordA2.guid, recordA2);
|
||||
repoA.wbos.put(recordA3.guid, recordA3);
|
||||
|
||||
final Synchronizer s = new Synchronizer();
|
||||
s.repositoryA = repoA;
|
||||
s.repositoryB = repoB;
|
||||
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
s.synchronize(getApplicationContext(), new SynchronizerDelegate() {
|
||||
|
||||
@Override
|
||||
public void onSynchronized(Synchronizer synchronizer) {
|
||||
long countA = repoA.counter.get();
|
||||
long countB = repoB.counter.get();
|
||||
Logger.debug(getName(), "Counts: " + countA + ", " + countB);
|
||||
assertEq(2L, countA);
|
||||
assertEq(3L, countB);
|
||||
|
||||
// Testing for store timestamp 'hack'.
|
||||
// We fetched from A first, and so its bundle timestamp will be the last
|
||||
// stored time. We fetched from B second, so its bundle timestamp will be
|
||||
// the last fetched time.
|
||||
final long timestampA = synchronizer.bundleA.getTimestamp();
|
||||
final long timestampB = synchronizer.bundleB.getTimestamp();
|
||||
Logger.debug(getName(), "Repo A timestamp: " + timestampA);
|
||||
Logger.debug(getName(), "Repo B timestamp: " + timestampB);
|
||||
Logger.debug(getName(), "Repo A fetch done: " + repoA.stats.fetchCompleted);
|
||||
Logger.debug(getName(), "Repo A store done: " + repoA.stats.storeCompleted);
|
||||
Logger.debug(getName(), "Repo B fetch done: " + repoB.stats.fetchCompleted);
|
||||
Logger.debug(getName(), "Repo B store done: " + repoB.stats.storeCompleted);
|
||||
|
||||
assertTrue(timestampB <= timestampA);
|
||||
assertTrue(repoA.stats.fetchCompleted <= timestampA);
|
||||
assertTrue(repoA.stats.storeCompleted >= repoA.stats.fetchCompleted);
|
||||
assertEquals(repoA.stats.storeCompleted, timestampA);
|
||||
assertEquals(repoB.stats.fetchCompleted, timestampB);
|
||||
performNotify();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSynchronizeFailed(Synchronizer synchronizer,
|
||||
Exception lastException, String reason) {
|
||||
Logger.debug(getName(), "Failed.");
|
||||
performNotify(new AssertionFailedError("Should not fail."));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
performWait(onThreadRunnable(r));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
public class TestSyncConfiguration extends AndroidSyncTestCase {
|
||||
public static final String TEST_PREFS_NAME = "test";
|
||||
|
||||
public SharedPreferences getPrefs(String name, int mode) {
|
||||
return this.getApplicationContext().getSharedPreferences(name, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that declined engines persist through prefs.
|
||||
*/
|
||||
public void testDeclinedEngineNames() {
|
||||
SyncConfiguration config = null;
|
||||
SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
|
||||
|
||||
config = newSyncConfiguration();
|
||||
config.declinedEngineNames = new HashSet<String>();
|
||||
config.declinedEngineNames.add("test1");
|
||||
config.declinedEngineNames.add("test2");
|
||||
config.persistToPrefs();
|
||||
assertTrue(prefs.contains(SyncConfiguration.PREF_DECLINED_ENGINE_NAMES));
|
||||
config = newSyncConfiguration();
|
||||
Set<String> expected = new HashSet<String>();
|
||||
for (String name : new String[] { "test1", "test2" }) {
|
||||
expected.add(name);
|
||||
}
|
||||
assertEquals(expected, config.declinedEngineNames);
|
||||
|
||||
config.declinedEngineNames = null;
|
||||
config.persistToPrefs();
|
||||
assertFalse(prefs.contains(SyncConfiguration.PREF_DECLINED_ENGINE_NAMES));
|
||||
config = newSyncConfiguration();
|
||||
assertNotNull(config.declinedEngineNames);
|
||||
assertTrue(config.declinedEngineNames.isEmpty());
|
||||
}
|
||||
|
||||
public void testEnabledEngineNames() {
|
||||
SyncConfiguration config = null;
|
||||
SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
|
||||
|
||||
config = newSyncConfiguration();
|
||||
config.enabledEngineNames = new HashSet<String>();
|
||||
config.enabledEngineNames.add("test1");
|
||||
config.enabledEngineNames.add("test2");
|
||||
config.persistToPrefs();
|
||||
assertTrue(prefs.contains(SyncConfiguration.PREF_ENABLED_ENGINE_NAMES));
|
||||
config = newSyncConfiguration();
|
||||
Set<String> expected = new HashSet<String>();
|
||||
for (String name : new String[] { "test1", "test2" }) {
|
||||
expected.add(name);
|
||||
}
|
||||
assertEquals(expected, config.enabledEngineNames);
|
||||
|
||||
config.enabledEngineNames = null;
|
||||
config.persistToPrefs();
|
||||
assertFalse(prefs.contains(SyncConfiguration.PREF_ENABLED_ENGINE_NAMES));
|
||||
config = newSyncConfiguration();
|
||||
assertNull(config.enabledEngineNames);
|
||||
}
|
||||
|
||||
public void testSyncID() {
|
||||
SyncConfiguration config = null;
|
||||
SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
|
||||
|
||||
config = newSyncConfiguration();
|
||||
config.syncID = "test1";
|
||||
config.persistToPrefs();
|
||||
assertTrue(prefs.contains(SyncConfiguration.PREF_SYNC_ID));
|
||||
config = newSyncConfiguration();
|
||||
assertEquals("test1", config.syncID);
|
||||
}
|
||||
|
||||
public void testStoreSelectedEnginesToPrefs() {
|
||||
SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
|
||||
// Store engines, excluding history/forms special case.
|
||||
Map<String, Boolean> expectedEngines = new HashMap<String, Boolean>();
|
||||
expectedEngines.put("test1", true);
|
||||
expectedEngines.put("test2", false);
|
||||
expectedEngines.put("test3", true);
|
||||
|
||||
SyncConfiguration.storeSelectedEnginesToPrefs(prefs, expectedEngines);
|
||||
|
||||
// Read values from selectedEngines.
|
||||
assertTrue(prefs.contains(SyncConfiguration.PREF_USER_SELECTED_ENGINES_TO_SYNC));
|
||||
SyncConfiguration config = null;
|
||||
config = newSyncConfiguration();
|
||||
config.loadFromPrefs(prefs);
|
||||
assertEquals(expectedEngines, config.userSelectedEngines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests dependency of forms engine on history engine.
|
||||
*/
|
||||
public void testSelectedEnginesHistoryAndForms() {
|
||||
SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
|
||||
// Store engines, excluding history/forms special case.
|
||||
Map<String, Boolean> storedEngines = new HashMap<String, Boolean>();
|
||||
storedEngines.put("history", true);
|
||||
|
||||
SyncConfiguration.storeSelectedEnginesToPrefs(prefs, storedEngines);
|
||||
|
||||
// Expected engines.
|
||||
storedEngines.put("forms", true);
|
||||
// Read values from selectedEngines.
|
||||
assertTrue(prefs.contains(SyncConfiguration.PREF_USER_SELECTED_ENGINES_TO_SYNC));
|
||||
SyncConfiguration config = null;
|
||||
config = newSyncConfiguration();
|
||||
config.loadFromPrefs(prefs);
|
||||
assertEquals(storedEngines, config.userSelectedEngines);
|
||||
}
|
||||
|
||||
public void testsSelectedEnginesNoHistoryNorForms() {
|
||||
SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
|
||||
// Store engines, excluding history/forms special case.
|
||||
Map<String, Boolean> storedEngines = new HashMap<String, Boolean>();
|
||||
storedEngines.put("forms", true);
|
||||
|
||||
SyncConfiguration.storeSelectedEnginesToPrefs(prefs, storedEngines);
|
||||
|
||||
// Read values from selectedEngines.
|
||||
assertTrue(prefs.contains(SyncConfiguration.PREF_USER_SELECTED_ENGINES_TO_SYNC));
|
||||
SyncConfiguration config = null;
|
||||
config = newSyncConfiguration();
|
||||
config.loadFromPrefs(prefs);
|
||||
// Forms should not be selected if history is not present.
|
||||
assertTrue(config.userSelectedEngines.isEmpty());
|
||||
}
|
||||
|
||||
protected SyncConfiguration newSyncConfiguration() {
|
||||
return new SyncConfiguration(null, null, getPrefs(TEST_PREFS_NAME, 0));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
|
||||
import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
|
||||
|
||||
/**
|
||||
* These tests are on device because the WebKit APIs are stubs on desktop.
|
||||
*/
|
||||
public class TestWebURLFinder extends AndroidSyncTestCase {
|
||||
public String find(String string) {
|
||||
return new WebURLFinder(string).bestWebURL();
|
||||
}
|
||||
|
||||
public String find(String[] strings) {
|
||||
return new WebURLFinder(Arrays.asList(strings)).bestWebURL();
|
||||
}
|
||||
|
||||
public void testNoEmail() {
|
||||
assertNull(find("test@test.com"));
|
||||
}
|
||||
|
||||
public void testSchemeFirst() {
|
||||
assertEquals("http://scheme.com", find("test.com http://scheme.com"));
|
||||
}
|
||||
|
||||
public void testFullURL() {
|
||||
assertEquals("http://scheme.com:8080/inner#anchor&arg=1", find("test.com http://scheme.com:8080/inner#anchor&arg=1"));
|
||||
}
|
||||
|
||||
public void testNoScheme() {
|
||||
assertEquals("noscheme.com", find("noscheme.com"));
|
||||
}
|
||||
|
||||
public void testNoBadScheme() {
|
||||
assertNull(find("file:///test javascript:///test.js"));
|
||||
}
|
||||
|
||||
public void testStrings() {
|
||||
assertEquals("http://test.com", find(new String[] { "http://test.com", "noscheme.com" }));
|
||||
assertEquals("http://test.com", find(new String[] { "noschemefirst.com", "http://test.com" }));
|
||||
assertEquals("http://test.com/inner#test", find(new String[] { "noschemefirst.com", "http://test.com/inner#test", "http://second.org/fark" }));
|
||||
assertEquals("http://test.com", find(new String[] { "javascript:///test.js", "http://test.com" }));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,216 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
|
||||
|
||||
public class BookmarkHelpers {
|
||||
|
||||
private static String mobileFolderGuid = "mobile";
|
||||
private static String mobileFolderName = "mobile";
|
||||
private static String topFolderGuid = Utils.generateGuid();
|
||||
private static String topFolderName = "My Top Folder";
|
||||
private static String middleFolderGuid = Utils.generateGuid();
|
||||
private static String middleFolderName = "My Middle Folder";
|
||||
private static String bottomFolderGuid = Utils.generateGuid();
|
||||
private static String bottomFolderName = "My Bottom Folder";
|
||||
private static String bmk1Guid = Utils.generateGuid();
|
||||
private static String bmk2Guid = Utils.generateGuid();
|
||||
private static String bmk3Guid = Utils.generateGuid();
|
||||
private static String bmk4Guid = Utils.generateGuid();
|
||||
|
||||
/*
|
||||
* Helpers for creating bookmark records of different types
|
||||
*/
|
||||
public static BookmarkRecord createBookmarkInMobileFolder1() {
|
||||
BookmarkRecord rec = createBookmark1();
|
||||
rec.guid = Utils.generateGuid();
|
||||
rec.parentID = mobileFolderGuid;
|
||||
rec.parentName = mobileFolderName;
|
||||
return rec;
|
||||
}
|
||||
|
||||
public static BookmarkRecord createBookmarkInMobileFolder2() {
|
||||
BookmarkRecord rec = createBookmark2();
|
||||
rec.guid = Utils.generateGuid();
|
||||
rec.parentID = mobileFolderGuid;
|
||||
rec.parentName = mobileFolderName;
|
||||
return rec;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createBookmark1() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
JSONArray tags = new JSONArray();
|
||||
tags.add("tag1");
|
||||
tags.add("tag2");
|
||||
tags.add("tag3");
|
||||
record.guid = bmk1Guid;
|
||||
record.title = "Foo!!!";
|
||||
record.bookmarkURI = "http://foo.bar.com";
|
||||
record.description = "This is a description for foo.bar.com";
|
||||
record.tags = tags;
|
||||
record.keyword = "fooooozzzzz";
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
record.type = "bookmark";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createBookmark2() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
JSONArray tags = new JSONArray();
|
||||
tags.add("tag1");
|
||||
tags.add("tag2");
|
||||
record.guid = bmk2Guid;
|
||||
record.title = "Bar???";
|
||||
record.bookmarkURI = "http://bar.foo.com";
|
||||
record.description = "This is a description for Bar???";
|
||||
record.tags = tags;
|
||||
record.keyword = "keywordzzz";
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
record.type = "bookmark";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createBookmark3() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
JSONArray tags = new JSONArray();
|
||||
tags.add("tag1");
|
||||
tags.add("tag2");
|
||||
record.guid = bmk3Guid;
|
||||
record.title = "Bmk3";
|
||||
record.bookmarkURI = "http://bmk3.com";
|
||||
record.description = "This is a description for bmk3";
|
||||
record.tags = tags;
|
||||
record.keyword = "snooozzz";
|
||||
record.parentID = middleFolderGuid;
|
||||
record.parentName = middleFolderName;
|
||||
record.type = "bookmark";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createBookmark4() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
JSONArray tags = new JSONArray();
|
||||
tags.add("tag1");
|
||||
tags.add("tag2");
|
||||
record.guid = bmk4Guid;
|
||||
record.title = "Bmk4";
|
||||
record.bookmarkURI = "http://bmk4.com";
|
||||
record.description = "This is a description for bmk4?";
|
||||
record.tags = tags;
|
||||
record.keyword = "booooozzz";
|
||||
record.parentID = bottomFolderGuid;
|
||||
record.parentName = bottomFolderName;
|
||||
record.type = "bookmark";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createMicrosummary() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
JSONArray tags = new JSONArray();
|
||||
tags.add("tag1");
|
||||
tags.add("tag2");
|
||||
record.guid = Utils.generateGuid();
|
||||
record.title = "Microsummary 1";
|
||||
record.bookmarkURI = "www.bmkuri.com";
|
||||
record.description = "microsummary description";
|
||||
record.tags = tags;
|
||||
record.keyword = "keywordzzz";
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
record.type = "microsummary";
|
||||
return record;
|
||||
}
|
||||
|
||||
public static BookmarkRecord createQuery() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
record.guid = Utils.generateGuid();
|
||||
record.title = "Query 1";
|
||||
record.bookmarkURI = "http://www.query.com";
|
||||
record.description = "Query 1 description";
|
||||
record.tags = new JSONArray();
|
||||
record.keyword = "queryKeyword";
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
record.type = "query";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createFolder1() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
record.guid = topFolderGuid;
|
||||
record.title = topFolderName;
|
||||
record.parentID = "mobile";
|
||||
record.parentName = "mobile";
|
||||
JSONArray children = new JSONArray();
|
||||
children.add(bmk1Guid);
|
||||
children.add(bmk2Guid);
|
||||
record.children = children;
|
||||
record.type = "folder";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createFolder2() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
record.guid = middleFolderGuid;
|
||||
record.title = middleFolderName;
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
JSONArray children = new JSONArray();
|
||||
children.add(bmk3Guid);
|
||||
record.children = children;
|
||||
record.type = "folder";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createFolder3() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
record.guid = bottomFolderGuid;
|
||||
record.title = bottomFolderName;
|
||||
record.parentID = middleFolderGuid;
|
||||
record.parentName = middleFolderName;
|
||||
JSONArray children = new JSONArray();
|
||||
children.add(bmk4Guid);
|
||||
record.children = children;
|
||||
record.type = "folder";
|
||||
return record;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static BookmarkRecord createLivemark() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
record.guid = Utils.generateGuid();
|
||||
record.title = "Livemark title";
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
JSONArray children = new JSONArray();
|
||||
children.add(Utils.generateGuid());
|
||||
children.add(Utils.generateGuid());
|
||||
record.children = children;
|
||||
record.type = "livemark";
|
||||
return record;
|
||||
}
|
||||
|
||||
public static BookmarkRecord createSeparator() {
|
||||
BookmarkRecord record = new BookmarkRecord();
|
||||
record.guid = Utils.generateGuid();
|
||||
record.androidPosition = 3;
|
||||
record.parentID = topFolderGuid;
|
||||
record.parentName = topFolderName;
|
||||
record.type = "separator";
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
|
||||
|
||||
public class DefaultBeginDelegate extends DefaultDelegate implements RepositorySessionBeginDelegate {
|
||||
@Override
|
||||
public void onBeginFailed(Exception ex) {
|
||||
performNotify("Begin failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBeginSucceeded(RepositorySession session) {
|
||||
performNotify("Default begin delegate hit.", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
|
||||
DefaultBeginDelegate copy;
|
||||
try {
|
||||
copy = (DefaultBeginDelegate) this.clone();
|
||||
copy.executor = executor;
|
||||
return copy;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCleanDelegate;
|
||||
|
||||
public class DefaultCleanDelegate extends DefaultDelegate implements RepositorySessionCleanDelegate {
|
||||
|
||||
@Override
|
||||
public void onCleaned(Repository repo) {
|
||||
performNotify("Default begin delegate hit.", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCleanFailed(Repository repo, Exception ex) {
|
||||
performNotify("Clean failed.", ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
|
||||
public abstract class DefaultDelegate {
|
||||
protected ExecutorService executor;
|
||||
|
||||
protected final WaitHelper waitHelper;
|
||||
|
||||
public DefaultDelegate() {
|
||||
waitHelper = WaitHelper.getTestWaiter();
|
||||
}
|
||||
|
||||
public DefaultDelegate(WaitHelper waitHelper) {
|
||||
this.waitHelper = waitHelper;
|
||||
}
|
||||
|
||||
protected WaitHelper getTestWaiter() {
|
||||
return waitHelper;
|
||||
}
|
||||
|
||||
public void performWait(Runnable runnable) throws AssertionFailedError {
|
||||
getTestWaiter().performWait(runnable);
|
||||
}
|
||||
|
||||
public void performNotify() {
|
||||
getTestWaiter().performNotify();
|
||||
}
|
||||
|
||||
public void performNotify(Throwable e) {
|
||||
getTestWaiter().performNotify(e);
|
||||
}
|
||||
|
||||
public void performNotify(String reason, Throwable e) {
|
||||
String message = reason;
|
||||
if (e != null) {
|
||||
message += ": " + e.getMessage();
|
||||
}
|
||||
AssertionFailedError ex = new AssertionFailedError(message);
|
||||
if (e != null) {
|
||||
ex.initCause(e);
|
||||
}
|
||||
getTestWaiter().performNotify(ex);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static junit.framework.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.DeferredRepositorySessionFetchRecordsDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
public class DefaultFetchDelegate extends DefaultDelegate implements RepositorySessionFetchRecordsDelegate {
|
||||
|
||||
private static final String LOG_TAG = "DefaultFetchDelegate";
|
||||
public ArrayList<Record> records = new ArrayList<Record>();
|
||||
public Set<String> ignore = new HashSet<String>();
|
||||
|
||||
@Override
|
||||
public void onFetchFailed(Exception ex, Record record) {
|
||||
performNotify("Fetch failed.", ex);
|
||||
}
|
||||
|
||||
protected void onDone(ArrayList<Record> records, HashMap<String, Record> expected, long end) {
|
||||
Logger.debug(LOG_TAG, "onDone.");
|
||||
Logger.debug(LOG_TAG, "End timestamp is " + end);
|
||||
Logger.debug(LOG_TAG, "Expected is " + expected);
|
||||
Logger.debug(LOG_TAG, "Records is " + records);
|
||||
Set<String> foundGuids = new HashSet<String>();
|
||||
try {
|
||||
int expectedCount = 0;
|
||||
int expectedFound = 0;
|
||||
Logger.debug(LOG_TAG, "Counting expected keys.");
|
||||
for (String key : expected.keySet()) {
|
||||
if (!ignore.contains(key)) {
|
||||
expectedCount++;
|
||||
}
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Expected keys: " + expectedCount);
|
||||
for (Record record : records) {
|
||||
Logger.debug(LOG_TAG, "Record.");
|
||||
Logger.debug(LOG_TAG, record.guid);
|
||||
|
||||
// Ignore special GUIDs (e.g., for bookmarks).
|
||||
if (!ignore.contains(record.guid)) {
|
||||
if (foundGuids.contains(record.guid)) {
|
||||
fail("Found duplicate guid " + record.guid);
|
||||
}
|
||||
Record expect = expected.get(record.guid);
|
||||
if (expect == null) {
|
||||
fail("Do not expect to get back a record with guid: " + record.guid); // Caught below
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Checking equality.");
|
||||
try {
|
||||
assertTrue(expect.equalPayloads(record)); // Caught below
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "ONOZ!", e);
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Checked equality.");
|
||||
expectedFound += 1;
|
||||
// Track record once we've found it.
|
||||
foundGuids.add(record.guid);
|
||||
}
|
||||
}
|
||||
assertEquals(expectedCount, expectedFound); // Caught below
|
||||
Logger.debug(LOG_TAG, "Notifying success.");
|
||||
performNotify();
|
||||
} catch (AssertionFailedError e) {
|
||||
Logger.error(LOG_TAG, "Notifying assertion failure.");
|
||||
performNotify(e);
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "No!");
|
||||
performNotify();
|
||||
}
|
||||
}
|
||||
|
||||
public int recordCount() {
|
||||
return (this.records == null) ? 0 : this.records.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchedRecord(Record record) {
|
||||
Logger.debug(LOG_TAG, "onFetchedRecord(" + record.guid + ")");
|
||||
records.add(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(final long fetchEnd) {
|
||||
Logger.debug(LOG_TAG, "onFetchCompleted. Doing nothing.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(final ExecutorService executor) {
|
||||
return new DeferredRepositorySessionFetchRecordsDelegate(this, executor);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
|
||||
|
||||
public class DefaultFinishDelegate extends DefaultDelegate implements RepositorySessionFinishDelegate {
|
||||
|
||||
@Override
|
||||
public void onFinishFailed(Exception ex) {
|
||||
performNotify("Finish failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
|
||||
performNotify("Hit default finish delegate", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFinishDelegate deferredFinishDelegate(final ExecutorService executor) {
|
||||
final RepositorySessionFinishDelegate self = this;
|
||||
|
||||
Logger.info("DefaultFinishDelegate", "Deferring…");
|
||||
return new RepositorySessionFinishDelegate() {
|
||||
@Override
|
||||
public void onFinishSucceeded(final RepositorySession session,
|
||||
final RepositorySessionBundle bundle) {
|
||||
Logger.info("DefaultFinishDelegate", "Executing onFinishSucceeded Runnable…");
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onFinishSucceeded(session, bundle);
|
||||
}});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishFailed(final Exception ex) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onFinishFailed(ex);
|
||||
}});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService newExecutor) {
|
||||
if (newExecutor == executor) {
|
||||
return this;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't re-defer this delegate.");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate;
|
||||
|
||||
public class DefaultGuidsSinceDelegate extends DefaultDelegate implements RepositorySessionGuidsSinceDelegate {
|
||||
|
||||
@Override
|
||||
public void onGuidsSinceFailed(Exception ex) {
|
||||
performNotify("shouldn't fail", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuidsSinceSucceeded(String[] guids) {
|
||||
performNotify("default guids since delegate called", null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
|
||||
public class DefaultSessionCreationDelegate extends DefaultDelegate implements
|
||||
RepositorySessionCreationDelegate {
|
||||
|
||||
@Override
|
||||
public void onSessionCreateFailed(Exception ex) {
|
||||
performNotify("Session creation failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionCreated(RepositorySession session) {
|
||||
performNotify("Should not have been created.", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionCreationDelegate deferredCreationDelegate() {
|
||||
final RepositorySessionCreationDelegate self = this;
|
||||
return new RepositorySessionCreationDelegate() {
|
||||
|
||||
@Override
|
||||
public void onSessionCreated(final RepositorySession session) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onSessionCreated(session);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionCreateFailed(final Exception ex) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onSessionCreateFailed(ex);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionCreationDelegate deferredCreationDelegate() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
|
||||
|
||||
public class DefaultStoreDelegate extends DefaultDelegate implements RepositorySessionStoreDelegate {
|
||||
|
||||
@Override
|
||||
public void onRecordStoreFailed(Exception ex, String guid) {
|
||||
performNotify("Store failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordStoreSucceeded(String guid) {
|
||||
performNotify("DefaultStoreDelegate used", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreCompleted(long storeEnd) {
|
||||
performNotify("DefaultStoreDelegate used", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionStoreDelegate deferredStoreDelegate(final ExecutorService executor) {
|
||||
final RepositorySessionStoreDelegate self = this;
|
||||
return new RepositorySessionStoreDelegate() {
|
||||
|
||||
@Override
|
||||
public void onRecordStoreSucceeded(final String guid) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onRecordStoreSucceeded(guid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordStoreFailed(final Exception ex, final String guid) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onRecordStoreFailed(ex, guid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreCompleted(final long storeEnd) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
self.onStoreCompleted(storeEnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService newExecutor) {
|
||||
if (newExecutor == executor) {
|
||||
return this;
|
||||
}
|
||||
throw new IllegalArgumentException("Can't re-defer this delegate.");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
|
||||
public class ExpectBeginDelegate extends DefaultBeginDelegate {
|
||||
@Override
|
||||
public void onBeginSucceeded(RepositorySession session) {
|
||||
try {
|
||||
assertNotNull(session);
|
||||
} catch (AssertionFailedError e) {
|
||||
performNotify("Expected non-null session", e);
|
||||
return;
|
||||
}
|
||||
performNotify();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
|
||||
|
||||
public class ExpectBeginFailDelegate extends DefaultBeginDelegate {
|
||||
|
||||
@Override
|
||||
public void onBeginFailed(Exception ex) {
|
||||
if (!(ex instanceof InvalidSessionTransitionException)) {
|
||||
performNotify("Expected InvalidSessionTransititionException but got ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
public class ExpectFetchDelegate extends DefaultFetchDelegate {
|
||||
private HashMap<String, Record> expect = new HashMap<String, Record>();
|
||||
|
||||
public ExpectFetchDelegate(Record[] records) {
|
||||
for(int i = 0; i < records.length; i++) {
|
||||
expect.put(records[i].guid, records[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchedRecord(Record record) {
|
||||
this.records.add(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(final long fetchEnd) {
|
||||
super.onDone(this.records, this.expect, fetchEnd);
|
||||
}
|
||||
|
||||
public Record recordAt(int i) {
|
||||
return this.records.get(i);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
public class ExpectFetchSinceDelegate extends DefaultFetchDelegate {
|
||||
private String[] expected;
|
||||
private long earliest;
|
||||
|
||||
public ExpectFetchSinceDelegate(long timestamp, String[] guids) {
|
||||
expected = guids;
|
||||
earliest = timestamp;
|
||||
Arrays.sort(expected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompleted(final long fetchEnd) {
|
||||
AssertionFailedError err = null;
|
||||
try {
|
||||
int countSpecials = 0;
|
||||
for (Record record : records) {
|
||||
// Check if record should be ignored.
|
||||
if (!ignore.contains(record.guid)) {
|
||||
assertFalse(-1 == Arrays.binarySearch(this.expected, record.guid));
|
||||
} else {
|
||||
countSpecials++;
|
||||
}
|
||||
// Check that record is later than timestamp-earliest.
|
||||
assertTrue(record.lastModified >= this.earliest);
|
||||
}
|
||||
assertEquals(this.expected.length, records.size() - countSpecials);
|
||||
} catch (AssertionFailedError e) {
|
||||
err = e;
|
||||
}
|
||||
performNotify(err);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
|
||||
|
||||
public class ExpectFinishDelegate extends DefaultFinishDelegate {
|
||||
|
||||
@Override
|
||||
public void onFinishSucceeded(RepositorySession session, RepositorySessionBundle bundle) {
|
||||
Logger.info("ExpectFinishDelegate", "Finish succeeded.");
|
||||
performNotify();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
|
||||
|
||||
public class ExpectFinishFailDelegate extends DefaultFinishDelegate {
|
||||
@Override
|
||||
public void onFinishFailed(Exception ex) {
|
||||
if (!(ex instanceof InvalidSessionTransitionException)) {
|
||||
performNotify("Expected InvalidSessionTransititionException but got ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
public class ExpectGuidsSinceDelegate extends DefaultGuidsSinceDelegate {
|
||||
private String[] expected;
|
||||
public Set<String> ignore = new HashSet<String>();
|
||||
|
||||
public ExpectGuidsSinceDelegate(String[] guids) {
|
||||
expected = guids;
|
||||
Arrays.sort(expected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuidsSinceSucceeded(String[] guids) {
|
||||
AssertionFailedError err = null;
|
||||
try {
|
||||
int notIgnored = 0;
|
||||
for (String guid : guids) {
|
||||
if (!ignore.contains(guid)) {
|
||||
notIgnored++;
|
||||
assertFalse(-1 == Arrays.binarySearch(this.expected, guid));
|
||||
}
|
||||
}
|
||||
assertEquals(this.expected.length, notIgnored);
|
||||
} catch (AssertionFailedError e) {
|
||||
err = e;
|
||||
}
|
||||
performNotify(err);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.InvalidRequestException;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
public class ExpectInvalidRequestFetchDelegate extends DefaultFetchDelegate {
|
||||
public static final String LOG_TAG = "ExpInvRequestFetchDel";
|
||||
|
||||
@Override
|
||||
public void onFetchFailed(Exception ex, Record rec) {
|
||||
if (ex instanceof InvalidRequestException) {
|
||||
onDone();
|
||||
} else {
|
||||
performNotify("Expected InvalidRequestException but got ", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDone() {
|
||||
performNotify();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.InvalidBookmarkTypeException;
|
||||
|
||||
public class ExpectInvalidTypeStoreDelegate extends DefaultStoreDelegate {
|
||||
|
||||
@Override
|
||||
public void onRecordStoreFailed(Exception ex, String guid) {
|
||||
assertEquals(InvalidBookmarkTypeException.class, ex.getClass());
|
||||
performNotify();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
public class ExpectManyStoredDelegate extends DefaultStoreDelegate {
|
||||
HashSet<String> expectedGUIDs;
|
||||
AtomicLong stored;
|
||||
|
||||
public ExpectManyStoredDelegate(Record[] records) {
|
||||
HashSet<String> s = new HashSet<String>();
|
||||
for (Record record : records) {
|
||||
s.add(record.guid);
|
||||
}
|
||||
expectedGUIDs = s;
|
||||
stored = new AtomicLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreCompleted(long storeEnd) {
|
||||
try {
|
||||
assertEquals(expectedGUIDs.size(), stored.get());
|
||||
performNotify();
|
||||
} catch (AssertionFailedError e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRecordStoreSucceeded(String guid) {
|
||||
try {
|
||||
assertTrue(expectedGUIDs.contains(guid));
|
||||
} catch (AssertionFailedError e) {
|
||||
performNotify(e);
|
||||
}
|
||||
stored.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
public class ExpectNoGUIDsSinceDelegate extends DefaultGuidsSinceDelegate {
|
||||
|
||||
public Set<String> ignore = new HashSet<String>();
|
||||
|
||||
@Override
|
||||
public void onGuidsSinceSucceeded(String[] guids) {
|
||||
AssertionFailedError err = null;
|
||||
try {
|
||||
int nonIgnored = 0;
|
||||
for (int i = 0; i < guids.length; i++) {
|
||||
if (!ignore.contains(guids[i])) {
|
||||
nonIgnored++;
|
||||
}
|
||||
}
|
||||
assertEquals(0, nonIgnored);
|
||||
} catch (AssertionFailedError e) {
|
||||
err = e;
|
||||
}
|
||||
performNotify(err);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
public class ExpectNoStoreDelegate extends ExpectStoreCompletedDelegate {
|
||||
@Override
|
||||
public void onRecordStoreSucceeded(String guid) {
|
||||
performNotify("Should not have stored record " + guid, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
public class ExpectStoreCompletedDelegate extends DefaultStoreDelegate {
|
||||
|
||||
@Override
|
||||
public void onRecordStoreSucceeded(String guid) {
|
||||
// That's fine.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoreCompleted(long storeEnd) {
|
||||
performNotify();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
public class ExpectStoredDelegate extends DefaultStoreDelegate {
|
||||
String expectedGUID;
|
||||
String storedGuid;
|
||||
|
||||
public ExpectStoredDelegate(String guid) {
|
||||
this.expectedGUID = guid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onStoreCompleted(long storeEnd) {
|
||||
try {
|
||||
assertNotNull(storedGuid);
|
||||
performNotify();
|
||||
} catch (AssertionFailedError e) {
|
||||
performNotify("GUID " + this.expectedGUID + " was not stored", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onRecordStoreSucceeded(String guid) {
|
||||
this.storedGuid = guid;
|
||||
try {
|
||||
if (this.expectedGUID != null) {
|
||||
assertEquals(this.expectedGUID, guid);
|
||||
}
|
||||
} catch (AssertionFailedError e) {
|
||||
performNotify(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.sync.repositories.domain.HistoryRecord;
|
||||
|
||||
public class HistoryHelpers {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static JSONArray getVisits1() {
|
||||
JSONArray json = new JSONArray();
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("date", 1320087601465600000L);
|
||||
obj.put("type", 2L);
|
||||
json.add(obj);
|
||||
obj = new JSONObject();
|
||||
obj.put("date", 1320084970724990000L);
|
||||
obj.put("type", 1L);
|
||||
json.add(obj);
|
||||
obj = new JSONObject();
|
||||
obj.put("date", 1319764134412287000L);
|
||||
obj.put("type", 1L);
|
||||
json.add(obj);
|
||||
obj = new JSONObject();
|
||||
obj.put("date", 1319681306455594000L);
|
||||
obj.put("type", 2L);
|
||||
json.add(obj);
|
||||
return json;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static JSONArray getVisits2() {
|
||||
JSONArray json = new JSONArray();
|
||||
JSONObject obj = new JSONObject();
|
||||
obj = new JSONObject();
|
||||
obj.put("date", 1319764134412345000L);
|
||||
obj.put("type", 4L);
|
||||
json.add(obj);
|
||||
obj = new JSONObject();
|
||||
obj.put("date", 1319681306454321000L);
|
||||
obj.put("type", 3L);
|
||||
json.add(obj);
|
||||
return json;
|
||||
}
|
||||
|
||||
public static HistoryRecord createHistory1() {
|
||||
HistoryRecord record = new HistoryRecord();
|
||||
record.title = "History 1";
|
||||
record.histURI = "http://history.page1.com";
|
||||
record.visits = getVisits1();
|
||||
return record;
|
||||
}
|
||||
|
||||
|
||||
public static HistoryRecord createHistory2() {
|
||||
HistoryRecord record = new HistoryRecord();
|
||||
record.title = "History 2";
|
||||
record.histURI = "http://history.page2.com";
|
||||
record.visits = getVisits2();
|
||||
return record;
|
||||
}
|
||||
|
||||
public static HistoryRecord createHistory3() {
|
||||
HistoryRecord record = new HistoryRecord();
|
||||
record.title = "History 3";
|
||||
record.histURI = "http://history.page3.com";
|
||||
record.visits = getVisits2();
|
||||
return record;
|
||||
}
|
||||
|
||||
public static HistoryRecord createHistory4() {
|
||||
HistoryRecord record = new HistoryRecord();
|
||||
record.title = "History 4";
|
||||
record.histURI = "http://history.page4.com";
|
||||
record.visits = getVisits1();
|
||||
return record;
|
||||
}
|
||||
|
||||
public static HistoryRecord createHistory5() {
|
||||
HistoryRecord record = new HistoryRecord();
|
||||
record.title = "History 5";
|
||||
record.histURI = "http://history.page5.com";
|
||||
record.visits = getVisits2();
|
||||
return record;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.domain.PasswordRecord;
|
||||
|
||||
public class PasswordHelpers {
|
||||
|
||||
public static PasswordRecord createPassword1() {
|
||||
PasswordRecord rec = new PasswordRecord();
|
||||
rec.encType = "some type";
|
||||
rec.formSubmitURL = "http://submit.html";
|
||||
rec.hostname = "http://hostname";
|
||||
rec.httpRealm = "httpRealm";
|
||||
rec.encryptedPassword ="12345";
|
||||
rec.passwordField = "box.pass.field";
|
||||
rec.timeCreated = 111111111L;
|
||||
rec.timeLastUsed = 123412352435L;
|
||||
rec.timePasswordChanged = 121111111L;
|
||||
rec.timesUsed = 5L;
|
||||
rec.encryptedUsername = "jvoll";
|
||||
rec.usernameField = "box.user.field";
|
||||
return rec;
|
||||
}
|
||||
|
||||
public static PasswordRecord createPassword2() {
|
||||
PasswordRecord rec = new PasswordRecord();
|
||||
rec.encType = "some type";
|
||||
rec.formSubmitURL = "http://submit2.html";
|
||||
rec.hostname = "http://hostname2";
|
||||
rec.httpRealm = "httpRealm2";
|
||||
rec.encryptedPassword ="54321";
|
||||
rec.passwordField = "box.pass.field2";
|
||||
rec.timeCreated = 12111111111L;
|
||||
rec.timeLastUsed = 123412352213L;
|
||||
rec.timePasswordChanged = 123111111111L;
|
||||
rec.timesUsed = 2L;
|
||||
rec.encryptedUsername = "rnewman";
|
||||
rec.usernameField = "box.user.field2";
|
||||
return rec;
|
||||
}
|
||||
|
||||
public static PasswordRecord createPassword3() {
|
||||
PasswordRecord rec = new PasswordRecord();
|
||||
rec.encType = "some type3";
|
||||
rec.formSubmitURL = "http://submit3.html";
|
||||
rec.hostname = "http://hostname3";
|
||||
rec.httpRealm = "httpRealm3";
|
||||
rec.encryptedPassword ="54321";
|
||||
rec.passwordField = "box.pass.field3";
|
||||
rec.timeCreated = 100000000000L;
|
||||
rec.timeLastUsed = 123412352213L;
|
||||
rec.timePasswordChanged = 110000000000L;
|
||||
rec.timesUsed = 2L;
|
||||
rec.encryptedUsername = "rnewman";
|
||||
rec.usernameField = "box.user.field3";
|
||||
return rec;
|
||||
}
|
||||
|
||||
public static PasswordRecord createPassword4() {
|
||||
PasswordRecord rec = new PasswordRecord();
|
||||
rec.encType = "some type";
|
||||
rec.formSubmitURL = "http://submit4.html";
|
||||
rec.hostname = "http://hostname4";
|
||||
rec.httpRealm = "httpRealm4";
|
||||
rec.encryptedPassword ="54324";
|
||||
rec.passwordField = "box.pass.field4";
|
||||
rec.timeCreated = 101000000000L;
|
||||
rec.timeLastUsed = 123412354444L;
|
||||
rec.timePasswordChanged = 110000000000L;
|
||||
rec.timesUsed = 4L;
|
||||
rec.encryptedUsername = "rnewman4";
|
||||
rec.usernameField = "box.user.field4";
|
||||
return rec;
|
||||
}
|
||||
|
||||
public static PasswordRecord createPassword5() {
|
||||
PasswordRecord rec = new PasswordRecord();
|
||||
rec.encType = "some type5";
|
||||
rec.formSubmitURL = "http://submit5.html";
|
||||
rec.hostname = "http://hostname5";
|
||||
rec.httpRealm = "httpRealm5";
|
||||
rec.encryptedPassword ="54325";
|
||||
rec.passwordField = "box.pass.field5";
|
||||
rec.timeCreated = 101000000000L;
|
||||
rec.timeLastUsed = 123412352555L;
|
||||
rec.timePasswordChanged = 111111111111L;
|
||||
rec.timesUsed = 5L;
|
||||
rec.encryptedUsername = "jvoll5";
|
||||
rec.usernameField = "box.user.field5";
|
||||
return rec;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.background.testhelpers.WaitHelper;
|
||||
import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.repositories.RepositorySession;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public class SessionTestHelper {
|
||||
|
||||
protected static RepositorySession prepareRepositorySession(
|
||||
final Context context,
|
||||
final boolean begin,
|
||||
final Repository repository) {
|
||||
|
||||
final WaitHelper testWaiter = WaitHelper.getTestWaiter();
|
||||
|
||||
final String logTag = "prepareRepositorySession";
|
||||
class CreationDelegate extends DefaultSessionCreationDelegate {
|
||||
private RepositorySession session;
|
||||
synchronized void setSession(RepositorySession session) {
|
||||
this.session = session;
|
||||
}
|
||||
synchronized RepositorySession getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionCreated(final RepositorySession session) {
|
||||
assertNotNull(session);
|
||||
Logger.info(logTag, "Setting session to " + session);
|
||||
setSession(session);
|
||||
if (begin) {
|
||||
Logger.info(logTag, "Calling session.begin on new session.");
|
||||
// The begin callbacks will notify.
|
||||
try {
|
||||
session.begin(new ExpectBeginDelegate());
|
||||
} catch (InvalidSessionTransitionException e) {
|
||||
testWaiter.performNotify(e);
|
||||
}
|
||||
} else {
|
||||
Logger.info(logTag, "Notifying after setting new session.");
|
||||
testWaiter.performNotify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final CreationDelegate delegate = new CreationDelegate();
|
||||
try {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
repository.createSession(delegate, context);
|
||||
}
|
||||
};
|
||||
testWaiter.performWait(runnable);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Logger.warn(logTag, "Caught IllegalArgumentException.");
|
||||
}
|
||||
|
||||
Logger.info(logTag, "Retrieving new session.");
|
||||
final RepositorySession session = delegate.getSession();
|
||||
assertNotNull(session);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
public static RepositorySession createSession(final Context context, final Repository repository) {
|
||||
return prepareRepositorySession(context, false, repository);
|
||||
}
|
||||
|
||||
public static RepositorySession createAndBeginSession(Context context, Repository repository) {
|
||||
return prepareRepositorySession(context, true, repository);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
|
||||
|
||||
public abstract class SimpleSuccessBeginDelegate extends DefaultDelegate implements RepositorySessionBeginDelegate {
|
||||
@Override
|
||||
public void onBeginFailed(Exception ex) {
|
||||
performNotify("Begin failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionBeginDelegate deferredBeginDelegate(ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
|
||||
|
||||
public abstract class SimpleSuccessCreationDelegate extends DefaultDelegate implements RepositorySessionCreationDelegate {
|
||||
@Override
|
||||
public void onSessionCreateFailed(Exception ex) {
|
||||
performNotify("Session creation failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionCreationDelegate deferredCreationDelegate() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
|
||||
import org.mozilla.gecko.sync.repositories.domain.Record;
|
||||
|
||||
public abstract class SimpleSuccessFetchDelegate extends DefaultDelegate implements
|
||||
RepositorySessionFetchRecordsDelegate {
|
||||
@Override
|
||||
public void onFetchFailed(Exception ex, Record record) {
|
||||
performNotify("Fetch failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFetchRecordsDelegate deferredFetchDelegate(ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFinishDelegate;
|
||||
|
||||
public abstract class SimpleSuccessFinishDelegate extends DefaultDelegate implements RepositorySessionFinishDelegate {
|
||||
@Override
|
||||
public void onFinishFailed(Exception ex) {
|
||||
performNotify("Finish failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionFinishDelegate deferredFinishDelegate(ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.sync.helpers;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
|
||||
|
||||
public abstract class SimpleSuccessStoreDelegate extends DefaultDelegate implements RepositorySessionStoreDelegate {
|
||||
@Override
|
||||
public void onRecordStoreFailed(Exception ex, String guid) {
|
||||
performNotify("Store failed", ex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepositorySessionStoreDelegate deferredStoreDelegate(ExecutorService executor) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/* 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.SynchronizerConfiguration;
|
||||
import org.mozilla.gecko.sync.repositories.RecordFactory;
|
||||
import org.mozilla.gecko.sync.repositories.Repository;
|
||||
import org.mozilla.gecko.sync.stage.ServerSyncStage;
|
||||
|
||||
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 Exception {
|
||||
return this.getConfig();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/* 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.testhelpers;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
|
||||
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/* 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";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.testhelpers;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/* 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/* 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;
|
||||
|
||||
public class MockRecord extends Record {
|
||||
|
||||
public MockRecord(String guid, String collection, long lastModified, boolean deleted) {
|
||||
super(guid, collection, lastModified, deleted);
|
||||
}
|
||||
|
||||
@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() {
|
||||
return "{\"id\":\"" + guid + "\", \"payload\": \"foo\"}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
/* 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.testhelpers;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
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 android.content.Context;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.testhelpers;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
||||
/**
|
||||
* Implements waiting for asynchronous test events.
|
||||
*
|
||||
* Call WaitHelper.getTestWaiter() to get the unique instance.
|
||||
*
|
||||
* Call performWait(runnable) to execute runnable synchronously.
|
||||
* runnable *must* call performNotify() on all exit paths to signal to
|
||||
* the TestWaiter that the runnable has completed.
|
||||
*
|
||||
* @author rnewman
|
||||
* @author nalexander
|
||||
*/
|
||||
public class WaitHelper {
|
||||
|
||||
public static final String LOG_TAG = "WaitHelper";
|
||||
|
||||
public static class Result {
|
||||
public Throwable error;
|
||||
public Result() {
|
||||
error = null;
|
||||
}
|
||||
|
||||
public Result(Throwable error) {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class WaitHelperError extends Error {
|
||||
private static final long serialVersionUID = 7074690961681883619L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*
|
||||
* @author rnewman
|
||||
*/
|
||||
public static class TimeoutError extends WaitHelperError {
|
||||
private static final long serialVersionUID = 8591672555848651736L;
|
||||
public final int waitTimeInMillis;
|
||||
|
||||
public TimeoutError(int waitTimeInMillis) {
|
||||
this.waitTimeInMillis = waitTimeInMillis;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleNotificationsError extends WaitHelperError {
|
||||
private static final long serialVersionUID = -9072736521571635495L;
|
||||
}
|
||||
|
||||
public static class InterruptedError extends WaitHelperError {
|
||||
private static final long serialVersionUID = 8383948170038639308L;
|
||||
}
|
||||
|
||||
public static class InnerError extends WaitHelperError {
|
||||
private static final long serialVersionUID = 3008502618576773778L;
|
||||
public Throwable innerError;
|
||||
|
||||
public InnerError(Throwable e) {
|
||||
innerError = e;
|
||||
if (e != null) {
|
||||
// Eclipse prints the stack trace of the cause.
|
||||
this.initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BlockingQueue<Result> queue = new ArrayBlockingQueue<Result>(1);
|
||||
|
||||
/**
|
||||
* How long performWait should wait for, in milliseconds, with the
|
||||
* convention that a negative value means "wait forever".
|
||||
*/
|
||||
public static int defaultWaitTimeoutInMillis = -1;
|
||||
|
||||
public void performWait(Runnable action) throws WaitHelperError {
|
||||
this.performWait(defaultWaitTimeoutInMillis, action);
|
||||
}
|
||||
|
||||
public void performWait(int waitTimeoutInMillis, Runnable action) throws WaitHelperError {
|
||||
Logger.debug(LOG_TAG, "performWait called.");
|
||||
|
||||
Result result = null;
|
||||
|
||||
try {
|
||||
if (action != null) {
|
||||
try {
|
||||
action.run();
|
||||
Logger.debug(LOG_TAG, "Action done.");
|
||||
} catch (Exception ex) {
|
||||
Logger.debug(LOG_TAG, "Performing action threw: " + ex.getMessage());
|
||||
throw new InnerError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (waitTimeoutInMillis < 0) {
|
||||
result = queue.take();
|
||||
} else {
|
||||
result = queue.poll(waitTimeoutInMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Got result from queue: " + result);
|
||||
} catch (InterruptedException e) {
|
||||
// We were interrupted.
|
||||
Logger.debug(LOG_TAG, "performNotify interrupted with InterruptedException " + e);
|
||||
final InterruptedError interruptedError = new InterruptedError();
|
||||
interruptedError.initCause(e);
|
||||
throw interruptedError;
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
// We timed out.
|
||||
throw new TimeoutError(waitTimeoutInMillis);
|
||||
} else if (result.error != null) {
|
||||
Logger.debug(LOG_TAG, "Notified with error: " + result.error.getMessage());
|
||||
|
||||
// Rethrow any assertion with which we were notified.
|
||||
InnerError innerError = new InnerError(result.error);
|
||||
throw innerError;
|
||||
}
|
||||
// Success!
|
||||
}
|
||||
|
||||
public void performNotify(final Throwable e) {
|
||||
if (e != null) {
|
||||
Logger.debug(LOG_TAG, "performNotify called with Throwable: " + e.getMessage());
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "performNotify called.");
|
||||
}
|
||||
|
||||
if (!queue.offer(new Result(e))) {
|
||||
// This could happen if performNotify is called multiple times (which is an error).
|
||||
throw new MultipleNotificationsError();
|
||||
}
|
||||
}
|
||||
|
||||
public void performNotify() {
|
||||
this.performNotify(null);
|
||||
}
|
||||
|
||||
public static Runnable onThreadRunnable(final Runnable r) {
|
||||
return new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(r).start();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static WaitHelper singleWaiter = new WaitHelper();
|
||||
public static WaitHelper getTestWaiter() {
|
||||
return singleWaiter;
|
||||
}
|
||||
|
||||
public static void resetTestWaiter() {
|
||||
singleWaiter = new WaitHelper();
|
||||
}
|
||||
|
||||
public boolean isIdle() {
|
||||
return queue.isEmpty();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"data":[
|
||||
{
|
||||
"id":"c906275c-3747-fe27-426f-6187526a6f06",
|
||||
"deleted": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"data":[
|
||||
{
|
||||
"kind":"font",
|
||||
"original": {
|
||||
"mimetype":"application/x-font-ttf",
|
||||
"filename":"CharisSILCompact-R.ttf",
|
||||
"hash":"4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067",
|
||||
"size":1727656
|
||||
},
|
||||
"last_modified":1455710632607,
|
||||
"attachment": {
|
||||
"mimetype":"application/x-gzip",
|
||||
"size":548720,
|
||||
"hash":"960be4fc5a92c1dc488582b215d5d75429fd4ffbee463105d29992cd792a912e",
|
||||
"location":"/attachments/0d28a72d-a51f-46f8-9e5a-f95c61de904e.gz",
|
||||
"filename":"CharisSILCompact-R.ttf.gz"
|
||||
},
|
||||
"type":"asset-archive",
|
||||
"id":"c906275c-3747-fe27-426f-6187526a6f06"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"data":[
|
||||
{
|
||||
"kind":"font",
|
||||
"last_modified":1455710632607,
|
||||
"attachment": {
|
||||
"mimetype":"application/x-gzip",
|
||||
"size":548720,
|
||||
"hash":"960be4fc5a92c1dc488582b215d5d75429fd4ffbee463105d29992cd792a912e",
|
||||
"location":"/attachments/0d28a72d-a51f-46f8-9e5a-f95c61de904e.gz",
|
||||
"filename":"CharisSILCompact-R.ttf.gz",
|
||||
"original": {
|
||||
"mimetype":"application/x-font-ttf",
|
||||
"filename":"CharisSILCompact-R.ttf",
|
||||
"hash":"4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067",
|
||||
"size":1727656
|
||||
}
|
||||
},
|
||||
"type":"asset-archive",
|
||||
"id":"c906275c-3747-fe27-426f-6187526a6f06"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"name": "active-experiment",
|
||||
"match": {
|
||||
},
|
||||
"buckets": {
|
||||
"min": "0",
|
||||
"max": "100"
|
||||
},
|
||||
"values": {
|
||||
"foo": true
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "inactive-experiment",
|
||||
"match": {
|
||||
"appId": "^NOPE$"
|
||||
},
|
||||
"buckets": {
|
||||
"min": "0",
|
||||
"max": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "bookmark-history-menu",
|
||||
"match": {
|
||||
},
|
||||
"buckets": {
|
||||
"min": "0",
|
||||
"max": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "is-matching",
|
||||
"match": {
|
||||
"appId": "^org.mozilla.gecko$"
|
||||
},
|
||||
"buckets": {
|
||||
"min": "0",
|
||||
"max": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "is-not-matching",
|
||||
"match": {
|
||||
"appId": "^org.mozilla.fennec|^org.mozilla.firefox_beta$"
|
||||
},
|
||||
"buckets": {
|
||||
"min": "0",
|
||||
"max": "100"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "promote-add-to-homescreen",
|
||||
"buckets": {
|
||||
"max": "100",
|
||||
"min": "50"
|
||||
},
|
||||
"last_modified": 1467705654772,
|
||||
"values": {
|
||||
"lastVisitMaximumAgeMs": 600000,
|
||||
"minimumTotalVisits": 5,
|
||||
"lastVisitMinimumAgeMs": 30000
|
||||
},
|
||||
"id": "20d278d7-0d35-4811-8f01-bf24e31ba51b",
|
||||
"match": {
|
||||
"appId": "^org.mozilla.fennec|^org.mozilla.firefox_beta$"
|
||||
},
|
||||
"schema": 1467705310595
|
||||
},
|
||||
{
|
||||
"name": "offline-cache",
|
||||
"buckets": {
|
||||
"max": "100",
|
||||
"min": "0"
|
||||
},
|
||||
"last_modified": 1467705429859,
|
||||
"id": "9f1ea043-c1d8-48ba-802d-aeabaf667afe",
|
||||
"match": {
|
||||
"appId": "^org.mozilla.fennec|^org.mozilla.firefox_beta$"
|
||||
},
|
||||
"schema": 1467705310595
|
||||
},
|
||||
{
|
||||
"name": "bookmark-history-menu",
|
||||
"buckets": {
|
||||
"max": "100",
|
||||
"min": "0"
|
||||
},
|
||||
"last_modified": 1467705381971,
|
||||
"id": "29988035-1a59-4671-b679-2c717a68bd12",
|
||||
"match": {},
|
||||
"schema": 1467705310595
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://en.wikipedia.org/wiki/Atom_%28standard%29#Example_of_an_Atom_1.0_feed
|
||||
-->
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<subtitle>A subtitle.</subtitle>
|
||||
<link href="http://example.org/feed/" rel="self" />
|
||||
<link href="http://example.org/" />
|
||||
<id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
|
||||
|
||||
<entry>
|
||||
<title>Atom-Powered Robots Run Amok</title>
|
||||
<link href="http://example.org/2003/12/13/atom03" />
|
||||
<link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>
|
||||
<link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>
|
||||
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
<summary>Some text.</summary>
|
||||
<content type="xhtml">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p>This is the entry content.</p>
|
||||
</div>
|
||||
</content>
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
<email>johndoe@example.com</email>
|
||||
</author>
|
||||
</entry>
|
||||
|
||||
</feed>
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1965
mobile/android/tests/background/junit4/resources/feed_rss_heise.xml
Normal file
1965
mobile/android/tests/background/junit4/resources/feed_rss_heise.xml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Snapshot from https://medium.com/feed/@Antlam/
|
||||
-->
|
||||
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
||||
<channel>
|
||||
<title><![CDATA[Anthony Lam on Medium]]></title>
|
||||
<description><![CDATA[Latest posts by Anthony Lam on Medium]]></description>
|
||||
<link>https://medium.com/@antlam?source=rss-59f49b9e4b19------2</link>
|
||||
<image>
|
||||
<url>https://d262ilb51hltx0.cloudfront.net/fit/c/150/150/1*BNfAhhQ8TybWsu_gMMixWw.jpeg</url>
|
||||
<title>Anthony Lam on Medium</title>
|
||||
<link>https://medium.com/@antlam?source=rss-59f49b9e4b19------2</link>
|
||||
</image>
|
||||
<generator>RSS for Node</generator>
|
||||
<lastBuildDate>Tue, 26 Jan 2016 17:06:09 GMT</lastBuildDate>
|
||||
<atom:link href="https://medium.com/feed/@antlam" rel="self" type="application/rss+xml"/>
|
||||
<webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
|
||||
<atom:link href="http://medium.superfeedr.com" rel="hub"/>
|
||||
<item>
|
||||
<title><![CDATA[UX thoughts for 2016]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*mOiPSWfvCBoLUrfQlIrdVQ.png" width="600" height="200"></a></p><p class="medium-feed-snippet">And just like that, another year.</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/ux-thoughts-for-2016-1fc1d6e515e8?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/1fc1d6e515e8</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Mon, 11 Jan 2016 18:43:58 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[A New Mobile Tabs tray]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/a-new-mobile-tabs-tray-327ac262eacb?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*scpLRAL_zy9CcW6BLGZHng.png" width="600" height="200"></a></p><p class="medium-feed-snippet">Why we’re giving it an update in Firefox for Android</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/a-new-mobile-tabs-tray-327ac262eacb?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/a-new-mobile-tabs-tray-327ac262eacb?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/327ac262eacb</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Fri, 06 Nov 2015 17:30:55 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Quick search]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/quick-search-bdd374257e75?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*DyBNARNADZsexPnxkMaEgQ.png" width="600" height="200"></a></p><p class="medium-feed-snippet">Instantly search with any of your search providers</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/quick-search-bdd374257e75?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/quick-search-bdd374257e75?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/bdd374257e75</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Tue, 01 Sep 2015 17:36:32 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Designing helpfulness]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/designing-helpfulness-c1777727faf?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*Cg8wkgjwCH7A1Aotec1Okw.png" width="600" height="200"></a></p><p class="medium-feed-snippet">Being there, without being annoying</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/designing-helpfulness-c1777727faf?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/designing-helpfulness-c1777727faf?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/c1777727faf</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Tue, 11 Aug 2015 20:59:13 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Share to… Firefox?]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/share-to-firefox-245984b2da33?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*H5wztlFTvbE1EM_5MRkuzg.png" width="600" height="200"></a></p><p class="medium-feed-snippet">You may remember this from such share intents as “Add to Firefox”</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/share-to-firefox-245984b2da33?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/share-to-firefox-245984b2da33?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/245984b2da33</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Fri, 08 May 2015 20:18:18 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Open multiple links]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/open-multiple-links-1ce475c47de3?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*yEQ5DjomHZIgVzUN-FKx2A.jpeg" width="600" height="200"></a></p><p class="medium-feed-snippet">Queue links in Firefox instead of switching applications each time.</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/open-multiple-links-1ce475c47de3?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/open-multiple-links-1ce475c47de3?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/1ce475c47de3</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Tue, 14 Apr 2015 18:44:59 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Firefox for Android on Tablets]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/firefox-for-android-on-tablets-f67edc83dd46?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*H5p3YQ1NGpfGPDryd22Ujg.png" width="600" height="200"></a></p><p class="medium-feed-snippet">Redesigning the browser interface — Part two</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/firefox-for-android-on-tablets-f67edc83dd46?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/firefox-for-android-on-tablets-f67edc83dd46?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/f67edc83dd46</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Tue, 03 Feb 2015 20:29:18 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Are big phones back?]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/are-big-phones-back-59550ba0f24e?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*XArwkY9ZcmtCkEhDwl0aKA.jpeg" width="600" height="200"></a></p><p class="medium-feed-snippet">Some thoughts and impressions</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/are-big-phones-back-59550ba0f24e?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/are-big-phones-back-59550ba0f24e?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/59550ba0f24e</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Tue, 23 Dec 2014 20:05:36 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[Firefox for Android looks a bit different]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/firefox-for-android-looks-a-bit-different-8ae8eba41b1f?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*Lk31G3Qt2fs5WcOtBQQVgw.png" width="600" height="200"></a></p><p class="medium-feed-snippet">Redesigning the browser interface</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/firefox-for-android-looks-a-bit-different-8ae8eba41b1f?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/firefox-for-android-looks-a-bit-different-8ae8eba41b1f?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/8ae8eba41b1f</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Fri, 29 Aug 2014 23:38:07 GMT</pubDate>
|
||||
</item>
|
||||
<item>
|
||||
<title><![CDATA[My fancy new watch]]></title>
|
||||
<description><![CDATA[<div class="medium-feed-item"><p class="medium-feed-image"><a href="https://medium.com/@antlam/my-fancy-new-watch-4856162890a3?source=rss-59f49b9e4b19------2"><img src="https://d262ilb51hltx0.cloudfront.net/fit/c/600/200/1*_gsBD-Vw7qevrwLxXZe8jA.jpeg" width="600" height="200"></a></p><p class="medium-feed-snippet">Early thoughts</p><p class="medium-feed-link"><a href="https://medium.com/@antlam/my-fancy-new-watch-4856162890a3?source=rss-59f49b9e4b19------2">Continue reading on Medium »</a></p></div>]]></description>
|
||||
<link>https://medium.com/@antlam/my-fancy-new-watch-4856162890a3?source=rss-59f49b9e4b19------2</link>
|
||||
<guid isPermaLink="false">https://medium.com/p/4856162890a3</guid>
|
||||
<dc:creator><![CDATA[Anthony Lam]]></dc:creator>
|
||||
<pubDate>Tue, 22 Jul 2014 01:36:52 GMT</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
|
||||
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
|
||||
<channel>
|
||||
<title>SPIEGEL ONLINE - Schlagzeilen</title>
|
||||
<link>http://www.spiegel.de</link>
|
||||
<description>Deutschlands führende Nachrichtenseite. Alles Wichtige aus Politik, Wirtschaft, Sport, Kultur, Wissenschaft, Technik und mehr.</description>
|
||||
<language>de</language>
|
||||
<pubDate>Wed, 27 Jan 2016 18:20:31 +0100</pubDate>
|
||||
<lastBuildDate>Wed, 27 Jan 2016 18:20:31 +0100</lastBuildDate>
|
||||
<image>
|
||||
<title>SPIEGEL ONLINE</title>
|
||||
<link>http://www.spiegel.de</link>
|
||||
<url>http://www.spiegel.de/static/sys/logo_120x61.gif</url>
|
||||
</image>
|
||||
<item>
|
||||
<title>Angebliche Vergewaltigung einer 13-Jährigen: Steinmeier kanzelt russischen Minister Lawrow ab</title>
|
||||
<link>http://www.spiegel.de/politik/ausland/steinmeier-kanzelt-lawrow-ab-aerger-um-angebliche-vergewaltigung-a-1074292.html#ref=rss</link>
|
||||
<description>Der Ton wird scharf zwischen Berlin und Moskau: Frank-Walter Steinmeier wirft dem russischen Außenminister Lawrow politische Propaganda vor - es geht um die angebliche Vergewaltigung einer 13-Jährigen in Berlin.</description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 18:16:16 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/ausland/steinmeier-kanzelt-lawrow-ab-aerger-um-angebliche-vergewaltigung-a-1074292.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949492-thumbsmall-wtgq.jpg" hspace="5" align="left" >Der Ton wird scharf zwischen Berlin und Moskau: Frank-Walter Steinmeier wirft dem russischen Außenminister Lawrow politische Propaganda vor - es geht um die angebliche Vergewaltigung einer 13-Jährigen in Berlin.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949492-thumbsmall-wtgq.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Grafischer Überblick: Hier gibt es die wenigsten Herzinfarkte in Deutschland</title>
|
||||
<link>http://www.spiegel.de/gesundheit/diagnose/ostdeutsche-sterben-deutlich-haeufiger-an-einem-herzinfarkt-a-1074231.html#ref=rss</link>
|
||||
<description>Nirgendwo arbeiten mehr Kardiologen als in Hamburg - auch deshalb gibt es dort weniger Herzinfarkte als in anderen Bundesländern. Wie sieht es in Ihrer Gegend aus, wo ist die Sterblichkeit am höchsten? Der Überblick.</description>
|
||||
<category>Gesundheit</category>
|
||||
<pubDate>Wed, 27 Jan 2016 18:08:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/gesundheit/diagnose/ostdeutsche-sterben-deutlich-haeufiger-an-einem-herzinfarkt-a-1074231.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-566602-thumbsmall-bxcg.jpg" hspace="5" align="left" >Nirgendwo arbeiten mehr Kardiologen als in Hamburg - auch deshalb gibt es dort weniger Herzinfarkte als in anderen Bundesländern. Wie sieht es in Ihrer Gegend aus, wo ist die Sterblichkeit am höchsten? Der Überblick.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-566602-thumbsmall-bxcg.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Nachhilfe für gute Schüler: Lasst Eure Kinder auch das Scheitern lernen</title>
|
||||
<link>http://www.spiegel.de/schulspiegel/nachhilfe-entspannte-schueler-brauchen-entspannte-eltern-a-1074197.html#ref=rss</link>
|
||||
<description>Selbst gute Schüler werden von ihren Eltern zur Nachhilfe geschickt. Das schadet mehr, als es nützt - denn die Kinder lernen dabei vor allem eines: Dass sie nicht scheitern dürfen.</description>
|
||||
<category>SchulSPIEGEL</category>
|
||||
<pubDate>Wed, 27 Jan 2016 18:02:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/schulspiegel/nachhilfe-entspannte-schueler-brauchen-entspannte-eltern-a-1074197.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-396678-thumbsmall-dhws.jpg" hspace="5" align="left" >Selbst gute Schüler werden von ihren Eltern zur Nachhilfe geschickt. Das schadet mehr, als es nützt - denn die Kinder lernen dabei vor allem eines: Dass sie nicht scheitern dürfen.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-396678-thumbsmall-dhws.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Germanwings-Katastrophe: Arbeitsgruppe regt Schleuse zwischen Kabine und Cockpit an</title>
|
||||
<link>http://www.spiegel.de/panorama/germanwings-katastrophe-arbeitsgruppe-legt-abschlussbericht-vor-a-1074294.html#ref=rss</link>
|
||||
<description>Wie sicher muss ein Flugzeugcockpit sein? Nach dem Germanwings-Absturz mit 150 Toten hat sich eine Arbeitsgruppe mit dieser Frage befasst - und nun ihren Abschlussbericht vorgelegt.</description>
|
||||
<category>Panorama</category>
|
||||
<pubDate>Wed, 27 Jan 2016 17:52:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/panorama/germanwings-katastrophe-arbeitsgruppe-legt-abschlussbericht-vor-a-1074294.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-828832-thumbsmall-gjvf.jpg" hspace="5" align="left" >Wie sicher muss ein Flugzeugcockpit sein? Nach dem Germanwings-Absturz mit 150 Toten hat sich eine Arbeitsgruppe mit dieser Frage befasst - und nun ihren Abschlussbericht vorgelegt.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-828832-thumbsmall-gjvf.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Polen: Händler wehren sich gegen Supermarktsteuer</title>
|
||||
<link>http://www.spiegel.de/wirtschaft/soziales/polen-will-internationale-einzelhaendler-hoeher-besteuern-a-1074101.html#ref=rss</link>
|
||||
<description>Polens Regierung plant, Einzelhändler höher zu besteuern - und will mit den Einnahmen soziale Wohltaten finanzieren. Besonders trifft es ausländische Unternehmen wie Kaufland und Metro.</description>
|
||||
<category>Wirtschaft</category>
|
||||
<pubDate>Wed, 27 Jan 2016 17:46:10 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/wirtschaft/soziales/polen-will-internationale-einzelhaendler-hoeher-besteuern-a-1074101.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949308-thumbsmall-wwsr.jpg" hspace="5" align="left" >Polens Regierung plant, Einzelhändler höher zu besteuern - und will mit den Einnahmen soziale Wohltaten finanzieren. Besonders trifft es ausländische Unternehmen wie Kaufland und Metro.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949308-thumbsmall-wwsr.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Übergriffe in Köln: Polizei erteilt Silvester-Verdächtigen Karnevalverbot</title>
|
||||
<link>http://www.spiegel.de/panorama/justiz/koeln-polizei-erteilt-silvester-verdaechtigen-verbote-fuer-karneval-a-1074274.html#ref=rss</link>
|
||||
<description>Kölns Polizei bereitet sich auf die Karnevalstage vor: Tatverdächtige aus der Silvesternacht sollen mit Zutrittsverboten von bestimmten Orten ferngehalten werden. Der Polizeipräsident forderte die Narren zudem auf, keine Spielzeugwaffen zu tragen.</description>
|
||||
<category>Panorama</category>
|
||||
<pubDate>Wed, 27 Jan 2016 17:23:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/panorama/justiz/koeln-polizei-erteilt-silvester-verdaechtigen-verbote-fuer-karneval-a-1074274.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-942482-thumbsmall-qyge.jpg" hspace="5" align="left" >Kölns Polizei bereitet sich auf die Karnevalstage vor: Tatverdächtige aus der Silvesternacht sollen mit Zutrittsverboten von bestimmten Orten ferngehalten werden. Der Polizeipräsident forderte die Narren zudem auf, keine Spielzeugwaffen zu tragen.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-942482-thumbsmall-qyge.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>"The Hateful 8"-Stars im Interview: "Wahnsinn, was da alles passiert!"</title>
|
||||
<link>http://www.spiegel.de/kultur/kino/the-hateful-8-quentin-tarantino-und-jennifer-jason-leigh-im-interview-a-1074202.html#ref=rss</link>
|
||||
<description>Sieben Männer und eine Frau - in seinem Western "The Hateful 8" entwirft Quentin Tarantino ein brutales Abbild der US-Gesellschaft. Hier erklären der Regisseur und seine Hauptdarstellerin, warum es sich lohnt, den Film gleich mehrmals anzusehen.</description>
|
||||
<category>Kultur</category>
|
||||
<pubDate>Wed, 27 Jan 2016 17:08:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/kultur/kino/the-hateful-8-quentin-tarantino-und-jennifer-jason-leigh-im-interview-a-1074202.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949256-thumbsmall-hbrj.jpg" hspace="5" align="left" >Sieben Männer und eine Frau - in seinem Western "The Hateful 8" entwirft Quentin Tarantino ein brutales Abbild der US-Gesellschaft. Hier erklären der Regisseur und seine Hauptdarstellerin, warum es sich lohnt, den Film gleich mehrmals anzusehen.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949256-thumbsmall-hbrj.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Drama auf zugefrorenem Teich: Baby in akuter Lebensgefahr - Messer gefunden</title>
|
||||
<link>http://www.spiegel.de/panorama/justiz/hamburg-vater-mit-baby-in-zugefrorenem-teich-eingebrochen-messer-gefunden-a-1074258.html#ref=rss</link>
|
||||
<description>Der Fall gibt Rätsel auf: In Hamburg wird ein Vater mit seinem Baby aus einem zugefrorenen Teich gerettet - zwei Männer hätten ihn überfallen, sagt der 24-Jährige. Nun haben Ermittler in der Nähe des Gewässers ein Messer gefunden.</description>
|
||||
<category>Panorama</category>
|
||||
<pubDate>Wed, 27 Jan 2016 16:32:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/panorama/justiz/hamburg-vater-mit-baby-in-zugefrorenem-teich-eingebrochen-messer-gefunden-a-1074258.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949367-thumbsmall-vpsq.jpg" hspace="5" align="left" >Der Fall gibt Rätsel auf: In Hamburg wird ein Vater mit seinem Baby aus einem zugefrorenen Teich gerettet - zwei Männer hätten ihn überfallen, sagt der 24-Jährige. Nun haben Ermittler in der Nähe des Gewässers ein Messer gefunden.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949367-thumbsmall-vpsq.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Rheinland-Pfalz: Elefantenrunde soll mit sechs Parteien stattfinden</title>
|
||||
<link>http://www.spiegel.de/politik/deutschland/swr-fernsehdebatte-jetzt-mit-sechs-parteien-a-1074262.html#ref=rss</link>
|
||||
<description>Erst drei, dann eine, jetzt sechs Parteien: Die TV-Diskussion im Südwestrundfunk zur rheinland-pfälzischen Landtagswahl findet nun in ganz großer Runde statt.</description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 16:29:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/deutschland/swr-fernsehdebatte-jetzt-mit-sechs-parteien-a-1074262.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-946925-thumbsmall-icoq.jpg" hspace="5" align="left" >Erst drei, dann eine, jetzt sechs Parteien: Die TV-Diskussion im Südwestrundfunk zur rheinland-pfälzischen Landtagswahl findet nun in ganz großer Runde statt.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-946925-thumbsmall-icoq.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Blauer Brief: Britische Schulleiterin mahnt Pyjama-Eltern ab</title>
|
||||
<link>http://www.spiegel.de/schulspiegel/direktorin-appelliert-an-eltern-bringt-eure-kinder-nicht-im-schlafanzug-zur-schule-a-1074167.html#ref=rss</link>
|
||||
<description>Rektorin Kate Chisholm hat genug: Ständig beobachtet sie Eltern, die noch im Schlafanzug stecken, wenn sie ihre Kinder an der Schule absetzen. Nun schrieb sie einen Brandbrief, die Elternschaft reagiert gespalten.</description>
|
||||
<category>SchulSPIEGEL</category>
|
||||
<pubDate>Wed, 27 Jan 2016 16:26:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/schulspiegel/direktorin-appelliert-an-eltern-bringt-eure-kinder-nicht-im-schlafanzug-zur-schule-a-1074167.html</guid>
|
||||
<content:encoded><![CDATA[Rektorin Kate Chisholm hat genug: Ständig beobachtet sie Eltern, die noch im Schlafanzug stecken, wenn sie ihre Kinder an der Schule absetzen. Nun schrieb sie einen Brandbrief, die Elternschaft reagiert gespalten.]]></content:encoded>
|
||||
</item>
|
||||
<item>
|
||||
<title>Zwischenruf bei Merkel-Besuch: Hochschule verwirft juristische Schritte gegen Stör-Professor</title>
|
||||
<link>http://www.spiegel.de/unispiegel/studium/zwischenruf-bei-merkel-besuch-hochschule-verwirft-juristische-schritte-gegen-professor-a-1074208.html#ref=rss</link>
|
||||
<description>Seine politische Stör-Aktion während einer Rede von Kanzlerin Merkel ist für einen Merseburger Professor glimpflich ausgegangen. Die Hochschule sieht von juristischen Schritten ab. </description>
|
||||
<category>UniSPIEGEL</category>
|
||||
<pubDate>Wed, 27 Jan 2016 16:08:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/unispiegel/studium/zwischenruf-bei-merkel-besuch-hochschule-verwirft-juristische-schritte-gegen-professor-a-1074208.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949364-thumbsmall-nnis.jpg" hspace="5" align="left" >Seine politische Stör-Aktion während einer Rede von Kanzlerin Merkel ist für einen Merseburger Professor glimpflich ausgegangen. Die Hochschule sieht von juristischen Schritten ab. ]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949364-thumbsmall-nnis.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Bundesautobahngesellschaft: Verkehrsminister wehren sich gegen "Mammutbehörde"</title>
|
||||
<link>http://www.spiegel.de/auto/aktuell/bundesautobahngesellschaft-widerstand-der-verkehrsminister-a-1074198.html#ref=rss</link>
|
||||
<description>Der Bund könnte die Gründung einer Bundesautobahngesellschaft vorantreiben. Mehrere Verkehrsminister der Länder stellen sich dagegen - auch aus Furcht vor privaten Investoren beim Fernstraßenbau. </description>
|
||||
<category>Auto</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:54:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/auto/aktuell/bundesautobahngesellschaft-widerstand-der-verkehrsminister-a-1074198.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949241-thumbsmall-cksv.jpg" hspace="5" align="left" >Der Bund könnte die Gründung einer Bundesautobahngesellschaft vorantreiben. Mehrere Verkehrsminister der Länder stellen sich dagegen - auch aus Furcht vor privaten Investoren beim Fernstraßenbau. ]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949241-thumbsmall-cksv.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Glasfaser-Ausbau: Dobrindt will schnelles Netz unter die Autobahnen legen</title>
|
||||
<link>http://www.spiegel.de/netzwelt/netzpolitik/alexander-dobrindt-will-schnelles-netz-unter-die-autobahnen-legen-a-1074151.html#ref=rss</link>
|
||||
<description>Jede Baustelle soll Bandbreite bringen: Die Bundesregierung beschließt, dass künftig beim Straßenbau automatisch Glasfaserkabel verlegt werden müssen. Kommt Deutschland so schneller ins Netz?</description>
|
||||
<category>Netzwelt</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:52:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/netzwelt/netzpolitik/alexander-dobrindt-will-schnelles-netz-unter-die-autobahnen-legen-a-1074151.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949166-thumbsmall-xrfd.jpg" hspace="5" align="left" >Jede Baustelle soll Bandbreite bringen: Die Bundesregierung beschließt, dass künftig beim Straßenbau automatisch Glasfaserkabel verlegt werden müssen. Kommt Deutschland so schneller ins Netz?]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949166-thumbsmall-xrfd.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Handball-Insolvenz: Flensburg will den HSV verklagen</title>
|
||||
<link>http://www.spiegel.de/sport/sonst/handball-bundesliga-sg-flensburg-handewitt-klagt-gegen-hsv-a-1074260.html#ref=rss</link>
|
||||
<description>Einem nackten Mann kann man nicht in die Tasche greifen? Die SG Flensburg-Handewitt will den insolventen HSV Handball verklagen. Der Traditionsverein will Schadenersatz aus Hamburg haben.</description>
|
||||
<category>Sport</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:49:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/sport/sonst/handball-bundesliga-sg-flensburg-handewitt-klagt-gegen-hsv-a-1074260.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-948459-thumbsmall-banl.jpg" hspace="5" align="left" >Einem nackten Mann kann man nicht in die Tasche greifen? Die SG Flensburg-Handewitt will den insolventen HSV Handball verklagen. Der Traditionsverein will Schadenersatz aus Hamburg haben.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-948459-thumbsmall-banl.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Lage am Lageso: Berliner Senatsverwaltung bestreitet Tod eines Flüchtlings</title>
|
||||
<link>http://www.spiegel.de/politik/deutschland/berlin-fluechtling-vom-lageso-tot-senat-widerspricht-a-1074255.html#ref=rss</link>
|
||||
<description>Ist in Berlin ein syrischer Flüchtling gestorben, nachdem er lange vor dem Lageso warten musste? Der Helfer, der darüber berichtete, ist abgetaucht - die Senatsverwaltung widerspricht der Darstellung über den Todesfall.</description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:45:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/deutschland/berlin-fluechtling-vom-lageso-tot-senat-widerspricht-a-1074255.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949380-thumbsmall-umdf.jpg" hspace="5" align="left" >Ist in Berlin ein syrischer Flüchtling gestorben, nachdem er lange vor dem Lageso warten musste? Der Helfer, der darüber berichtete, ist abgetaucht - die Senatsverwaltung widerspricht der Darstellung über den Todesfall.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949380-thumbsmall-umdf.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Tierarzt-Ikone: Evan Antin, der "heißeste Veterinär der Welt" </title>
|
||||
<link>http://www.spiegel.de/panorama/leute/tierarzt-evan-antin-ist-der-sexiest-veterinaer-ever-laut-people-a-1074229.html#ref=rss</link>
|
||||
<description>Evan Antin ist Tierarzt, Model und Personal Trainer. So irritierend gutaussehend, dass er zum "Sexiest Tierbetörer" avancierte. Trotz seiner Vorliebe für blutiges Gedärm und anatomische Anomalien.</description>
|
||||
<category>Panorama</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:31:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/panorama/leute/tierarzt-evan-antin-ist-der-sexiest-veterinaer-ever-laut-people-a-1074229.html</guid>
|
||||
<content:encoded><![CDATA[Evan Antin ist Tierarzt, Model und Personal Trainer. So irritierend gutaussehend, dass er zum "Sexiest Tierbetörer" avancierte. Trotz seiner Vorliebe für blutiges Gedärm und anatomische Anomalien.]]></content:encoded>
|
||||
</item>
|
||||
<item>
|
||||
<title>Verteidigungshaushalt: Schäuble will mehr Geld für Bundeswehr ausgeben</title>
|
||||
<link>http://www.spiegel.de/politik/deutschland/wolfgang-schaeuble-einverstanden-mit-mehr-geld-fuer-ruestung-a-1074242.html#ref=rss</link>
|
||||
<description>Drei Milliarden Euro mehr pro Jahr für Waffen: Finanzminister Schäuble sieht die Aufrüstungspläne der Verteidigungsministerin von der Leyen positiv.</description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:29:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/deutschland/wolfgang-schaeuble-einverstanden-mit-mehr-geld-fuer-ruestung-a-1074242.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-947025-thumbsmall-qecb.jpg" hspace="5" align="left" >Drei Milliarden Euro mehr pro Jahr für Waffen: Finanzminister Schäuble sieht die Aufrüstungspläne der Verteidigungsministerin von der Leyen positiv.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-947025-thumbsmall-qecb.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Doping-Vorwürfe: NFL leitet Untersuchung gegen Manning ein</title>
|
||||
<link>http://www.spiegel.de/sport/sonst/nfl-peyton-manning-unter-doping-verdacht-a-1074230.html#ref=rss</link>
|
||||
<description>Die NFL prüft Vorwürfe gegen einen Superstar: Die Football-Liga geht jetzt offiziell den Doping-Gerüchten um Peyton Manning nach. Der Quarterback soll über seine Frau Hormone geordert haben.</description>
|
||||
<category>Sport</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:23:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/sport/sonst/nfl-peyton-manning-unter-doping-verdacht-a-1074230.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949287-thumbsmall-cwxj.jpg" hspace="5" align="left" >Die NFL prüft Vorwürfe gegen einen Superstar: Die Football-Liga geht jetzt offiziell den Doping-Gerüchten um Peyton Manning nach. Der Quarterback soll über seine Frau Hormone geordert haben.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949287-thumbsmall-cwxj.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Posse um SWR-Elefantenrunde: Feigheit vor dem Feind</title>
|
||||
<link>http://www.spiegel.de/politik/deutschland/spd-und-swr-posse-um-tv-auftritt-in-rheinland-pfalz-kommentar-a-1074235.html#ref=rss</link>
|
||||
<description>Die Sozialdemokraten weigern sich, an einer TV-Debatte mit der AfD teilzunehmen, jetzt geht das Spektakel in eine neue Runde. Statt der Ministerpräsidentin soll der SPD-Landeschef ran. Wie absurd ist das denn? </description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:23:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/deutschland/spd-und-swr-posse-um-tv-auftritt-in-rheinland-pfalz-kommentar-a-1074235.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949309-thumbsmall-zhac.jpg" hspace="5" align="left" >Die Sozialdemokraten weigern sich, an einer TV-Debatte mit der AfD teilzunehmen, jetzt geht das Spektakel in eine neue Runde. Statt der Ministerpräsidentin soll der SPD-Landeschef ran. Wie absurd ist das denn? ]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949309-thumbsmall-zhac.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Apple-Browser: Und plötzlich stürzt Safari ab</title>
|
||||
<link>http://www.spiegel.de/netzwelt/apps/apple-safari-browser-stuerzt-ploetzlich-ab-a-1074217.html#ref=rss</link>
|
||||
<description>Seit einigen Stunden haben Apple-Nutzer Probleme mit dem Safari-Browser. Wodurch die Abstürze ausgelöst werden, ist unklar. Doch mit einem kleinen Trick kann man sich helfen.</description>
|
||||
<category>Netzwelt</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:18:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/netzwelt/apps/apple-safari-browser-stuerzt-ploetzlich-ab-a-1074217.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949312-thumbsmall-jqlt.jpg" hspace="5" align="left" >Seit einigen Stunden haben Apple-Nutzer Probleme mit dem Safari-Browser. Wodurch die Abstürze ausgelöst werden, ist unklar. Doch mit einem kleinen Trick kann man sich helfen.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949312-thumbsmall-jqlt.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>EU-Bericht zu Grenzsicherung: Griechenland droht Schengen-Rauswurf</title>
|
||||
<link>http://www.spiegel.de/politik/ausland/griechenland-eu-kommission-droht-mit-schengen-rauswurf-a-1074201.html#ref=rss</link>
|
||||
<description>Griechenland gerät in der Flüchtlingskrise unter massiven Druck der EU: Die Kommission will Athen Forderungen zum Grenzschutz schicken. Werden die nicht binnen drei Monaten erfüllt, droht der Ausschluss aus dem Schengen-Raum.</description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:04:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/ausland/griechenland-eu-kommission-droht-mit-schengen-rauswurf-a-1074201.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949347-thumbsmall-vbmi.jpg" hspace="5" align="left" >Griechenland gerät in der Flüchtlingskrise unter massiven Druck der EU: Die Kommission will Athen Forderungen zum Grenzschutz schicken. Werden die nicht binnen drei Monaten erfüllt, droht der Ausschluss aus dem Schengen-Raum.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949347-thumbsmall-vbmi.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Protest gegen dänisches Asylrecht: Ai Weiwei schließt Ausstellung in Kopenhagen</title>
|
||||
<link>http://www.spiegel.de/kultur/gesellschaft/protest-gegen-daenisches-asylrecht-ai-weiwei-schliesst-ausstellung-a-1074241.html#ref=rss</link>
|
||||
<description>Ai Weiwei, Chinas wohl bekanntester Künstler, beendet seine Ausstellung in Kopenhagen vorzeitig. Grund ist eine Verschärfung der Asylregeln in Dänemark.</description>
|
||||
<category>Kultur</category>
|
||||
<pubDate>Wed, 27 Jan 2016 15:01:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/kultur/gesellschaft/protest-gegen-daenisches-asylrecht-ai-weiwei-schliesst-ausstellung-a-1074241.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949292-thumbsmall-uhox.jpg" hspace="5" align="left" >Ai Weiwei, Chinas wohl bekanntester Künstler, beendet seine Ausstellung in Kopenhagen vorzeitig. Grund ist eine Verschärfung der Asylregeln in Dänemark.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949292-thumbsmall-uhox.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Deutsche Handballer gegen Dänemark: Jetzt muss es schmutzig werden</title>
|
||||
<link>http://www.spiegel.de/sport/sonst/handball-em-2016-jetzt-muss-es-schmutzig-werden-a-1074123.html#ref=rss</link>
|
||||
<description>Noch ein Sieg bis zum Halbfinale: Die deutsche Handball-Nationalmannschaft hat bei der EM einen Lauf, sie ist gierig auf den Titel. Jetzt geht es gegen einen großen Favoriten - und Dänemark wirkt angeschlagen. </description>
|
||||
<category>Sport</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:59:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/sport/sonst/handball-em-2016-jetzt-muss-es-schmutzig-werden-a-1074123.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949116-thumbsmall-fpcj.jpg" hspace="5" align="left" >Noch ein Sieg bis zum Halbfinale: Die deutsche Handball-Nationalmannschaft hat bei der EM einen Lauf, sie ist gierig auf den Titel. Jetzt geht es gegen einen großen Favoriten - und Dänemark wirkt angeschlagen. ]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949116-thumbsmall-fpcj.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Sicherheitslage nach Anschlag: Russland spricht Reisewarnung für Türkei aus</title>
|
||||
<link>http://www.spiegel.de/reise/aktuell/russland-spricht-reisewarnung-fuer-tuerkei-aus-a-1074225.html#ref=rss</link>
|
||||
<description>Die russische Regierung warnt ihre Bürger vor Reisen in die Türkei. Damit schränkt sie den Tourismus in das Land weiter ein. Nach dem Anschlag in Istanbul sind auch die Buchungen von deutschen Urlaubern eingebrochen.</description>
|
||||
<category>Reise</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:58:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/reise/aktuell/russland-spricht-reisewarnung-fuer-tuerkei-aus-a-1074225.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-378624-thumbsmall-xtnj.jpg" hspace="5" align="left" >Die russische Regierung warnt ihre Bürger vor Reisen in die Türkei. Damit schränkt sie den Tourismus in das Land weiter ein. Nach dem Anschlag in Istanbul sind auch die Buchungen von deutschen Urlaubern eingebrochen.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-378624-thumbsmall-xtnj.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Bayern-Deal mit Katar: Scheich Di!</title>
|
||||
<link>http://www.spiegel.de/sport/fussball/fc-bayern-muenchen-und-der-katar-deal-in-der-pflicht-kommentar-a-1074187.html#ref=rss</link>
|
||||
<description>Der FC Bayern schließt einen Sponsoren-Deal mit Katar ab, die Kritik darüber ist ebenso berechtigt wie vorhersehbar. Dabei darf es nicht bleiben. Nehmen wir den Rekordmeister doch beim Wort.</description>
|
||||
<category>Sport</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:52:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/sport/fussball/fc-bayern-muenchen-und-der-katar-deal-in-der-pflicht-kommentar-a-1074187.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949236-thumbsmall-hyyw.jpg" hspace="5" align="left" >Der FC Bayern schließt einen Sponsoren-Deal mit Katar ab, die Kritik darüber ist ebenso berechtigt wie vorhersehbar. Dabei darf es nicht bleiben. Nehmen wir den Rekordmeister doch beim Wort.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949236-thumbsmall-hyyw.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Flüchtlingskrise: EU wirft Griechenland schwere Mängel bei Grenzkontrolle vor</title>
|
||||
<link>http://www.spiegel.de/politik/ausland/fluechtlinge-eu-wirft-griechenland-maengel-bei-grenzkontrolle-vor-a-1074219.html#ref=rss</link>
|
||||
<description>Die griechische Regierung verletzt ihre Pflichten bei der Grenzsicherung - zu diesem Schluss kommt ein Untersuchungsbericht der EU-Kommission. Brüssel spricht nun eine klare Drohung gegen Athen aus.</description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:47:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/ausland/fluechtlinge-eu-wirft-griechenland-maengel-bei-grenzkontrolle-vor-a-1074219.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949273-thumbsmall-ifxg.jpg" hspace="5" align="left" >Die griechische Regierung verletzt ihre Pflichten bei der Grenzsicherung - zu diesem Schluss kommt ein Untersuchungsbericht der EU-Kommission. Brüssel spricht nun eine klare Drohung gegen Athen aus.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949273-thumbsmall-ifxg.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Abgasaffäre: Brüssel will Autokonzerne bestrafen können</title>
|
||||
<link>http://www.spiegel.de/wirtschaft/soziales/abgasaffaere-bruessel-will-autokonzerne-bestrafen-koennen-a-1074228.html#ref=rss</link>
|
||||
<description>Im Abgas-Skandal ist der VW-Konzern Schuldiger - aber auch die Aufseher in Brüssel sind blamiert. Denn US-Behörden enthüllten, was ihnen jahrelang durch die Lappen ging. Die EU-Kommission will jetzt dafür sorgen, dass sich das nicht wiederholt.</description>
|
||||
<category>Wirtschaft</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:46:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/wirtschaft/soziales/abgasaffaere-bruessel-will-autokonzerne-bestrafen-koennen-a-1074228.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-938136-thumbsmall-hxbc.jpg" hspace="5" align="left" >Im Abgas-Skandal ist der VW-Konzern Schuldiger - aber auch die Aufseher in Brüssel sind blamiert. Denn US-Behörden enthüllten, was ihnen jahrelang durch die Lappen ging. Die EU-Kommission will jetzt dafür sorgen, dass sich das nicht wiederholt.]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-938136-thumbsmall-hxbc.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Bundesverfassungsgericht: Wer alles gegen die Vorratsdatenspeicherung klagt</title>
|
||||
<link>http://www.spiegel.de/netzwelt/netzpolitik/vorratsdatenspeicherung-wer-klagt-vor-dem-bundesverfassungsgericht-a-1074152.html#ref=rss</link>
|
||||
<description>Widerstand gegen Vorratsdatenspeicherung: Die FDP hat Klage in Karlsruhe eingereicht, und auch weitere Gegner versuchen, das Gesetz vor Gericht noch zu kippen. Der Überblick. </description>
|
||||
<category>Netzwelt</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:42:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/netzwelt/netzpolitik/vorratsdatenspeicherung-wer-klagt-vor-dem-bundesverfassungsgericht-a-1074152.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-925932-thumbsmall-zbbn.jpg" hspace="5" align="left" >Widerstand gegen Vorratsdatenspeicherung: Die FDP hat Klage in Karlsruhe eingereicht, und auch weitere Gegner versuchen, das Gesetz vor Gericht noch zu kippen. Der Überblick. ]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-925932-thumbsmall-zbbn.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Berliner Flüchtlingsamt: Das Scheitern des Lageso - das Protokoll</title>
|
||||
<link>http://www.spiegel.de/politik/deutschland/berlin-das-scheitern-des-lageso-eine-chronik-a-1074186.html#ref=rss</link>
|
||||
<description>Tagelanges Warten, keine Auszahlung von Essensgeld, Gewalt - jetzt angeblich ein Toter: Seit Monaten steht das Berliner Flüchtlingsamt Lageso in den Schlagzeilen. Die Chronologie des Scheiterns. </description>
|
||||
<category>Politik</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:22:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/politik/deutschland/berlin-das-scheitern-des-lageso-eine-chronik-a-1074186.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949137-thumbsmall-pgoa.jpg" hspace="5" align="left" >Tagelanges Warten, keine Auszahlung von Essensgeld, Gewalt - jetzt angeblich ein Toter: Seit Monaten steht das Berliner Flüchtlingsamt Lageso in den Schlagzeilen. Die Chronologie des Scheiterns. ]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949137-thumbsmall-pgoa.jpg"/>
|
||||
</item>
|
||||
<item>
|
||||
<title>Apple-Gerüchte: So könnte das neue iPhone aussehen. Aber was wünschen Sie sich?</title>
|
||||
<link>http://www.spiegel.de/netzwelt/gadgets/apple-so-gut-muss-das-iphone-7-sein-a-1074158.html#ref=rss</link>
|
||||
<description>Ist der iPhone-Boom vorbei? Nicht unbedingt - das nächste Gerät könnte starke neue Features haben. Hier der Stand der Gerüchte. Und Sie können abstimmen: Was brauchen Sie wirklich?</description>
|
||||
<category>Netzwelt</category>
|
||||
<pubDate>Wed, 27 Jan 2016 14:17:00 +0100</pubDate>
|
||||
<guid>http://www.spiegel.de/netzwelt/gadgets/apple-so-gut-muss-das-iphone-7-sein-a-1074158.html</guid>
|
||||
<content:encoded><![CDATA[<img src="http://www.spiegel.de/images/image-949140-thumbsmall-bdof.jpg" hspace="5" align="left" >Ist der iPhone-Boom vorbei? Nicht unbedingt - das nächste Gerät könnte starke neue Features haben. Hier der Stand der Gerüchte. Und Sie können abstimmen: Was brauchen Sie wirklich?]]></content:encoded>
|
||||
<enclosure type="image/jpeg" url="http://www.spiegel.de/images/image-949140-thumbsmall-bdof.jpg"/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
https://en.wikipedia.org/wiki/RSS#Example
|
||||
-->
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>RSS Title</title>
|
||||
<description>This is an example of an RSS feed</description>
|
||||
<link>http://www.example.com/main.html</link>
|
||||
<lastBuildDate>Mon, 06 Sep 2010 00:01:00 +0000 </lastBuildDate>
|
||||
<pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
|
||||
<ttl>1800</ttl>
|
||||
<item>
|
||||
<title>Example entry</title>
|
||||
<description>Here is some text containing an interesting description.</description>
|
||||
<link>http://www.example.com/blog/post/1</link>
|
||||
<guid isPermaLink="true">7bd204c6-1655-4c27-aeee-53f933c5395f</guid>
|
||||
<pubDate>Sun, 06 Sep 2009 16:20:00 +0000</pubDate>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
|
||||
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
|
||||
xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
|
||||
>
|
||||
|
||||
<channel>
|
||||
<title>justasimpletest2016</title>
|
||||
<atom:link href="https://justasimpletest2016.wordpress.com/feed/" rel="self" type="application/rss+xml" />
|
||||
<link>https://justasimpletest2016.wordpress.com</link>
|
||||
<description></description>
|
||||
<lastBuildDate>Fri, 26 Feb 2016 22:08:00 +0000</lastBuildDate>
|
||||
<language>en</language>
|
||||
<sy:updatePeriod>hourly</sy:updatePeriod>
|
||||
<sy:updateFrequency>1</sy:updateFrequency>
|
||||
<generator>http://wordpress.com/</generator>
|
||||
<cloud domain='justasimpletest2016.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
|
||||
<image>
|
||||
<url>https://s2.wp.com/i/buttonw-com.png</url>
|
||||
<title>justasimpletest2016</title>
|
||||
<link>https://justasimpletest2016.wordpress.com</link>
|
||||
</image>
|
||||
<atom:link rel="search" type="application/opensearchdescription+xml" href="https://justasimpletest2016.wordpress.com/osd.xml" title="justasimpletest2016" />
|
||||
<atom:link rel='hub' href='https://justasimpletest2016.wordpress.com/?pushpress=hub'/>
|
||||
<item>
|
||||
<title>Hello World!</title>
|
||||
<link>https://justasimpletest2016.wordpress.com/2016/02/26/hello-world/</link>
|
||||
<comments>https://justasimpletest2016.wordpress.com/2016/02/26/hello-world/#respond</comments>
|
||||
<pubDate>Fri, 26 Feb 2016 22:07:46 +0000</pubDate>
|
||||
<dc:creator><![CDATA[justasimpletest2016]]></dc:creator>
|
||||
<category><![CDATA[Uncategorized]]></category>
|
||||
|
||||
<guid isPermaLink="false">http://justasimpletest2016.wordpress.com/?p=6</guid>
|
||||
<description><![CDATA[What’s up?<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=justasimpletest2016.wordpress.com&blog=107552275&post=6&subd=justasimpletest2016&ref=&feed=1" width="1" height="1" />]]></description>
|
||||
<content:encoded><![CDATA[<p>What’s up?</p><br /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/justasimpletest2016.wordpress.com/6/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/justasimpletest2016.wordpress.com/6/" /></a> <img alt="" border="0" src="https://pixel.wp.com/b.gif?host=justasimpletest2016.wordpress.com&blog=107552275&post=6&subd=justasimpletest2016&ref=&feed=1" width="1" height="1" />]]></content:encoded>
|
||||
<wfw:commentRss>https://justasimpletest2016.wordpress.com/2016/02/26/hello-world/feed/</wfw:commentRss>
|
||||
<slash:comments>0</slash:comments>
|
||||
|
||||
<media:content url="https://2.gravatar.com/avatar/ba82024e6f43884f4ae0e379273b0743?s=96&d=identicon&r=G" medium="image">
|
||||
<media:title type="html">justasimpletest2016</media:title>
|
||||
</media:content>
|
||||
</item>
|
||||
<item>
|
||||
<title>The second post</title>
|
||||
<link>https://justasimpletest2016.wordpress.com/2016/02/26/the-second-post/</link>
|
||||
<comments>https://justasimpletest2016.wordpress.com/2016/02/26/the-second-post/#respond</comments>
|
||||
<pubDate>Fri, 26 Feb 2016 00:23:04 +0000</pubDate>
|
||||
<dc:creator><![CDATA[justasimpletest2016]]></dc:creator>
|
||||
<category><![CDATA[Uncategorized]]></category>
|
||||
|
||||
<guid isPermaLink="false">http://justasimpletest2016.wordpress.com/2016/02/26/the-second-post/</guid>
|
||||
<description><![CDATA[Hello.<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=justasimpletest2016.wordpress.com&blog=107552275&post=4&subd=justasimpletest2016&ref=&feed=1" width="1" height="1" />]]></description>
|
||||
<content:encoded><![CDATA[<p>Hello.</p><br /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/justasimpletest2016.wordpress.com/4/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/justasimpletest2016.wordpress.com/4/" /></a> <img alt="" border="0" src="https://pixel.wp.com/b.gif?host=justasimpletest2016.wordpress.com&blog=107552275&post=4&subd=justasimpletest2016&ref=&feed=1" width="1" height="1" />]]></content:encoded>
|
||||
<wfw:commentRss>https://justasimpletest2016.wordpress.com/2016/02/26/the-second-post/feed/</wfw:commentRss>
|
||||
<slash:comments>0</slash:comments>
|
||||
|
||||
<media:content url="https://2.gravatar.com/avatar/ba82024e6f43884f4ae0e379273b0743?s=96&d=identicon&r=G" medium="image">
|
||||
<media:title type="html">justasimpletest2016</media:title>
|
||||
</media:content>
|
||||
</item>
|
||||
<item>
|
||||
<title>This is just a test</title>
|
||||
<link>https://justasimpletest2016.wordpress.com/2016/02/26/this-is-just-a-test/</link>
|
||||
<comments>https://justasimpletest2016.wordpress.com/2016/02/26/this-is-just-a-test/#respond</comments>
|
||||
<pubDate>Fri, 26 Feb 2016 00:22:58 +0000</pubDate>
|
||||
<dc:creator><![CDATA[justasimpletest2016]]></dc:creator>
|
||||
<category><![CDATA[Uncategorized]]></category>
|
||||
|
||||
<guid isPermaLink="false">http://justasimpletest2016.wordpress.com/?p=2</guid>
|
||||
<description><![CDATA[Hello World. First blog post from WordPress.<img alt="" border="0" src="https://pixel.wp.com/b.gif?host=justasimpletest2016.wordpress.com&blog=107552275&post=2&subd=justasimpletest2016&ref=&feed=1" width="1" height="1" />]]></description>
|
||||
<content:encoded><![CDATA[<p>Hello World. First blog post from WordPress.</p><br /> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/justasimpletest2016.wordpress.com/2/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/justasimpletest2016.wordpress.com/2/" /></a> <img alt="" border="0" src="https://pixel.wp.com/b.gif?host=justasimpletest2016.wordpress.com&blog=107552275&post=2&subd=justasimpletest2016&ref=&feed=1" width="1" height="1" />]]></content:encoded>
|
||||
<wfw:commentRss>https://justasimpletest2016.wordpress.com/2016/02/26/this-is-just-a-test/feed/</wfw:commentRss>
|
||||
<slash:comments>0</slash:comments>
|
||||
|
||||
<media:content url="https://2.gravatar.com/avatar/ba82024e6f43884f4ae0e379273b0743?s=96&d=identicon&r=G" medium="image">
|
||||
<media:title type="html">justasimpletest2016</media:title>
|
||||
</media:content>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
sdk=21
|
||||
constants=org.mozilla.gecko.BuildConfig
|
||||
packageName=org.mozilla.gecko
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
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"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/* 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/* 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());
|
||||
}
|
||||
}
|
||||
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