michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Preferences.jsm"); michael@0: Cu.import("resource://gre/modules/services/datareporting/policy.jsm"); michael@0: Cu.import("resource://testing-common/services/datareporting/mocks.jsm"); michael@0: Cu.import("resource://gre/modules/UpdateChannel.jsm"); michael@0: michael@0: function getPolicy(name, michael@0: aCurrentPolicyVersion = 1, michael@0: aMinimumPolicyVersion = 1, michael@0: aBranchMinimumVersionOverride) { michael@0: let branch = "testing.datareporting." + name; michael@0: michael@0: // The version prefs should not be removed on reset, so set them in the michael@0: // default branch. michael@0: let defaultPolicyPrefs = new Preferences({ branch: branch + ".policy." michael@0: , defaultBranch: true }); michael@0: defaultPolicyPrefs.set("currentPolicyVersion", aCurrentPolicyVersion); michael@0: defaultPolicyPrefs.set("minimumPolicyVersion", aMinimumPolicyVersion); michael@0: let branchOverridePrefName = "minimumPolicyVersion.channel-" + UpdateChannel.get(false); michael@0: if (aBranchMinimumVersionOverride !== undefined) michael@0: defaultPolicyPrefs.set(branchOverridePrefName, aBranchMinimumVersionOverride); michael@0: else michael@0: defaultPolicyPrefs.reset(branchOverridePrefName); michael@0: michael@0: let policyPrefs = new Preferences(branch + ".policy."); michael@0: let healthReportPrefs = new Preferences(branch + ".healthreport."); michael@0: michael@0: let listener = new MockPolicyListener(); michael@0: let policy = new DataReportingPolicy(policyPrefs, healthReportPrefs, listener); michael@0: michael@0: return [policy, policyPrefs, healthReportPrefs, listener]; michael@0: } michael@0: michael@0: function defineNow(policy, now) { michael@0: print("Adjusting fake system clock to " + now); michael@0: Object.defineProperty(policy, "now", { michael@0: value: function customNow() { michael@0: return now; michael@0: }, michael@0: writable: true, michael@0: }); michael@0: } michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: } michael@0: michael@0: add_test(function test_constructor() { michael@0: let policyPrefs = new Preferences("foo.bar.policy."); michael@0: let hrPrefs = new Preferences("foo.bar.healthreport."); michael@0: let listener = { michael@0: onRequestDataUpload: function() {}, michael@0: onRequestRemoteDelete: function() {}, michael@0: onNotifyDataPolicy: function() {}, michael@0: }; michael@0: michael@0: let policy = new DataReportingPolicy(policyPrefs, hrPrefs, listener); michael@0: do_check_true(Date.now() - policy.firstRunDate.getTime() < 1000); michael@0: michael@0: let tomorrow = Date.now() + 24 * 60 * 60 * 1000; michael@0: do_check_true(tomorrow - policy.nextDataSubmissionDate.getTime() < 1000); michael@0: michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_UNNOTIFIED); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_prefs() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("prefs"); michael@0: michael@0: let now = new Date(); michael@0: let nowT = now.getTime(); michael@0: michael@0: policy.firstRunDate = now; michael@0: do_check_eq(policyPrefs.get("firstRunTime"), nowT); michael@0: do_check_eq(policy.firstRunDate.getTime(), nowT); michael@0: michael@0: policy.dataSubmissionPolicyNotifiedDate= now; michael@0: do_check_eq(policyPrefs.get("dataSubmissionPolicyNotifiedTime"), nowT); michael@0: do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), nowT); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = now; michael@0: do_check_eq(policyPrefs.get("dataSubmissionPolicyResponseTime"), nowT); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseDate.getTime(), nowT); michael@0: michael@0: policy.dataSubmissionPolicyResponseType = "type-1"; michael@0: do_check_eq(policyPrefs.get("dataSubmissionPolicyResponseType"), "type-1"); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, "type-1"); michael@0: michael@0: policy.dataSubmissionEnabled = false; michael@0: do_check_false(policyPrefs.get("dataSubmissionEnabled", true)); michael@0: do_check_false(policy.dataSubmissionEnabled); michael@0: michael@0: policy.dataSubmissionPolicyAccepted = false; michael@0: do_check_false(policyPrefs.get("dataSubmissionPolicyAccepted", true)); michael@0: do_check_false(policy.dataSubmissionPolicyAccepted); michael@0: michael@0: do_check_false(policy.dataSubmissionPolicyBypassAcceptance); michael@0: policyPrefs.set("dataSubmissionPolicyBypassAcceptance", true); michael@0: do_check_true(policy.dataSubmissionPolicyBypassAcceptance); michael@0: michael@0: policy.lastDataSubmissionRequestedDate = now; michael@0: do_check_eq(hrPrefs.get("lastDataSubmissionRequestedTime"), nowT); michael@0: do_check_eq(policy.lastDataSubmissionRequestedDate.getTime(), nowT); michael@0: michael@0: policy.lastDataSubmissionSuccessfulDate = now; michael@0: do_check_eq(hrPrefs.get("lastDataSubmissionSuccessfulTime"), nowT); michael@0: do_check_eq(policy.lastDataSubmissionSuccessfulDate.getTime(), nowT); michael@0: michael@0: policy.lastDataSubmissionFailureDate = now; michael@0: do_check_eq(hrPrefs.get("lastDataSubmissionFailureTime"), nowT); michael@0: do_check_eq(policy.lastDataSubmissionFailureDate.getTime(), nowT); michael@0: michael@0: policy.nextDataSubmissionDate = now; michael@0: do_check_eq(hrPrefs.get("nextDataSubmissionTime"), nowT); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), nowT); michael@0: michael@0: policy.currentDaySubmissionFailureCount = 2; michael@0: do_check_eq(hrPrefs.get("currentDaySubmissionFailureCount", 0), 2); michael@0: do_check_eq(policy.currentDaySubmissionFailureCount, 2); michael@0: michael@0: policy.pendingDeleteRemoteData = true; michael@0: do_check_true(hrPrefs.get("pendingDeleteRemoteData")); michael@0: do_check_true(policy.pendingDeleteRemoteData); michael@0: michael@0: policy.healthReportUploadEnabled = false; michael@0: do_check_false(hrPrefs.get("uploadEnabled")); michael@0: do_check_false(policy.healthReportUploadEnabled); michael@0: michael@0: do_check_false(policy.healthReportUploadLocked); michael@0: hrPrefs.lock("uploadEnabled"); michael@0: do_check_true(policy.healthReportUploadLocked); michael@0: hrPrefs.unlock("uploadEnabled"); michael@0: do_check_false(policy.healthReportUploadLocked); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_notify_state_prefs() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notify_state_prefs"); michael@0: michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_UNNOTIFIED); michael@0: michael@0: policy._dataSubmissionPolicyNotifiedDate = new Date(); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_WAIT); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = new Date(); michael@0: policy._dataSubmissionPolicyNotifiedDate = null; michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_COMPLETE); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_task(function test_initial_submission_notification() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("initial_submission_notification"); michael@0: michael@0: do_check_eq(listener.notifyUserCount, 0); michael@0: michael@0: // Fresh instances should not do anything initially. michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 0); michael@0: michael@0: // We still shouldn't notify up to the millisecond before the barrier. michael@0: defineNow(policy, new Date(policy.firstRunDate.getTime() + michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC - 1)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 0); michael@0: do_check_null(policy._dataSubmissionPolicyNotifiedDate); michael@0: do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), 0); michael@0: michael@0: // We have crossed the threshold. We should see notification. michael@0: defineNow(policy, new Date(policy.firstRunDate.getTime() + michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 1); michael@0: yield listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: do_check_true(policy._dataSubmissionPolicyNotifiedDate instanceof Date); michael@0: do_check_true(policy.dataSubmissionPolicyNotifiedDate.getTime() > 0); michael@0: do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), michael@0: policy._dataSubmissionPolicyNotifiedDate.getTime()); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_WAIT); michael@0: }); michael@0: michael@0: add_test(function test_bypass_acceptance() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("bypass_acceptance"); michael@0: michael@0: policyPrefs.set("dataSubmissionPolicyBypassAcceptance", true); michael@0: do_check_false(policy.dataSubmissionPolicyAccepted); michael@0: do_check_true(policy.dataSubmissionPolicyBypassAcceptance); michael@0: defineNow(policy, new Date(policy.nextDataSubmissionDate.getTime())); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_task(function test_notification_implicit_acceptance() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_implicit_acceptance"); michael@0: michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime() - michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 1); michael@0: yield listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, "none-recorded"); michael@0: michael@0: do_check_true(5000 < policy.IMPLICIT_ACCEPTANCE_INTERVAL_MSEC); michael@0: defineNow(policy, new Date(now.getTime() + 5000)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 1); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_WAIT); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseDate.getTime(), 0); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, "none-recorded"); michael@0: michael@0: defineNow(policy, new Date(now.getTime() + policy.IMPLICIT_ACCEPTANCE_INTERVAL_MSEC + 1)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 1); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_COMPLETE); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseDate.getTime(), policy.now().getTime()); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, "accepted-implicit-time-elapsed"); michael@0: }); michael@0: michael@0: add_task(function test_notification_rejected() { michael@0: // User notification failed. We should not record it as being presented. michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_failed"); michael@0: michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime() - michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, 1); michael@0: yield listener.lastNotifyRequest.onUserNotifyFailed(new Error("testing failed.")); michael@0: do_check_null(policy._dataSubmissionPolicyNotifiedDate); michael@0: do_check_eq(policy.dataSubmissionPolicyNotifiedDate.getTime(), 0); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_UNNOTIFIED); michael@0: }); michael@0: michael@0: add_task(function test_notification_accepted() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_accepted"); michael@0: michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime() - michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: yield listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_WAIT); michael@0: do_check_false(policy.dataSubmissionPolicyAccepted); michael@0: listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: listener.lastNotifyRequest.onUserAccept("foo-bar"); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_COMPLETE); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, "accepted-foo-bar"); michael@0: do_check_true(policy.dataSubmissionPolicyAccepted); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseDate.getTime(), now.getTime()); michael@0: }); michael@0: michael@0: add_task(function test_notification_rejected() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("notification_rejected"); michael@0: michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime() - michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC + 1); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: yield listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_WAIT); michael@0: do_check_false(policy.dataSubmissionPolicyAccepted); michael@0: listener.lastNotifyRequest.onUserReject(); michael@0: do_check_eq(policy.notifyState, policy.STATE_NOTIFY_COMPLETE); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, "rejected-no-reason"); michael@0: do_check_false(policy.dataSubmissionPolicyAccepted); michael@0: michael@0: // No requests for submission should occur if user has rejected. michael@0: defineNow(policy, new Date(policy.nextDataSubmissionDate.getTime() + 10000)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: }); michael@0: michael@0: add_test(function test_submission_kill_switch() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_kill_switch"); michael@0: michael@0: policy.firstRunDate = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); michael@0: policy.nextDataSubmissionDate = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: policy.recordUserAcceptance("accept-old-ack"); michael@0: do_check_true(policyPrefs.has("dataSubmissionPolicyAcceptedVersion")); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: defineNow(policy, michael@0: new Date(Date.now() + policy.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC + 100)); michael@0: policy.dataSubmissionEnabled = false; michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_upload_kill_switch() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("upload_kill_switch"); michael@0: michael@0: defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); michael@0: policy.recordUserAcceptance(); michael@0: defineNow(policy, policy.nextDataSubmissionDate); michael@0: michael@0: // So that we don't trigger deletions, which cause uploads to be delayed. michael@0: hrPrefs.ignore("uploadEnabled", policy.uploadEnabledObserver); michael@0: michael@0: policy.healthReportUploadEnabled = false; michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: policy.healthReportUploadEnabled = true; michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_data_submission_no_data() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_no_data"); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: policy.dataSubmissionPolicyAccepted = true; michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime() + 1); michael@0: defineNow(policy, now); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: listener.lastDataRequest.onNoDataAvailable(); michael@0: michael@0: // The next trigger should try again. michael@0: defineNow(policy, new Date(now.getTime() + 155 * 60 * 1000)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 2); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_task(function test_data_submission_submit_failure_hard() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_submit_failure_hard"); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: policy.dataSubmissionPolicyAccepted = true; michael@0: let nextDataSubmissionDate = policy.nextDataSubmissionDate; michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime() + 1); michael@0: defineNow(policy, now); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: yield listener.lastDataRequest.onSubmissionFailureHard(); michael@0: do_check_eq(listener.lastDataRequest.state, michael@0: listener.lastDataRequest.SUBMISSION_FAILURE_HARD); michael@0: michael@0: let expected = new Date(now.getTime() + 24 * 60 * 60 * 1000); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), expected.getTime()); michael@0: michael@0: defineNow(policy, new Date(now.getTime() + 10)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: }); michael@0: michael@0: add_task(function test_data_submission_submit_try_again() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("data_submission_failure_soft"); michael@0: michael@0: policy.recordUserAcceptance(); michael@0: let nextDataSubmissionDate = policy.nextDataSubmissionDate; michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime()); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: yield listener.lastDataRequest.onSubmissionFailureSoft(); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), michael@0: nextDataSubmissionDate.getTime() + 15 * 60 * 1000); michael@0: }); michael@0: michael@0: add_task(function test_submission_daily_scheduling() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_daily_scheduling"); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: policy.dataSubmissionPolicyAccepted = true; michael@0: let nextDataSubmissionDate = policy.nextDataSubmissionDate; michael@0: michael@0: // Skip ahead to next submission date. We should get a submission request. michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime()); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: do_check_eq(policy.lastDataSubmissionRequestedDate.getTime(), now.getTime()); michael@0: michael@0: let finishedDate = new Date(now.getTime() + 250); michael@0: defineNow(policy, new Date(finishedDate.getTime() + 50)); michael@0: yield listener.lastDataRequest.onSubmissionSuccess(finishedDate); michael@0: do_check_eq(policy.lastDataSubmissionSuccessfulDate.getTime(), finishedDate.getTime()); michael@0: michael@0: // Next scheduled submission should be exactly 1 day after the reported michael@0: // submission success. michael@0: michael@0: let nextScheduled = new Date(finishedDate.getTime() + 24 * 60 * 60 * 1000); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), nextScheduled.getTime()); michael@0: michael@0: // Fast forward some arbitrary time. We shouldn't do any work yet. michael@0: defineNow(policy, new Date(now.getTime() + 40000)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: defineNow(policy, nextScheduled); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 2); michael@0: yield listener.lastDataRequest.onSubmissionSuccess(new Date(nextScheduled.getTime() + 200)); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), michael@0: new Date(nextScheduled.getTime() + 24 * 60 * 60 * 1000 + 200).getTime()); michael@0: }); michael@0: michael@0: add_test(function test_submission_far_future_scheduling() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_far_future_scheduling"); michael@0: michael@0: let now = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: defineNow(policy, now); michael@0: policy.recordUserAcceptance(); michael@0: now = new Date(); michael@0: defineNow(policy, now); michael@0: michael@0: let nextDate = policy._futureDate(3 * 24 * 60 * 60 * 1000 - 1); michael@0: policy.nextDataSubmissionDate = nextDate; michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), nextDate.getTime()); michael@0: michael@0: policy.nextDataSubmissionDate = new Date(nextDate.getTime() + 1); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), michael@0: policy._futureDate(24 * 60 * 60 * 1000).getTime()); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_task(function test_submission_backoff() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_backoff"); michael@0: michael@0: do_check_eq(policy.FAILURE_BACKOFF_INTERVALS.length, 2); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: policy.dataSubmissionPolicyAccepted = true; michael@0: michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime()); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: do_check_eq(policy.currentDaySubmissionFailureCount, 0); michael@0: michael@0: now = new Date(now.getTime() + 5000); michael@0: defineNow(policy, now); michael@0: michael@0: // On first soft failure we should back off by scheduled interval. michael@0: yield listener.lastDataRequest.onSubmissionFailureSoft(); michael@0: do_check_eq(policy.currentDaySubmissionFailureCount, 1); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), michael@0: new Date(now.getTime() + policy.FAILURE_BACKOFF_INTERVALS[0]).getTime()); michael@0: do_check_eq(policy.lastDataSubmissionFailureDate.getTime(), now.getTime()); michael@0: michael@0: // Should not request submission until scheduled. michael@0: now = new Date(policy.nextDataSubmissionDate.getTime() - 1); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: // 2nd request for submission. michael@0: now = new Date(policy.nextDataSubmissionDate.getTime()); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 2); michael@0: michael@0: now = new Date(now.getTime() + 5000); michael@0: defineNow(policy, now); michael@0: michael@0: // On second failure we should back off by more. michael@0: yield listener.lastDataRequest.onSubmissionFailureSoft(); michael@0: do_check_eq(policy.currentDaySubmissionFailureCount, 2); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), michael@0: new Date(now.getTime() + policy.FAILURE_BACKOFF_INTERVALS[1]).getTime()); michael@0: michael@0: now = new Date(policy.nextDataSubmissionDate.getTime()); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 3); michael@0: michael@0: now = new Date(now.getTime() + 5000); michael@0: defineNow(policy, now); michael@0: michael@0: // On 3rd failure we should back off by a whole day. michael@0: yield listener.lastDataRequest.onSubmissionFailureSoft(); michael@0: do_check_eq(policy.currentDaySubmissionFailureCount, 0); michael@0: do_check_eq(policy.nextDataSubmissionDate.getTime(), michael@0: new Date(now.getTime() + 24 * 60 * 60 * 1000).getTime()); michael@0: }); michael@0: michael@0: // Ensure that only one submission request can be active at a time. michael@0: add_test(function test_submission_expiring() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("submission_expiring"); michael@0: michael@0: policy.dataSubmissionPolicyResponseDate = new Date(Date.now() - 24 * 60 * 60 * 1000); michael@0: policy.dataSubmissionPolicyAccepted = true; michael@0: let nextDataSubmission = policy.nextDataSubmissionDate; michael@0: let now = new Date(policy.nextDataSubmissionDate.getTime()); michael@0: defineNow(policy, now); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: defineNow(policy, new Date(now.getTime() + 500)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: defineNow(policy, new Date(policy.now().getTime() + michael@0: policy.SUBMISSION_REQUEST_EXPIRE_INTERVAL_MSEC)); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 2); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_task(function test_delete_remote_data() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data"); michael@0: michael@0: do_check_false(policy.pendingDeleteRemoteData); michael@0: let nextSubmissionDate = policy.nextDataSubmissionDate; michael@0: michael@0: let now = new Date(); michael@0: defineNow(policy, now); michael@0: michael@0: policy.deleteRemoteData(); michael@0: do_check_true(policy.pendingDeleteRemoteData); michael@0: do_check_neq(nextSubmissionDate.getTime(), michael@0: policy.nextDataSubmissionDate.getTime()); michael@0: do_check_eq(now.getTime(), policy.nextDataSubmissionDate.getTime()); michael@0: michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: do_check_true(listener.lastRemoteDeleteRequest.isDelete); michael@0: defineNow(policy, policy._futureDate(1000)); michael@0: michael@0: yield listener.lastRemoteDeleteRequest.onSubmissionSuccess(policy.now()); michael@0: do_check_false(policy.pendingDeleteRemoteData); michael@0: }); michael@0: michael@0: // Ensure that deletion requests take priority over regular data submission. michael@0: add_test(function test_delete_remote_data_priority() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_priority"); michael@0: michael@0: let now = new Date(); michael@0: defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); michael@0: policy.recordUserAcceptance(); michael@0: defineNow(policy, new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000)); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: policy._inProgressSubmissionRequest = null; michael@0: michael@0: policy.deleteRemoteData(); michael@0: policy.checkStateAndTrigger(); michael@0: michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: add_test(function test_delete_remote_data_backoff() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_backoff"); michael@0: michael@0: let now = new Date(); michael@0: defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); michael@0: policy.recordUserAcceptance(); michael@0: defineNow(policy, now); michael@0: policy.nextDataSubmissionDate = now; michael@0: policy.deleteRemoteData(); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: defineNow(policy, policy._futureDate(1000)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: michael@0: defineNow(policy, policy._futureDate(500)); michael@0: listener.lastRemoteDeleteRequest.onSubmissionFailureSoft(); michael@0: defineNow(policy, policy._futureDate(50)); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: michael@0: defineNow(policy, policy._futureDate(policy.FAILURE_BACKOFF_INTERVALS[0] - 50)); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 2); michael@0: michael@0: run_next_test(); michael@0: }); michael@0: michael@0: // If we request delete while an upload is in progress, delete should be michael@0: // scheduled immediately after upload. michael@0: add_task(function test_delete_remote_data_in_progress_upload() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("delete_remote_data_in_progress_upload"); michael@0: michael@0: let now = new Date(); michael@0: defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000)); michael@0: policy.recordUserAcceptance(); michael@0: defineNow(policy, policy.nextDataSubmissionDate); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: defineNow(policy, policy._futureDate(50 * 1000)); michael@0: michael@0: // If we request a delete during a pending request, nothing should be done. michael@0: policy.deleteRemoteData(); michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 0); michael@0: michael@0: // Now wait a little bit and finish the request. michael@0: defineNow(policy, policy._futureDate(10 * 1000)); michael@0: yield listener.lastDataRequest.onSubmissionSuccess(policy._futureDate(1000)); michael@0: defineNow(policy, policy._futureDate(5000)); michael@0: michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: }); michael@0: michael@0: add_test(function test_polling() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("polling"); michael@0: let intended = 500; michael@0: let acceptable = 250; // Because nsITimer doesn't guarantee times. michael@0: michael@0: // Ensure checkStateAndTrigger is called at a regular interval. michael@0: let then = Date.now(); michael@0: print("Starting run: " + then); michael@0: Object.defineProperty(policy, "POLL_INTERVAL_MSEC", { michael@0: value: intended, michael@0: }); michael@0: let count = 0; michael@0: michael@0: Object.defineProperty(policy, "checkStateAndTrigger", { michael@0: value: function fakeCheckStateAndTrigger() { michael@0: let now = Date.now(); michael@0: let after = now - then; michael@0: count++; michael@0: michael@0: print("Polled at " + now + " after " + after + "ms, intended " + intended); michael@0: do_check_true(after >= acceptable); michael@0: DataReportingPolicy.prototype.checkStateAndTrigger.call(policy); michael@0: michael@0: if (count >= 2) { michael@0: policy.stopPolling(); michael@0: michael@0: do_check_eq(listener.notifyUserCount, 0); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: michael@0: run_next_test(); michael@0: } michael@0: michael@0: // "Specified timer period will be at least the time between when michael@0: // processing for last firing the callback completes and when the next michael@0: // firing occurs." michael@0: // michael@0: // That means we should set 'then' at the *end* of our handler, not michael@0: // earlier. michael@0: then = Date.now(); michael@0: } michael@0: }); michael@0: policy.startPolling(); michael@0: }); michael@0: michael@0: // Ensure that implicit acceptance of policy is resolved through polling. michael@0: // michael@0: // This is probably covered by other tests. But, it's best to have explicit michael@0: // coverage from a higher-level. michael@0: add_test(function test_polling_implicit_acceptance() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("polling_implicit_acceptance"); michael@0: michael@0: // Redefine intervals with shorter, test-friendly values. michael@0: Object.defineProperty(policy, "POLL_INTERVAL_MSEC", { michael@0: value: 250, michael@0: }); michael@0: michael@0: Object.defineProperty(policy, "IMPLICIT_ACCEPTANCE_INTERVAL_MSEC", { michael@0: value: 700, michael@0: }); michael@0: michael@0: let count = 0; michael@0: michael@0: // Track JS elapsed time, so we can decide if we've waited for enough ticks. michael@0: let start; michael@0: Object.defineProperty(policy, "checkStateAndTrigger", { michael@0: value: function CheckStateAndTriggerProxy() { michael@0: count++; michael@0: let now = Date.now(); michael@0: let delta = now - start; michael@0: print("checkStateAndTrigger count: " + count + ", now " + now + michael@0: ", delta " + delta); michael@0: michael@0: // Account for some slack. michael@0: DataReportingPolicy.prototype.checkStateAndTrigger.call(policy); michael@0: michael@0: // What should happen on different invocations: michael@0: // michael@0: // 1) We are inside the prompt interval so user gets prompted. michael@0: // 2) still ~300ms away from implicit acceptance michael@0: // 3) still ~50ms away from implicit acceptance michael@0: // 4) Implicit acceptance recorded. Data submission requested. michael@0: // 5) Request still pending. No new submission requested. michael@0: // michael@0: // Note that, due to the inaccuracy of timers, 4 might not happen until 5 michael@0: // firings have occurred. Yay. So we watch times, not just counts. michael@0: michael@0: do_check_eq(listener.notifyUserCount, 1); michael@0: michael@0: if (count == 1) { michael@0: listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: } michael@0: michael@0: if (delta <= (policy.IMPLICIT_ACCEPTANCE_INTERVAL_MSEC + policy.POLL_INTERVAL_MSEC)) { michael@0: do_check_false(policy.dataSubmissionPolicyAccepted); michael@0: do_check_eq(listener.requestDataUploadCount, 0); michael@0: } else if (count > 3) { michael@0: do_check_true(policy.dataSubmissionPolicyAccepted); michael@0: do_check_eq(policy.dataSubmissionPolicyResponseType, michael@0: "accepted-implicit-time-elapsed"); michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: } michael@0: michael@0: if ((count > 4) && policy.dataSubmissionPolicyAccepted) { michael@0: do_check_eq(listener.requestDataUploadCount, 1); michael@0: policy.stopPolling(); michael@0: run_next_test(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: policy.firstRunDate = new Date(Date.now() - 4 * 24 * 60 * 60 * 1000); michael@0: policy.nextDataSubmissionDate = new Date(Date.now()); michael@0: start = Date.now(); michael@0: policy.startPolling(); michael@0: }); michael@0: michael@0: add_task(function test_record_health_report_upload_enabled() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("record_health_report_upload_enabled"); michael@0: michael@0: // Preconditions. michael@0: do_check_false(policy.pendingDeleteRemoteData); michael@0: do_check_true(policy.healthReportUploadEnabled); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 0); michael@0: michael@0: // User intent to disable should immediately result in a pending michael@0: // delete request. michael@0: policy.recordHealthReportUploadEnabled(false, "testing 1 2 3"); michael@0: do_check_false(policy.healthReportUploadEnabled); michael@0: do_check_true(policy.pendingDeleteRemoteData); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 1); michael@0: michael@0: // Fulfilling it should make it go away. michael@0: yield listener.lastRemoteDeleteRequest.onNoDataAvailable(); michael@0: do_check_false(policy.pendingDeleteRemoteData); michael@0: michael@0: // User intent to enable should get us back to default state. michael@0: policy.recordHealthReportUploadEnabled(true, "testing 1 2 3"); michael@0: do_check_false(policy.pendingDeleteRemoteData); michael@0: do_check_true(policy.healthReportUploadEnabled); michael@0: }); michael@0: michael@0: add_test(function test_pref_change_initiates_deletion() { michael@0: let [policy, policyPrefs, hrPrefs, listener] = getPolicy("record_health_report_upload_enabled"); michael@0: michael@0: // Preconditions. michael@0: do_check_false(policy.pendingDeleteRemoteData); michael@0: do_check_true(policy.healthReportUploadEnabled); michael@0: do_check_eq(listener.requestRemoteDeleteCount, 0); michael@0: michael@0: // User intent to disable should indirectly result in a pending michael@0: // delete request, because the policy is watching for the pref michael@0: // to change. michael@0: Object.defineProperty(policy, "deleteRemoteData", { michael@0: value: function deleteRemoteDataProxy() { michael@0: do_check_false(policy.healthReportUploadEnabled); michael@0: do_check_false(policy.pendingDeleteRemoteData); // Just called. michael@0: michael@0: run_next_test(); michael@0: }, michael@0: }); michael@0: michael@0: hrPrefs.set("uploadEnabled", false); michael@0: }); michael@0: michael@0: add_task(function* test_policy_version() { michael@0: let policy, policyPrefs, hrPrefs, listener, now, firstRunTime; michael@0: function createPolicy(shouldBeAccepted = false, michael@0: currentPolicyVersion = 1, minimumPolicyVersion = 1, michael@0: branchMinimumVersionOverride) { michael@0: [policy, policyPrefs, hrPrefs, listener] = michael@0: getPolicy("policy_version_test", currentPolicyVersion, michael@0: minimumPolicyVersion, branchMinimumVersionOverride); michael@0: let firstRun = now === undefined; michael@0: if (firstRun) { michael@0: firstRunTime = policy.firstRunDate.getTime(); michael@0: do_check_true(firstRunTime > 0); michael@0: now = new Date(policy.firstRunDate.getTime() + michael@0: policy.SUBMISSION_NOTIFY_INTERVAL_MSEC); michael@0: } michael@0: else { michael@0: // The first-run time should not be reset even after policy-version michael@0: // upgrades. michael@0: do_check_eq(policy.firstRunDate.getTime(), firstRunTime); michael@0: } michael@0: defineNow(policy, now); michael@0: do_check_eq(policy.dataSubmissionPolicyAccepted, shouldBeAccepted); michael@0: } michael@0: michael@0: function* triggerPolicyCheckAndEnsureNotified(notified = true, accept = true) { michael@0: policy.checkStateAndTrigger(); michael@0: do_check_eq(listener.notifyUserCount, Number(notified)); michael@0: if (notified) { michael@0: yield listener.lastNotifyRequest.onUserNotifyComplete(); michael@0: if (accept) { michael@0: listener.lastNotifyRequest.onUserAccept("because,"); michael@0: do_check_true(policy.dataSubmissionPolicyAccepted); michael@0: do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"), michael@0: policyPrefs.get("currentPolicyVersion")); michael@0: } michael@0: else { michael@0: do_check_false(policyPrefs.has("dataSubmissionPolicyAcceptedVersion")); michael@0: } michael@0: } michael@0: } michael@0: michael@0: createPolicy(); michael@0: yield triggerPolicyCheckAndEnsureNotified(); michael@0: michael@0: // We shouldn't be notified again if the current version is still valid; michael@0: createPolicy(true); michael@0: yield triggerPolicyCheckAndEnsureNotified(false); michael@0: michael@0: // Just increasing the current version isn't enough. The minimum michael@0: // version must be changed. michael@0: let currentPolicyVersion = policyPrefs.get("currentPolicyVersion"); michael@0: let minimumPolicyVersion = policyPrefs.get("minimumPolicyVersion"); michael@0: createPolicy(true, ++currentPolicyVersion, minimumPolicyVersion); michael@0: yield triggerPolicyCheckAndEnsureNotified(false); michael@0: do_check_true(policy.dataSubmissionPolicyAccepted); michael@0: do_check_eq(policyPrefs.get("dataSubmissionPolicyAcceptedVersion"), michael@0: minimumPolicyVersion); michael@0: michael@0: // Increase the minimum policy version and check if we're notified. michael@0: createPolicy(false, currentPolicyVersion, ++minimumPolicyVersion); michael@0: do_check_false(policyPrefs.has("dataSubmissionPolicyAcceptedVersion")); michael@0: yield triggerPolicyCheckAndEnsureNotified(); michael@0: michael@0: // Test increasing the minimum version just on the current channel. michael@0: createPolicy(true, currentPolicyVersion, minimumPolicyVersion); michael@0: yield triggerPolicyCheckAndEnsureNotified(false); michael@0: createPolicy(false, ++currentPolicyVersion, minimumPolicyVersion, minimumPolicyVersion + 1); michael@0: yield triggerPolicyCheckAndEnsureNotified(true); michael@0: });