summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/addrbook/test/unit/test_cardDAV_offline.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/addrbook/test/unit/test_cardDAV_offline.js')
-rw-r--r--comm/mailnews/addrbook/test/unit/test_cardDAV_offline.js550
1 files changed, 550 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/test/unit/test_cardDAV_offline.js b/comm/mailnews/addrbook/test/unit/test_cardDAV_offline.js
new file mode 100644
index 0000000000..fa32260328
--- /dev/null
+++ b/comm/mailnews/addrbook/test/unit/test_cardDAV_offline.js
@@ -0,0 +1,550 @@
+/* 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/. */
+
+// Tests that changes in a CardDAV directory when offline or unable to reach
+// the server are (a) visible in the client immediately, and (b) sent to the
+// server when it's next available.
+//
+// Note that we close the server rather than using Services.io.offline, as
+// the server is localhost and therefore not affected by the offline setting.
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+var directory, restart, useSyncV1;
+
+async function subtest() {
+ // Put some cards on the server.
+
+ CardDAVServer.putCardInternal(
+ "change-me.vcf",
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I shall be changed.\r\nEND:VCARD\r\n"
+ );
+ CardDAVServer.putCardInternal(
+ "delete-me.vcf",
+ "BEGIN:VCARD\r\nUID:delete-me\r\nFN:Please delete me.\r\nEND:VCARD\r\n"
+ );
+
+ directory = initDirectory();
+
+ info("Initial sync with server.");
+ await directory.fetchAllFromServer();
+
+ if (useSyncV1) {
+ directory._syncToken = null;
+ }
+
+ await subtestCreateCard();
+ await subtestUpdateCard();
+ await subtestDeleteCard();
+ await subtestCreateDeleteCard();
+ await subtestStillOffline();
+
+ // Check everything is still correct at the end.
+
+ info("Checking cards on client are correct.");
+ Assert.deepEqual(
+ directory.childCards.map(c => c.UID).sort(),
+ ["another-new-card", "change-me"],
+ "right cards remain on client"
+ );
+
+ await clearDirectory(directory);
+ CardDAVServer.reset();
+}
+
+function promiseSyncFailed() {
+ return TestUtils.topicObserved("addrbook-directory-sync-failed");
+}
+
+function promiseSyncSucceeded() {
+ return TestUtils.topicObserved("addrbook-directory-synced");
+}
+
+/**
+ * The behaviour should remain the same even if Thunderbird restarts.
+ * If `restart` is true, simulate restarting.
+ */
+async function pretendToRestart() {
+ // Ensure we've finished any async stuff.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 125));
+
+ if (!restart) {
+ return;
+ }
+
+ await directory.cleanUp();
+
+ info("Shutdown simulated, now restarting.");
+ directory = new CardDAVDirectory();
+ directory.init("jscarddav://carddav.sqlite");
+}
+
+/** Creating a new card while "offline". */
+async function subtestCreateCard() {
+ Assert.equal(
+ directory.childCards.length,
+ 2,
+ "card count on client before test"
+ );
+ Assert.equal(CardDAVServer.cards.size, 2, "card count on server before test");
+
+ info("Going offline, creating a new card.");
+ await CardDAVServer.close();
+
+ let contactPromise = TestUtils.topicObserved("addrbook-contact-created");
+ let syncFailedPromise = promiseSyncFailed();
+ let newCard = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
+ Ci.nsIAbCard
+ );
+ newCard.displayName = "A New Card";
+ newCard.UID = "a-new-card";
+ newCard = directory.addCard(newCard);
+ await contactPromise;
+ await syncFailedPromise;
+
+ Assert.equal(
+ directory.childCards.length,
+ 3,
+ "card should have been added on client while offline"
+ );
+ Assert.ok(
+ directory.childCards.find(c => c.UID == "a-new-card"),
+ "card should have been added on client"
+ );
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 2,
+ "card should NOT have been added on server while offline"
+ );
+
+ info("Going online and syncing.");
+ await pretendToRestart(directory);
+ CardDAVServer.reopen();
+
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 2,
+ "card should NOT have been added on server before syncing"
+ );
+
+ contactPromise = TestUtils.topicObserved("addrbook-contact-updated");
+ let syncSucceededPromise = promiseSyncSucceeded();
+ await directory.syncWithServer();
+ await syncSucceededPromise;
+ let [notificationCard] = await contactPromise;
+ notificationCard.QueryInterface(Ci.nsIAbCard);
+ Assert.equal(
+ notificationCard.UID,
+ "a-new-card",
+ "correct card should have been updated"
+ );
+
+ Assert.equal(
+ notificationCard.getProperty("_href", "WRONG"),
+ `${CardDAVServer.path}a-new-card.vcf`,
+ "card should have been given _href property"
+ );
+ Assert.equal(
+ notificationCard.getProperty("_etag", "WRONG"),
+ "68",
+ "card should have been given _etag property"
+ );
+ vCardEqual(
+ notificationCard.getProperty("_vCard", "WRONG"),
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:A New Card\r\nUID:a-new-card\r\nEND:VCARD\r\n",
+ "card should have been given _vCard property"
+ );
+
+ await checkCardsOnServer({
+ ["change-me"]: {
+ etag: "63",
+ href: `${CardDAVServer.path}change-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I shall be changed.\r\nEND:VCARD\r\n",
+ },
+ ["delete-me"]: {
+ etag: "61",
+ href: `${CardDAVServer.path}delete-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:delete-me\r\nFN:Please delete me.\r\nEND:VCARD\r\n",
+ },
+ ["a-new-card"]: {
+ etag: "68",
+ href: `${CardDAVServer.path}a-new-card.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:A New Card\r\nUID:a-new-card\r\nEND:VCARD\r\n",
+ },
+ });
+}
+
+/** Changing an existing card while "offline". */
+async function subtestUpdateCard() {
+ Assert.equal(
+ directory.childCards.length,
+ 3,
+ "card count on client before test"
+ );
+ Assert.equal(CardDAVServer.cards.size, 3, "card count on server before test");
+
+ info("Going offline, changing a card.");
+ await CardDAVServer.close();
+
+ let contactPromise = TestUtils.topicObserved("addrbook-contact-updated");
+ let syncFailedPromise = promiseSyncFailed();
+ let cardToChange = directory.childCards.find(c => c.UID == "change-me");
+ cardToChange.displayName = "I'm a new man!";
+ cardToChange = directory.modifyCard(cardToChange);
+ await contactPromise;
+ await syncFailedPromise;
+
+ Assert.equal(
+ directory.childCards.find(c => c.UID == "change-me").displayName,
+ "I'm a new man!",
+ "card should have been changed on client while offline"
+ );
+ Assert.stringContains(
+ CardDAVServer.cards.get(`${CardDAVServer.path}change-me.vcf`).vCard,
+ "I shall be changed.",
+ "card should NOT have been changed on server while offline"
+ );
+
+ info("Going online and syncing.");
+ await pretendToRestart(directory);
+ CardDAVServer.reopen();
+
+ Assert.stringContains(
+ CardDAVServer.cards.get(`${CardDAVServer.path}change-me.vcf`).vCard,
+ "I shall be changed.",
+ "card should NOT have been changed on server before syncing"
+ );
+
+ contactPromise = TestUtils.topicObserved("addrbook-contact-updated");
+ let syncSucceededPromise = promiseSyncSucceeded();
+ await directory.syncWithServer();
+ await syncSucceededPromise;
+ let [notificationCard] = await contactPromise;
+ notificationCard.QueryInterface(Ci.nsIAbCard);
+ Assert.equal(
+ notificationCard.UID,
+ "change-me",
+ "correct card should have been updated"
+ );
+
+ Assert.equal(
+ notificationCard.getProperty("_href", "WRONG"),
+ `${CardDAVServer.path}change-me.vcf`,
+ "card _href property didn't change"
+ );
+ Assert.equal(
+ notificationCard.getProperty("_etag", "WRONG"),
+ "58",
+ "card _etag property did change"
+ );
+ vCardEqual(
+ notificationCard.getProperty("_vCard", "WRONG"),
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I'm a new man!\r\nEND:VCARD\r\n",
+ "card _vCard property did change"
+ );
+
+ await checkCardsOnServer({
+ ["change-me"]: {
+ etag: "58",
+ href: `${CardDAVServer.path}change-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I'm a new man!\r\nEND:VCARD\r\n",
+ },
+ ["delete-me"]: {
+ etag: "61",
+ href: `${CardDAVServer.path}delete-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:delete-me\r\nFN:Please delete me.\r\nEND:VCARD\r\n",
+ },
+ ["a-new-card"]: {
+ etag: "68",
+ href: `${CardDAVServer.path}a-new-card.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:A New Card\r\nUID:a-new-card\r\nEND:VCARD\r\n",
+ },
+ });
+}
+
+/** Deleting an existing card while "offline". */
+async function subtestDeleteCard() {
+ Assert.equal(
+ directory.childCards.length,
+ 3,
+ "card count on client before test"
+ );
+ Assert.equal(CardDAVServer.cards.size, 3, "card count on server before test");
+
+ info("Going offline, deleting a card.");
+ await CardDAVServer.close();
+
+ let contactPromise = TestUtils.topicObserved("addrbook-contact-deleted");
+ let syncFailedPromise = promiseSyncFailed();
+ let cardToDelete = directory.childCards.find(c => c.UID == "delete-me");
+ directory.deleteCards([cardToDelete]);
+ await contactPromise;
+ await syncFailedPromise;
+
+ Assert.equal(
+ directory.childCards.length,
+ 2,
+ "card should have been removed on client while offline"
+ );
+ Assert.ok(
+ !directory.childCards.find(c => c.UID == "delete-me"),
+ "card should have been removed on client while offline"
+ );
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 3,
+ "card should NOT have been removed on server while offline"
+ );
+
+ info("Going online and syncing.");
+ await pretendToRestart(directory);
+ CardDAVServer.reopen();
+
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 3,
+ "card should NOT have been removed on server before syncing"
+ );
+
+ let syncSucceededPromise = promiseSyncSucceeded();
+ await directory.syncWithServer();
+ await syncSucceededPromise;
+
+ await checkCardsOnServer({
+ ["change-me"]: {
+ etag: "58",
+ href: `${CardDAVServer.path}change-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I'm a new man!\r\nEND:VCARD\r\n",
+ },
+ ["a-new-card"]: {
+ etag: "68",
+ href: `${CardDAVServer.path}a-new-card.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:A New Card\r\nUID:a-new-card\r\nEND:VCARD\r\n",
+ },
+ });
+}
+
+/** Adding a new card and deleting it again while "offline". */
+async function subtestCreateDeleteCard() {
+ Assert.equal(
+ directory.childCards.length,
+ 2,
+ "card count on client before test"
+ );
+ Assert.equal(CardDAVServer.cards.size, 2, "card count on server before test");
+
+ info("Going offline, adding a card.");
+ await CardDAVServer.close();
+
+ let contactPromise = TestUtils.topicObserved("addrbook-contact-created");
+ let syncFailedPromise = promiseSyncFailed();
+ let newCard = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
+ Ci.nsIAbCard
+ );
+ newCard.displayName = "A Temporary Card";
+ newCard.UID = "a-temporary-card";
+ newCard = directory.addCard(newCard);
+ await contactPromise;
+ await syncFailedPromise;
+
+ // Ensure we've finished any async stuff.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 125));
+
+ Assert.equal(
+ directory.childCards.length,
+ 3,
+ "card should have been added on client while offline"
+ );
+ Assert.ok(
+ directory.childCards.find(c => c.UID == "a-temporary-card"),
+ "card should have been added on client while offline"
+ );
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 2,
+ "card should NOT have been added on server while offline"
+ );
+
+ info("Deleting the same card before syncing.");
+ contactPromise = TestUtils.topicObserved("addrbook-contact-deleted");
+ directory.deleteCards([newCard]);
+ await contactPromise;
+ // No addrbook-directory-sync-failed notification here, we didn't attempt to
+ // delete a card that wasn't on the server (it had no _href property).
+
+ Assert.equal(
+ directory.childCards.length,
+ 2,
+ "card should have been removed on client while offline"
+ );
+ Assert.ok(
+ !directory.childCards.find(c => c.UID == "a-temporary-card"),
+ "card should have been removed on client while offline"
+ );
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 2,
+ "card should NOT have been on server while offline"
+ );
+
+ info("Going online and syncing.");
+ await pretendToRestart(directory);
+ CardDAVServer.reopen();
+
+ let syncSucceededPromise = promiseSyncSucceeded();
+ await directory.syncWithServer();
+ await syncSucceededPromise;
+
+ await checkCardsOnServer({
+ ["change-me"]: {
+ etag: "58",
+ href: `${CardDAVServer.path}change-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I'm a new man!\r\nEND:VCARD\r\n",
+ },
+ ["a-new-card"]: {
+ etag: "68",
+ href: `${CardDAVServer.path}a-new-card.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:A New Card\r\nUID:a-new-card\r\nEND:VCARD\r\n",
+ },
+ });
+}
+
+/**
+ * Check that doing a sync while offline does nothing crazy. First make both
+ * kinds of changes, then sync while offline.
+ */
+async function subtestStillOffline() {
+ Assert.equal(
+ directory.childCards.length,
+ 2,
+ "card count on client before test"
+ );
+ Assert.equal(CardDAVServer.cards.size, 2, "card count on server before test");
+
+ info("Going offline, adding a card.");
+ await CardDAVServer.close();
+
+ let contactPromise = TestUtils.topicObserved("addrbook-contact-created");
+ let syncFailedPromise = promiseSyncFailed();
+ let newCard = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
+ Ci.nsIAbCard
+ );
+ newCard.displayName = "Another New Card";
+ newCard.UID = "another-new-card";
+ newCard = directory.addCard(newCard);
+ await contactPromise;
+ await syncFailedPromise;
+
+ // Ensure we've finished any async stuff.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 125));
+
+ Assert.equal(
+ directory.childCards.length,
+ 3,
+ "card should have been added on client while offline"
+ );
+ Assert.ok(
+ directory.childCards.find(c => c.UID == "another-new-card"),
+ "card should have been added on client while offline"
+ );
+ Assert.equal(
+ CardDAVServer.cards.size,
+ 2,
+ "card should NOT have been added on server while offline"
+ );
+
+ info("Still offline, deleting a card.");
+ let cardToDelete = directory.childCards.find(c => c.UID == "a-new-card");
+ contactPromise = TestUtils.topicObserved("addrbook-contact-deleted");
+ syncFailedPromise = promiseSyncFailed();
+ directory.deleteCards([cardToDelete]);
+ await contactPromise;
+ await syncFailedPromise;
+
+ // Ensure we've finished any async stuff.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 125));
+
+ info("Still offline, attempting to sync.");
+ syncFailedPromise = promiseSyncFailed();
+ // Assert.rejects eats the thrown exception, so we don't see it logged here.
+ await Assert.rejects(
+ directory.syncWithServer(),
+ /NS_ERROR_CONNECTION_REFUSED/,
+ "Attempt to sync threw an exception"
+ );
+ await syncFailedPromise;
+
+ await pretendToRestart();
+ syncFailedPromise = promiseSyncFailed();
+ // Assert.rejects eats the thrown exception, so we don't see it logged here.
+ await Assert.rejects(
+ directory.syncWithServer(),
+ /NS_ERROR_CONNECTION_REFUSED/,
+ "Attempt to sync threw an exception"
+ );
+ await syncFailedPromise;
+
+ info("Going online and syncing.");
+ await pretendToRestart(directory);
+ CardDAVServer.reopen();
+
+ let syncSucceededPromise = promiseSyncSucceeded();
+ await directory.syncWithServer();
+ await syncSucceededPromise;
+
+ await checkCardsOnServer({
+ ["change-me"]: {
+ etag: "58",
+ href: `${CardDAVServer.path}change-me.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nUID:change-me\r\nFN:I'm a new man!\r\nEND:VCARD\r\n",
+ },
+ ["another-new-card"]: {
+ etag: "80",
+ href: `${CardDAVServer.path}another-new-card.vcf`,
+ vCard:
+ "BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Another New Card\r\nUID:another-new-card\r\nEND:VCARD\r\n",
+ },
+ });
+}
+
+add_task(async function test_syncV1_noRestart() {
+ restart = false;
+ useSyncV1 = true;
+ await subtest();
+});
+
+add_task(async function test_syncV1_restart() {
+ restart = true;
+ useSyncV1 = true;
+ await subtest();
+});
+
+add_task(async function test_syncV2_noRestart() {
+ restart = false;
+ useSyncV1 = false;
+ await subtest();
+});
+
+add_task(async function test_syncV2_restart() {
+ restart = true;
+ useSyncV1 = false;
+ await subtest();
+});