зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 3 changesets (bug 1363924) for android-test failures. CLOSED TREE
Backed out changeset 08747734c0fd (bug 1363924) Backed out changeset fbba0f584237 (bug 1363924) Backed out changeset d30f27293117 (bug 1363924)
This commit is contained in:
Родитель
7c03ced862
Коммит
8b633f366a
|
@ -1,76 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.telemetry.pingbuilders;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryContract;
|
||||
|
||||
@RunWith(TestRunner.class)
|
||||
public class TelemetrySyncEventPingBuilderTest {
|
||||
|
||||
@Test
|
||||
public void testGeneralShape() throws Exception {
|
||||
JSONArray payload = buildPayloadArray(123456L, "sync", "object", "method", null, null);
|
||||
Assert.assertArrayEquals(new Object[] {123456L, "sync", "method", "object"}, payload.toArray());
|
||||
|
||||
payload = buildPayloadArray(123456L, "sync", "object", "method", "value", null);
|
||||
Assert.assertArrayEquals(new Object[] {123456L, "sync", "method", "object", "value"}, payload.toArray());
|
||||
|
||||
Bundle extra = new Bundle();
|
||||
extra.putString("extra-key", "extra-value");
|
||||
|
||||
payload = buildPayloadArray(123456L, "sync", "object", "method", null, extra);
|
||||
Assert.assertEquals("[123456,\"sync\",\"method\",\"object\",null,{\"extra\":\"extra\"}]",
|
||||
payload.toJSONString());
|
||||
|
||||
payload = buildPayloadArray(123456L, "sync", "object", "method", "value", extra);
|
||||
Assert.assertEquals("[123456,\"sync\",\"method\",\"object\",\"value\",{\"extra\":\"extra\"}]",
|
||||
payload.toJSONString());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testNullTimestamp() throws Exception {
|
||||
buildPayloadArray(null, "category", "object", "method", null, null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testNullCategory() throws Exception {
|
||||
buildPayloadArray(123456L, null, "object", "method", null, null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testNullObject() throws Exception {
|
||||
buildPayloadArray(123456L, "category", null, "method", null, null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testNullMethod() throws Exception {
|
||||
buildPayloadArray(123456L, "category", "object", null, null, null);
|
||||
}
|
||||
|
||||
private JSONArray buildPayloadArray(Long ts, String category, String object, String method,
|
||||
String value, Bundle extra) throws Exception {
|
||||
Bundle bundle = new Bundle();
|
||||
if (ts != null) {
|
||||
bundle.putLong(TelemetryContract.KEY_EVENT_TIMESTAMP, ts);
|
||||
}
|
||||
bundle.putString(TelemetryContract.KEY_EVENT_CATEGORY, category);
|
||||
bundle.putString(TelemetryContract.KEY_EVENT_OBJECT, object);
|
||||
bundle.putString(TelemetryContract.KEY_EVENT_METHOD, method);
|
||||
if (value != null) {
|
||||
bundle.putString(TelemetryContract.KEY_EVENT_VALUE, value);
|
||||
}
|
||||
if (extra != null) {
|
||||
bundle.putBundle(TelemetryContract.KEY_EVENT_EXTRA, extra);
|
||||
}
|
||||
return new TelemetrySyncEventPingBuilder().fromEventTelemetry(bundle)
|
||||
.build().getPayload().getArray("event");
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@ import org.mozilla.gecko.background.testhelpers.TestRunner;
|
|||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.synchronizer.StoreBatchTracker;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryStageCollector;
|
||||
import org.mozilla.gecko.sync.validation.BookmarkValidationResults;
|
||||
import org.mozilla.gecko.sync.validation.ValidationResults;
|
||||
import org.mozilla.gecko.telemetry.TelemetryLocalPing;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -37,19 +39,27 @@ public class TelemetrySyncPingBuilderTest {
|
|||
@Test
|
||||
public void testGeneralShape() throws Exception {
|
||||
TelemetryLocalPing localPing = builder
|
||||
.setDeviceID("device-id")
|
||||
.setUID("uid")
|
||||
.setTook(123L)
|
||||
.setRestarted(false)
|
||||
.build();
|
||||
ExtendedJSONObject payload = localPing.getPayload();
|
||||
assertEquals("uid", payload.getString("uid"));
|
||||
assertEquals(Long.valueOf(123L), payload.getLong("took"));
|
||||
assertEquals("device-id", payload.getString("deviceID"));
|
||||
assertFalse(payload.containsKey("restarted"));
|
||||
|
||||
localPing = builder
|
||||
.setDeviceID("device-id")
|
||||
.setUID("uid")
|
||||
.setTook(123L)
|
||||
.setRestarted(true)
|
||||
.build();
|
||||
payload = localPing.getPayload();
|
||||
assertEquals("uid", payload.getString("uid"));
|
||||
assertEquals(Long.valueOf(123L), payload.getLong("took"));
|
||||
assertEquals("device-id", payload.getString("deviceID"));
|
||||
assertTrue(payload.getLong("when") != null);
|
||||
assertEquals(true, payload.getBoolean("restarted"));
|
||||
|
||||
|
|
|
@ -3,19 +3,16 @@
|
|||
|
||||
package org.mozilla.gecko.telemetry.pingbuilders;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryContract;
|
||||
import org.mozilla.gecko.telemetry.TelemetryOutgoingPing;
|
||||
import org.mozilla.gecko.telemetry.TelemetryPing;
|
||||
import org.mozilla.gecko.telemetry.stores.TelemetryJSONFilePingStore;
|
||||
|
@ -108,8 +105,6 @@ public class TelemetrySyncPingBundleBuilderTest {
|
|||
public void testGeneralShape() throws Exception {
|
||||
builder.setSyncStore(syncPings);
|
||||
builder.setSyncEventStore(eventPings);
|
||||
builder.setDeviceID("device-id-1");
|
||||
builder.setUID("uid-1");
|
||||
|
||||
TelemetryOutgoingPing outgoingPing = builder.build();
|
||||
|
||||
|
@ -132,15 +127,15 @@ public class TelemetrySyncPingBundleBuilderTest {
|
|||
assertTrue(application.containsKey("xpcomAbi"));
|
||||
|
||||
|
||||
// Test general shape of payload. Expecting {"why":"schedule", "version": 1,
|
||||
// "os": {"name": "Android", "version": "<version>", "locale": "<locale>"},
|
||||
// "deviceID": <Hashed Device ID>, "uid": <Hashed UID>}.
|
||||
// Test general shape of payload. Expecting {"syncs":[],"why":"schedule", "version": 1,
|
||||
// "os": {"name": "Android", "version": "<version>", "locale": "<locale>"}}.
|
||||
// NB that even though we set an empty sync event store, it's not in the json string.
|
||||
// That's because sync events are not yet instrumented.
|
||||
ExtendedJSONObject payload = outgoingPing.getPayload().getObject("payload");
|
||||
assertEquals(5, payload.keySet().size());
|
||||
assertEquals(4, payload.keySet().size());
|
||||
assertEquals("schedule", payload.getString("why"));
|
||||
assertEquals(Integer.valueOf(1), payload.getIntegerSafely("version"));
|
||||
assertEquals(payload.getString("uid"), "uid-1");
|
||||
assertEquals(payload.getString("deviceID"), "device-id-1");
|
||||
assertEquals(0, payload.getArray("syncs").size());
|
||||
// Test os key.
|
||||
ExtendedJSONObject os = payload.getObject("os");
|
||||
assertEquals(3, os.keySet().size());
|
||||
|
@ -156,8 +151,10 @@ public class TelemetrySyncPingBundleBuilderTest {
|
|||
public void testBundlingOfMultiplePings() throws Exception {
|
||||
// Try just one ping first.
|
||||
syncPings.storePing(new TelemetrySyncPingBuilder()
|
||||
.setDeviceID("test-device-id")
|
||||
.setRestarted(true)
|
||||
.setTook(123L)
|
||||
.setUID("test-uid")
|
||||
.build()
|
||||
);
|
||||
builder.setSyncStore(syncPings);
|
||||
|
@ -169,12 +166,14 @@ public class TelemetrySyncPingBundleBuilderTest {
|
|||
assertEquals("schedule", payload.getString("why"));
|
||||
JSONArray syncs = payload.getArray("syncs");
|
||||
assertEquals(1, syncs.size());
|
||||
assertSync((ExtendedJSONObject) syncs.get(0), 123L, true);
|
||||
assertSync((ExtendedJSONObject) syncs.get(0), "test-uid", 123L, "test-device-id", true);
|
||||
|
||||
// Add another ping.
|
||||
syncPings.storePing(new TelemetrySyncPingBuilder()
|
||||
.setDeviceID("test-device-id")
|
||||
.setRestarted(false)
|
||||
.setTook(321L)
|
||||
.setUID("test-uid")
|
||||
.build()
|
||||
);
|
||||
builder.setSyncStore(syncPings);
|
||||
|
@ -185,37 +184,14 @@ public class TelemetrySyncPingBundleBuilderTest {
|
|||
.getObject("payload")
|
||||
.getArray("syncs");
|
||||
assertEquals(2, syncs.size());
|
||||
assertSync((ExtendedJSONObject) syncs.get(0), 123L, true);
|
||||
assertSync((ExtendedJSONObject) syncs.get(1), 321L, false);
|
||||
|
||||
// And add an event ping!
|
||||
Bundle event = new Bundle();
|
||||
event.putLong(TelemetryContract.KEY_EVENT_TIMESTAMP, 123456L);
|
||||
event.putString(TelemetryContract.KEY_EVENT_CATEGORY, "sync");
|
||||
event.putString(TelemetryContract.KEY_EVENT_OBJECT, "object");
|
||||
event.putString(TelemetryContract.KEY_EVENT_METHOD, "method");
|
||||
event.putString(TelemetryContract.KEY_EVENT_VALUE, "value");
|
||||
Bundle extra = new Bundle();
|
||||
extra.putString("extra-key", "extra-value");
|
||||
event.putBundle(TelemetryContract.KEY_EVENT_EXTRA, extra);
|
||||
eventPings.storePing(new TelemetrySyncEventPingBuilder()
|
||||
.fromEventTelemetry(event)
|
||||
.build()
|
||||
);
|
||||
builder.setSyncEventStore(eventPings);
|
||||
|
||||
// We should have three pings now.
|
||||
outgoingPing = builder.build();
|
||||
JSONArray events = outgoingPing.getPayload()
|
||||
.getObject("payload")
|
||||
.getArray("events");
|
||||
assertEquals(1, events.size());
|
||||
Assert.assertEquals("[[123456,\"sync\",\"method\",\"object\",\"value\",{\"extra\":\"extra\"}]]",
|
||||
events.toJSONString());
|
||||
assertSync((ExtendedJSONObject) syncs.get(0), "test-uid", 123L, "test-device-id", true);
|
||||
assertSync((ExtendedJSONObject) syncs.get(1), "test-uid", 321L, "test-device-id", false);
|
||||
}
|
||||
|
||||
private void assertSync(ExtendedJSONObject sync, long took, boolean restarted) throws JSONException {
|
||||
private void assertSync(ExtendedJSONObject sync, String uid, long took, String deviceID, boolean restarted) throws JSONException {
|
||||
assertEquals(uid, sync.getString("uid"));
|
||||
assertEquals(Long.valueOf(took), sync.getLong("took"));
|
||||
assertEquals(deviceID, sync.getString("deviceID"));
|
||||
|
||||
// Test that 'when' timestamp looks generally sane.
|
||||
final long now = System.currentTimeMillis();
|
||||
|
|
|
@ -1087,7 +1087,6 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
|
|||
'sync/SynchronizerConfiguration.java',
|
||||
'sync/telemetry/TelemetryCollector.java',
|
||||
'sync/telemetry/TelemetryContract.java',
|
||||
'sync/telemetry/TelemetryEventCollector.java',
|
||||
'sync/telemetry/TelemetryStageCollector.java',
|
||||
'sync/ThreadPool.java',
|
||||
'sync/UnexpectedJSONException.java',
|
||||
|
|
|
@ -151,6 +151,14 @@ public class TelemetryBackgroundReceiver extends BroadcastReceiver {
|
|||
telemetryStore = syncTelemetryStore;
|
||||
TelemetrySyncPingBuilder localPingBuilder = new TelemetrySyncPingBuilder();
|
||||
|
||||
if (uid != null) {
|
||||
localPingBuilder.setUID(uid);
|
||||
}
|
||||
|
||||
if (deviceID != null) {
|
||||
localPingBuilder.setDeviceID(deviceID);
|
||||
}
|
||||
|
||||
if (devices != null) {
|
||||
localPingBuilder.setDevices(devices);
|
||||
}
|
||||
|
@ -171,7 +179,9 @@ public class TelemetryBackgroundReceiver extends BroadcastReceiver {
|
|||
case TelemetryContract.KEY_TYPE_EVENT:
|
||||
telemetryStore = syncEventTelemetryStore;
|
||||
localPing = new TelemetrySyncEventPingBuilder()
|
||||
.fromEventTelemetry(telemetryBundle)
|
||||
.fromEventTelemetry(
|
||||
(Bundle) intent.getParcelableExtra(
|
||||
TelemetryContract.KEY_TELEMETRY))
|
||||
.build();
|
||||
break;
|
||||
default:
|
||||
|
@ -223,8 +233,6 @@ public class TelemetryBackgroundReceiver extends BroadcastReceiver {
|
|||
|
||||
// Bundle up all that we have in our telemetry stores.
|
||||
final TelemetryOutgoingPing syncPing = new TelemetrySyncPingBundleBuilder()
|
||||
.setUID(uid)
|
||||
.setDeviceID(deviceID)
|
||||
.setSyncStore(syncTelemetryStore)
|
||||
.setSyncEventStore(syncEventTelemetryStore)
|
||||
.setReason(reasonToUpload)
|
||||
|
|
|
@ -6,53 +6,19 @@ package org.mozilla.gecko.telemetry.pingbuilders;
|
|||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryContract;
|
||||
import org.mozilla.gecko.telemetry.TelemetryLocalPing;
|
||||
|
||||
/**
|
||||
* Local ping builder which understands how to process event data.
|
||||
* This is a placeholder, to be implemented in Bug 1363924.
|
||||
*/
|
||||
public class TelemetrySyncEventPingBuilder extends TelemetryLocalPingBuilder {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public TelemetrySyncEventPingBuilder fromEventTelemetry(Bundle data) {
|
||||
final long timestamp = data.getLong(TelemetryContract.KEY_EVENT_TIMESTAMP, -1L);
|
||||
final String category = data.getString(TelemetryContract.KEY_EVENT_CATEGORY);
|
||||
final String object = data.getString(TelemetryContract.KEY_EVENT_OBJECT);
|
||||
final String method = data.getString(TelemetryContract.KEY_EVENT_METHOD);
|
||||
final String value = data.getString(TelemetryContract.KEY_EVENT_VALUE);
|
||||
final Bundle extra = data.getBundle(TelemetryContract.KEY_EVENT_EXTRA);
|
||||
|
||||
if (timestamp == -1L || category == null || object == null || method == null) {
|
||||
throw new IllegalStateException("Bundle should be well formed.");
|
||||
}
|
||||
|
||||
final JSONArray event = new JSONArray();
|
||||
// Events are serialized as arrays when sending the sync ping. The order of the following
|
||||
// statements SHOULD NOT be changed unless the telemetry server specification changes.
|
||||
event.add(timestamp);
|
||||
event.add(category);
|
||||
event.add(method);
|
||||
event.add(object);
|
||||
if (value != null || extra != null) {
|
||||
event.add(value);
|
||||
if (extra != null) {
|
||||
final ExtendedJSONObject extraJSON = new ExtendedJSONObject();
|
||||
for (final String k : extra.keySet()) {
|
||||
extraJSON.put(k, extra.getString(k));
|
||||
}
|
||||
event.add(extraJSON);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Note: {@link org.mozilla.gecko.telemetry.TelemetryOutgoingPing#getPayload()}
|
||||
* returns ExtendedJSONObject. Wrap our JSONArray into the payload JSON object.
|
||||
*/
|
||||
payload.put("event", event);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelemetryLocalPing build() {
|
||||
return new TelemetryLocalPing(payload, docID);
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,6 +89,16 @@ public class TelemetrySyncPingBuilder extends TelemetryLocalPingBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public TelemetrySyncPingBuilder setUID(@NonNull String uid) {
|
||||
payload.put("uid", uid);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TelemetrySyncPingBuilder setDeviceID(@NonNull String deviceID) {
|
||||
payload.put("deviceID", deviceID);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static JSONArray buildOutgoing(List<StoreBatchTracker.Batch> batches) {
|
||||
if (batches == null || batches.size() == 0) {
|
||||
|
|
|
@ -8,13 +8,11 @@ package org.mozilla.gecko.telemetry.pingbuilders;
|
|||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.Locales;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.NonArrayJSONException;
|
||||
import org.mozilla.gecko.telemetry.TelemetryOutgoingPing;
|
||||
import org.mozilla.gecko.telemetry.TelemetryPing;
|
||||
import org.mozilla.gecko.telemetry.stores.TelemetryPingStore;
|
||||
|
@ -35,6 +33,8 @@ import java.util.TimeZone;
|
|||
*
|
||||
* This builder takes two stores ('sync' and 'event') and produces a single "sync ping".
|
||||
*
|
||||
* Note that until Bug 1363924, event telemetry will be ignored.
|
||||
*
|
||||
* Sample result will look something like:
|
||||
* {
|
||||
* "syncs": [list of syncs, as produced by the SyncBuilder],
|
||||
|
@ -42,8 +42,6 @@ import java.util.TimeZone;
|
|||
* }
|
||||
*/
|
||||
public class TelemetrySyncPingBundleBuilder extends TelemetryPingBuilder {
|
||||
public static final String LOG_TAG = "SyncPingBundleBuilder";
|
||||
|
||||
private static final String PING_TYPE = "sync";
|
||||
private static final int PING_BUNDLE_VERSION = 4; // Bug 1410145
|
||||
private static final int PING_SYNC_DATA_FORMAT_VERSION = 1; // Bug 1374758
|
||||
|
@ -71,16 +69,6 @@ public class TelemetrySyncPingBundleBuilder extends TelemetryPingBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
public TelemetrySyncPingBundleBuilder setUID(@NonNull String uid) {
|
||||
pingData.put("uid", uid);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TelemetrySyncPingBundleBuilder setDeviceID(@NonNull String deviceID) {
|
||||
pingData.put("deviceID", deviceID);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TelemetryOutgoingPing build() {
|
||||
final DateFormat pingCreationDateFormat = new SimpleDateFormat(
|
||||
|
@ -130,27 +118,12 @@ public class TelemetrySyncPingBundleBuilder extends TelemetryPingBuilder {
|
|||
syncs.add(ping.getPayload());
|
||||
}
|
||||
|
||||
if (syncs.size() > 0) {
|
||||
pingData.put("syncs", syncs);
|
||||
}
|
||||
pingData.put("syncs", syncs);
|
||||
return this;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
// Event telemetry will be implemented in Bug 1363924.
|
||||
public TelemetrySyncPingBundleBuilder setSyncEventStore(TelemetryPingStore store) {
|
||||
final JSONArray events = new JSONArray();
|
||||
List<TelemetryPing> pings = store.getAllPings();
|
||||
|
||||
for (TelemetryPing ping : pings) {
|
||||
try {
|
||||
events.add(ping.getPayload().getArray("event"));
|
||||
} catch (NonArrayJSONException ex) {
|
||||
Log.e(LOG_TAG, "Invalid state: Non JSONArray for event payload.");
|
||||
}
|
||||
}
|
||||
|
||||
if (events.size() > 0) {
|
||||
pingData.put("events", events);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,165 @@
|
|||
/* 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.Collection;
|
||||
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);
|
||||
}
|
||||
|
||||
public void testFetchNonStaleClients() throws NullCursorException {
|
||||
String goodRecord1 = Utils.generateGuid();
|
||||
ClientRecord record1 = new ClientRecord(goodRecord1);
|
||||
record1.fxaDeviceId = "fxa1";
|
||||
ClientRecord record2 = new ClientRecord(Utils.generateGuid());
|
||||
record2.fxaDeviceId = "fxa2";
|
||||
String goodRecord2 = Utils.generateGuid();
|
||||
ClientRecord record3 = new ClientRecord(goodRecord2);
|
||||
record3.fxaDeviceId = "fxa4";
|
||||
|
||||
ArrayList<ClientRecord> list = new ArrayList<>();
|
||||
list.add(record1);
|
||||
list.add(record2);
|
||||
list.add(record3);
|
||||
db.store(list);
|
||||
|
||||
assertTrue(db.hasNonStaleClients(new String[]{"fxa1", "fxa-unknown"}));
|
||||
assertFalse(db.hasNonStaleClients(new String[]{}));
|
||||
|
||||
String noFxADeviceId = Utils.generateGuid();
|
||||
ClientRecord record4 = new ClientRecord(noFxADeviceId);
|
||||
record4.fxaDeviceId = null;
|
||||
list.clear();
|
||||
list.add(record4);
|
||||
db.store(list);
|
||||
|
||||
assertTrue(db.hasNonStaleClients(new String[]{}));
|
||||
|
||||
Collection<ClientRecord> filtered = db.fetchNonStaleClients(new String[]{"fxa1", "fxa4", "fxa-unknown"});
|
||||
ClientRecord[] filteredArr = filtered.toArray(new ClientRecord[0]);
|
||||
assertEquals(3, filteredArr.length);
|
||||
assertEquals(filteredArr[0].guid, goodRecord1);
|
||||
assertEquals(filteredArr[1].guid, goodRecord2);
|
||||
assertEquals(filteredArr[2].guid, noFxADeviceId);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -110,13 +110,6 @@ public class AndroidFxAccount {
|
|||
private static final String ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR = "devicePushRegistrationError";
|
||||
private static final String ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR_TIME = "devicePushRegistrationErrorTime";
|
||||
|
||||
// We only see the hashed FxA UID once every sync.
|
||||
// We might need it later for telemetry purposes outside of the context of a sync, which
|
||||
// is why it is persisted.
|
||||
// It is not expected to change during the lifetime of an account, but we set
|
||||
// that value every time we see an FxA token nonetheless.
|
||||
private static final String ACCOUNT_KEY_HASHED_FXA_UID = "hashedFxAUID";
|
||||
|
||||
// Account authentication token type for fetching account profile.
|
||||
private static final String PROFILE_OAUTH_TOKEN_TYPE = "oauth::profile";
|
||||
|
||||
|
@ -1045,14 +1038,6 @@ public class AndroidFxAccount {
|
|||
setDevicePushRegistrationError(0L, 0l);
|
||||
}
|
||||
|
||||
public synchronized void setCachedHashedFxAUID(final String newHashedFxAUID) {
|
||||
accountManager.setUserData(account, ACCOUNT_KEY_HASHED_FXA_UID, newHashedFxAUID);
|
||||
}
|
||||
|
||||
public synchronized String getCachedHashedFxAUID() {
|
||||
return accountManager.getUserData(account, ACCOUNT_KEY_HASHED_FXA_UID);
|
||||
}
|
||||
|
||||
@SuppressLint("ParcelCreator") // The CREATOR field is defined in the super class.
|
||||
private class ProfileResultReceiver extends ResultReceiver {
|
||||
/* package-private */ ProfileResultReceiver(Handler handler) {
|
||||
|
|
|
@ -348,7 +348,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
@Override
|
||||
public void handleSuccess(final TokenServerToken token) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
|
||||
fxAccount.setCachedHashedFxAUID(token.hashedFxaUid);
|
||||
fxAccount.releaseSharedAccountStateLock();
|
||||
|
||||
if (!didReceiveBackoff) {
|
||||
|
|
|
@ -8,24 +8,15 @@ import android.content.ComponentName;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.repositories.NullCursorException;
|
||||
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
|
||||
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
|
||||
import org.mozilla.gecko.sync.telemetry.TelemetryEventCollector;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
@ -64,12 +55,10 @@ public class CommandProcessor {
|
|||
public final String commandType;
|
||||
public final JSONArray args;
|
||||
private List<String> argsList;
|
||||
@Nullable public String flowID;
|
||||
|
||||
public Command(String commandType, JSONArray args, @Nullable String flowID) {
|
||||
public Command(String commandType, JSONArray args) {
|
||||
this.commandType = commandType;
|
||||
this.args = args;
|
||||
this.flowID = flowID;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,9 +88,6 @@ public class CommandProcessor {
|
|||
JSONObject out = new JSONObject();
|
||||
out.put("command", this.commandType);
|
||||
out.put("args", this.args);
|
||||
if (this.flowID != null) {
|
||||
out.put("flowID", this.flowID);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
@ -141,20 +127,8 @@ public class CommandProcessor {
|
|||
Logger.debug(LOG_TAG, "Command \"" + command.commandType + "\" not registered and will not be processed.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
recordProcessCommandTelemetryEvent(session.getContext(), command);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Could not record telemetry event.");
|
||||
}
|
||||
executableCommand.executeCommand(session, command.getArgsList());
|
||||
}
|
||||
|
||||
private static void recordProcessCommandTelemetryEvent(Context context, Command command) {
|
||||
final HashMap<String, String> extra = new HashMap<>();
|
||||
if (command.flowID != null) {
|
||||
extra.put("flowID", command.flowID);
|
||||
}
|
||||
TelemetryEventCollector.recordEvent(context, "processcommand", command.commandType, null, extra);
|
||||
executableCommand.executeCommand(session, command.getArgsList());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -175,9 +149,8 @@ public class CommandProcessor {
|
|||
if (unparsedArgs == null) {
|
||||
return null;
|
||||
}
|
||||
final String flowID = unparsedCommand.getString("flowID");
|
||||
|
||||
return new Command(type, unparsedArgs, flowID);
|
||||
return new Command(type, unparsedArgs);
|
||||
} catch (NonArrayJSONException e) {
|
||||
Logger.debug(LOG_TAG, "Unable to parse args array. Invalid command");
|
||||
return null;
|
||||
|
@ -196,8 +169,7 @@ public class CommandProcessor {
|
|||
args.add(sender);
|
||||
args.add(title);
|
||||
|
||||
final String flowID = Utils.generateGuid();
|
||||
final Command displayURICommand = new Command("displayURI", args, flowID);
|
||||
final Command displayURICommand = new Command("displayURI", args);
|
||||
this.sendCommand(clientID, displayURICommand, context);
|
||||
}
|
||||
|
||||
|
@ -250,39 +222,8 @@ public class CommandProcessor {
|
|||
}
|
||||
}
|
||||
|
||||
private static void recordSendCommandTelemetryEvent(Context context, Command command, String clientID) {
|
||||
final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
|
||||
if (fxAccount == null) {
|
||||
Log.e(LOG_TAG, "Can't record telemetry event: FxAccount doesn't exist.");
|
||||
return;
|
||||
}
|
||||
final String hashedFxAUID = fxAccount.getCachedHashedFxAUID();
|
||||
if (TextUtils.isEmpty(hashedFxAUID)) {
|
||||
Log.e(LOG_TAG, "Can't record telemetry event: The hashed FxA UID is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, String> extra = new HashMap<>();
|
||||
if (!TextUtils.isEmpty(command.flowID)) {
|
||||
extra.put("flowID", command.flowID);
|
||||
}
|
||||
try {
|
||||
extra.put("deviceID", Utils.byte2Hex(Utils.sha256(clientID.concat(hashedFxAUID).getBytes("UTF-8"))));
|
||||
} catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
|
||||
// Should not happen.
|
||||
Log.e(LOG_TAG, "Either UTF-8 or SHA-256 are not supported", e);
|
||||
}
|
||||
|
||||
TelemetryEventCollector.recordEvent(context, "sendcommand", command.commandType, null, extra);
|
||||
}
|
||||
|
||||
protected void sendCommandToClient(String clientID, Command command, Context context) {
|
||||
Logger.info(LOG_TAG, "Sending " + command.commandType + " to " + clientID);
|
||||
try {
|
||||
recordSendCommandTelemetryEvent(context, command, clientID);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Could not record telemetry event.");
|
||||
}
|
||||
|
||||
ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(context);
|
||||
try {
|
||||
|
|
|
@ -79,7 +79,7 @@ public class BaseResource implements Resource {
|
|||
protected DefaultHttpClient client;
|
||||
public ResourceDelegate delegate;
|
||||
protected HttpRequestBase request;
|
||||
public static final String charset = "utf-8";
|
||||
public final String charset = "utf-8";
|
||||
|
||||
private boolean shouldGzipCompress = false;
|
||||
// A hint whether uploaded payloads are chunked. Default true to use GzipCompressingEntity, which is built-in functionality.
|
||||
|
|
|
@ -19,7 +19,7 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
|
||||
// Database Specifications.
|
||||
protected static final String DB_NAME = "clients_database";
|
||||
protected static final int SCHEMA_VERSION = 5;
|
||||
protected static final int SCHEMA_VERSION = 4;
|
||||
|
||||
// Clients Table.
|
||||
public static final String TBL_CLIENTS = "clients";
|
||||
|
@ -46,9 +46,8 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
public static final String TBL_COMMANDS = "commands";
|
||||
public static final String COL_COMMAND = "command";
|
||||
public static final String COL_ARGS = "args";
|
||||
public static final String COL_FLOW_ID = "flow_id";
|
||||
|
||||
public static final String[] TBL_COMMANDS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_COMMAND, COL_ARGS, COL_FLOW_ID };
|
||||
public static final String[] TBL_COMMANDS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_COMMAND, COL_ARGS };
|
||||
public static final String TBL_COMMANDS_KEY = COL_ACCOUNT_GUID + " = ? AND " +
|
||||
COL_COMMAND + " = ? AND " +
|
||||
COL_ARGS + " = ?";
|
||||
|
@ -92,7 +91,6 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
+ COL_ACCOUNT_GUID + " TEXT, "
|
||||
+ COL_COMMAND + " TEXT, "
|
||||
+ COL_ARGS + " TEXT, "
|
||||
+ COL_FLOW_ID + " TEXT, "
|
||||
+ "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_COMMAND + ", " + COL_ARGS + "), "
|
||||
+ "FOREIGN KEY (" + COL_ACCOUNT_GUID + ") REFERENCES " + TBL_CLIENTS + " (" + COL_ACCOUNT_GUID + "))";
|
||||
db.execSQL(createCommandsTableSql);
|
||||
|
@ -122,10 +120,6 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_FXA_DEVICE_ID + " TEXT");
|
||||
db.execSQL("CREATE INDEX idx_fxa_device_id ON " + TBL_CLIENTS + "(" + COL_FXA_DEVICE_ID + ")");
|
||||
}
|
||||
|
||||
if (oldVersion < 5 && newVersion >= 5) {
|
||||
db.execSQL("ALTER TABLE " + TBL_COMMANDS + " ADD COLUMN " + COL_FLOW_ID + " TEXT");
|
||||
}
|
||||
}
|
||||
|
||||
public void wipeDB() {
|
||||
|
@ -195,10 +189,9 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
* @param accountGUID
|
||||
* @param command - The command type
|
||||
* @param args - A JSON string of args
|
||||
* @param flowID - Optional - The flowID
|
||||
* @throws NullCursorException
|
||||
*/
|
||||
public void store(String accountGUID, String command, String args, String flowID) throws NullCursorException {
|
||||
public void store(String accountGUID, String command, String args) throws NullCursorException {
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.pii(LOG_TAG, "Storing command " + command + " with args " + args);
|
||||
} else {
|
||||
|
@ -214,9 +207,6 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
} else {
|
||||
cv.put(COL_ARGS, args);
|
||||
}
|
||||
if (flowID != null) {
|
||||
cv.put(COL_FLOW_ID, flowID);
|
||||
}
|
||||
|
||||
Cursor cur = this.fetchSpecificCommand(accountGUID, command, args);
|
||||
try {
|
||||
|
@ -239,8 +229,6 @@ public class ClientsDatabase extends CachedSQLiteOpenHelper {
|
|||
return queryHelper.safeQuery(db, ".fetchClientsCursor", TBL_CLIENTS, TBL_CLIENTS_COLUMNS, TBL_CLIENTS_KEY, args);
|
||||
}
|
||||
|
||||
// This method does not check flowID on purpose because we do not want to take it into account
|
||||
// when de-duping commands.
|
||||
public Cursor fetchSpecificCommand(String accountGUID, String command, String commandArgs) throws NullCursorException {
|
||||
String[] args = new String[] { accountGUID, command, commandArgs };
|
||||
SQLiteDatabase db = this.getCachedReadableDatabase();
|
||||
|
|
|
@ -50,7 +50,7 @@ public class ClientsDatabaseAccessor {
|
|||
}
|
||||
|
||||
public void store(String accountGUID, Command command) throws NullCursorException {
|
||||
db.store(accountGUID, command.commandType, command.args.toJSONString(), command.flowID);
|
||||
db.store(accountGUID, command.commandType, command.args.toJSONString());
|
||||
}
|
||||
|
||||
public ClientRecord fetchClient(String accountGUID) throws NullCursorException {
|
||||
|
@ -195,10 +195,9 @@ public class ClientsDatabaseAccessor {
|
|||
}
|
||||
|
||||
protected static Command commandFromCursor(Cursor cur) {
|
||||
final String commandType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_COMMAND);
|
||||
final JSONArray commandArgs = RepoUtils.getJSONArrayFromCursor(cur, ClientsDatabase.COL_ARGS);
|
||||
final String flowID = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_FLOW_ID);
|
||||
return new Command(commandType, commandArgs, flowID);
|
||||
String commandType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_COMMAND);
|
||||
JSONArray commandArgs = RepoUtils.getJSONArrayFromCursor(cur, ClientsDatabase.COL_ARGS);
|
||||
return new Command(commandType, commandArgs);
|
||||
}
|
||||
|
||||
public int clientsCount() {
|
||||
|
|
|
@ -25,11 +25,4 @@ public class TelemetryContract {
|
|||
public static final String KEY_DEVICE_OS = "os";
|
||||
public static final String KEY_DEVICE_VERSION = "version";
|
||||
public static final String KEY_DEVICE_ID = "id";
|
||||
|
||||
public static final String KEY_EVENT_TIMESTAMP = "ts";
|
||||
public static final String KEY_EVENT_CATEGORY = "category";
|
||||
public static final String KEY_EVENT_METHOD = "method";
|
||||
public static final String KEY_EVENT_OBJECT = "object";
|
||||
public static final String KEY_EVENT_VALUE = "value";
|
||||
public static final String KEY_EVENT_EXTRA = "extra";
|
||||
}
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.telemetry;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TelemetryEventCollector {
|
||||
private static final String LOG_TAG = "TelemetryEventCollector";
|
||||
|
||||
private static final String ACTION_BACKGROUND_TELEMETRY = "org.mozilla.gecko.telemetry.BACKGROUND";
|
||||
private static final String EVENT_CATEGORY_SYNC = "sync"; // Max byte length: 30.
|
||||
|
||||
public static void recordEvent(final Context context, final String object, final String method,
|
||||
@Nullable final String value,
|
||||
@Nullable final HashMap<String, String> extra) {
|
||||
if (!validateTelemetryEvent(object, method, value, extra)) {
|
||||
throw new IllegalArgumentException("Could not validate telemetry event.");
|
||||
}
|
||||
|
||||
Log.d(LOG_TAG, "Recording event {" + object + ", " + method + ", " + value + ", " + extra);
|
||||
final Bundle event = new Bundle();
|
||||
event.putLong(TelemetryContract.KEY_EVENT_TIMESTAMP, SystemClock.elapsedRealtime());
|
||||
event.putString(TelemetryContract.KEY_EVENT_CATEGORY, EVENT_CATEGORY_SYNC);
|
||||
event.putString(TelemetryContract.KEY_EVENT_OBJECT, object);
|
||||
event.putString(TelemetryContract.KEY_EVENT_METHOD, method);
|
||||
if (value != null) {
|
||||
event.putString(TelemetryContract.KEY_EVENT_VALUE, value);
|
||||
}
|
||||
if (extra != null) {
|
||||
final Bundle extraBundle = new Bundle();
|
||||
for (Map.Entry<String, String> e : extra.entrySet()) {
|
||||
extraBundle.putString(e.getKey(), e.getValue());
|
||||
}
|
||||
event.putBundle(TelemetryContract.KEY_EVENT_EXTRA, extraBundle);
|
||||
}
|
||||
if (!setIDs(context, event)) {
|
||||
throw new IllegalStateException("UID and deviceID need to be set.");
|
||||
}
|
||||
|
||||
final Intent telemetryIntent = new Intent();
|
||||
telemetryIntent.setAction(ACTION_BACKGROUND_TELEMETRY);
|
||||
telemetryIntent.putExtra(TelemetryContract.KEY_TYPE, TelemetryContract.KEY_TYPE_EVENT);
|
||||
telemetryIntent.putExtra(TelemetryContract.KEY_TELEMETRY, event);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(telemetryIntent);
|
||||
}
|
||||
|
||||
// The Firefox Telemetry pipeline imposes size limits.
|
||||
// See toolkit/components/telemetry/docs/collection/events.rst
|
||||
@VisibleForTesting
|
||||
static boolean validateTelemetryEvent(final String object, final String method,
|
||||
@Nullable final String value, @Nullable final HashMap<String, String> extra) {
|
||||
// The Telemetry Sender uses BaseResource under the hood.
|
||||
final Charset charset = Charset.forName(BaseResource.charset);
|
||||
// Length checks.
|
||||
if (method.getBytes(charset).length > 20 ||
|
||||
object.getBytes(charset).length > 20 ||
|
||||
(value != null && value.getBytes(charset).length > 80)) {
|
||||
Log.w(LOG_TAG, "Invalid event parameters - wrong lengths: " + method + " " +
|
||||
object + " " + value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extra != null) {
|
||||
if (extra.size() > 10) {
|
||||
Log.w(LOG_TAG, "Invalid event parameters - too many extra keys: " + extra);
|
||||
return false;
|
||||
}
|
||||
for (Map.Entry<String, String> e : extra.entrySet()) {
|
||||
if (e.getKey().getBytes(charset).length > 15 ||
|
||||
e.getValue().getBytes(charset).length > 80) {
|
||||
Log.w(LOG_TAG, "Invalid event parameters: extra item \"" + e.getKey() +
|
||||
"\" is invalid: " + extra);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean setIDs(final Context context, final Bundle event) {
|
||||
final SharedPreferences sharedPrefs;
|
||||
final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
|
||||
if (fxAccount == null) {
|
||||
Log.e(LOG_TAG, "Can't record telemetry event: FxAccount doesn't exist.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
sharedPrefs = fxAccount.getSyncPrefs();
|
||||
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
|
||||
Log.e(LOG_TAG, "Can't record telemetry event: Could not retrieve Sync Prefs", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
final String hashedFxAUID = fxAccount.getCachedHashedFxAUID();
|
||||
if (TextUtils.isEmpty(hashedFxAUID)) {
|
||||
Log.e(LOG_TAG, "Can't record telemetry event: The hashed FxA UID is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs, context);
|
||||
try {
|
||||
final String hashedDeviceID = Utils.byte2Hex(Utils.sha256(
|
||||
clientsDataDelegate.getAccountGUID().concat(hashedFxAUID).getBytes("UTF-8")
|
||||
));
|
||||
event.putString(TelemetryContract.KEY_LOCAL_DEVICE_ID, hashedDeviceID);
|
||||
} catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
|
||||
// Should not happen.
|
||||
Log.e(LOG_TAG, "Either UTF-8 or SHA-256 are not supported", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
event.putString(TelemetryContract.KEY_LOCAL_UID, hashedFxAUID);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.background.db;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.mozilla.gecko.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 org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestClientsDatabase {
|
||||
|
||||
protected ClientsDatabase db;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
final Context context = RuntimeEnvironment.application;
|
||||
db = new ClientsDatabase(context);
|
||||
db.wipeDB();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
Assert.assertTrue(cur.moveToFirst());
|
||||
Assert.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);
|
||||
|
||||
Assert.assertEquals(record.guid, guid);
|
||||
Assert.assertEquals(profileConst, profileId);
|
||||
Assert.assertEquals(record.name, clientName);
|
||||
Assert.assertEquals(record.type, clientType);
|
||||
} catch (NullCursorException e) {
|
||||
Assert.fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndFetchSpecificCommands() {
|
||||
String accountGUID = Utils.generateGuid();
|
||||
ArrayList<String> args = new ArrayList<>();
|
||||
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, "flowID");
|
||||
|
||||
// This row should not show up in the fetch.
|
||||
args.add("Another arg.");
|
||||
db.store(accountGUID, "displayURI", JSONArray.toJSONString(args), "flowID");
|
||||
|
||||
// Test stored item gets fetched correctly.
|
||||
cur = db.fetchSpecificCommand(accountGUID, "displayURI", jsonArgs);
|
||||
Assert.assertTrue(cur.moveToFirst());
|
||||
Assert.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);
|
||||
|
||||
Assert.assertEquals(accountGUID, guid);
|
||||
Assert.assertEquals("displayURI", commandType);
|
||||
Assert.assertEquals(jsonArgs, fetchedArgs);
|
||||
} catch (NullCursorException e) {
|
||||
Assert.fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetchCommandsForClient() {
|
||||
String accountGUID = Utils.generateGuid();
|
||||
ArrayList<String> args = new ArrayList<>();
|
||||
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, "flowID");
|
||||
|
||||
// This row should ALSO show up in the fetch.
|
||||
args.add("Another arg.");
|
||||
db.store(accountGUID, "displayURI", JSONArray.toJSONString(args), "flowID");
|
||||
|
||||
// Test both stored items with the same GUID but different command are fetched.
|
||||
cur = db.fetchCommandsForClient(accountGUID);
|
||||
Assert.assertTrue(cur.moveToFirst());
|
||||
Assert.assertEquals(2, cur.getCount());
|
||||
} catch (NullCursorException e) {
|
||||
Assert.fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@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);
|
||||
Assert.assertFalse(cur.moveToFirst());
|
||||
Assert.assertEquals(0, cur.getCount());
|
||||
|
||||
// Test record2 still there after deleting record1.
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
Assert.assertTrue(cur.moveToFirst());
|
||||
Assert.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);
|
||||
|
||||
Assert.assertEquals(record2.guid, guid);
|
||||
Assert.assertEquals(profileConst, profileId);
|
||||
Assert.assertEquals(record2.name, clientName);
|
||||
Assert.assertEquals(record2.type, clientType);
|
||||
} catch (NullCursorException e) {
|
||||
Assert.fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@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);
|
||||
Assert.assertTrue(cur.moveToFirst());
|
||||
Assert.assertEquals(1, cur.getCount());
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
Assert.assertTrue(cur.moveToFirst());
|
||||
Assert.assertEquals(1, cur.getCount());
|
||||
|
||||
// Test after wipe neither record exists.
|
||||
db.wipeClientsTable();
|
||||
cur = db.fetchClientsCursor(record2.guid, profileConst);
|
||||
Assert.assertFalse(cur.moveToFirst());
|
||||
Assert.assertEquals(0, cur.getCount());
|
||||
cur = db.fetchClientsCursor(record1.guid, profileConst);
|
||||
Assert.assertFalse(cur.moveToFirst());
|
||||
Assert.assertEquals(0, cur.getCount());
|
||||
} catch (NullCursorException e) {
|
||||
Assert.fail("Should not have NullCursorException");
|
||||
} finally {
|
||||
if (cur != null) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
/* 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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.CommandHelpers;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
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 org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestClientsDatabaseAccessor {
|
||||
|
||||
ClientsDatabaseAccessor db;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
final Context context = RuntimeEnvironment.application;
|
||||
db = new ClientsDatabaseAccessor(context);
|
||||
db.wipeDB();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
public void testStoreArrayListAndFetch() throws NullCursorException {
|
||||
ArrayList<ClientRecord> list = new ArrayList<>();
|
||||
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);
|
||||
|
||||
Assert.assertNotNull(r1);
|
||||
Assert.assertNotNull(r2);
|
||||
Assert.assertNull(r3);
|
||||
Assert.assertTrue(record1.equals(r1));
|
||||
Assert.assertTrue(record2.equals(r2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreAndFetchCommandsForClient() {
|
||||
String accountGUID1 = Utils.generateGuid();
|
||||
String accountGUID2 = Utils.generateGuid();
|
||||
|
||||
Command command1 = CommandHelpers.getCommand1();
|
||||
Command command2 = CommandHelpers.getCommand2();
|
||||
Command command3 = CommandHelpers.getCommand3();
|
||||
|
||||
try {
|
||||
db.store(accountGUID1, command1);
|
||||
db.store(accountGUID1, command2);
|
||||
db.store(accountGUID2, command3);
|
||||
|
||||
List<Command> commands = db.fetchCommandsForClient(accountGUID1);
|
||||
Assert.assertEquals(2, commands.size());
|
||||
Assert.assertEquals(1, commands.get(0).args.size());
|
||||
Assert.assertEquals(1, commands.get(1).args.size());
|
||||
} catch (NullCursorException e) {
|
||||
Assert.fail("Should not have NullCursorException");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumClients() {
|
||||
final int COUNT = 5;
|
||||
ArrayList<ClientRecord> list = new ArrayList<>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
list.add(new ClientRecord());
|
||||
}
|
||||
db.store(list);
|
||||
Assert.assertEquals(COUNT, db.clientsCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetchAll() throws NullCursorException {
|
||||
ArrayList<ClientRecord> list = new ArrayList<>();
|
||||
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();
|
||||
|
||||
Assert.assertNotNull(records);
|
||||
Assert.assertEquals(0, records.size());
|
||||
|
||||
db.store(list);
|
||||
records = db.fetchAllClients();
|
||||
Assert.assertNotNull(records);
|
||||
Assert.assertEquals(2, records.size());
|
||||
Assert.assertTrue(record1.equals(records.get(record1.guid)));
|
||||
Assert.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;
|
||||
}
|
||||
Assert.assertTrue(thrown);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFetchNonStaleClients() throws NullCursorException {
|
||||
String goodRecord1 = Utils.generateGuid();
|
||||
ClientRecord record1 = new ClientRecord(goodRecord1);
|
||||
record1.fxaDeviceId = "fxa1";
|
||||
ClientRecord record2 = new ClientRecord(Utils.generateGuid());
|
||||
record2.fxaDeviceId = "fxa2";
|
||||
String goodRecord2 = Utils.generateGuid();
|
||||
ClientRecord record3 = new ClientRecord(goodRecord2);
|
||||
record3.fxaDeviceId = "fxa4";
|
||||
|
||||
ArrayList<ClientRecord> list = new ArrayList<>();
|
||||
list.add(record1);
|
||||
list.add(record2);
|
||||
list.add(record3);
|
||||
db.store(list);
|
||||
|
||||
Assert.assertTrue(db.hasNonStaleClients(new String[]{"fxa1", "fxa-unknown"}));
|
||||
Assert.assertFalse(db.hasNonStaleClients(new String[]{}));
|
||||
|
||||
String noFxADeviceId = Utils.generateGuid();
|
||||
ClientRecord record4 = new ClientRecord(noFxADeviceId);
|
||||
record4.fxaDeviceId = null;
|
||||
list.clear();
|
||||
list.add(record4);
|
||||
db.store(list);
|
||||
|
||||
Assert.assertTrue(db.hasNonStaleClients(new String[]{}));
|
||||
|
||||
Collection<ClientRecord> filtered = db.fetchNonStaleClients(new String[]{"fxa1", "fxa4", "fxa-unknown"});
|
||||
ClientRecord[] filteredArr = filtered.toArray(new ClientRecord[0]);
|
||||
Assert.assertEquals(3, filteredArr.length);
|
||||
Assert.assertEquals(filteredArr[0].guid, goodRecord1);
|
||||
Assert.assertEquals(filteredArr[1].guid, goodRecord2);
|
||||
Assert.assertEquals(filteredArr[2].guid, noFxADeviceId);
|
||||
}
|
||||
}
|
|
@ -12,21 +12,21 @@ public class CommandHelpers {
|
|||
public static Command getCommand1() {
|
||||
JSONArray args = new JSONArray();
|
||||
args.add("argsA");
|
||||
return new Command("displayURI", args, null);
|
||||
return new Command("displayURI", args);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Command getCommand2() {
|
||||
JSONArray args = new JSONArray();
|
||||
args.add("argsB");
|
||||
return new Command("displayURI", args, null);
|
||||
return new Command("displayURI", args);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Command getCommand3() {
|
||||
JSONArray args = new JSONArray();
|
||||
args.add("argsC");
|
||||
return new Command("displayURI", args, null);
|
||||
return new Command("displayURI", args);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -35,6 +35,6 @@ public class CommandHelpers {
|
|||
args.add("URI of Page");
|
||||
args.add("Sender ID");
|
||||
args.add("Title of Page");
|
||||
return new Command("displayURI", args, null);
|
||||
return new Command("displayURI", args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
package org.mozilla.gecko.sync.telemetry;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
@RunWith(TestRunner.class)
|
||||
public class TelemetryEventCollectorTest {
|
||||
|
||||
@Test
|
||||
public void testValidateTelemetryEvent() {
|
||||
// object arg bytes len > 20.
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent(repeat("c", 21), "met", null, null));
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent(repeat("©", 11), "met", null, null));
|
||||
|
||||
// method arg bytes len > 20.
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", repeat("c", 21), null, null));
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", repeat("©", 11), null, null));
|
||||
|
||||
// val arg bytes len > 80.
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", "met", repeat("c", 81), null));
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", "met", repeat("©", 41), null));
|
||||
|
||||
// extra arg len > 10.
|
||||
HashMap<String, String> extra = new HashMap<>();
|
||||
for (int i = 0; i < 11; i++) {
|
||||
extra.put("" + i, "" + i);
|
||||
}
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", "met", "val", extra));
|
||||
|
||||
// extra arg key len > 15.
|
||||
extra.clear();
|
||||
for (int i = 0; i < 9; i++) {
|
||||
extra.put("" + i, "" + i);
|
||||
}
|
||||
extra.put("HashMapKeyInstanceFactory", "val");
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", "met", "val", extra));
|
||||
|
||||
// extra arg val bytes len > 80.
|
||||
extra.clear();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
extra.put("" + i, "" + i);
|
||||
}
|
||||
extra.put("key", repeat("©", 41));
|
||||
Assert.assertFalse(TelemetryEventCollector.validateTelemetryEvent("obj", "met", "val", extra));
|
||||
|
||||
// Happy case.
|
||||
extra.clear();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
extra.put(repeat(i + "", 15), repeat("" + i, 80));
|
||||
}
|
||||
Assert.assertTrue(TelemetryEventCollector.validateTelemetryEvent(repeat("c", 20), repeat("c", 20), repeat("c", 80), extra));
|
||||
}
|
||||
|
||||
private static String repeat(String c, int times) {
|
||||
return new String(new char[times]).replace("\0", c);
|
||||
}
|
||||
}
|
|
@ -229,6 +229,7 @@ client. This is logically the "other end" of ``sendcommand``.
|
|||
- value: Not used (ie, ``null``)
|
||||
- extra: An object with the following attributes:
|
||||
|
||||
- deviceID: A GUID which identifies the device the command is being sent to.
|
||||
- flowID: A GUID which uniquely identifies this command invocation. The value
|
||||
for this GUID will be the same as the flowID sent to the client via
|
||||
``sendcommand``.
|
||||
|
|
Загрузка…
Ссылка в новой задаче