const TEST_PROFILE_1 = {
name: "Timothy John Berners-Lee",
organization: "World Wide Web Consortium", "street-address": "32 Vassar Street\nMIT Room 32-G524", "address-level2": "Cambridge", "address-level1": "MA", "postal-code": "02139",
country: "US",
tel: "+16172535702",
email: "timbl@w3.org", // A field this client doesn't "understand" from another client "unknown-1": "some unknown data from another client",
};
async function expectLocalProfiles(profileStorage, expected) {
let profiles = await profileStorage.addresses.getAll({
rawData: true,
includeDeleted: true,
});
expected.sort((a, b) => a.guid.localeCompare(b.guid));
profiles.sort((a, b) => a.guid.localeCompare(b.guid)); try {
deepEqual(
profiles.map(p => p.guid),
expected.map(p => p.guid)
); for (let i = 0; i < expected.length; i++) {
let thisExpected = expected[i];
let thisGot = profiles[i]; // always check "deleted".
equal(thisExpected.deleted, thisGot.deleted);
ok(objectMatches(thisGot, thisExpected));
}
} catch (ex) {
info("Comparing expected profiles:");
info(JSON.stringify(expected, undefined, 2));
info("against actual profiles:");
info(JSON.stringify(profiles, undefined, 2)); throw ex;
}
}
async function setup() {
let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME); // should always start with no profiles. Assert.equal(
(await profileStorage.addresses.getAll({ includeDeleted: true })).length,
0
);
Services.prefs.setCharPref( "services.sync.log.logger.engine.addresses", "Trace"
);
let engine = new AddressesEngine(Service);
await engine.initialize(); // Avoid accidental automatic sync due to our own changes
Service.scheduler.syncThreshold = 10000000;
let syncID = await engine.resetLocalSyncID();
let server = serverForUsers(
{ foo: "password" },
{
meta: {
global: { engines: { addresses: { version: engine.version, syncID } } },
},
addresses: {},
}
);
async function cleanup(server) {
let promiseStartOver = promiseOneObserver("weave:service:start-over:finish");
await Service.startOver();
await promiseStartOver;
await promiseStopServer(server);
}
add_task(async function test_log_sanitization() {
let sanitized = sanitizeStorageObject(TEST_PROFILE_1); // all strings have been mangled. for (let key of Object.keys(TEST_PROFILE_1)) {
let val = TEST_PROFILE_1[key]; if (typeof val == "string") {
notEqual(sanitized[key], val);
}
} // And check that stringifying a sync record is sanitized.
let record = new AutofillRecord("collection", "some-id");
record.entry = TEST_PROFILE_1;
let serialized = record.toString(); // None of the string values should appear in the output. for (let key of Object.keys(TEST_PROFILE_1)) {
let val = TEST_PROFILE_1[key]; if (typeof val == "string") {
ok(!serialized.includes(val), `"${val}" shouldn't be in: ${serialized}`);
}
}
});
add_task(async function test_outgoing() {
let { profileStorage, server, collection, engine } = await setup(); try {
equal(engine._tracker.score, 0);
let existingGUID = await profileStorage.addresses.add(TEST_PROFILE_1); // And a deleted item.
let deletedGUID = profileStorage.addresses._generateGUID();
await profileStorage.addresses.add({ guid: deletedGUID, deleted: true });
await engine._tracker.asyncObserver.promiseObserversComplete(); // The tracker should have a score recorded for the 2 additions we had.
equal(engine._tracker.score, SCORE_INCREMENT_XLARGE * 2);
// Validate incoming records with unknown fields get stored
let localRecord = await profileStorage.addresses.get(profileID);
equal(localRecord["unknown-1"], TEST_PROFILE_1["unknown-1"]);
// The sync applied new records - ensure our tracker knew it came from // sync and didn't bump the score.
equal(engine._tracker.score, 0);
} finally {
await cleanup(server);
}
});
add_task(async function test_incoming_existing() {
let { profileStorage, server, engine } = await setup(); try {
let guid1 = await profileStorage.addresses.add(TEST_PROFILE_1);
let guid2 = await profileStorage.addresses.add(TEST_PROFILE_2);
// an initial sync so we don't think they are locally modified.
await engine.setLastSync(0);
await engine.sync();
// now server records that modify the existing items.
let modifiedEntry1 = Object.assign({}, TEST_PROFILE_1, {
version: 1,
name: "NewName",
});
// should still exist, but now be a tombstone. Assert.equal(collection.count(), 1);
payload = collection.payloads()[0];
equal(payload.id, existingGUID);
equal(payload.deleted, true);
} finally {
await cleanup(server);
}
});
add_task(async function test_applyIncoming_both_deleted() {
let { profileStorage, server, engine } = await setup(); try {
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
await engine.setLastSync(0);
await engine.sync();
// Delete synced record locally.
profileStorage.addresses.remove(guid);
// Delete same record remotely.
let lastSync = await engine.getLastSync();
let collection = server.user("foo").collection("addresses");
collection.insert(
guid,
encryptPayload({
id: guid,
deleted: true,
}),
lastSync + 10
);
await engine.sync();
ok(
!(await profileStorage.addresses.get(guid)), "Should not return record for locally deleted item"
);
let localRecords = await profileStorage.addresses.getAll({
includeDeleted: true,
});
equal(localRecords.length, 1, "Only tombstone should exist locally");
equal(collection.count(), 1, "Only tombstone should exist on server");
} finally {
await cleanup(server);
}
});
add_task(async function test_applyIncoming_nonexistent_tombstone() {
let { profileStorage, server, engine } = await setup(); try {
let guid = profileStorage.addresses._generateGUID();
let collection = server.user("foo").collection("addresses");
collection.insert(
guid,
encryptPayload({
id: guid,
deleted: true,
}),
getDateForSync()
);
await engine.setLastSync(0);
await engine.sync();
ok(
!(await profileStorage.addresses.get(guid)), "Should not return record for unknown deleted item"
);
let localTombstone = (
await profileStorage.addresses.getAll({
includeDeleted: true,
})
).find(record => record.guid == guid);
ok(localTombstone, "Should store tombstone for unknown item");
} finally {
await cleanup(server);
}
});
add_task(async function test_applyIncoming_incoming_deleted() {
let { profileStorage, server, engine } = await setup(); try {
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
await engine.setLastSync(0);
await engine.sync();
// Delete the record remotely.
let lastSync = await engine.getLastSync();
let collection = server.user("foo").collection("addresses");
collection.insert(
guid,
encryptPayload({
id: guid,
deleted: true,
}),
lastSync + 10
);
let localTombstone = (
await profileStorage.addresses.getAll({
includeDeleted: true,
})
).find(record => record.guid == guid);
ok(localTombstone, "Should keep local tombstone for remotely deleted item");
strictEqual(
getSyncChangeCounter(profileStorage.addresses, guid),
0, "Local tombstone should be marked as syncing"
);
} finally {
await cleanup(server);
}
});
add_task(async function test_applyIncoming_incoming_restored() {
let { profileStorage, server, engine } = await setup(); try {
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
// Upload the record to the server.
await engine.setLastSync(0);
await engine.sync();
// Removing a synced record should write a tombstone.
profileStorage.addresses.remove(guid);
// Modify the deleted record remotely.
let collection = server.user("foo").collection("addresses");
let serverPayload = JSON.parse(
JSON.parse(collection.payload(guid)).ciphertext
);
serverPayload.entry["street-address"] = "I moved!";
let lastSync = await engine.getLastSync();
collection.insert(guid, encryptPayload(serverPayload), lastSync + 10);
// Sync again.
await engine.sync();
// We should replace our tombstone with the server's version.
let localRecord = await profileStorage.addresses.get(guid);
ok(
objectMatches(localRecord, {
name: "Timothy John Berners-Lee", "street-address": "I moved!",
})
);
let maybeNewServerPayload = JSON.parse(
JSON.parse(collection.payload(guid)).ciphertext
);
deepEqual(
maybeNewServerPayload,
serverPayload, "Should not change record on server"
);
} finally {
await cleanup(server);
}
});
add_task(async function test_applyIncoming_outgoing_restored() {
let { profileStorage, server, engine } = await setup(); try {
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
// Upload the record to the server.
await engine.setLastSync(0);
await engine.sync();
// Modify the local record.
let localCopy = Object.assign({}, TEST_PROFILE_1);
localCopy["street-address"] = "I moved!";
await profileStorage.addresses.update(guid, localCopy);
// Replace the record with a tombstone on the server.
let lastSync = await engine.getLastSync();
let collection = server.user("foo").collection("addresses");
collection.insert(
guid,
encryptPayload({
id: guid,
deleted: true,
}),
lastSync + 10
);
// Sync again.
await engine.sync();
// We should resurrect the record on the server.
let serverPayload = JSON.parse(
JSON.parse(collection.payload(guid)).ciphertext
);
ok(!serverPayload.deleted, "Should resurrect record on server");
ok(
objectMatches(serverPayload.entry, {
name: "Timothy John Berners-Lee", "street-address": "I moved!", // resurrection also beings back any unknown fields we had "unknown-1": "some unknown data from another client",
})
);
let localRecord = await profileStorage.addresses.get(guid);
ok(localRecord, "Modified record should not be deleted locally");
} finally {
await cleanup(server);
}
});
// Unlike most sync engines, we want "both modified" to inspect the records, // and if materially different, create a duplicate.
add_task(async function test_reconcile_both_modified_identical() {
let { profileStorage, server, engine } = await setup(); try { // create a record locally.
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
// and an identical record on the server.
server.insertWBO( "foo", "addresses", new ServerWBO(
guid,
encryptPayload({
id: guid,
entry: TEST_PROFILE_1,
}),
getDateForSync()
)
);
add_task(async function test_incoming_dupes() {
let { profileStorage, server, engine } = await setup(); try { // Create a profile locally, then sync to upload the new profile to the // server.
let guid1 = await profileStorage.addresses.add(TEST_PROFILE_1);
await engine.setLastSync(0);
await engine.sync();
// Create another profile locally, but don't sync it yet.
await profileStorage.addresses.add(TEST_PROFILE_2);
// Now create two records on the server with the same contents as our local // profiles, but different GUIDs.
let lastSync = await engine.getLastSync();
let guid1_dupe = Utils.makeGUID();
server.insertWBO( "foo", "addresses", new ServerWBO(
guid1_dupe,
encryptPayload({
id: guid1_dupe,
entry: Object.assign(
{
version: 1,
},
TEST_PROFILE_1
),
}),
lastSync + 10
)
);
let guid2_dupe = Utils.makeGUID();
server.insertWBO( "foo", "addresses", new ServerWBO(
guid2_dupe,
encryptPayload({
id: guid2_dupe,
entry: Object.assign(
{
version: 1,
},
TEST_PROFILE_2
),
}),
lastSync + 10
)
);
// Sync again. We should download `guid1_dupe` and `guid2_dupe`, then // reconcile changes.
await engine.sync();
await expectLocalProfiles(profileStorage, [ // We uploaded `guid1` during the first sync. Even though its contents // are the same as `guid1_dupe`, we keep both.
Object.assign({}, TEST_PROFILE_1, { guid: guid1 }),
Object.assign({}, TEST_PROFILE_1, { guid: guid1_dupe }), // However, we didn't upload `guid2` before downloading `guid2_dupe`, so // we *should* dedupe `guid2` to `guid2_dupe`.
Object.assign({}, TEST_PROFILE_2, { guid: guid2_dupe }),
]);
} finally {
await cleanup(server);
}
});
add_task(async function test_dedupe_identical_unsynced() {
let { profileStorage, server, engine } = await setup(); try { // create a record locally.
let localGuid = await profileStorage.addresses.add(TEST_PROFILE_1);
// and an identical record on the server but different GUID.
let remoteGuid = Utils.makeGUID();
notEqual(localGuid, remoteGuid);
server.insertWBO( "foo", "addresses", new ServerWBO(
remoteGuid,
encryptPayload({
id: remoteGuid,
entry: Object.assign(
{
version: 1,
},
TEST_PROFILE_1
),
}),
getDateForSync()
)
);
await engine.setLastSync(0);
await engine.sync();
// Should have 1 item locally with GUID changed to the remote one. // There's no tombstone as the original was unsynced.
await expectLocalProfiles(profileStorage, [
{
guid: remoteGuid,
},
]);
} finally {
await cleanup(server);
}
});
add_task(async function test_dedupe_identical_synced() {
let { profileStorage, server, engine } = await setup(); try { // create a record locally.
let localGuid = await profileStorage.addresses.add(TEST_PROFILE_1);
// sync it - it will no longer be a candidate for de-duping.
await engine.setLastSync(0);
await engine.sync();
// and an identical record on the server but different GUID.
let lastSync = await engine.getLastSync();
let remoteGuid = Utils.makeGUID();
server.insertWBO( "foo", "addresses", new ServerWBO(
remoteGuid,
encryptPayload({
id: remoteGuid,
entry: Object.assign(
{
version: 1,
},
TEST_PROFILE_1
),
}),
lastSync + 10
)
);
await engine.sync();
// Should have 2 items locally, since the first was synced.
await expectLocalProfiles(profileStorage, [
{ guid: localGuid },
{ guid: remoteGuid },
]);
} finally {
await cleanup(server);
}
});
add_task(async function test_dedupe_multiple_candidates() {
let { profileStorage, server, engine } = await setup(); try { // It's possible to have duplicate local profiles, with the same fields but // different GUIDs. After a node reassignment, or after disconnecting and // reconnecting to Sync, we might dedupe a local record A to a remote record // B, if we see B before we download and apply A. Since A and B are dupes, // that's OK. We'll write a tombstone for A when we dedupe A to B, and // overwrite that tombstone when we see A.
// We don't pass `sourceSync` so that the records are marked as NEW.
let aGuid = await profileStorage.addresses.add(localRecord);
let bGuid = await profileStorage.addresses.add(localRecord);
// Insert B before A.
server.insertWBO( "foo", "addresses", new ServerWBO(
bGuid,
encryptPayload({
id: bGuid,
entry: serverRecord,
}),
getDateForSync()
)
);
server.insertWBO( "foo", "addresses", new ServerWBO(
aGuid,
encryptPayload({
id: aGuid,
entry: serverRecord,
}),
getDateForSync()
)
);
await engine.setLastSync(0);
await engine.sync();
await expectLocalProfiles(profileStorage, [
{
guid: aGuid,
name: "Mark Hammond",
organization: "Mozilla",
country: "AU",
tel: "+12345678910",
},
{
guid: bGuid,
name: "Mark Hammond",
organization: "Mozilla",
country: "AU",
tel: "+12345678910",
},
]); // Make sure these are both syncing.
strictEqual(
getSyncChangeCounter(profileStorage.addresses, aGuid),
0, "A should be marked as syncing"
);
strictEqual(
getSyncChangeCounter(profileStorage.addresses, bGuid),
0, "B should be marked as syncing"
);
} finally {
await cleanup(server);
}
});
// Unlike most sync engines, we want "both modified" to inspect the records, // and if materially different, create a duplicate.
add_task(async function test_reconcile_both_modified_conflict() {
let { profileStorage, server, engine } = await setup(); try { // create a record locally.
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
// Upload the record to the server.
await engine.setLastSync(0);
await engine.sync();
strictEqual(
getSyncChangeCounter(profileStorage.addresses, guid),
0, "Original record should be marked as syncing"
);
// Change the same field locally and on the server.
let localCopy = Object.assign({}, TEST_PROFILE_1);
localCopy["street-address"] = "I moved!";
await profileStorage.addresses.update(guid, localCopy);
let lastSync = await engine.getLastSync();
let collection = server.user("foo").collection("addresses");
let serverPayload = JSON.parse(
JSON.parse(collection.payload(guid)).ciphertext
);
serverPayload.entry["street-address"] = "I moved, too!";
collection.insert(guid, encryptPayload(serverPayload), lastSync + 10);
// Sync again.
await engine.sync();
// Since we wait to pull changes until we're ready to upload, both records // should now exist on the server; we don't need a follow-up sync.
let serverPayloads = collection.payloads();
equal(serverPayloads.length, 2, "Both records should exist on server");
let forkedPayload = serverPayloads.find(payload => payload.id != guid);
ok(forkedPayload, "Forked record should exist on server");
await expectLocalProfiles(profileStorage, [
{
guid,
name: "Timothy John Berners-Lee", "street-address": "I moved, too!",
},
{
guid: forkedPayload.id,
name: "Timothy John Berners-Lee", "street-address": "I moved!",
},
]);
let changeCounter = getSyncChangeCounter(
profileStorage.addresses,
forkedPayload.id
);
strictEqual(changeCounter, 0, "Forked record should be marked as syncing");
} finally {
await cleanup(server);
}
});
add_task(async function test_wipe() {
let { profileStorage, server, engine } = await setup(); try {
let guid = await profileStorage.addresses.add(TEST_PROFILE_1);
let promiseObserved = promiseOneObserver("formautofill-storage-changed");
await engine._wipeClient();
let { subject, data } = await promiseObserved; Assert.equal(
subject.wrappedJSObject.sourceSync, true, "it should be noted this came from sync"
); Assert.equal(
subject.wrappedJSObject.collectionName, "addresses", "got the correct collection"
); Assert.equal(data, "removeAll", "a removeAll should be noted");
// Other clients might have data that we aren't able to process/understand yet // We should keep that data and ensure when we sync we don't lose that data
add_task(async function test_full_roundtrip_unknown_data() {
let { profileStorage, server, engine } = await setup(); try {
let profileID = Utils.makeGUID();
info("Incoming records with unknown fields are properly stored"); // Insert a record onto the server
server.insertWBO( "foo", "addresses", new ServerWBO(
profileID,
encryptPayload({
id: profileID,
entry: Object.assign(
{
version: 1,
},
TEST_PROFILE_1
),
}),
getDateForSync()
)
);
// The tracker should start with no score.
equal(engine._tracker.score, 0);
// The sync applied new records - ensure our tracker knew it came from // sync and didn't bump the score.
equal(engine._tracker.score, 0);
// Validate incoming records with unknown fields are correctly stored
let localRecord = await profileStorage.addresses.get(profileID);
equal(localRecord["unknown-1"], TEST_PROFILE_1["unknown-1"]);
let onChanged = TestUtils.topicObserved( "formautofill-storage-changed",
(subject, data) => data == "update"
);
// Validate we can update the records locally and not drop any unknown fields
info("Unknown fields are sent back up to the server");
// Modify the local copy
let localCopy = Object.assign({}, TEST_PROFILE_1);
localCopy["street-address"] = "I moved!";
await profileStorage.addresses.update(profileID, localCopy);
await onChanged;
await profileStorage._saveImmediately();
let updatedCopy = await profileStorage.addresses.get(profileID);
equal(updatedCopy["street-address"], "I moved!");
// Sync our changes to the server
await engine.setLastSync(0);
await engine.sync();
let collection = server.user("foo").collection("addresses");
Assert.ok(collection.wbo(profileID));
let serverPayload = JSON.parse(
JSON.parse(collection.payload(profileID)).ciphertext
);
// The server has the updated field as well as any unknown fields
equal(
serverPayload.entry["unknown-1"], "some unknown data from another client"
);
equal(serverPayload.entry["street-address"], "I moved!");
} finally {
await cleanup(server);
}
});
Messung V0.5
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.