mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1898446 - Introduce a new sendAbuseReport() method on the AddonManager web API (mozAddonManager). r=rpl,smaug
Differential Revision: https://phabricator.services.mozilla.com/D209017
This commit is contained in:
parent
09a14c53a9
commit
a3fca22df4
@ -56,6 +56,11 @@ dictionary addonInstallOptions {
|
||||
DOMString? hash = null;
|
||||
};
|
||||
|
||||
dictionary sendAbuseReportOptions {
|
||||
// This should be an Authorization HTTP header value.
|
||||
DOMString? authorization = null;
|
||||
};
|
||||
|
||||
[HeaderFile="mozilla/AddonManagerWebAPI.h",
|
||||
Func="mozilla::AddonManagerWebAPI::IsAPIEnabled",
|
||||
JSImplementation="@mozilla.org/addon-web-api/manager;1",
|
||||
@ -79,6 +84,30 @@ interface AddonManager : EventTarget {
|
||||
* @return A promise that resolves to an instance of AddonInstall.
|
||||
*/
|
||||
Promise<AddonInstall> createInstall(optional addonInstallOptions options = {});
|
||||
|
||||
/**
|
||||
* Sends an abuse report to the AMO API.
|
||||
*
|
||||
* NOTE: The type for `data` and for the return value are loose because both
|
||||
* the AMO API might change its response and the caller (AMO frontend) might
|
||||
* also want to pass slightly different data in the future.
|
||||
*
|
||||
* @param addonId
|
||||
* The ID of the add-on to report.
|
||||
* @param data
|
||||
* The caller passes the data to be sent to the AMO API.
|
||||
* @param options
|
||||
* Optional - A set of options. It currently only supports
|
||||
* `authorization`, which is expected to be the value of an
|
||||
* Authorization HTTP header when provided.
|
||||
* @return A promise that resolves to the AMO API response, or an error when
|
||||
* something went wrong.
|
||||
*/
|
||||
[NewObject] Promise<any> sendAbuseReport(
|
||||
DOMString addonId,
|
||||
record<DOMString, DOMString?> data,
|
||||
optional sendAbuseReportOptions options = {}
|
||||
);
|
||||
};
|
||||
|
||||
[ChromeOnly,Exposed=Window,HeaderFile="mozilla/AddonManagerWebAPI.h"]
|
||||
|
@ -1859,6 +1859,7 @@ pref("services.common.uptake.sampleRate", 1); // 1%
|
||||
pref("extensions.abuseReport.enabled", false);
|
||||
// Whether Firefox integrated abuse reporting feature should be opening the new abuse report form hosted on AMO.
|
||||
pref("extensions.abuseReport.amoFormURL", "https://addons.mozilla.org/%LOCALE%/%APP%/feedback/addon/%addonID%/");
|
||||
pref("extensions.addonAbuseReport.url", "https://services.addons.mozilla.org/api/v5/abuse/report/addon/");
|
||||
|
||||
// Blocklist preferences
|
||||
pref("extensions.blocklist.enabled", true);
|
||||
|
@ -3,6 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
// Maximum length of the string properties sent to the API endpoint.
|
||||
const MAX_STRING_LENGTH = 255;
|
||||
@ -16,6 +17,8 @@ const AMO_SUPPORTED_ADDON_TYPES = [
|
||||
"dictionary",
|
||||
];
|
||||
|
||||
const PREF_ADDON_ABUSE_REPORT_URL = "extensions.addonAbuseReport.url";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
@ -23,6 +26,50 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ClientID: "resource://gre/modules/ClientID.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"ADDON_ABUSE_REPORT_URL",
|
||||
PREF_ADDON_ABUSE_REPORT_URL
|
||||
);
|
||||
|
||||
const ERROR_TYPES = Object.freeze([
|
||||
"ERROR_CLIENT",
|
||||
"ERROR_NETWORK",
|
||||
"ERROR_SERVER",
|
||||
"ERROR_UNKNOWN",
|
||||
]);
|
||||
|
||||
export class AbuseReportError extends Error {
|
||||
constructor(errorType, errorInfo = undefined) {
|
||||
if (!ERROR_TYPES.includes(errorType)) {
|
||||
throw new Error(`Unexpected AbuseReportError type "${errorType}"`);
|
||||
}
|
||||
|
||||
let message = errorInfo ? `${errorType} - ${errorInfo}` : errorType;
|
||||
|
||||
super(message);
|
||||
this.name = "AbuseReportError";
|
||||
this.errorType = errorType;
|
||||
this.errorInfo = errorInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error info string from a fetch response object.
|
||||
*
|
||||
* @param {Response} response
|
||||
* A fetch response object to convert into an errorInfo string.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* The errorInfo string to be included in an AbuseReportError.
|
||||
*/
|
||||
async function responseToErrorInfo(response) {
|
||||
return JSON.stringify({
|
||||
status: response.status,
|
||||
responseText: await response.text().catch(() => ""),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A singleton used to manage abuse reports for add-ons.
|
||||
*/
|
||||
@ -37,6 +84,76 @@ export const AbuseReporter = {
|
||||
return AMO_SUPPORTED_ADDON_TYPES.includes(addonType);
|
||||
},
|
||||
|
||||
/**
|
||||
* Send an add-on abuse report using the AMO API. The data passed to this
|
||||
* method might be augmented with report data known by Firefox.
|
||||
*
|
||||
* @param {string} addonId
|
||||
* @param {{[key: string]: string|null}} data
|
||||
* Abuse report data to be submitting to the AMO API along with the
|
||||
* additional abuse report data known by Firefox.
|
||||
* @param {object} [options]
|
||||
* @param {string} [options.authorization]
|
||||
* An optional value of an Authorization HTTP header to be set on the
|
||||
* submission request.
|
||||
*
|
||||
* @returns {Promise<object>} Return a promise that resolves to the JSON AMO
|
||||
* API response (or an error when something went wrong).
|
||||
*/
|
||||
async sendAbuseReport(addonId, data, options = {}) {
|
||||
const rejectReportError = async (errorType, { response } = {}) => {
|
||||
// Leave errorInfo empty if there is no response or fails to be converted
|
||||
// into an error info object.
|
||||
const errorInfo = response
|
||||
? await responseToErrorInfo(response).catch(() => undefined)
|
||||
: undefined;
|
||||
|
||||
throw new AbuseReportError(errorType, errorInfo);
|
||||
};
|
||||
|
||||
let abuseReport = { addon: addonId, ...data };
|
||||
|
||||
// If the add-on is installed, augment the data with internal report data.
|
||||
const addon = await lazy.AddonManager.getAddonByID(addonId);
|
||||
if (addon) {
|
||||
const metadata = await AbuseReporter.getReportData(addon);
|
||||
abuseReport = { ...abuseReport, ...metadata };
|
||||
}
|
||||
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (options?.authorization?.length) {
|
||||
headers.authorization = options.authorization;
|
||||
}
|
||||
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(lazy.ADDON_ABUSE_REPORT_URL, {
|
||||
method: "POST",
|
||||
credentials: "omit",
|
||||
referrerPolicy: "no-referrer",
|
||||
headers,
|
||||
body: JSON.stringify(abuseReport),
|
||||
});
|
||||
} catch (err) {
|
||||
Cu.reportError(err);
|
||||
return rejectReportError("ERROR_NETWORK");
|
||||
}
|
||||
|
||||
if (response.ok && response.status >= 200 && response.status < 400) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status >= 400 && response.status < 500) {
|
||||
return rejectReportError("ERROR_CLIENT", { response });
|
||||
}
|
||||
|
||||
if (response.status >= 500 && response.status < 600) {
|
||||
return rejectReportError("ERROR_SERVER", { response });
|
||||
}
|
||||
|
||||
return rejectReportError("ERROR_UNKNOWN", { response });
|
||||
},
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -80,6 +80,7 @@ var AsyncShutdown = realAsyncShutdown;
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs",
|
||||
AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
|
||||
Extension: "resource://gre/modules/Extension.sys.mjs",
|
||||
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
|
||||
@ -3553,6 +3554,10 @@ var AddonManagerInternal = {
|
||||
});
|
||||
},
|
||||
|
||||
async sendAbuseReport(target, addonId, data, options) {
|
||||
return lazy.AbuseReporter.sendAbuseReport(addonId, data, options);
|
||||
},
|
||||
|
||||
async addonUninstall(target, id) {
|
||||
let addon = await AddonManager.getAddonByID(id);
|
||||
if (!addon) {
|
||||
|
@ -244,6 +244,18 @@ export class WebAPI extends APIObject {
|
||||
});
|
||||
}
|
||||
|
||||
sendAbuseReport(addonId, data, options) {
|
||||
return this._apiTask(
|
||||
"sendAbuseReport",
|
||||
[addonId, data, options],
|
||||
result => {
|
||||
// The result below is a JS object coming from the expected AMO API
|
||||
// endpoint response in JSON format.
|
||||
return Cu.cloneInto(result, this.window);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
eventListenerAdded() {
|
||||
if (this.listenerCount == 0) {
|
||||
this.broker.setAddonListener(data => {
|
||||
|
@ -175,6 +175,8 @@ https_first_disabled = true
|
||||
|
||||
["browser_webapi_install_disabled.js"]
|
||||
|
||||
["browser_webapi_sendAbuseReport.js"]
|
||||
|
||||
["browser_webapi_theme.js"]
|
||||
|
||||
["browser_webapi_uninstall.js"]
|
||||
|
@ -64,3 +64,22 @@ add_task(async function test_report_action_hidden_on_langpack_addons() {
|
||||
);
|
||||
await closeAboutAddons();
|
||||
});
|
||||
|
||||
add_task(async function test_report_action_hidden_on_system_addons() {
|
||||
await openAboutAddons("extension");
|
||||
await AbuseReportTestUtils.assertReportActionHidden(
|
||||
gManagerWindow,
|
||||
EXT_SYSTEM_ADDON_ID
|
||||
);
|
||||
await closeAboutAddons();
|
||||
});
|
||||
|
||||
add_task(async function test_report_action_hidden_on_builtin_addons() {
|
||||
const DEFAULT_BUILTIN_THEME_ID = "default-theme@mozilla.org";
|
||||
await openAboutAddons("theme");
|
||||
await AbuseReportTestUtils.assertReportActionHidden(
|
||||
gManagerWindow,
|
||||
DEFAULT_BUILTIN_THEME_ID
|
||||
);
|
||||
await closeAboutAddons();
|
||||
});
|
||||
|
@ -0,0 +1,109 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { AddonTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/AddonTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
|
||||
const ADDON_ID = "@test-extension-to-report";
|
||||
|
||||
AddonTestUtils.initMochitest(this);
|
||||
|
||||
const server = AddonTestUtils.createHttpServer({ hosts: ["test.addons.org"] });
|
||||
let apiRequestHandler;
|
||||
server.registerPathHandler("/api/abuse/report/addon/", (request, response) => {
|
||||
apiRequestHandler(request, response);
|
||||
});
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["extensions.webapi.testing", true],
|
||||
[
|
||||
"extensions.addonAbuseReport.url",
|
||||
// eslint-disable-next-line @microsoft/sdl/no-insecure-url
|
||||
"http://test.addons.org/api/abuse/report/addon/",
|
||||
],
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_mozAddonManager_sendAbuseReport() {
|
||||
apiRequestHandler = (req, res) => {
|
||||
res.setStatusLine(req.httpVersion, 200, "OK");
|
||||
res.setHeader("Content-Type", "application/json", false);
|
||||
res.write('{"ok":true}');
|
||||
};
|
||||
|
||||
await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
name: "some extension reported",
|
||||
browser_specific_settings: { gecko: { id: ADDON_ID } },
|
||||
},
|
||||
useAddonManager: "temporary",
|
||||
});
|
||||
await extension.startup();
|
||||
|
||||
const response = await SpecialPowers.spawn(browser, [ADDON_ID], addonId => {
|
||||
const data = { some: "data" };
|
||||
return content.navigator.mozAddonManager.sendAbuseReport(addonId, data);
|
||||
});
|
||||
Assert.deepEqual(
|
||||
response,
|
||||
{ ok: true },
|
||||
"expected API response to be returned"
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_mozAddonManager_sendAbuseReport_error() {
|
||||
apiRequestHandler = (req, res) => {
|
||||
res.setStatusLine(req.httpVersion, 400, "BAD REQUEST");
|
||||
};
|
||||
|
||||
await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
|
||||
const extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
name: "some extension reported",
|
||||
browser_specific_settings: { gecko: { id: ADDON_ID } },
|
||||
},
|
||||
useAddonManager: "temporary",
|
||||
});
|
||||
await extension.startup();
|
||||
|
||||
const webApiResult = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[ADDON_ID],
|
||||
addonId => {
|
||||
const data = { some: "data" };
|
||||
return content.navigator.mozAddonManager
|
||||
.sendAbuseReport(addonId, data)
|
||||
.then(
|
||||
res => ({ gotRejection: false, result: res }),
|
||||
err => ({
|
||||
gotRejection: true,
|
||||
message: err.message,
|
||||
errorName: err.name,
|
||||
isErrorInstance: err instanceof content.Error,
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
Assert.deepEqual(
|
||||
webApiResult,
|
||||
{
|
||||
gotRejection: true,
|
||||
message: 'ERROR_CLIENT - {"status":400,"responseText":""}',
|
||||
errorName: "Error",
|
||||
isErrorInstance: true,
|
||||
},
|
||||
"expected rejection"
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
});
|
@ -15,10 +15,7 @@ const { AddonTestUtils } = ChromeUtils.importESModule(
|
||||
|
||||
const EXT_DICTIONARY_ADDON_ID = "fake-dictionary@mochi.test";
|
||||
const EXT_LANGPACK_ADDON_ID = "fake-langpack@mochi.test";
|
||||
const EXT_WITH_PRIVILEGED_URL_ID = "ext-with-privileged-url@mochi.test";
|
||||
const EXT_SYSTEM_ADDON_ID = "test-system-addon@mochi.test";
|
||||
const EXT_UNSUPPORTED_TYPE_ADDON_ID = "report-unsupported-type@mochi.test";
|
||||
const THEME_NO_UNINSTALL_ID = "theme-without-perm-can-uninstall@mochi.test";
|
||||
|
||||
let gManagerWindow;
|
||||
|
||||
@ -37,11 +34,6 @@ async function closeAboutAddons() {
|
||||
|
||||
const AbuseReportTestUtils = {
|
||||
_mockProvider: null,
|
||||
_mockServer: null,
|
||||
_abuseRequestHandlers: [],
|
||||
|
||||
// Mock addon details API endpoint.
|
||||
amoAddonDetailsMap: new Map(),
|
||||
|
||||
// Setup the test environment by setting the expected prefs and initializing
|
||||
// MockProvider.
|
||||
@ -87,21 +79,6 @@ const AbuseReportTestUtils = {
|
||||
_setupMockProvider() {
|
||||
this._mockProvider = new MockProvider();
|
||||
this._mockProvider.createAddons([
|
||||
{
|
||||
id: THEME_NO_UNINSTALL_ID,
|
||||
name: "This theme cannot be uninstalled",
|
||||
version: "1.1",
|
||||
creator: { name: "Theme creator", url: "http://example.com/creator" },
|
||||
type: "theme",
|
||||
permissions: 0,
|
||||
},
|
||||
{
|
||||
id: EXT_WITH_PRIVILEGED_URL_ID,
|
||||
name: "This extension has an unexpected privileged creator URL",
|
||||
version: "1.1",
|
||||
creator: { name: "creator", url: "about:config" },
|
||||
type: "extension",
|
||||
},
|
||||
{
|
||||
id: EXT_SYSTEM_ADDON_ID,
|
||||
name: "This is a system addon",
|
||||
@ -110,12 +87,6 @@ const AbuseReportTestUtils = {
|
||||
type: "extension",
|
||||
isSystem: true,
|
||||
},
|
||||
{
|
||||
id: EXT_UNSUPPORTED_TYPE_ADDON_ID,
|
||||
name: "This is a fake unsupported addon type",
|
||||
version: "1.1",
|
||||
type: "unsupported_addon_type",
|
||||
},
|
||||
{
|
||||
id: EXT_LANGPACK_ADDON_ID,
|
||||
name: "This is a fake langpack",
|
||||
|
@ -2,7 +2,7 @@
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const { AbuseReporter } = ChromeUtils.importESModule(
|
||||
const { AbuseReporter, AbuseReportError } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AbuseReporter.sys.mjs"
|
||||
);
|
||||
|
||||
@ -17,6 +17,10 @@ const FAKE_INSTALL_INFO = {
|
||||
source: "fake-Install:Source",
|
||||
method: "fake:install method",
|
||||
};
|
||||
const EXPECTED_API_RESPONSE = {
|
||||
id: ADDON_ID,
|
||||
some: "other-props",
|
||||
};
|
||||
|
||||
async function installTestExtension(overrideOptions = {}) {
|
||||
const extOptions = {
|
||||
@ -96,9 +100,57 @@ async function assertBaseReportData({ reportData, addon }) {
|
||||
);
|
||||
}
|
||||
|
||||
async function assertRejectsAbuseReportError(promise, errorType, errorInfo) {
|
||||
let error;
|
||||
|
||||
await Assert.rejects(
|
||||
promise,
|
||||
err => {
|
||||
error = err;
|
||||
return err instanceof AbuseReportError;
|
||||
},
|
||||
`Got an AbuseReportError`
|
||||
);
|
||||
|
||||
equal(error.errorType, errorType, "Got the expected errorType");
|
||||
equal(error.errorInfo, errorInfo, "Got the expected errorInfo");
|
||||
ok(
|
||||
error.message.includes(errorType),
|
||||
"errorType should be included in the error message"
|
||||
);
|
||||
if (errorInfo) {
|
||||
ok(
|
||||
error.message.includes(errorInfo),
|
||||
"errorInfo should be included in the error message"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmitRequest({ request, response }) {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.setHeader("Content-Type", "application/json", false);
|
||||
response.write(JSON.stringify(EXPECTED_API_RESPONSE));
|
||||
}
|
||||
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
|
||||
|
||||
const server = createHttpServer({ hosts: ["test.addons.org"] });
|
||||
|
||||
// Mock abuse report API endpoint.
|
||||
let apiRequestHandler;
|
||||
server.registerPathHandler("/api/abuse/report/addon/", (request, response) => {
|
||||
const stream = request.bodyInputStream;
|
||||
const buffer = NetUtil.readInputStream(stream, stream.available());
|
||||
const data = new TextDecoder().decode(buffer);
|
||||
apiRequestHandler({ data, request, response });
|
||||
});
|
||||
|
||||
add_setup(async () => {
|
||||
Services.prefs.setCharPref(
|
||||
"extensions.addonAbuseReport.url",
|
||||
"http://test.addons.org/api/abuse/report/addon/"
|
||||
);
|
||||
|
||||
await promiseStartupManager();
|
||||
});
|
||||
|
||||
@ -221,3 +273,240 @@ add_task(async function test_normalized_addon_install_source_and_method() {
|
||||
await assertAddonInstallMethod(test, expect);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_sendAbuseReport() {
|
||||
const { addon, extension } = await installTestExtension();
|
||||
// Data passed by the caller.
|
||||
const formData = { "some-data-from-the-caller": true };
|
||||
// Metadata stored by Gecko, only passed when the add-on is installed, which
|
||||
// is what this test case verifies.
|
||||
//
|
||||
// NOTE: We JSON stringify + parse to get rid of the undefined values, which
|
||||
// we do not send to the server.
|
||||
const metadata = JSON.parse(
|
||||
JSON.stringify(await AbuseReporter.getReportData(addon))
|
||||
);
|
||||
// Register a request handler to (1) access the data submitted and (2) return
|
||||
// a 200 response.
|
||||
let dataSubmitted;
|
||||
apiRequestHandler = ({ data, request, response }) => {
|
||||
Assert.equal(
|
||||
request.getHeader("content-type"),
|
||||
"application/json",
|
||||
"expected content-type header"
|
||||
);
|
||||
Assert.ok(
|
||||
!request.hasHeader("authorization"),
|
||||
"expected no authorization header"
|
||||
);
|
||||
|
||||
dataSubmitted = JSON.parse(data);
|
||||
handleSubmitRequest({ request, response });
|
||||
};
|
||||
|
||||
const response = await AbuseReporter.sendAbuseReport(ADDON_ID, formData);
|
||||
|
||||
Assert.deepEqual(
|
||||
response,
|
||||
EXPECTED_API_RESPONSE,
|
||||
"expected successful response"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
dataSubmitted,
|
||||
{
|
||||
...formData,
|
||||
...metadata,
|
||||
// The add-on ID is unconditionally passed as `addon` on purpose. See:
|
||||
// https://mozilla.github.io/addons-server/topics/api/abuse.html#submitting-an-add-on-abuse-report
|
||||
addon: ADDON_ID,
|
||||
},
|
||||
"expected the right data to be sent to the server"
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_sendAbuseReport_addon_not_installed() {
|
||||
const formData = { "some-data-from-the-caller": true };
|
||||
// Register a request handler to (1) access the data submitted and (2) return
|
||||
// a 200 response.
|
||||
let dataSubmitted;
|
||||
apiRequestHandler = ({ data, request, response }) => {
|
||||
Assert.equal(
|
||||
request.getHeader("content-type"),
|
||||
"application/json",
|
||||
"expected content-type header"
|
||||
);
|
||||
Assert.ok(
|
||||
!request.hasHeader("authorization"),
|
||||
"expected no authorization header"
|
||||
);
|
||||
|
||||
dataSubmitted = JSON.parse(data);
|
||||
handleSubmitRequest({ request, response });
|
||||
};
|
||||
|
||||
const response = await AbuseReporter.sendAbuseReport(ADDON_ID, formData);
|
||||
|
||||
Assert.deepEqual(
|
||||
response,
|
||||
EXPECTED_API_RESPONSE,
|
||||
"expected successful response"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
dataSubmitted,
|
||||
{
|
||||
...formData,
|
||||
// The add-on ID is unconditionally passed as `addon` on purpose. See:
|
||||
// https://mozilla.github.io/addons-server/topics/api/abuse.html#submitting-an-add-on-abuse-report
|
||||
addon: ADDON_ID,
|
||||
},
|
||||
"expected the right data to be sent to the server"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_sendAbuseReport_with_authorization() {
|
||||
const { addon, extension } = await installTestExtension();
|
||||
// Data passed by the caller.
|
||||
const formData = { "some-data-from-the-caller": true };
|
||||
// Metadata stored by Gecko, only passed when the add-on is installed, which
|
||||
// is what this test case verifies.
|
||||
//
|
||||
// NOTE: We JSON stringify + parse to get rid of the undefined values, which
|
||||
// we do not send to the server.
|
||||
const metadata = JSON.parse(
|
||||
JSON.stringify(await AbuseReporter.getReportData(addon))
|
||||
);
|
||||
const authorization = "some authorization header";
|
||||
// Register a request handler to (1) access the data submitted and (2) return
|
||||
// a 200 response.
|
||||
let dataSubmitted;
|
||||
apiRequestHandler = ({ data, request, response }) => {
|
||||
Assert.equal(
|
||||
request.getHeader("content-type"),
|
||||
"application/json",
|
||||
"expected content-type header"
|
||||
);
|
||||
Assert.equal(
|
||||
request.getHeader("authorization"),
|
||||
authorization,
|
||||
"expected authorization header"
|
||||
);
|
||||
|
||||
dataSubmitted = JSON.parse(data);
|
||||
handleSubmitRequest({ request, response });
|
||||
};
|
||||
|
||||
const response = await AbuseReporter.sendAbuseReport(ADDON_ID, formData, {
|
||||
authorization,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
response,
|
||||
EXPECTED_API_RESPONSE,
|
||||
"expected successful response"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
dataSubmitted,
|
||||
{
|
||||
...formData,
|
||||
...metadata,
|
||||
// The add-on ID is unconditionally passed as `addon` on purpose. See:
|
||||
// https://mozilla.github.io/addons-server/topics/api/abuse.html#submitting-an-add-on-abuse-report
|
||||
addon: ADDON_ID,
|
||||
},
|
||||
"expected the right data to be sent to the server"
|
||||
);
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
||||
add_task(async function test_sendAbuseReport_errors() {
|
||||
const { extension } = await installTestExtension();
|
||||
|
||||
async function testErrorCode({
|
||||
responseStatus,
|
||||
responseText = "",
|
||||
expectedErrorType,
|
||||
expectedErrorInfo,
|
||||
expectRequest = true,
|
||||
}) {
|
||||
info(
|
||||
`Test expected AbuseReportError on response status "${responseStatus}"`
|
||||
);
|
||||
|
||||
let requestReceived = false;
|
||||
apiRequestHandler = ({ request, response }) => {
|
||||
requestReceived = true;
|
||||
response.setStatusLine(request.httpVersion, responseStatus, "Error");
|
||||
response.write(responseText);
|
||||
};
|
||||
|
||||
const promise = AbuseReporter.sendAbuseReport(ADDON_ID, {});
|
||||
if (typeof expectedErrorType === "string") {
|
||||
// Assert a specific AbuseReportError errorType.
|
||||
await assertRejectsAbuseReportError(
|
||||
promise,
|
||||
expectedErrorType,
|
||||
expectedErrorInfo
|
||||
);
|
||||
} else {
|
||||
// Assert on a given Error class.
|
||||
await Assert.rejects(
|
||||
promise,
|
||||
expectedErrorType,
|
||||
"expected correct Error class"
|
||||
);
|
||||
}
|
||||
equal(
|
||||
requestReceived,
|
||||
expectRequest,
|
||||
`${expectRequest ? "" : "Not "}received a request as expected`
|
||||
);
|
||||
}
|
||||
|
||||
await testErrorCode({
|
||||
responseStatus: 500,
|
||||
responseText: "A server error",
|
||||
expectedErrorType: "ERROR_SERVER",
|
||||
expectedErrorInfo: JSON.stringify({
|
||||
status: 500,
|
||||
responseText: "A server error",
|
||||
}),
|
||||
});
|
||||
await testErrorCode({
|
||||
responseStatus: 404,
|
||||
responseText: "Not found error",
|
||||
expectedErrorType: "ERROR_CLIENT",
|
||||
expectedErrorInfo: JSON.stringify({
|
||||
status: 404,
|
||||
responseText: "Not found error",
|
||||
}),
|
||||
});
|
||||
// Test response with unexpected status code.
|
||||
await testErrorCode({
|
||||
responseStatus: 604,
|
||||
responseText: "An unexpected status code",
|
||||
expectedErrorType: "ERROR_UNKNOWN",
|
||||
expectedErrorInfo: JSON.stringify({
|
||||
status: 604,
|
||||
responseText: "An unexpected status code",
|
||||
}),
|
||||
});
|
||||
// Test response status 200 with invalid json data.
|
||||
await testErrorCode({
|
||||
responseStatus: 200,
|
||||
expectedErrorType: /SyntaxError: JSON.parse/,
|
||||
});
|
||||
// Test on invalid url.
|
||||
Services.prefs.setCharPref(
|
||||
"extensions.addonAbuseReport.url",
|
||||
"invalid-protocol://abuse-report"
|
||||
);
|
||||
await testErrorCode({
|
||||
expectedErrorType: "ERROR_NETWORK",
|
||||
expectRequest: false,
|
||||
});
|
||||
|
||||
await extension.unload();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user