--- a/services/sync/tests/tps/.eslintrc.js
+++ b/services/sync/tests/tps/.eslintrc.js
@@ -3,17 +3,19 @@
module.exports = {
"extends": [
"plugin:mozilla/mochitest-test"
],
globals: {
// Injected into tests via tps.jsm
"Addons": false,
+ "Addresses": false,
"Bookmarks": false,
+ "CreditCards": false,
"EnableEngines": false,
"EnsureTracking": false,
"Formdata": false,
"History": false,
"Login": false,
"Passwords": false,
"Phase": false,
"Prefs": false,
--- a/services/sync/tests/tps/all_tests.json
+++ b/services/sync/tests/tps/all_tests.json
@@ -20,13 +20,15 @@
"test_privbrw_tabs.js",
"test_bookmarks_in_same_named_folder.js",
"test_client_wipe.js",
"test_special_tabs.js",
"test_addon_restartless_xpi.js",
"test_addon_nonrestartless_xpi.js",
"test_addon_reconciling.js",
"test_addon_wipe.js",
- "test_existing_bookmarks.js"
+ "test_existing_bookmarks.js",
+ "test_addresses.js",
+ "test_creditcards.js",
]
}
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/tps/test_addresses.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global Services */
+Services.prefs.setBoolPref("services.sync.engine.addresses", true);
+
+EnableEngines(["addresses"]);
+
+var phases = {
+ "phase1": "profile1",
+ "phase2": "profile2",
+ "phase3": "profile1"
+};
+
+const address1 = [{
+ "given-name": "Timothy",
+ "additional-name": "John",
+ "family-name": "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",
+ changes: {
+ "organization": "W3C"
+ }
+}];
+
+const address1_after = [{
+ "given-name": "Timothy",
+ "additional-name": "John",
+ "family-name": "Berners-Lee",
+ "organization": "W3C",
+ "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",
+}];
+
+const address2 = [{
+ "given-name": "John",
+ "additional-name": "R.",
+ "family-name": "Smith",
+ "organization": "Mozilla",
+ "street-address": "Geb\u00E4ude 3, 4. Obergeschoss\nSchlesische Stra\u00DFe 27",
+ "address-level2": "Berlin",
+ "address-level1": "BE",
+ "postal-code": "10997",
+ "country": "DE",
+ "tel": "+4930983333000",
+ "email": "timbl@w3.org",
+}];
+
+Phase("phase1", [
+ [Addresses.add, address1],
+ [Sync]
+]);
+
+Phase("phase2", [
+ [Sync],
+ [Addresses.verify, address1],
+ [Addresses.modify, address1],
+ [Addresses.add, address2],
+ [Sync]
+]);
+
+Phase("phase3", [
+ [Sync],
+ [Addresses.verify, address1_after],
+ [Addresses.verify, address2]
+]);
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/tps/test_creditcards.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global Services */
+Services.prefs.setBoolPref("services.sync.engine.creditcards", true);
+
+EnableEngines(["creditcards"]);
+
+var phases = {
+ "phase1": "profile1",
+ "phase2": "profile2",
+ "phase3": "profile1"
+};
+
+const cc1 = [{
+ "cc-name": "John Doe",
+ "cc-number": "1234567812345678",
+ "cc-exp-month": 4,
+ "cc-exp-year": 2017,
+ "changes": {
+ "cc-exp-year": 2018
+ }
+}];
+
+const cc1_after = [{
+ "cc-name": "John Doe",
+ "cc-number": "1234567812345678",
+ "cc-exp-month": 4,
+ "cc-exp-year": 2018,
+}];
+
+const cc2 = [{
+ "cc-name": "Timothy Berners-Lee",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ "cc-exp-year": 2022,
+}];
+
+Phase("phase1", [
+ [CreditCards.add, cc1],
+ [Sync]
+]);
+
+Phase("phase2", [
+ [Sync],
+ [CreditCards.verify, cc1],
+ [CreditCards.modify, cc1],
+ [CreditCards.add, cc2],
+ [Sync]
+]);
+
+Phase("phase3", [
+ [Sync],
+ [CreditCards.verifyNot, cc1],
+ [CreditCards.verify, cc1_after],
+ [CreditCards.verify, cc2]
+]);
--- a/services/sync/tests/unit/sync_ping_schema.json
+++ b/services/sync/tests/unit/sync_ping_schema.json
@@ -73,17 +73,17 @@
"version": { "type": "string" }
}
},
"engine": {
"required": ["name"],
"additionalProperties": false,
"properties": {
"failureReason": { "$ref": "#/definitions/error" },
- "name": { "enum": ["addons", "bookmarks", "clients", "forms", "history", "passwords", "prefs", "tabs", "extension-storage"] },
+ "name": { "type": "string" },
"took": { "type": "integer", "minimum": 1 },
"status": { "type": "string" },
"incoming": {
"type": "object",
"additionalProperties": false,
"anyOf": [
{"required": ["applied"]},
{"required": ["failed"]},
new file mode 100644
--- /dev/null
+++ b/services/sync/tps/extensions/tps/resource/modules/formautofill.jsm
@@ -0,0 +1,111 @@
+/* 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/. */
+
+ /* This is a JavaScript module (JSM) to be imported via
+ * Components.utils.import() and acts as a singleton. Only the following
+ * listed symbols will exposed on import, and only when and where imported.
+ */
+
+var EXPORTED_SYMBOLS = ["Address", "CreditCard", "DumpAddresses", "DumpCreditCards"];
+
+ChromeUtils.import("resource://tps/logger.jsm");
+ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
+ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
+
+class FormAutofillBase {
+ constructor(props, subStorageName, fields) {
+ this._subStorageName = subStorageName;
+ this._fields = fields;
+
+ this.props = {};
+ this.updateProps = null;
+ if ("changes" in props) {
+ this.updateProps = props.changes;
+ }
+ for (const field of this._fields) {
+ this.props[field] = (field in props) ? props[field] : null;
+ }
+ }
+
+ get storage() {
+ return profileStorage[this._subStorageName];
+ }
+
+ Create() {
+ this.storage.add(this.props);
+ }
+
+ Find() {
+ return this.storage._data.find(entry =>
+ this._fields.every(field => entry[field] === this.props[field])
+ );
+ }
+
+ Update() {
+ const {guid} = this.Find();
+ this.storage.update(guid, this.updateProps, true);
+ }
+
+ Remove() {
+ const {guid} = this.Find();
+ this.storage.remove(guid);
+ }
+}
+
+function DumpStorage(subStorageName) {
+ Logger.logInfo(`\ndumping ${subStorageName} list\n`, true);
+ const entries = profileStorage[subStorageName]._data;
+ for (const entry of entries) {
+ Logger.logInfo(JSON.stringify(entry), true);
+ }
+ Logger.logInfo(`\n\nend ${subStorageName} list\n`, true);
+}
+
+const ADDRESS_FIELDS = [
+ "given-name",
+ "additional-name",
+ "family-name",
+ "organization",
+ "street-address",
+ "address-level2",
+ "address-level1",
+ "postal-code",
+ "country",
+ "tel",
+ "email",
+];
+
+class Address extends FormAutofillBase {
+ constructor(props) {
+ super(props, "addresses", ADDRESS_FIELDS);
+ }
+}
+
+function DumpAddresses() {
+ DumpStorage("addresses");
+}
+
+const CREDIT_CARD_FIELDS = [
+ "cc-name",
+ "cc-number",
+ "cc-exp-month",
+ "cc-exp-year",
+];
+
+class CreditCard extends FormAutofillBase {
+ constructor(props) {
+ super(props, "creditCards", CREDIT_CARD_FIELDS);
+ }
+
+ Find() {
+ return this.storage._data.find(entry => {
+ entry["cc-number"] = MasterPassword.decryptSync(entry["cc-number-encrypted"]);
+ return this._fields.every(field => entry[field] === this.props[field]);
+ });
+ }
+}
+
+function DumpCreditCards() {
+ DumpStorage("creditCards");
+}
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -7,16 +7,17 @@
* listed symbols will exposed on import, and only when and where imported.
*/
var EXPORTED_SYMBOLS = ["ACTIONS", "TPS"];
var module = this;
// Global modules
+ChromeUtils.import("resource://formautofill/FormAutofillSync.jsm");
ChromeUtils.import("resource://gre/modules/Log.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
ChromeUtils.import("resource://gre/modules/Timer.jsm");
ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
@@ -32,16 +33,17 @@ ChromeUtils.import("resource://services-
ChromeUtils.import("resource://services-sync/engines/forms.js");
ChromeUtils.import("resource://services-sync/engines/addons.js");
// TPS modules
ChromeUtils.import("resource://tps/logger.jsm");
// Module wrappers for tests
ChromeUtils.import("resource://tps/modules/addons.jsm");
ChromeUtils.import("resource://tps/modules/bookmarks.jsm");
+ChromeUtils.import("resource://tps/modules/formautofill.jsm");
ChromeUtils.import("resource://tps/modules/forms.jsm");
ChromeUtils.import("resource://tps/modules/history.jsm");
ChromeUtils.import("resource://tps/modules/passwords.jsm");
ChromeUtils.import("resource://tps/modules/prefs.jsm");
ChromeUtils.import("resource://tps/modules/tabs.jsm");
ChromeUtils.import("resource://tps/modules/windows.jsm");
var hh = Cc["@mozilla.org/network/protocol;1?name=http"]
@@ -570,16 +572,88 @@ var TPS = {
Logger.logPass("executing action " + action.toUpperCase() +
" on bookmarks");
} catch (e) {
await DumpBookmarks();
throw (e);
}
},
+ async HandleAddresses(addresses, action) {
+ try {
+ for (let address of addresses) {
+ Logger.logInfo("executing action " + action.toUpperCase() +
+ " on address " + JSON.stringify(address));
+ let addressOb = new Address(address);
+ switch (action) {
+ case ACTION_ADD:
+ addressOb.Create();
+ break;
+ case ACTION_MODIFY:
+ addressOb.Update();
+ break;
+ case ACTION_VERIFY:
+ Logger.AssertTrue(addressOb.Find(), "address not found");
+ break;
+ case ACTION_VERIFY_NOT:
+ Logger.AssertTrue(!addressOb.Find(),
+ "address found, but it shouldn't exist");
+ break;
+ case ACTION_DELETE:
+ Logger.AssertTrue(addressOb.Find(), "address not found");
+ addressOb.Remove();
+ break;
+ default:
+ Logger.AssertTrue(false, "invalid action: " + action);
+ }
+ }
+ Logger.logPass("executing action " + action.toUpperCase() +
+ " on addresses");
+ } catch (e) {
+ DumpAddresses();
+ throw (e);
+ }
+ },
+
+ async HandleCreditCards(creditCards, action) {
+ try {
+ for (let creditCard of creditCards) {
+ Logger.logInfo("executing action " + action.toUpperCase() +
+ " on creditCard " + JSON.stringify(creditCard));
+ let creditCardOb = new CreditCard(creditCard);
+ switch (action) {
+ case ACTION_ADD:
+ creditCardOb.Create();
+ break;
+ case ACTION_MODIFY:
+ creditCardOb.Update();
+ break;
+ case ACTION_VERIFY:
+ Logger.AssertTrue(creditCardOb.Find(), "creditCard not found");
+ break;
+ case ACTION_VERIFY_NOT:
+ Logger.AssertTrue(!creditCardOb.Find(),
+ "creditCard found, but it shouldn't exist");
+ break;
+ case ACTION_DELETE:
+ Logger.AssertTrue(creditCardOb.Find(), "creditCard not found");
+ creditCardOb.Remove();
+ break;
+ default:
+ Logger.AssertTrue(false, "invalid action: " + action);
+ }
+ }
+ Logger.logPass("executing action " + action.toUpperCase() +
+ " on creditCards");
+ } catch (e) {
+ DumpCreditCards();
+ throw (e);
+ }
+ },
+
async Cleanup() {
try {
await this.WipeServer();
} catch (ex) {
Logger.logError("Failed to wipe server: " + Log.exceptionStr(ex));
}
try {
if (await Authentication.isLoggedIn()) {
@@ -1186,16 +1260,34 @@ var Addons = {
async verifyNot(addons) {
await TPS.HandleAddons(addons, ACTION_VERIFY_NOT);
},
skipValidation() {
TPS.shouldValidateAddons = false;
}
};
+var Addresses = {
+ async add(addresses) {
+ await this.HandleAddresses(addresses, ACTION_ADD);
+ },
+ async modify(addresses) {
+ await this.HandleAddresses(addresses, ACTION_MODIFY);
+ },
+ async delete(addresses) {
+ await this.HandleAddresses(addresses, ACTION_DELETE);
+ },
+ async verify(addresses) {
+ await this.HandleAddresses(addresses, ACTION_VERIFY);
+ },
+ async verifyNot(addresses) {
+ await this.HandleAddresses(addresses, ACTION_VERIFY_NOT);
+ }
+};
+
var Bookmarks = {
async add(bookmarks) {
await TPS.HandleBookmarks(bookmarks, ACTION_ADD);
},
async modify(bookmarks) {
await TPS.HandleBookmarks(bookmarks, ACTION_MODIFY);
},
async delete(bookmarks) {
@@ -1207,16 +1299,34 @@ var Bookmarks = {
async verifyNot(bookmarks) {
await TPS.HandleBookmarks(bookmarks, ACTION_VERIFY_NOT);
},
skipValidation() {
TPS.shouldValidateBookmarks = false;
}
};
+var CreditCards = {
+ async add(creditCards) {
+ await this.HandleCreditCards(creditCards, ACTION_ADD);
+ },
+ async modify(creditCards) {
+ await this.HandleCreditCards(creditCards, ACTION_MODIFY);
+ },
+ async delete(creditCards) {
+ await this.HandleCreditCards(creditCards, ACTION_DELETE);
+ },
+ async verify(creditCards) {
+ await this.HandleCreditCards(creditCards, ACTION_VERIFY);
+ },
+ async verifyNot(creditCards) {
+ await this.HandleCreditCards(creditCards, ACTION_VERIFY_NOT);
+ }
+};
+
var Formdata = {
async add(formdata) {
await this.HandleForms(formdata, ACTION_ADD);
},
async delete(formdata) {
await this.HandleForms(formdata, ACTION_DELETE);
},
async verify(formdata) {
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -63,16 +63,17 @@
"ExtensionXPCShellUtils.jsm": ["ExtensionTestUtils"],
"NativeManifests.jsm": ["NativeManifests"],
"fakeservices.js": ["FakeCryptoService", "FakeFilesystemService", "FakeGUIDService", "fakeSHA256HMAC"],
"file_expandosharing.jsm": ["checkFromJSM"],
"file_stringencoding.jsm": ["checkFromJSM"],
"file_url.jsm": ["checkFromJSM"],
"file_worker_url.jsm": ["checkFromJSM"],
"Finder.jsm": ["Finder", "GetClipboardSearchString"],
+ "formautofill.jsm": ["Address", "CreditCard", "DumpAddresses", "DumpCreditCards"],
"forms.js": ["FormEngine", "FormRec", "FormValidator"],
"forms.jsm": ["FormData"],
"FormAutofillHeuristics.jsm": ["FormAutofillHeuristics", "LabelUtils"],
"FormAutofillSync.jsm": ["AddressesEngine", "CreditCardsEngine"],
"FormAutofillUtils.jsm": ["FormAutofillUtils", "AddressDataLoader"],
"FrameScriptManager.jsm": ["getNewLoaderID"],
"fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
"fxaccounts.jsm": ["Authentication"],