summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/unit/test_telemetry.js
blob: 71c0cec1402c6d53c013a80457c9298513003096 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * Tests the statistics and other counters reported through telemetry.
 */

"use strict";

// Globals

const { TestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TestUtils.sys.mjs"
);

const MS_PER_DAY = 24 * 60 * 60 * 1000;

// To prevent intermittent failures when the test is executed at a time that is
// very close to a day boundary, we make it deterministic by using a static
// reference date for all the time-based statistics.
const gReferenceTimeMs = new Date("2000-01-01T00:00:00").getTime();

// Returns a milliseconds value to use with nsILoginMetaInfo properties, falling
// approximately in the middle of the specified number of days before the
// reference time, where zero days indicates a time within the past 24 hours.
const daysBeforeMs = days => gReferenceTimeMs - (days + 0.5) * MS_PER_DAY;

/**
 * Contains metadata that will be attached to test logins in order to verify
 * that the statistics collection is working properly. Most properties of the
 * logins are initialized to the default test values already.
 *
 * If you update this data or any of the telemetry histograms it checks, you'll
 * probably need to update the expected statistics in the test below.
 */
const StatisticsTestData = [
  {
    timeLastUsed: daysBeforeMs(0),
  },
  {
    timeLastUsed: daysBeforeMs(1),
  },
  {
    timeLastUsed: daysBeforeMs(7),
    formActionOrigin: null,
    httpRealm: "The HTTP Realm",
  },
  {
    username: "",
    timeLastUsed: daysBeforeMs(7),
  },
  {
    username: "",
    timeLastUsed: daysBeforeMs(30),
  },
  {
    username: "",
    timeLastUsed: daysBeforeMs(31),
  },
  {
    timeLastUsed: daysBeforeMs(365),
  },
  {
    username: "",
    timeLastUsed: daysBeforeMs(366),
  },
  {
    // If the login was saved in the future, it is ignored for statistiscs.
    timeLastUsed: daysBeforeMs(-1),
  },
  {
    timeLastUsed: daysBeforeMs(1000),
  },
];

/**
 * Triggers the collection of those statistics that are not accumulated each
 * time an action is taken, but are a static snapshot of the current state.
 */
async function triggerStatisticsCollection() {
  Services.obs.notifyObservers(null, "gather-telemetry", "" + gReferenceTimeMs);
  await TestUtils.topicObserved("passwordmgr-gather-telemetry-complete");
}

/**
 * Tests the telemetry histogram with the given ID contains only the specified
 * non-zero ranges, expressed in the format { range1: value1, range2: value2 }.
 */
function testHistogram(histogramId, expectedNonZeroRanges) {
  let snapshot = Services.telemetry.getHistogramById(histogramId).snapshot();

  // Compute the actual ranges in the format { range1: value1, range2: value2 }.
  let actualNonZeroRanges = {};
  for (let [range, value] of Object.entries(snapshot.values)) {
    if (value > 0) {
      actualNonZeroRanges[range] = value;
    }
  }

  // These are stringified to visualize the differences between the values.
  info("Testing histogram: " + histogramId);
  Assert.equal(
    JSON.stringify(actualNonZeroRanges),
    JSON.stringify(expectedNonZeroRanges)
  );
}

// Tests

/**
 * Enable local telemetry recording for the duration of the tests, and prepare
 * the test data that will be used by the following tests.
 */
add_task(function test_initialize() {
  let oldCanRecord = Services.telemetry.canRecordExtended;
  Services.telemetry.canRecordExtended = true;
  registerCleanupFunction(function() {
    Services.telemetry.canRecordExtended = oldCanRecord;
  });

  let uniqueNumber = 1;
  for (let loginModifications of StatisticsTestData) {
    loginModifications.origin = `http://${uniqueNumber++}.example.com`;
    let login;
    if (typeof loginModifications.httpRealm != "undefined") {
      login = TestData.authLogin(loginModifications);
    } else {
      login = TestData.formLogin(loginModifications);
    }
    Services.logins.addLogin(login);
  }
});

/**
 * Tests the collection of statistics related to login metadata.
 */
add_task(async function test_logins_statistics() {
  // Repeat the operation twice to test that histograms are not accumulated.
  for (let pass of [1, 2]) {
    info(`pass ${pass}`);
    await triggerStatisticsCollection();

    // Should record 1 in the bucket corresponding to the number of passwords.
    testHistogram("PWMGR_NUM_SAVED_PASSWORDS", { 10: 1 });

    // Should record 1 in the bucket corresponding to the number of passwords.
    testHistogram("PWMGR_NUM_HTTPAUTH_PASSWORDS", { 1: 1 });

    // For each saved login, should record 1 in the bucket corresponding to the
    // age in days since the login was last used.
    testHistogram("PWMGR_LOGIN_LAST_USED_DAYS", {
      0: 1,
      1: 1,
      7: 2,
      29: 2,
      356: 2,
      750: 1,
    });

    // Should record the number of logins without a username in bucket 0, and
    // the number of logins with a username in bucket 1.
    testHistogram("PWMGR_USERNAME_PRESENT", { 0: 4, 1: 6 });
  }
});

/**
 * Tests the collection of statistics related to hosts for which passowrd saving
 * has been explicitly disabled.
 */
add_task(async function test_disabledHosts_statistics() {
  // Should record 1 in the bucket corresponding to the number of sites for
  // which password saving is disabled.
  Services.logins.setLoginSavingEnabled("http://www.example.com", false);
  await triggerStatisticsCollection();
  testHistogram("PWMGR_BLOCKLIST_NUM_SITES", { 1: 1 });

  Services.logins.setLoginSavingEnabled("http://www.example.com", true);
  await triggerStatisticsCollection();
  testHistogram("PWMGR_BLOCKLIST_NUM_SITES", { 0: 1 });
});

/**
 * Tests the collection of statistics related to general settings.
 */
add_task(async function test_settings_statistics() {
  let oldRememberSignons = Services.prefs.getBoolPref("signon.rememberSignons");
  registerCleanupFunction(function() {
    Services.prefs.setBoolPref("signon.rememberSignons", oldRememberSignons);
  });

  // Repeat the operation twice per value to test that histograms are reset.
  for (let remember of [false, true, false, true]) {
    // This change should be observed immediately by the login service.
    Services.prefs.setBoolPref("signon.rememberSignons", remember);

    await triggerStatisticsCollection();

    // Should record 1 in either bucket 0 or bucket 1 based on the preference.
    testHistogram("PWMGR_SAVING_ENABLED", remember ? { 1: 1 } : { 0: 1 });
  }
});