From 80740451d8b141d36ce659886a1956980bd9d46a Mon Sep 17 00:00:00 2001 From: Gregory Szorc Date: Fri, 9 Nov 2012 13:59:40 -0800 Subject: [PATCH] Bug 810132 - Add remote deletion requests to policy; r=rnewman --- .../healthreport/modules-testing/mocks.jsm | 17 +- services/healthreport/policy.jsm | 241 ++++++++++++++---- .../tests/xpcshell/test_policy.js | 182 +++++++++++-- 3 files changed, 355 insertions(+), 85 deletions(-) diff --git a/services/healthreport/modules-testing/mocks.jsm b/services/healthreport/modules-testing/mocks.jsm index fbd377c751e..ea0b40a1174 100644 --- a/services/healthreport/modules-testing/mocks.jsm +++ b/services/healthreport/modules-testing/mocks.jsm @@ -15,20 +15,29 @@ this.MockPolicyListener = function MockPolicyListener() { this._log = Log4Moz.repository.getLogger("HealthReport.Testing.MockPolicyListener"); this._log.level = Log4Moz.Level["Debug"]; - this.requestDataSubmissionCount = 0; + this.requestDataUploadCount = 0; this.lastDataRequest = null; + this.requestRemoteDeleteCount = 0; + this.lastRemoteDeleteRequest = null; + this.notifyUserCount = 0; this.lastNotifyRequest = null; } MockPolicyListener.prototype = { - onRequestDataSubmission: function onRequestDataSubmission(request) { - this._log.info("onRequestDataSubmission invoked."); - this.requestDataSubmissionCount++; + onRequestDataUpload: function onRequestDataUpload(request) { + this._log.info("onRequestDataUpload invoked."); + this.requestDataUploadCount++; this.lastDataRequest = request; }, + onRequestRemoteDelete: function onRequestRemoteDelete(request) { + this._log.info("onRequestRemoteDelete invoked."); + this.requestRemoteDeleteCount++; + this.lastRemoteDeleteRequest = request; + }, + onNotifyDataPolicy: function onNotifyDataPolicy(request) { this._log.info("onNotifyUser invoked."); this.notifyUserCount++; diff --git a/services/healthreport/policy.jsm b/services/healthreport/policy.jsm index 0310d473bed..ff181c213dd 100644 --- a/services/healthreport/policy.jsm +++ b/services/healthreport/policy.jsm @@ -107,7 +107,9 @@ Object.freeze(NotifyPolicyRequest.prototype); /** * Represents a request to submit data. * - * Instances of this are created when the policy requests data submission. + * Instances of this are created when the policy requests data upload or + * deletion. + * * Receivers are expected to call one of the provided on* functions to signal * completion of the request. * @@ -115,9 +117,10 @@ Object.freeze(NotifyPolicyRequest.prototype); * Receivers of instances of this type should not attempt to do anything with * the instance except call one of the on* methods. */ -function DataSubmissionRequest(promise, expiresDate) { +function DataSubmissionRequest(promise, expiresDate, isDelete) { this.promise = promise; this.expiresDate = expiresDate; + this.isDelete = isDelete; this.state = null; this.reason = null; @@ -131,6 +134,10 @@ DataSubmissionRequest.prototype = { /** * No submission was attempted because no data was available. + * + * In the case of upload, this means there is no data to upload (perhaps + * it isn't available yet). In case of remote deletion, it means that there + * is no remote data to delete. */ onNoDataAvailable: function onNoDataAvailable() { this.state = this.NO_DATA_AVAILABLE; @@ -140,6 +147,9 @@ DataSubmissionRequest.prototype = { /** * Data submission has completed successfully. * + * In case of upload, this means the upload completed successfully. In case + * of deletion, the data was deleted successfully. + * * @param date * (Date) When data submission occurred. */ @@ -204,11 +214,16 @@ Object.freeze(DataSubmissionRequest.prototype); * The listener passed into the instance must have the following properties * (which are callbacks that will be invoked at certain key events): * - * * onRequestDataSubmission(request) - Called when the policy is requesting + * * onRequestDataUpload(request) - Called when the policy is requesting * data to be submitted. The function is passed a `DataSubmissionRequest`. * The listener should call one of the special resolving functions on that * instance (see the documentation for that type). * + * * onRequestRemoteDelete(request) - Called when the policy is requesting + * deletion of remotely stored data. The function is passed a + * `DataSubmissionRequest`. The listener should call one of the special + * resolving functions on that instance (just like `onRequestDataUpload`). + * * * onNotifyDataPolicy(request) - Called when the policy is requesting the * user to be notified that data submission will occur. The function * receives a `NotifyPolicyRequest` instance. The callee should call one or @@ -321,7 +336,11 @@ HealthReportPolicy.prototype = { STATE_NOTIFY_WAIT: "waiting", STATE_NOTIFY_COMPLETE: "ok", - REQUIRED_LISTENERS: ["onRequestDataSubmission", "onNotifyDataPolicy"], + REQUIRED_LISTENERS: [ + "onRequestDataUpload", + "onRequestRemoteDelete", + "onNotifyDataPolicy", + ], /** * The first time the health report policy came into existence. @@ -402,8 +421,8 @@ HealthReportPolicy.prototype = { /** * Whether submission of data is allowed. * - * This is the master switch for data submission. If it is off, we will - * never submit data, even if the user has agreed to it. + * This is the master switch for remote server communication. If it is + * false, we never request upload or deletion. */ get dataSubmissionEnabled() { // Default is true because we are opt-out. @@ -414,6 +433,22 @@ HealthReportPolicy.prototype = { this._prefs.set("dataSubmissionEnabled", !!value); }, + /** + * Whether upload of data is allowed. + * + * This is a kill switch for upload. It is meant to reflect a system or + * deployment policy decision. User intent should be reflected in the + * "dataSubmissionPolicy" prefs. + */ + get dataUploadEnabled() { + // Default is true because we are opt-out. + return this._prefs.get("dataUploadEnabled", true); + }, + + set dataUploadEnabled(value) { + this._prefs.set("dataUploadEnabled", !!value); + }, + /** * Whether the user has accepted that data submission can occur. * @@ -541,6 +576,21 @@ HealthReportPolicy.prototype = { this._prefs.set("currentDaySubmissionFailureCount", value); }, + /** + * Whether a request to delete remote data is awaiting completion. + * + * If this is true, the policy will request that remote data be deleted. + * Furthermore, no new data will be uploaded (if it's even allowed) until + * the remote deletion is fulfilled. + */ + get pendingDeleteRemoteData() { + return !!this._prefs.get("pendingDeleteRemoteData", false); + }, + + set pendingDeleteRemoteData(value) { + this._prefs.set("pendingDeleteRemoteData", !!value); + }, + /** * Record user acceptance of data submission policy. * @@ -578,6 +628,25 @@ HealthReportPolicy.prototype = { this.dataSubmissionPolicyAccepted = false; }, + /** + * Request that remote data be deleted. + * + * This will record an intent that previously uploaded data is to be deleted. + * The policy will eventually issue a request to the listener for data + * deletion. It will keep asking for deletion until the listener acknowledges + * that data has been deleted. + */ + deleteRemoteData: function deleteRemoteData(reason="no-reason") { + this._log.info("Remote data deletion requested: " + reason); + + this.pendingDeleteRemoteData = true; + + // We want delete deletion to occur as soon as possible. Move up any + // pending scheduled data submission and try to trigger. + this.nextDataSubmissionDate = this.now(); + this.checkStateAndTrigger(); + }, + /** * Start background polling for activity. * @@ -654,7 +723,29 @@ HealthReportPolicy.prototype = { // should be pretty safe. this._moveScheduleForward24h(); - // Fall through and prompt for user notification, if necessary. + // Fall through since we may have other actions. + } + + // Tend to any in progress work. + if (this._processInProgressSubmission()) { + return; + } + + // Requests to delete remote data take priority above everything else. + if (this.pendingDeleteRemoteData) { + if (nowT < nextSubmissionDate.getTime()) { + this._log.debug("Deletion request is scheduled for the future: " + + nextSubmissionDate); + return; + } + + this._dispatchSubmissionRequest("onRequestRemoteDelete", true); + return; + } + + if (!this.dataUploadEnabled) { + this._log.debug("Data upload is disabled. Doing nothing."); + return; } // If the user hasn't responded to the data policy, don't do anything. @@ -677,53 +768,7 @@ HealthReportPolicy.prototype = { return; } - if (this._inProgressSubmissionRequest) { - if (this._inProgressSubmissionRequest.expiresDate.getTime() > nowT) { - this._log.info("Waiting on in-progress submission request to finish."); - return; - } - - this._log.warn("Old submission request has expired from no activity."); - this._inProgressSubmissionRequest.promise.reject(new Error("Request has expired.")); - this._inProgressSubmissionRequest = null; - if (!this._handleSubmissionFailure()) { - return; - } - } - - // We're past our scheduled next data submission date, so let's do it! - this.lastDataSubmissionRequestedDate = now; - let deferred = Promise.defer(); - let requestExpiresDate = - this._futureDate(this.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC); - this._inProgressSubmissionRequest = new DataSubmissionRequest(deferred, - requestExpiresDate); - - let onSuccess = function onSuccess(result) { - this._inProgressSubmissionRequest = null; - this._handleSubmissionResult(result); - }.bind(this); - - let onError = function onError(error) { - this._log.error("Error when handling data submission result: " + - CommonUtils.exceptionStr(result)); - this._inProgressSubmissionRequest = null; - this._handleSubmissionFailure(); - }.bind(this); - - deferred.promise.then(onSuccess, onError); - - this._log.info("Requesting data submission. Will expire at " + - requestExpiresDate); - try { - this._listener.onRequestDataSubmission(this._inProgressSubmissionRequest); - } catch (ex) { - this._log.warn("Exception when calling onRequestDataSubmission: " + - CommonUtils.exceptionStr(ex)); - this._inProgressSubmissionRequest = null; - this._handleSubmissionFailure(); - return; - } + this._dispatchSubmissionRequest("onRequestDataUpload", false); }, /** @@ -797,26 +842,109 @@ HealthReportPolicy.prototype = { return true; }, + _processInProgressSubmission: function _processInProgressSubmission() { + if (!this._inProgressSubmissionRequest) { + return false; + } + + let now = this.now().getTime(); + if (this._inProgressSubmissionRequest.expiresDate.getTime() > now) { + this._log.info("Waiting on in-progress submission request to finish."); + return true; + } + + this._log.warn("Old submission request has expired from no activity."); + this._inProgressSubmissionRequest.promise.reject(new Error("Request has expired.")); + this._inProgressSubmissionRequest = null; + this._handleSubmissionFailure(); + + return false; + }, + + _dispatchSubmissionRequest: function _dispatchSubmissionRequest(handler, isDelete) { + let now = this.now(); + + // We're past our scheduled next data submission date, so let's do it! + this.lastDataSubmissionRequestedDate = now; + let deferred = Promise.defer(); + let requestExpiresDate = + this._futureDate(this.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC); + this._inProgressSubmissionRequest = new DataSubmissionRequest(deferred, + requestExpiresDate, + isDelete); + + let onSuccess = function onSuccess(result) { + this._inProgressSubmissionRequest = null; + this._handleSubmissionResult(result); + }.bind(this); + + let onError = function onError(error) { + this._log.error("Error when handling data submission result: " + + CommonUtils.exceptionStr(result)); + this._inProgressSubmissionRequest = null; + this._handleSubmissionFailure(); + }.bind(this); + + deferred.promise.then(onSuccess, onError); + + this._log.info("Requesting data submission. Will expire at " + + requestExpiresDate); + try { + this._listener[handler](this._inProgressSubmissionRequest); + } catch (ex) { + this._log.warn("Exception when calling " + handler + ": " + + CommonUtils.exceptionStr(ex)); + this._inProgressSubmissionRequest = null; + this._handleSubmissionFailure(); + return; + } + }, + _handleSubmissionResult: function _handleSubmissionResult(request) { let state = request.state; let reason = request.reason || "no reason"; this._log.info("Got submission request result: " + state); if (state == request.SUBMISSION_SUCCESS) { - this._log.info("Successful data submission reported."); + if (request.isDelete) { + this.pendingDeleteRemoteData = false; + this._log.info("Successful data delete reported."); + } else { + this._log.info("Successful data upload reported."); + } + this.lastDataSubmissionSuccessfulDate = request.submissionDate; - this.nextDataSubmissionDate = + + let nextSubmissionDate = new Date(request.submissionDate.getTime() + MILLISECONDS_PER_DAY); + + // Schedule pending deletes immediately. This has potential to overload + // the server. However, the frequency of delete requests across all + // clients should be low, so this shouldn't pose a problem. + if (this.pendingDeleteRemoteData) { + nextSubmissionDate = this.now(); + } + + this.nextDataSubmissionDate = nextSubmissionDate; this.currentDaySubmissionFailureCount = 0; return; } if (state == request.NO_DATA_AVAILABLE) { + if (request.isDelete) { + this._log.info("Remote data delete requested but no remote data was stored."); + this.pendingDeleteRemoteData = false; + return; + } + this._log.info("No data was available to submit. May try later."); this._handleSubmissionFailure(); return; } + // We don't special case request.isDelete for these failures because it + // likely means there was a server error. + if (state == request.SUBMISSION_FAILURE_SOFT) { this._log.warn("Soft error submitting data: " + reason); this.lastDataSubmissionFailureDate = this.now(); @@ -862,3 +990,4 @@ HealthReportPolicy.prototype = { }; Object.freeze(HealthReportPolicy.prototype); + diff --git a/services/healthreport/tests/xpcshell/test_policy.js b/services/healthreport/tests/xpcshell/test_policy.js index 0eb4e5e2c17..a91d23cb4f2 100644 --- a/services/healthreport/tests/xpcshell/test_policy.js +++ b/services/healthreport/tests/xpcshell/test_policy.js @@ -34,7 +34,8 @@ function run_test() { add_test(function test_constructor() { let prefs = new Preferences("foo.bar"); let listener = { - onRequestDataSubmission: function() {}, + onRequestDataUpload: function() {}, + onRequestRemoteDelete: function() {}, onNotifyDataPolicy: function() {}, }; @@ -99,6 +100,10 @@ add_test(function test_prefs() { do_check_eq(prefs.get("currentDaySubmissionFailureCount", 0), 2); do_check_eq(policy.currentDaySubmissionFailureCount, 2); + policy.pendingDeleteRemoteData = true; + do_check_true(prefs.get("pendingDeleteRemoteData")); + do_check_true(policy.pendingDeleteRemoteData); + run_next_test(); }); @@ -233,7 +238,7 @@ add_test(function test_notification_rejected() { // No requests for submission should occur if user has rejected. defineNow(policy, new Date(policy.nextDataSubmissionDate.getTime() + 10000)); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 0); + do_check_eq(listener.requestDataUploadCount, 0); run_next_test(); }); @@ -245,13 +250,30 @@ add_test(function test_submission_kill_switch() { policy.nextDataSubmissionDate = new Date(Date.now() - 24 * 60 * 60 * 1000); policy.recordUserAcceptance("accept-old-ack"); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); defineNow(policy, new Date(Date.now() + policy.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC + 100)); policy.dataSubmissionEnabled = false; policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); + + run_next_test(); +}); + +add_test(function test_upload_kill_switch() { + let [policy, prefs, listener] = getPolicy("upload_kill_switch"); + + defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); + policy.recordUserAcceptance(); + defineNow(policy, policy.nextDataSubmissionDate); + + policy.dataUploadEnabled = false; + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 0); + policy.dataUploadEnabled = true; + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 1); run_next_test(); }); @@ -263,15 +285,15 @@ add_test(function test_data_submission_no_data() { policy.dataSubmissionPolicyAccepted = true; let now = new Date(policy.nextDataSubmissionDate.getTime() + 1); defineNow(policy, now); - do_check_eq(listener.requestDataSubmissionCount, 0); + do_check_eq(listener.requestDataUploadCount, 0); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); listener.lastDataRequest.onNoDataAvailable(); // The next trigger should try again. defineNow(policy, new Date(now.getTime() + 155 * 60 * 1000)); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 2); + do_check_eq(listener.requestDataUploadCount, 2); run_next_test(); }); @@ -286,7 +308,7 @@ add_test(function test_data_submission_submit_failure_hard() { defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); listener.lastDataRequest.onSubmissionFailureHard(); do_check_eq(listener.lastDataRequest.state, listener.lastDataRequest.SUBMISSION_FAILURE_HARD); @@ -296,7 +318,7 @@ add_test(function test_data_submission_submit_failure_hard() { defineNow(policy, new Date(now.getTime() + 10)); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); run_next_test(); }); @@ -327,7 +349,7 @@ add_test(function test_submission_daily_scheduling() { let now = new Date(policy.nextDataSubmissionDate.getTime()); defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); do_check_eq(policy.lastDataSubmissionRequestedDate.getTime(), now.getTime()); let finishedDate = new Date(now.getTime() + 250); @@ -344,11 +366,11 @@ add_test(function test_submission_daily_scheduling() { // Fast forward some arbitrary time. We shouldn't do any work yet. defineNow(policy, new Date(now.getTime() + 40000)); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); defineNow(policy, nextScheduled); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 2); + do_check_eq(listener.requestDataUploadCount, 2); listener.lastDataRequest.onSubmissionSuccess(new Date(nextScheduled.getTime() + 200)); do_check_eq(policy.nextDataSubmissionDate.getTime(), new Date(nextScheduled.getTime() + 24 * 60 * 60 * 1000 + 200).getTime()); @@ -368,12 +390,12 @@ add_test(function test_submission_far_future_scheduling() { let nextDate = policy._futureDate(3 * 24 * 60 * 60 * 1000 - 1); policy.nextDataSubmissionDate = nextDate; policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 0); + do_check_eq(listener.requestDataUploadCount, 0); do_check_eq(policy.nextDataSubmissionDate.getTime(), nextDate.getTime()); policy.nextDataSubmissionDate = new Date(nextDate.getTime() + 1); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 0); + do_check_eq(listener.requestDataUploadCount, 0); do_check_eq(policy.nextDataSubmissionDate.getTime(), policy._futureDate(24 * 60 * 60 * 1000).getTime()); @@ -391,7 +413,7 @@ add_test(function test_submission_backoff() { let now = new Date(policy.nextDataSubmissionDate.getTime()); defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); do_check_eq(policy.currentDaySubmissionFailureCount, 0); now = new Date(now.getTime() + 5000); @@ -408,13 +430,13 @@ add_test(function test_submission_backoff() { now = new Date(policy.nextDataSubmissionDate.getTime() - 1); defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); // 2nd request for submission. now = new Date(policy.nextDataSubmissionDate.getTime()); defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 2); + do_check_eq(listener.requestDataUploadCount, 2); now = new Date(now.getTime() + 5000); defineNow(policy, now); @@ -428,7 +450,7 @@ add_test(function test_submission_backoff() { now = new Date(policy.nextDataSubmissionDate.getTime()); defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 3); + do_check_eq(listener.requestDataUploadCount, 3); now = new Date(now.getTime() + 5000); defineNow(policy, now); @@ -452,16 +474,126 @@ add_test(function test_submission_expiring() { let now = new Date(policy.nextDataSubmissionDate.getTime()); defineNow(policy, now); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); defineNow(policy, new Date(now.getTime() + 500)); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); defineNow(policy, new Date(policy.now().getTime() + policy.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC)); policy.checkStateAndTrigger(); - do_check_eq(listener.requestDataSubmissionCount, 2); + do_check_eq(listener.requestDataUploadCount, 2); + + run_next_test(); +}); + +add_test(function test_delete_remote_data() { + let [policy, prefs, listener] = getPolicy("delete_remote_data"); + + do_check_false(policy.pendingDeleteRemoteData); + let nextSubmissionDate = policy.nextDataSubmissionDate; + + let now = new Date(); + defineNow(policy, now); + + policy.deleteRemoteData(); + do_check_true(policy.pendingDeleteRemoteData); + do_check_neq(nextSubmissionDate.getTime(), + policy.nextDataSubmissionDate.getTime()); + do_check_eq(now.getTime(), policy.nextDataSubmissionDate.getTime()); + + do_check_eq(listener.requestRemoteDeleteCount, 1); + do_check_true(listener.lastRemoteDeleteRequest.isDelete); + defineNow(policy, policy._futureDate(1000)); + + listener.lastRemoteDeleteRequest.onSubmissionSuccess(policy.now()); + do_check_false(policy.pendingDeleteRemoteData); + + run_next_test(); +}); + +// Ensure that deletion requests take priority over regular data submission. +add_test(function test_delete_remote_data_priority() { + let [policy, prefs, listener] = getPolicy("delete_remote_data_priority"); + + let now = new Date(); + defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); + policy.recordUserAcceptance(); + defineNow(policy, new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000)); + + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 1); + policy._inProgressSubmissionRequest = null; + + policy.deleteRemoteData(); + policy.checkStateAndTrigger(); + + do_check_eq(listener.requestRemoteDeleteCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); + + run_next_test(); +}); + +add_test(function test_delete_remote_data_backoff() { + let [policy, prefs, listener] = getPolicy("delete_remote_data_backoff"); + + let now = new Date(); + defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); + policy.recordUserAcceptance(); + defineNow(policy, now); + policy.nextDataSubmissionDate = now; + policy.deleteRemoteData(); + + policy.checkStateAndTrigger(); + do_check_eq(listener.requestRemoteDeleteCount, 1); + defineNow(policy, policy._futureDate(1000)); + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 0); + do_check_eq(listener.requestRemoteDeleteCount, 1); + + defineNow(policy, policy._futureDate(500)); + listener.lastRemoteDeleteRequest.onSubmissionFailureSoft(); + defineNow(policy, policy._futureDate(50)); + + policy.checkStateAndTrigger(); + do_check_eq(listener.requestRemoteDeleteCount, 1); + + defineNow(policy, policy._futureDate(policy.FAILURE_BACKOFF_INTERVALS[0] - 50)); + policy.checkStateAndTrigger(); + do_check_eq(listener.requestRemoteDeleteCount, 2); + + run_next_test(); +}); + +// If we request delete while an upload is in progress, delete should be +// scheduled immediately after upload. +add_test(function test_delete_remote_data_in_progress_upload() { + let [policy, prefs, listener] = getPolicy("delete_remote_data_in_progress_upload"); + + let now = new Date(); + defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); + policy.recordUserAcceptance(); + defineNow(policy, policy.nextDataSubmissionDate); + + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 1); + defineNow(policy, policy._futureDate(50 * 1000)); + + // If we request a delete during a pending request, nothing should be done. + policy.deleteRemoteData(); + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 1); + do_check_eq(listener.requestRemoteDeleteCount, 0); + + // Now wait a little bit and finish the request. + defineNow(policy, policy._futureDate(10 * 1000)); + listener.lastDataRequest.onSubmissionSuccess(policy._futureDate(1000)); + defineNow(policy, policy._futureDate(5000)); + + policy.checkStateAndTrigger(); + do_check_eq(listener.requestDataUploadCount, 1); + do_check_eq(listener.requestRemoteDeleteCount, 1); run_next_test(); }); @@ -489,7 +621,7 @@ add_test(function test_polling() { policy.stopPolling(); do_check_eq(listener.notifyUserCount, 0); - do_check_eq(listener.requestDataSubmissionCount, 0); + do_check_eq(listener.requestDataUploadCount, 0); run_next_test(); } @@ -539,16 +671,16 @@ add_test(function test_polling_implicit_acceptance() { if (count < 4) { do_check_false(policy.dataSubmissionPolicyAccepted); - do_check_eq(listener.requestDataSubmissionCount, 0); + do_check_eq(listener.requestDataUploadCount, 0); } else { do_check_true(policy.dataSubmissionPolicyAccepted); do_check_eq(policy.dataSubmissionPolicyResponseType, "accepted-implicit-time-elapsed"); - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); } if (count > 4) { - do_check_eq(listener.requestDataSubmissionCount, 1); + do_check_eq(listener.requestDataUploadCount, 1); policy.stopPolling(); run_next_test(); }