tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_MiscDataBackupResource.js (9508B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { MiscDataBackupResource } = ChromeUtils.importESModule(
      7  "resource:///modules/backup/MiscDataBackupResource.sys.mjs"
      8 );
      9 
     10 const { ASRouterStorage } = ChromeUtils.importESModule(
     11  "resource:///modules/asrouter/ASRouterStorage.sys.mjs"
     12 );
     13 
     14 const { ProfileAge } = ChromeUtils.importESModule(
     15  "resource://gre/modules/ProfileAge.sys.mjs"
     16 );
     17 
     18 /**
     19 * Tests that we can measure miscellaneous files in the profile directory.
     20 */
     21 add_task(async function test_measure() {
     22  Services.fog.testResetFOG();
     23 
     24  const EXPECTED_MISC_KILOBYTES_SIZE = 231;
     25  const tempDir = await IOUtils.createUniqueDirectory(
     26    PathUtils.tempDir,
     27    "MiscDataBackupResource-measurement-test"
     28  );
     29 
     30  const mockFiles = [
     31    { path: "enumerate_devices.txt", sizeInKB: 1 },
     32    { path: "protections.sqlite", sizeInKB: 100 },
     33    { path: "SiteSecurityServiceState.bin", sizeInKB: 10 },
     34    { path: ["storage", "permanent", "chrome", "123ABC.sqlite"], sizeInKB: 40 },
     35    { path: ["storage", "permanent", "chrome", "456DEF.sqlite"], sizeInKB: 40 },
     36    {
     37      path: ["storage", "permanent", "chrome", "mockIDBDir", "890HIJ.sqlite"],
     38      sizeInKB: 40,
     39    },
     40  ];
     41 
     42  await createTestFiles(tempDir, mockFiles);
     43 
     44  let miscDataBackupResource = new MiscDataBackupResource();
     45  await miscDataBackupResource.measure(tempDir);
     46 
     47  let measurement = Glean.browserBackup.miscDataSize.testGetValue();
     48  let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
     49 
     50  TelemetryTestUtils.assertScalar(
     51    scalars,
     52    "browser.backup.misc_data_size",
     53    measurement,
     54    "Glean and telemetry measurements for misc data should be equal"
     55  );
     56  Assert.equal(
     57    measurement,
     58    EXPECTED_MISC_KILOBYTES_SIZE,
     59    "Should have collected the correct glean measurement for misc files"
     60  );
     61 
     62  await maybeRemovePath(tempDir);
     63 });
     64 
     65 add_task(async function test_backup() {
     66  let sandbox = sinon.createSandbox();
     67 
     68  let miscDataBackupResource = new MiscDataBackupResource();
     69  let sourcePath = await IOUtils.createUniqueDirectory(
     70    PathUtils.tempDir,
     71    "MiscDataBackupResource-source-test"
     72  );
     73  let stagingPath = await IOUtils.createUniqueDirectory(
     74    PathUtils.tempDir,
     75    "MiscDataBackupResource-staging-test"
     76  );
     77 
     78  const simpleCopyFiles = [
     79    { path: "enumerate_devices.txt" },
     80    { path: "SiteSecurityServiceState.bin" },
     81  ];
     82  await createTestFiles(sourcePath, simpleCopyFiles);
     83 
     84  // Create our fake database files. We don't expect this to be copied to the
     85  // staging directory in this test due to our stubbing of the backup method, so
     86  // we don't include it in `simpleCopyFiles`.
     87  await createTestFiles(sourcePath, [{ path: "protections.sqlite" }]);
     88 
     89  // We have no need to test that Sqlite.sys.mjs's backup method is working -
     90  // this is something that is tested in Sqlite's own tests. We can just make
     91  // sure that it's being called using sinon. Unfortunately, we cannot do the
     92  // same thing with IOUtils.copy, as its methods are not stubbable.
     93  let fakeConnection = {
     94    backup: sandbox.stub().resolves(true),
     95    close: sandbox.stub().resolves(true),
     96  };
     97  sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
     98 
     99  let snippetsTableStub = {
    100    getAllKeys: sandbox.stub().resolves(["key1", "key2"]),
    101    get: sandbox.stub().callsFake(key => {
    102      return { key: `value for ${key}` };
    103    }),
    104  };
    105 
    106  sandbox
    107    .stub(ASRouterStorage.prototype, "getDbTable")
    108    .withArgs("snippets")
    109    .resolves(snippetsTableStub);
    110 
    111  let manifestEntry = await miscDataBackupResource.backup(
    112    stagingPath,
    113    sourcePath
    114  );
    115  Assert.equal(
    116    manifestEntry,
    117    null,
    118    "MiscDataBackupResource.backup should return null as its ManifestEntry"
    119  );
    120 
    121  await assertFilesExist(stagingPath, simpleCopyFiles);
    122 
    123  // Next, we'll make sure that the Sqlite connection had `backup` called on it
    124  // with the right arguments.
    125  Assert.ok(
    126    fakeConnection.backup.calledOnce,
    127    "Called backup the expected number of times for all connections"
    128  );
    129  Assert.ok(
    130    fakeConnection.backup.firstCall.calledWith(
    131      PathUtils.join(stagingPath, "protections.sqlite")
    132    ),
    133    "Called backup on the protections.sqlite Sqlite connection"
    134  );
    135 
    136  // Bug 1890585 - we don't currently have the generalized ability to copy the
    137  // chrome-privileged IndexedDB databases under storage/permanent/chrome, but
    138  // we do support copying individual IndexedDB databases by manually exporting
    139  // and re-importing their contents.
    140  let snippetsBackupPath = PathUtils.join(
    141    stagingPath,
    142    "activity-stream-snippets.json"
    143  );
    144  Assert.ok(
    145    await IOUtils.exists(snippetsBackupPath),
    146    "The activity-stream-snippets.json file should exist"
    147  );
    148  let snippetsBackupContents = await IOUtils.readJSON(snippetsBackupPath);
    149  Assert.deepEqual(
    150    snippetsBackupContents,
    151    {
    152      key1: { key: "value for key1" },
    153      key2: { key: "value for key2" },
    154    },
    155    "The contents of the activity-stream-snippets.json file should be as expected"
    156  );
    157 
    158  await maybeRemovePath(stagingPath);
    159  await maybeRemovePath(sourcePath);
    160 
    161  sandbox.restore();
    162 });
    163 
    164 /**
    165 * Test that the recover method correctly copies items from the recovery
    166 * directory into the destination profile directory.
    167 */
    168 add_task(async function test_recover() {
    169  let miscBackupResource = new MiscDataBackupResource();
    170  let recoveryPath = await IOUtils.createUniqueDirectory(
    171    PathUtils.tempDir,
    172    "MiscDataBackupResource-recovery-test"
    173  );
    174  let destProfilePath = await IOUtils.createUniqueDirectory(
    175    PathUtils.tempDir,
    176    "MiscDataBackupResource-test-profile"
    177  );
    178 
    179  // Write a dummy times.json into the xpcshell test profile directory. We
    180  // expect it to be copied into the destination profile.
    181  let originalProfileAge = await ProfileAge(PathUtils.profileDir);
    182  await originalProfileAge.computeAndPersistCreated();
    183  Assert.ok(
    184    await IOUtils.exists(PathUtils.join(PathUtils.profileDir, "times.json"))
    185  );
    186 
    187  const simpleCopyFiles = [
    188    { path: "enumerate_devices.txt" },
    189    { path: "protections.sqlite" },
    190    { path: "SiteSecurityServiceState.bin" },
    191  ];
    192  await createTestFiles(recoveryPath, simpleCopyFiles);
    193 
    194  const SNIPPETS_BACKUP_FILE = "activity-stream-snippets.json";
    195 
    196  // We'll also separately create the activity-stream-snippets.json file, which
    197  // is not expected to be copied into the profile directory, but is expected
    198  // to exist in the recovery path.
    199  await createTestFiles(recoveryPath, [{ path: SNIPPETS_BACKUP_FILE }]);
    200 
    201  // The backup method is expected to have returned a null ManifestEntry
    202  let postRecoveryEntry = await miscBackupResource.recover(
    203    null /* manifestEntry */,
    204    recoveryPath,
    205    destProfilePath
    206  );
    207  Assert.deepEqual(
    208    postRecoveryEntry,
    209    {
    210      snippetsBackupFile: PathUtils.join(recoveryPath, SNIPPETS_BACKUP_FILE),
    211    },
    212    "MiscDataBackupResource.recover should return the snippets backup data " +
    213      "path as its post recovery entry"
    214  );
    215 
    216  await assertFilesExist(destProfilePath, simpleCopyFiles);
    217 
    218  // The activity-stream-snippets.json path should _not_ have been written to
    219  // the profile path.
    220  Assert.ok(
    221    !(await IOUtils.exists(
    222      PathUtils.join(destProfilePath, SNIPPETS_BACKUP_FILE)
    223    )),
    224    "Snippets backup data should not have gone into the profile directory"
    225  );
    226 
    227  // The times.json file should have been copied over and a backup recovery
    228  // time written into it.
    229  Assert.ok(
    230    await IOUtils.exists(PathUtils.join(destProfilePath, "times.json"))
    231  );
    232  let copiedProfileAge = await ProfileAge(destProfilePath);
    233  Assert.equal(
    234    await originalProfileAge.created,
    235    await copiedProfileAge.created,
    236    "Created timestamp should match."
    237  );
    238  Assert.equal(
    239    await originalProfileAge.firstUse,
    240    await copiedProfileAge.firstUse,
    241    "First use timestamp should match."
    242  );
    243  Assert.ok(
    244    await copiedProfileAge.recoveredFromBackup,
    245    "Backup recovery timestamp should have been set."
    246  );
    247 
    248  await maybeRemovePath(recoveryPath);
    249  await maybeRemovePath(destProfilePath);
    250 });
    251 
    252 /**
    253 * Test that the postRecovery method correctly writes the snippets backup data
    254 * into the snippets IndexedDB table.
    255 */
    256 add_task(async function test_postRecovery() {
    257  let sandbox = sinon.createSandbox();
    258 
    259  let fakeProfilePath = await IOUtils.createUniqueDirectory(
    260    PathUtils.tempDir,
    261    "MiscDataBackupResource-test-profile"
    262  );
    263  let fakeSnippetsData = {
    264    key1: "value1",
    265    key2: "value2",
    266  };
    267  const SNIPPEST_BACKUP_FILE = PathUtils.join(
    268    fakeProfilePath,
    269    "activity-stream-snippets.json"
    270  );
    271 
    272  await IOUtils.writeJSON(SNIPPEST_BACKUP_FILE, fakeSnippetsData);
    273 
    274  let snippetsTableStub = {
    275    set: sandbox.stub(),
    276  };
    277 
    278  sandbox
    279    .stub(ASRouterStorage.prototype, "getDbTable")
    280    .withArgs("snippets")
    281    .resolves(snippetsTableStub);
    282 
    283  let miscBackupResource = new MiscDataBackupResource();
    284  await miscBackupResource.postRecovery({
    285    snippetsBackupFile: SNIPPEST_BACKUP_FILE,
    286  });
    287 
    288  Assert.ok(
    289    snippetsTableStub.set.calledTwice,
    290    "The snippets table's set method was called twice"
    291  );
    292  Assert.ok(
    293    snippetsTableStub.set.firstCall.calledWith("key1", "value1"),
    294    "The snippets table's set method was called with the first key-value pair"
    295  );
    296  Assert.ok(
    297    snippetsTableStub.set.secondCall.calledWith("key2", "value2"),
    298    "The snippets table's set method was called with the second key-value pair"
    299  );
    300 
    301  sandbox.restore();
    302 });