summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_install_file_change.js
blob: 75cc91038e1789b79001b00299b920d31be7a67a (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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/
 */
"use strict";

const server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] });

// The test extension uses an insecure update url.
Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);

/* globals browser */

add_task(async function setup() {
  await ExtensionTestUtils.startAddonManager();
});

async function createXPIWithID(addonId, version = "1.0") {
  let xpiFile = await createTempWebExtensionFile({
    manifest: {
      version,
      browser_specific_settings: { gecko: { id: addonId } },
    },
  });
  return xpiFile;
}

const ERROR_PATTERN_INSTALL_FAIL = /Failed to install .+ from .+ to /;
const ERROR_PATTERN_POSTPONE_FAIL = /Failed to postpone install of /;

async function promiseInstallFail(install, expectedErrorPattern) {
  let { messages } = await promiseConsoleOutput(async () => {
    await Assert.rejects(
      install.install(),
      /^Error: Install failed: onInstallFailed$/
    );
  });
  messages = messages.filter(msg => expectedErrorPattern.test(msg.message));
  equal(messages.length, 1, "Expected log messages");
  equal(install.state, AddonManager.STATE_INSTALL_FAILED);
  equal(install.error, AddonManager.ERROR_FILE_ACCESS);
  equal((await AddonManager.getAllInstalls()).length, 0, "no pending installs");
}

add_task(async function test_file_deleted() {
  let xpiFile = await createXPIWithID("delete@me");
  let install = await AddonManager.getInstallForFile(xpiFile);
  equal(install.state, AddonManager.STATE_DOWNLOADED);

  xpiFile.remove(false);

  await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL);

  equal(await AddonManager.getAddonByID("delete@me"), null);
});

add_task(async function test_file_emptied() {
  let xpiFile = await createXPIWithID("empty@me");
  let install = await AddonManager.getInstallForFile(xpiFile);
  equal(install.state, AddonManager.STATE_DOWNLOADED);

  await IOUtils.write(xpiFile.path, new Uint8Array());

  await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL);

  equal(await AddonManager.getAddonByID("empty@me"), null);
});

add_task(async function test_file_replaced() {
  let xpiFile = await createXPIWithID("replace@me");
  let install = await AddonManager.getInstallForFile(xpiFile);
  equal(install.state, AddonManager.STATE_DOWNLOADED);

  await IOUtils.copy(
    (
      await createXPIWithID("replace@me", "2")
    ).path,
    xpiFile.path
  );

  await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL);

  equal(await AddonManager.getAddonByID("replace@me"), null);
});

async function do_test_update_with_file_replaced(wantPostponeTest) {
  const ADDON_ID = wantPostponeTest ? "postpone@me" : "update@me";
  function backgroundWithPostpone() {
    // The registration of this listener postpones the update.
    browser.runtime.onUpdateAvailable.addListener(() => {
      browser.test.fail("Unusable update should not call onUpdateAvailable");
    });
  }
  await promiseInstallWebExtension({
    manifest: {
      version: "1.0",
      browser_specific_settings: {
        gecko: {
          id: ADDON_ID,
          update_url: `http://example.com/update-${ADDON_ID}.json`,
        },
      },
    },
    background: wantPostponeTest ? backgroundWithPostpone : () => {},
  });

  server.registerFile(
    `/update-${ADDON_ID}.xpi`,
    await createTempWebExtensionFile({
      manifest: {
        version: "2.0",
        browser_specific_settings: { gecko: { id: ADDON_ID } },
      },
    })
  );
  AddonTestUtils.registerJSON(server, `/update-${ADDON_ID}.json`, {
    addons: {
      [ADDON_ID]: {
        updates: [
          {
            version: "2.0",
            update_link: `http://example.com/update-${ADDON_ID}.xpi`,
          },
        ],
      },
    },
  });

  // Setup completed, let's try to verify that file corruption halts the update.

  let addon = await promiseAddonByID(ADDON_ID);
  equal(addon.version, "1.0");

  let update = await promiseFindAddonUpdates(
    addon,
    AddonManager.UPDATE_WHEN_USER_REQUESTED
  );
  let install = update.updateAvailable;
  equal(install.version, "2.0");
  equal(install.state, AddonManager.STATE_AVAILABLE);
  equal(install.existingAddon, addon);
  equal(install.file, null);

  let promptCount = 0;
  let didReplaceFile = false;
  install.promptHandler = async function () {
    ++promptCount;
    equal(install.state, AddonManager.STATE_DOWNLOADED);
    await IOUtils.copy(
      (
        await createXPIWithID(ADDON_ID, "3")
      ).path,
      install.file.path
    );
    didReplaceFile = true;
    equal(install.state, AddonManager.STATE_DOWNLOADED, "State not changed");
  };

  if (wantPostponeTest) {
    await promiseInstallFail(install, ERROR_PATTERN_POSTPONE_FAIL);
  } else {
    await promiseInstallFail(install, ERROR_PATTERN_INSTALL_FAIL);
  }

  equal(promptCount, 1);
  ok(didReplaceFile, "Replaced update with different file");

  // Now verify that the add-on is still at the old version.
  addon = await promiseAddonByID(ADDON_ID);
  equal(addon.version, "1.0");

  await addon.uninstall();
}

add_task(async function test_update_and_file_replaced() {
  await do_test_update_with_file_replaced();
});

add_task(async function test_update_postponed_and_file_replaced() {
  await do_test_update_with_file_replaced(/* wantPostponeTest = */ true);
});