зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1543377 - Add abuse report submission helpers. r=janerik,aswan
This patch contains a new jsm file which provides some helpers to be used for the abuse report submission in the UI components related to abuse reporting, and a new xpcshell test that unit test these helpers. Differential Revision: https://phabricator.services.mozilla.com/D27938 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
707e8a73dc
Коммит
c5e820dd3d
|
@ -2730,6 +2730,8 @@ pref("services.settings.security.onecrl.collection", "onecrl");
|
|||
pref("services.settings.security.onecrl.signer", "onecrl.content-signature.mozilla.org");
|
||||
pref("services.settings.security.onecrl.checked", 0);
|
||||
|
||||
pref("extensions.abuseReport.url", "https://addons.mozilla.org/api/v4/abuse/report/addon/");
|
||||
|
||||
// Blocklist preferences
|
||||
pref("extensions.blocklist.enabled", true);
|
||||
// OneCRL freshness checking depends on this value, so if you change it,
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const EXPORTED_SYMBOLS = [ "AbuseReporter", "AbuseReportError" ];
|
||||
|
||||
const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
Cu.importGlobalProperties(["fetch"]);
|
||||
|
||||
const PREF_ABUSE_REPORT_URL = "extensions.abuseReport.url";
|
||||
// Minimum time between report submissions (in ms).
|
||||
const MIN_MS_BETWEEN_SUBMITS = 30000;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
||||
AppConstants: "resource://gre/modules/AppConstants.jsm",
|
||||
ClientID: "resource://gre/modules/ClientID.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "ABUSE_REPORT_URL", PREF_ABUSE_REPORT_URL);
|
||||
|
||||
const PRIVATE_REPORT_PROPS = Symbol("privateReportProps");
|
||||
|
||||
const ERROR_TYPES = Object.freeze([
|
||||
"ERROR_ABORTED_SUBMIT",
|
||||
"ERROR_ADDON_NOTFOUND",
|
||||
"ERROR_CLIENT",
|
||||
"ERROR_NETWORK",
|
||||
"ERROR_UNKNOWN",
|
||||
"ERROR_RECENT_SUBMIT",
|
||||
"ERROR_SERVER",
|
||||
]);
|
||||
|
||||
class AbuseReportError extends Error {
|
||||
constructor(errorType) {
|
||||
if (!ERROR_TYPES.includes(errorType)) {
|
||||
throw new Error(`Unknown AbuseReportError type "${errorType}"`);
|
||||
}
|
||||
super(errorType);
|
||||
this.name = "AbuseReportError";
|
||||
this.errorType = errorType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A singleton object used to create new AbuseReport instances for a given addonId
|
||||
* and enforce a minium amount of time between two report submissions .
|
||||
*/
|
||||
const AbuseReporter = {
|
||||
_lastReportTimestamp: null,
|
||||
|
||||
// Error types.
|
||||
updateLastReportTimestamp() {
|
||||
this._lastReportTimestamp = Date.now();
|
||||
},
|
||||
|
||||
getTimeFromLastReport() {
|
||||
const currentTimestamp = Date.now();
|
||||
if (this._lastReportTimestamp > currentTimestamp) {
|
||||
// Reset the last report timestamp if it is in the future.
|
||||
this._lastReportTimestamp = null;
|
||||
}
|
||||
|
||||
if (!this._lastReportTimestamp) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
return currentTimestamp - this._lastReportTimestamp;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an AbuseReport instance, given the addonId and a reportEntryPoint.
|
||||
*
|
||||
* @param {string} addonId
|
||||
* The id of the addon to create the report instance for.
|
||||
* @param {object} options
|
||||
* @param {string} options.reportEntryPoint
|
||||
* An identifier that represent the entry point for the report flow.
|
||||
*
|
||||
* @returns {AbuseReport}
|
||||
* An instance of the AbuseReport class, which represent an ongoing
|
||||
* report.
|
||||
*/
|
||||
async createAbuseReport(addonId, {reportEntryPoint} = {}) {
|
||||
const addon = await AddonManager.getAddonByID(addonId);
|
||||
|
||||
if (!addon) {
|
||||
throw new AbuseReportError("ERROR_ADDON_NOTFOUND");
|
||||
}
|
||||
|
||||
const reportData = await this.getReportData(addon);
|
||||
|
||||
return new AbuseReport({
|
||||
addon,
|
||||
reportData,
|
||||
reportEntryPoint,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function that retrieves from an addon object all the data to send
|
||||
* as part of the submission request, besides the `reason`, `message` which are
|
||||
* going to be received from the submit method of the report object returned
|
||||
* by `createAbuseReport`.
|
||||
* (See https://addons-server.readthedocs.io/en/latest/topics/api/abuse.html)
|
||||
*
|
||||
* @param {AddonWrapper} addon
|
||||
* The addon object to collect the detail from.
|
||||
*
|
||||
* @return {object}
|
||||
* An object that contains the collected details.
|
||||
*/
|
||||
async getReportData(addon) {
|
||||
const data = {
|
||||
addon: addon.id,
|
||||
addon_version: addon.version,
|
||||
addon_summary: addon.description,
|
||||
addon_install_origin: addon.sourceURI && addon.sourceURI.spec,
|
||||
install_date: addon.installDate && addon.installDate.toISOString(),
|
||||
};
|
||||
|
||||
// Map addon.installTelemetryInfo values to the supported addon_install_method
|
||||
// values supported by the API endpoint (See API endpoint docs at
|
||||
// https://addons-server.readthedocs.io/en/latest/topics/api/abuse.html).
|
||||
let install_method = "other";
|
||||
if (addon.installTelemetryInfo) {
|
||||
const {source, method} = addon.installTelemetryInfo;
|
||||
switch (source) {
|
||||
case "enterprise-policy":
|
||||
case "file-uri":
|
||||
case "system-addon":
|
||||
case "temporary-addon":
|
||||
install_method = source.replace(/-/g, "_");
|
||||
break;
|
||||
case "distribution":
|
||||
case "sideload":
|
||||
case "sync":
|
||||
install_method = source;
|
||||
break;
|
||||
default:
|
||||
install_method = "other";
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case "link":
|
||||
install_method = method;
|
||||
break;
|
||||
case "amWebAPI":
|
||||
case "installTrigger":
|
||||
install_method = method.toLowerCase();
|
||||
break;
|
||||
case "drag-and-drop":
|
||||
case "install-from-file":
|
||||
case "management-webext-api":
|
||||
install_method = method.replace(/-/g, "_");
|
||||
break;
|
||||
}
|
||||
}
|
||||
data.addon_install_method = install_method;
|
||||
|
||||
// TODO: Add support for addon_signature "curated" in AbuseReport
|
||||
// (Bug 1549290).
|
||||
switch (addon.signedState) {
|
||||
case AddonManager.SIGNEDSTATE_BROKEN:
|
||||
data.addon_signature = "broken";
|
||||
break;
|
||||
case AddonManager.SIGNEDSTATE_UNKNOWN:
|
||||
data.addon_signature = "unknown";
|
||||
break;
|
||||
case AddonManager.SIGNEDSTATE_MISSING:
|
||||
data.addon_signature = "missing";
|
||||
break;
|
||||
case AddonManager.SIGNEDSTATE_PRELIMINARY:
|
||||
data.addon_signature = "preliminary";
|
||||
break;
|
||||
case AddonManager.SIGNEDSTATE_SIGNED:
|
||||
data.addon_signature = "signed";
|
||||
break;
|
||||
case AddonManager.SIGNEDSTATE_SYSTEM:
|
||||
data.addon_signature = "system";
|
||||
break;
|
||||
case AddonManager.SIGNEDSTATE_PRIVILEGED:
|
||||
data.addon_signature = "privileged";
|
||||
break;
|
||||
default:
|
||||
data.addon_signature = `unknown: ${addon.signedState}`;
|
||||
}
|
||||
|
||||
data.client_id = await ClientID.getClientIdHash();
|
||||
|
||||
data.app = Services.appinfo.name.toLowerCase();
|
||||
data.appversion = Services.appinfo.version;
|
||||
data.lang = Services.locale.appLocaleAsLangTag;
|
||||
data.operating_system = AppConstants.platform;
|
||||
data.operating_system_version = Services.sysinfo.getProperty("version");
|
||||
|
||||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents an ongoing abuse report. Instances of this class are created
|
||||
* by the `AbuseReporter.createAbuseReport` method.
|
||||
*
|
||||
* This object is used by the reporting UI panel and message bars to:
|
||||
*
|
||||
* - get an errorType in case of a report creation error (e.g. because of a
|
||||
* previously submitted report)
|
||||
* - get the addon details used inside the reporting panel
|
||||
* - submit the abuse report (and re-submit if a previous submission failed
|
||||
* and the user choose to retry to submit it again)
|
||||
* - abort an ongoing submission
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {AddonWrapper|null} options.addon
|
||||
* AddonWrapper instance for the extension/theme being reported.
|
||||
* (May be null if the extension has not been found).
|
||||
* @param {object|null} options.reportData
|
||||
* An object which contains addon and environment details to send as part of a submission
|
||||
* (may be null if the report has a createErrorType).
|
||||
* @param {string} options.reportEntryPoint
|
||||
* A string that identify how the report has been triggered.
|
||||
*/
|
||||
class AbuseReport {
|
||||
constructor({addon, createErrorType, reportData, reportEntryPoint}) {
|
||||
this[PRIVATE_REPORT_PROPS] = {
|
||||
aborted: false,
|
||||
abortController: new AbortController(),
|
||||
addon,
|
||||
reportData,
|
||||
reportEntryPoint,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the current report, given a reason and a message.
|
||||
*
|
||||
* @params {object} options
|
||||
* @params {string} options.reason
|
||||
* String identifier for the report reason.
|
||||
* @params {string} [options.message]
|
||||
* An optional string which contains a description for the reported issue.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
* Resolves once the report has been successfully submitted.
|
||||
* It rejects with an AbuseReportError if the report couldn't be
|
||||
* submitted for a known reason (or another Error type otherwise).
|
||||
*/
|
||||
async submit({reason, message}) {
|
||||
const {
|
||||
aborted, abortController,
|
||||
reportData,
|
||||
reportEntryPoint,
|
||||
} = this[PRIVATE_REPORT_PROPS];
|
||||
|
||||
if (aborted) {
|
||||
// Report aborted before being actually submitted.
|
||||
throw new AbuseReportError("ERROR_ABORTED_SUBMIT");
|
||||
}
|
||||
|
||||
// Prevent submit of a new abuse report in less than MIN_MS_BETWEEN_SUBMITS.
|
||||
let msFromLastReport = AbuseReporter.getTimeFromLastReport();
|
||||
if (msFromLastReport < MIN_MS_BETWEEN_SUBMITS) {
|
||||
throw new AbuseReportError("ERROR_RECENT_SUBMIT");
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(ABUSE_REPORT_URL, {
|
||||
signal: abortController.signal,
|
||||
method: "POST",
|
||||
credentials: "omit",
|
||||
referrerPolicy: "no-referrer",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({
|
||||
...reportData,
|
||||
report_entry_point: reportEntryPoint,
|
||||
message,
|
||||
reason,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.name === "AbortError") {
|
||||
throw new AbuseReportError("ERROR_ABORTED_SUBMIT");
|
||||
}
|
||||
Cu.reportError(err);
|
||||
throw new AbuseReportError("ERROR_NETWORK");
|
||||
}
|
||||
|
||||
if (response.ok && response.status >= 200 && response.status < 400) {
|
||||
// Ensure that the response is also a valid json format.
|
||||
await response.json();
|
||||
AbuseReporter.updateLastReportTimestamp();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status >= 400 && response.status < 500) {
|
||||
throw new AbuseReportError("ERROR_CLIENT");
|
||||
}
|
||||
|
||||
if (response.status >= 500 && response.status < 600) {
|
||||
throw new AbuseReportError("ERROR_SERVER");
|
||||
}
|
||||
|
||||
// We got an unexpected HTTP status code.
|
||||
throw new AbuseReportError("ERROR_UNKNOWN");
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the report submission.
|
||||
*/
|
||||
abort() {
|
||||
const {abortController} = this[PRIVATE_REPORT_PROPS];
|
||||
abortController.abort();
|
||||
this[PRIVATE_REPORT_PROPS].aborted = true;
|
||||
}
|
||||
|
||||
get addon() {
|
||||
return this[PRIVATE_REPORT_PROPS].addon;
|
||||
}
|
||||
|
||||
get reportEntryPoint() {
|
||||
return this[PRIVATE_REPORT_PROPS].reportEntryPoint;
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ EXTRA_PP_COMPONENTS += [
|
|||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AbuseReporter.jsm',
|
||||
'addonManager.js',
|
||||
'AddonManager.jsm',
|
||||
'amContentHandler.jsm',
|
||||
|
|
|
@ -0,0 +1,331 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const {
|
||||
AbuseReporter,
|
||||
AbuseReportError,
|
||||
} = ChromeUtils.import("resource://gre/modules/AbuseReporter.jsm");
|
||||
|
||||
const {ClientID} = ChromeUtils.import("resource://gre/modules/ClientID.jsm");
|
||||
|
||||
const APPNAME = "XPCShell";
|
||||
const APPVERSION = "1";
|
||||
const ADDON_ID = "test-addon@tests.mozilla.org";
|
||||
const ADDON_ID2 = "test-addon2@tests.mozilla.org";
|
||||
const FAKE_INSTALL_INFO = {source: "fake-install-method"};
|
||||
const REPORT_OPTIONS = {reportEntryPoint: "menu"};
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
|
||||
|
||||
let apiRequestHandler;
|
||||
const server = createHttpServer({hosts: ["test.addons.org"]});
|
||||
server.registerPathHandler("/api/report/", (request, response) => {
|
||||
const stream = request.bodyInputStream;
|
||||
const buffer = NetUtil.readInputStream(stream, stream.available());
|
||||
const data = new TextDecoder().decode(buffer);
|
||||
apiRequestHandler({data, request, response});
|
||||
});
|
||||
|
||||
function handleSubmitRequest({request, response}) {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "application/json", false);
|
||||
response.write("{}");
|
||||
}
|
||||
|
||||
async function clearAbuseReportState() {
|
||||
// Clear the timestamp of the last submission.
|
||||
AbuseReporter._lastReportTimestamp = null;
|
||||
}
|
||||
|
||||
async function installTestExtension(overrideOptions = {}) {
|
||||
const extOptions = {
|
||||
manifest: {
|
||||
applications: {gecko: {id: ADDON_ID}},
|
||||
name: "Test Extension",
|
||||
},
|
||||
useAddonManager: "permanent",
|
||||
amInstallTelemetryInfo: FAKE_INSTALL_INFO,
|
||||
...overrideOptions,
|
||||
};
|
||||
|
||||
const extension = ExtensionTestUtils.loadExtension(extOptions);
|
||||
await extension.startup();
|
||||
|
||||
const addon = await AddonManager.getAddonByID(ADDON_ID);
|
||||
|
||||
return {extension, addon};
|
||||
}
|
||||
|
||||
async function assertRejectsAbuseReportError(promise, errorType) {
|
||||
await Assert.rejects(promise, error => {
|
||||
ok(error instanceof AbuseReportError);
|
||||
return error.errorType === errorType;
|
||||
});
|
||||
}
|
||||
|
||||
async function assertBaseReportData({reportData, addon}) {
|
||||
// Report properties related to addon metadata.
|
||||
equal(reportData.addon, ADDON_ID, "Got expected 'addon'");
|
||||
equal(reportData.addon_version, addon.version, "Got expected 'addon_version'");
|
||||
equal(reportData.install_date, addon.installDate.toISOString(),
|
||||
"Got expected 'install_date' in ISO format");
|
||||
equal(reportData.addon_install_origin, addon.sourceURI.spec,
|
||||
"Got expected 'addon_install_origin'");
|
||||
equal(reportData.addon_install_method, "other",
|
||||
"Got expected 'addon_install_method'");
|
||||
equal(reportData.addon_signature, "privileged", "Got expected 'addon_signature'");
|
||||
|
||||
// Report properties related to the environment.
|
||||
equal(reportData.client_id, await ClientID.getClientIdHash(),
|
||||
"Got the expected 'client_id'");
|
||||
equal(reportData.app, APPNAME.toLowerCase(), "Got expected 'app'");
|
||||
equal(reportData.appversion, APPVERSION, "Got expected 'appversion'");
|
||||
equal(reportData.lang, Services.locale.appLocaleAsLangTag, "Got expected 'lang'");
|
||||
equal(reportData.operating_system, AppConstants.platform, "Got expected 'operating_system'");
|
||||
equal(reportData.operating_system_version, Services.sysinfo.getProperty("version"),
|
||||
"Got expected 'operating_system_version'");
|
||||
}
|
||||
|
||||
add_task(async function test_setup() {
|
||||
Services.prefs.setCharPref("extensions.abuseReport.url", "http://test.addons.org/api/report/");
|
||||
await promiseStartupManager();
|
||||
});
|
||||
|
||||
add_task(async function test_addon_report_data() {
|
||||
info("Verify report property for a privileged extension");
|
||||
const {addon, extension} = await installTestExtension();
|
||||
const data = await AbuseReporter.getReportData(addon);
|
||||
await assertBaseReportData({reportData: data, addon});
|
||||
await extension.unload();
|
||||
|
||||
info("Verify 'addon_signature' report property for non privileged extension");
|
||||
AddonTestUtils.usePrivilegedSignatures = false;
|
||||
const {
|
||||
addon: addon2,
|
||||
extension: extension2,
|
||||
} = await installTestExtension();
|
||||
const data2 = await AbuseReporter.getReportData(addon2);
|
||||
equal(data2.addon_signature, "signed",
|
||||
"Got expected 'addon_signature' for non privileged extension");
|
||||
await extension2.unload();
|
||||
|
||||
info("Verify 'addon_install_method' report property on temporary install");
|
||||
const {
|
||||
addon: addon3,
|
||||
extension: extension3,
|
||||
} = await installTestExtension({useAddonManager: "temporary"});
|
||||
const data3 = await AbuseReporter.getReportData(addon3);
|
||||
equal(data3.addon_install_method, "temporary_addon",
|
||||
"Got expected 'addon_install_method' on temporary install");
|
||||
await extension3.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_report_on_not_installed_addon() {
|
||||
await assertRejectsAbuseReportError(
|
||||
AbuseReporter.createAbuseReport(ADDON_ID, REPORT_OPTIONS),
|
||||
"ERROR_ADDON_NOTFOUND");
|
||||
});
|
||||
|
||||
// This tests verifies the mapping between the addon installTelemetryInfo
|
||||
// values and the addon_install_method expected by the API endpoint.
|
||||
add_task(async function test_addon_install_method_mapping() {
|
||||
async function assertAddonInstallMethod(amInstallTelemetryInfo, expected) {
|
||||
const {addon, extension} = await installTestExtension({amInstallTelemetryInfo});
|
||||
const {addon_install_method} = await AbuseReporter.getReportData(addon);
|
||||
equal(addon_install_method, expected,
|
||||
`Got the expected addon_install_method for ${JSON.stringify(amInstallTelemetryInfo)}`);
|
||||
await extension.unload();
|
||||
}
|
||||
|
||||
// Array of [ expected, amInstallTelemetryInfo ]
|
||||
const TEST_CASES = [
|
||||
["amwebapi", {source: "amo", method: "amWebAPI"}],
|
||||
["amwebapi", {source: "disco", method: "amWebAPI"}],
|
||||
["distribution", {source: "distribution"}],
|
||||
["drag_and_drop", {source: "about:addons", method: "drag-and-drop"}],
|
||||
["enterprise_policy", {source: "enterprise-policy"}],
|
||||
["file_uri", {source: "file-uri"}],
|
||||
["install_from_file", {source: "about:addons", method: "install-from-file"}],
|
||||
["installtrigger", {source: "test-host", method: "installTrigger"}],
|
||||
["link", {source: "unknown", method: "link"}],
|
||||
["management_webext_api", {source: "extension", method: "management-webext-api"}],
|
||||
["sideload", {source: "sideload"}],
|
||||
["sync", {source: "sync"}],
|
||||
["system_addon", {source: "system-addon"}],
|
||||
["temporary_addon", {source: "temporary-addon"}],
|
||||
["other", {source: "internal"}],
|
||||
["other", {source: "about:debugging"}],
|
||||
["other", {source: "webide"}],
|
||||
];
|
||||
|
||||
for (const [expected, telemetryInfo] of TEST_CASES) {
|
||||
await assertAddonInstallMethod(telemetryInfo, expected);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_report_create_and_submit() {
|
||||
// Override the test api server request handler, to be able to
|
||||
// intercept the submittions to the test api server.
|
||||
let reportSubmitted;
|
||||
apiRequestHandler = ({data, request, response}) => {
|
||||
reportSubmitted = JSON.parse(data);
|
||||
handleSubmitRequest({request, response});
|
||||
};
|
||||
|
||||
const {addon, extension} = await installTestExtension();
|
||||
|
||||
const reportEntryPoint = "menu";
|
||||
const report = await AbuseReporter.createAbuseReport(ADDON_ID, {reportEntryPoint});
|
||||
|
||||
equal(report.addon, addon, "Got the expected addon property");
|
||||
equal(report.reportEntryPoint, reportEntryPoint, "Got the expected reportEntryPoint");
|
||||
|
||||
const baseReportData = await AbuseReporter.getReportData(addon);
|
||||
const reportProperties = {
|
||||
message: "test message",
|
||||
reason: "test-reason",
|
||||
};
|
||||
|
||||
info("Submitting report");
|
||||
await report.submit(reportProperties);
|
||||
|
||||
const expectedEntries = Object.entries({
|
||||
report_entry_point: reportEntryPoint,
|
||||
...baseReportData,
|
||||
...reportProperties,
|
||||
});
|
||||
|
||||
for (const [expectedKey, expectedValue] of expectedEntries) {
|
||||
equal(reportSubmitted[expectedKey], expectedValue,
|
||||
`Got the expected submitted value for "${expectedKey}"`);
|
||||
}
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_error_recent_submit() {
|
||||
await clearAbuseReportState();
|
||||
|
||||
let reportSubmitted;
|
||||
apiRequestHandler = ({data, request, response}) => {
|
||||
reportSubmitted = JSON.parse(data);
|
||||
handleSubmitRequest({request, response});
|
||||
};
|
||||
|
||||
const {extension} = await installTestExtension();
|
||||
const report = await AbuseReporter.createAbuseReport(ADDON_ID, REPORT_OPTIONS);
|
||||
|
||||
const {extension: extension2} = await installTestExtension({
|
||||
manifest: {
|
||||
applications: {gecko: {id: ADDON_ID2}},
|
||||
name: "Test Extension2",
|
||||
},
|
||||
});
|
||||
const report2 = await AbuseReporter.createAbuseReport(ADDON_ID2, REPORT_OPTIONS);
|
||||
|
||||
// Submit the two reports in fast sequence.
|
||||
await report.submit({reason: "reason1"});
|
||||
await assertRejectsAbuseReportError(report2.submit({reason: "reason2"}),
|
||||
"ERROR_RECENT_SUBMIT");
|
||||
equal(reportSubmitted.reason, "reason1",
|
||||
"Server only received the data from the first submission");
|
||||
|
||||
await extension.unload();
|
||||
await extension2.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_submission_server_error() {
|
||||
const {extension} = await installTestExtension();
|
||||
|
||||
async function testErrorCode(
|
||||
responseStatus, expectedErrorType, expectRequest = true
|
||||
) {
|
||||
info(`Test expected AbuseReportError on response status "${responseStatus}"`);
|
||||
await clearAbuseReportState();
|
||||
|
||||
let requestReceived = false;
|
||||
apiRequestHandler = ({request, response}) => {
|
||||
requestReceived = true;
|
||||
response.setStatusLine(request.httpVersion, responseStatus, "Error");
|
||||
response.write("");
|
||||
};
|
||||
|
||||
const report = await AbuseReporter.createAbuseReport(ADDON_ID, REPORT_OPTIONS);
|
||||
const promiseSubmit = report.submit({reason: "a-reason"});
|
||||
if (typeof expectedErrorType === "string") {
|
||||
// Assert a specific AbuseReportError errorType.
|
||||
await assertRejectsAbuseReportError(promiseSubmit, expectedErrorType);
|
||||
} else {
|
||||
// Assert on a given Error class.
|
||||
await Assert.rejects(promiseSubmit, expectedErrorType);
|
||||
}
|
||||
equal(requestReceived, expectRequest,
|
||||
`${expectRequest ? "" : "Not "}received a request as expected`);
|
||||
}
|
||||
|
||||
await testErrorCode(500, "ERROR_SERVER");
|
||||
await testErrorCode(404, "ERROR_CLIENT");
|
||||
// Test response with unexpected status code.
|
||||
await testErrorCode(604, "ERROR_UNKNOWN");
|
||||
// Test response status 200 with invalid json data.
|
||||
await testErrorCode(200, /SyntaxError: JSON.parse/);
|
||||
|
||||
// Test on invalid url.
|
||||
Services.prefs.setCharPref("extensions.abuseReport.url",
|
||||
"invalid-protocol://abuse-report");
|
||||
await testErrorCode(200, "ERROR_NETWORK", false);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function set_test_abusereport_url() {
|
||||
Services.prefs.setCharPref("extensions.abuseReport.url",
|
||||
"http://test.addons.org/api/report/");
|
||||
});
|
||||
|
||||
add_task(async function test_submission_aborting() {
|
||||
await clearAbuseReportState();
|
||||
|
||||
const {extension} = await installTestExtension();
|
||||
|
||||
// override the api request handler with one that is never going to reply.
|
||||
let receivedRequestsCount = 0;
|
||||
let resolvePendingResponses;
|
||||
const waitToReply = new Promise(resolve => resolvePendingResponses = resolve);
|
||||
|
||||
const onRequestReceived = new Promise(resolve => {
|
||||
apiRequestHandler = ({request, response}) => {
|
||||
response.processAsync();
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
receivedRequestsCount++;
|
||||
resolve();
|
||||
|
||||
// Keep the request pending until resolvePendingResponses have been
|
||||
// called.
|
||||
waitToReply.then(() => {
|
||||
response.finish();
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
const report = await AbuseReporter.createAbuseReport(ADDON_ID, REPORT_OPTIONS);
|
||||
const promiseResult = report.submit({reason: "a-reason"});
|
||||
|
||||
await onRequestReceived;
|
||||
|
||||
ok(receivedRequestsCount > 0, "Got the expected number of requests");
|
||||
ok(await Promise.race([promiseResult, Promise.resolve("pending")]) === "pending",
|
||||
"Submission fetch request should still be pending");
|
||||
|
||||
report.abort();
|
||||
|
||||
await assertRejectsAbuseReportError(promiseResult, "ERROR_ABORTED_SUBMIT");
|
||||
|
||||
await extension.unload();
|
||||
|
||||
// Unblock pending requests on the server request handler side, so that the
|
||||
// test file can shutdown (otherwise the test run will be stuck after this
|
||||
// task completed).
|
||||
resolvePendingResponses();
|
||||
});
|
|
@ -7,7 +7,7 @@ dupe-manifest =
|
|||
support-files =
|
||||
data/**
|
||||
|
||||
[test_addon_manager_telemetry_events.js]
|
||||
[test_AbuseReporter.js]
|
||||
[test_AddonRepository.js]
|
||||
[test_AddonRepository_cache.js]
|
||||
# Bug 676992: test consistently hangs on Android
|
||||
|
@ -17,6 +17,7 @@ skip-if = os == "android"
|
|||
[test_ProductAddonChecker.js]
|
||||
[test_XPIStates.js]
|
||||
[test_XPIcancel.js]
|
||||
[test_addon_manager_telemetry_events.js]
|
||||
[test_addonStartup.js]
|
||||
[test_bad_json.js]
|
||||
[test_badschema.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче