summaryrefslogtreecommitdiffstats
path: root/services/settings/test/unit/test_remote_settings_offline.js
blob: d3403d4d899de605b58a365f818773fbac211d4f (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
const { RemoteSettingsClient } = ChromeUtils.importESModule(
  "resource://services-settings/RemoteSettingsClient.sys.mjs"
);
const { RemoteSettingsWorker } = ChromeUtils.importESModule(
  "resource://services-settings/RemoteSettingsWorker.sys.mjs"
);
const { SharedUtils } = ChromeUtils.import(
  "resource://services-settings/SharedUtils.jsm"
);

// A collection with a dump that's packaged on all builds where this test runs,
// including on Android at mobile/android/installer/package-manifest.in
const TEST_BUCKET = "main";
const TEST_COLLECTION = "password-recipes";

let client;
let DUMP_RECORDS;
let DUMP_LAST_MODIFIED;

add_task(async function setup() {
  // "services.settings.server" pref is not set.
  // Test defaults to an unreachable server,
  // and will only load from the dump if any.

  client = new RemoteSettingsClient(TEST_COLLECTION, {
    bucketName: TEST_BUCKET,
  });

  const dump = await SharedUtils.loadJSONDump(TEST_BUCKET, TEST_COLLECTION);
  DUMP_RECORDS = dump.data;
  DUMP_LAST_MODIFIED = dump.timestamp;

  // Dumps are fetched via the following, which sorts the records, newest first.
  // https://searchfox.org/mozilla-central/rev/5b3444ad300e244b5af4214212e22bd9e4b7088a/taskcluster/docker/periodic-updates/scripts/periodic_file_updates.sh#304
  equal(
    DUMP_LAST_MODIFIED,
    DUMP_RECORDS[0].last_modified,
    "records in dump ought to be sorted by last_modified"
  );
});

async function importData(records) {
  await RemoteSettingsWorker._execute("_test_only_import", [
    TEST_BUCKET,
    TEST_COLLECTION,
    records,
    records[0]?.last_modified || 0,
  ]);
}

async function clear_state() {
  await client.db.clear();
}

add_task(async function test_load_from_dump_when_offline() {
  // Baseline: verify that the collection is empty at first,
  // but non-empty after loading from the dump.
  const before = await client.get({ syncIfEmpty: false });
  equal(before.length, 0, "collection empty when offline");

  // should import from dump since collection was not initialized.
  const after = await client.get();
  equal(after.length, DUMP_RECORDS.length, "collection loaded from dump");
  equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp");
});
add_task(clear_state);

add_task(async function test_optional_skip_dump_after_empty_import() {
  // clear_state should have wiped the database.
  const before = await client.get({ syncIfEmpty: false });
  equal(before.length, 0, "collection empty after clearing");

  // Verify that the dump is not imported again by client.get()
  // when the database is initialized with an empty dump
  // with `loadDumpIfNewer` disabled.
  await importData([]); // <-- Empty set of records.

  const after = await client.get({ loadDumpIfNewer: false });
  equal(after.length, 0, "collection still empty due to import");
  equal(await client.getLastModified(), 0, "Empty dump has no timestamp");
});
add_task(clear_state);

add_task(async function test_optional_skip_dump_after_non_empty_import() {
  await importData([{ last_modified: 1234, id: "dummy" }]);

  const after = await client.get({ loadDumpIfNewer: false });
  equal(after.length, 1, "Imported dummy data");
  equal(await client.getLastModified(), 1234, "Expected timestamp of import");

  await importData([]);
  const after2 = await client.get({ loadDumpIfNewer: false });
  equal(after2.length, 0, "Previous data wiped on duplicate import");
  equal(await client.getLastModified(), 0, "Timestamp of empty collection");
});
add_task(clear_state);

add_task(async function test_load_dump_after_empty_import() {
  await importData([]); // <-- Empty set of records, i.e. last_modified = 0.

  const after = await client.get();
  equal(after.length, DUMP_RECORDS.length, "Imported dump");
  equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp");
});
add_task(clear_state);

add_task(async function test_load_dump_after_non_empty_import() {
  // Dump is updated regularly, verify that the dump matches our expectations
  // before running the test.
  ok(DUMP_LAST_MODIFIED > 1234, "Assuming dump to be newer than dummy 1234");

  await importData([{ last_modified: 1234, id: "dummy" }]);

  const after = await client.get();
  equal(after.length, DUMP_RECORDS.length, "Imported dump");
  equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp");
});
add_task(clear_state);

add_task(async function test_load_dump_after_import_from_broken_distro() {
  // Dump is updated regularly, verify that the dump matches our expectations
  // before running the test.
  ok(DUMP_LAST_MODIFIED > 1234, "Assuming dump to be newer than dummy 1234");

  // No last_modified time.
  await importData([{ id: "dummy" }]);

  const after = await client.get();
  equal(after.length, DUMP_RECORDS.length, "Imported dump");
  equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "dump's timestamp");
});
add_task(clear_state);

add_task(async function test_skip_dump_if_same_last_modified() {
  await importData([{ last_modified: DUMP_LAST_MODIFIED, id: "dummy" }]);

  const after = await client.get();
  equal(after.length, 1, "Not importing dump when time matches");
  equal(await client.getLastModified(), DUMP_LAST_MODIFIED, "Same timestamp");
});
add_task(clear_state);