summaryrefslogtreecommitdiffstats
path: root/browser/components/attribution/test/xpcshell
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/components/attribution/test/xpcshell
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/attribution/test/xpcshell')
-rw-r--r--browser/components/attribution/test/xpcshell/head.js136
-rw-r--r--browser/components/attribution/test/xpcshell/test_AttributionCode.js153
-rw-r--r--browser/components/attribution/test/xpcshell/test_MacAttribution.js137
-rw-r--r--browser/components/attribution/test/xpcshell/test_attribution_parsing.js44
-rw-r--r--browser/components/attribution/test/xpcshell/xpcshell.toml14
5 files changed, 484 insertions, 0 deletions
diff --git a/browser/components/attribution/test/xpcshell/head.js b/browser/components/attribution/test/xpcshell/head.js
new file mode 100644
index 0000000000..67a93563d4
--- /dev/null
+++ b/browser/components/attribution/test/xpcshell/head.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const { AttributionCode } = ChromeUtils.importESModule(
+ "resource:///modules/AttributionCode.sys.mjs"
+);
+
+let validAttrCodes = [
+ {
+ code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D(not%20set)%26content%3D(not%20set)",
+ parsed: {
+ source: "google.com",
+ medium: "organic",
+ campaign: "(not%20set)",
+ content: "(not%20set)",
+ },
+ },
+ {
+ code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D(not%20set)%26content%3D(not%20set)%26msstoresignedin%3Dtrue",
+ parsed: {
+ source: "google.com",
+ medium: "organic",
+ campaign: "(not%20set)",
+ content: "(not%20set)",
+ msstoresignedin: true,
+ },
+ platforms: ["win"],
+ },
+ {
+ code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D%26content%3D",
+ parsed: { source: "google.com", medium: "organic" },
+ doesNotRoundtrip: true, // `campaign=` and `=content` are dropped.
+ },
+ {
+ code: "source%3Dgoogle.com%26medium%3Dorganic%26campaign%3D(not%20set)",
+ parsed: {
+ source: "google.com",
+ medium: "organic",
+ campaign: "(not%20set)",
+ },
+ },
+ {
+ code: "source%3Dgoogle.com%26medium%3Dorganic",
+ parsed: { source: "google.com", medium: "organic" },
+ },
+ { code: "source%3Dgoogle.com", parsed: { source: "google.com" } },
+ { code: "medium%3Dgoogle.com", parsed: { medium: "google.com" } },
+ { code: "campaign%3Dgoogle.com", parsed: { campaign: "google.com" } },
+ { code: "content%3Dgoogle.com", parsed: { content: "google.com" } },
+ {
+ code: "experiment%3Dexperimental",
+ parsed: { experiment: "experimental" },
+ },
+ { code: "variation%3Dvaried", parsed: { variation: "varied" } },
+ {
+ code: "ua%3DGoogle%20Chrome%20123",
+ parsed: { ua: "Google%20Chrome%20123" },
+ },
+ {
+ code: "dltoken%3Dc18f86a3-f228-4d98-91bb-f90135c0aa9c",
+ parsed: { dltoken: "c18f86a3-f228-4d98-91bb-f90135c0aa9c" },
+ },
+ {
+ code: "dlsource%3Dsome-dl-source",
+ parsed: {
+ dlsource: "some-dl-source",
+ },
+ },
+];
+
+let invalidAttrCodes = [
+ // Empty string
+ "",
+ // Not escaped
+ "source=google.com&medium=organic&campaign=(not set)&content=(not set)",
+ // Too long
+ "campaign%3D" + "a".repeat(1000),
+ // Unknown key name
+ "source%3Dgoogle.com%26medium%3Dorganic%26large%3Dgeneticallymodified",
+ // Empty key name
+ "source%3Dgoogle.com%26medium%3Dorganic%26%3Dgeneticallymodified",
+];
+
+/**
+ * Arrange for each test to have a unique application path for storing
+ * quarantine data.
+ *
+ * The quarantine data is necessarily a shared system resource, managed by the
+ * OS, so we need to avoid polluting it during tests.
+ *
+ * There are at least two ways to achieve this. Here we use Sinon to stub the
+ * relevant accessors: this has the advantage of being local and relatively easy
+ * to follow. In the App Update Service tests, an `nsIDirectoryServiceProvider`
+ * is installed, which is global and much harder to extract for re-use.
+ */
+async function setupStubs() {
+ // Local imports to avoid polluting the global namespace.
+ const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+ const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+ );
+
+ // This depends on the caller to invoke it by name. We do try to
+ // prevent the most obvious incorrect invocation, namely
+ // `add_task(setupStubs)`.
+ let caller = Components.stack.caller;
+ const testID = caller.filename.toString().split("/").pop().split(".")[0];
+ notEqual(testID, "head");
+
+ let applicationFile = do_get_tempdir();
+ applicationFile.append(testID);
+ applicationFile.append("App.app");
+
+ if (AppConstants.platform == "macosx") {
+ // We're implicitly using the fact that modules are shared between importers here.
+ const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+ );
+ sinon
+ .stub(MacAttribution, "applicationPath")
+ .get(() => applicationFile.path);
+ }
+
+ // The macOS quarantine database applies to existing paths only, so make
+ // sure our mock application path exists. This also creates the parent
+ // directory for the attribution file, needed on both macOS and Windows. We
+ // don't ignore existing paths because we're inside a temporary directory:
+ // this should never be invoked twice for the same test.
+ await IOUtils.makeDirectory(applicationFile.path, {
+ from: do_get_tempdir().path,
+ });
+}
diff --git a/browser/components/attribution/test/xpcshell/test_AttributionCode.js b/browser/components/attribution/test/xpcshell/test_AttributionCode.js
new file mode 100644
index 0000000000..69aaa5fdaf
--- /dev/null
+++ b/browser/components/attribution/test/xpcshell/test_AttributionCode.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+add_task(async () => {
+ await setupStubs();
+});
+
+/**
+ * Test validation of attribution codes,
+ * to make sure we reject bad ones and accept good ones.
+ */
+add_task(async function testValidAttrCodes() {
+ let msixCampaignIdStub = sinon.stub(AttributionCode, "msixCampaignId");
+
+ let currentCode = null;
+ for (let entry of validAttrCodes) {
+ currentCode = entry.code;
+ // Attribution for MSIX builds works quite differently than regular Windows
+ // builds: the attribution codes come from a Windows API that we've wrapped
+ // with an XPCOM class. We don't have a way to inject codes into the build,
+ // so instead we mock out our XPCOM class and return the desired values from
+ // there. (A potential alternative is to have MSIX tests rely on attribution
+ // files, but that more or less invalidates the tests.)
+ if (
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")
+ ) {
+ // In real life, the attribution codes returned from Microsoft APIs
+ // are not URI encoded, and the AttributionCode code that deals with
+ // them expects that - so we have to simulate that as well.
+ msixCampaignIdStub.callsFake(async () => decodeURIComponent(currentCode));
+ } else if (AppConstants.platform === "macosx") {
+ const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+ );
+
+ await MacAttribution.setAttributionString(currentCode);
+ } else {
+ // non-msix windows
+ await AttributionCode.writeAttributionFile(currentCode);
+ }
+ AttributionCode._clearCache();
+ let result = await AttributionCode.getAttrDataAsync();
+
+ Assert.deepEqual(
+ result,
+ entry.parsed,
+ "Parsed code should match expected value, code was: " + currentCode
+ );
+ }
+ AttributionCode._clearCache();
+
+ // Restore the msixCampaignId stub so that other tests don't fail stubbing it
+ msixCampaignIdStub.restore();
+});
+
+/**
+ * Make sure codes with various formatting errors are not seen as valid.
+ */
+add_task(async function testInvalidAttrCodes() {
+ let msixCampaignIdStub = sinon.stub(AttributionCode, "msixCampaignId");
+ let currentCode = null;
+
+ for (let code of invalidAttrCodes) {
+ currentCode = code;
+
+ if (
+ AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")
+ ) {
+ if (code.includes("not set")) {
+ // One of the "invalid" codes that we test is an unescaped one.
+ // This is valid for most platforms, but we actually _expect_
+ // unescaped codes for MSIX builds, so that particular test is not
+ // valid for this case.
+ continue;
+ }
+
+ msixCampaignIdStub.callsFake(async () => decodeURIComponent(currentCode));
+ } else if (AppConstants.platform === "macosx") {
+ const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+ );
+
+ await MacAttribution.setAttributionString(currentCode);
+ } else {
+ // non-msix windows
+ await AttributionCode.writeAttributionFile(currentCode);
+ }
+ AttributionCode._clearCache();
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ {},
+ "Code should have failed to parse: " + currentCode
+ );
+ }
+ AttributionCode._clearCache();
+
+ // Restore the msixCampaignId stub so that other tests don't fail stubbing it
+ msixCampaignIdStub.restore();
+});
+
+/**
+ * Test the cache by deleting the attribution data file
+ * and making sure we still get the expected code.
+ */
+let condition = {
+ // macOS and MSIX attribution codes are not cached by us, thus this test is
+ // unnecessary for those builds.
+ skip_if: () =>
+ (AppConstants.platform === "win" &&
+ Services.sysinfo.getProperty("hasWinPackageId")) ||
+ AppConstants.platform === "macosx",
+};
+add_task(condition, async function testDeletedFile() {
+ // Set up the test by clearing the cache and writing a valid file.
+ await AttributionCode.writeAttributionFile(validAttrCodes[0].code);
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ validAttrCodes[0].parsed,
+ "The code should be readable directly from the file"
+ );
+
+ // Delete the file and make sure we can still read the value back from cache.
+ await AttributionCode.deleteFileAsync();
+ result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ validAttrCodes[0].parsed,
+ "The code should be readable from the cache"
+ );
+
+ // Clear the cache and check we can't read anything.
+ AttributionCode._clearCache();
+ result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ {},
+ "Shouldn't be able to get a code after file is deleted and cache is cleared"
+ );
+});
diff --git a/browser/components/attribution/test/xpcshell/test_MacAttribution.js b/browser/components/attribution/test/xpcshell/test_MacAttribution.js
new file mode 100644
index 0000000000..10bbe6c965
--- /dev/null
+++ b/browser/components/attribution/test/xpcshell/test_MacAttribution.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+const { MacAttribution } = ChromeUtils.importESModule(
+ "resource:///modules/MacAttribution.sys.mjs"
+);
+
+let extendedAttributeTestCases = [
+ {
+ raw: "__MOZCUSTOM__dlsource%3D=mozci",
+ expected: "dlsource%3D=mozci",
+ error: false,
+ },
+ {
+ raw: "__MOZCUSTOM__dlsource%3D=mozci\0\0\0\0\0",
+ expected: "dlsource%3D=mozci",
+ error: false,
+ },
+ {
+ raw: "__MOZCUSTOM__dlsource%3D=mozci\t\t\t\t\t",
+ expected: "dlsource%3D=mozci",
+ error: false,
+ },
+ {
+ raw: "__MOZCUSTOM__dlsource%3D=mozci\0\0\0\t\t",
+ expected: "dlsource%3D=mozci",
+ error: false,
+ },
+ {
+ raw: "__MOZCUSTOM__dlsource%3D=mozci\t\t\0\0",
+ expected: "dlsource%3D=mozci",
+ error: false,
+ },
+ {
+ raw: "__MOZCUSTOM__dlsource%3D=mozci\0\t\0\t\0\t",
+ expected: "dlsource%3D=mozci",
+ error: false,
+ },
+ {
+ raw: "",
+ expected: "",
+ error: true,
+ },
+ {
+ raw: "dlsource%3D=mozci\0\t\0\t\0\t",
+ expected: "",
+ error: true,
+ },
+];
+
+add_task(async () => {
+ await setupStubs();
+});
+
+// Tests to ensure that MacAttribution.getAttributionString
+// strips away the parts of the extended attribute that it should,
+// and that invalid extended attribute values result in no attribution
+// data.
+add_task(async function testExtendedAttributeProcessing() {
+ for (let entry of extendedAttributeTestCases) {
+ // We use setMacXAttr directly here rather than setAttributionString because
+ // we need the ability to set invalid attribution strings.
+ await IOUtils.setMacXAttr(
+ MacAttribution.applicationPath,
+ "com.apple.application-instance",
+ new TextEncoder().encode(entry.raw)
+ );
+ try {
+ let got = await MacAttribution.getAttributionString();
+ if (entry.error === true) {
+ Assert.ok(false, "Expected error, raw code was: " + entry.raw);
+ }
+ Assert.equal(
+ got,
+ entry.expected,
+ "Returned code should match expected value, raw code was: " + entry.raw
+ );
+ } catch (err) {
+ if (entry.error === false) {
+ Assert.ok(
+ false,
+ "Unexpected error, raw code was: " + entry.raw + " error is: " + err
+ );
+ }
+ }
+ }
+});
+
+add_task(async function testValidAttrCodes() {
+ for (let entry of validAttrCodes) {
+ if (entry.platforms && !entry.platforms.includes("mac")) {
+ continue;
+ }
+
+ await MacAttribution.setAttributionString(entry.code);
+
+ // Read attribution code from xattr.
+ AttributionCode._clearCache();
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ entry.parsed,
+ "Parsed code should match expected value, code was: " + entry.code
+ );
+
+ // Read attribution code from cache.
+ result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ entry.parsed,
+ "Parsed code should match expected value, code was: " + entry.code
+ );
+
+ // Does not overwrite cached existing attribution code.
+ await MacAttribution.setAttributionString("__MOZCUSTOM__testcode");
+ result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(
+ result,
+ entry.parsed,
+ "Parsed code should match expected value, code was: " + entry.code
+ );
+ }
+});
+
+add_task(async function testInvalidAttrCodes() {
+ for (let code of invalidAttrCodes) {
+ await MacAttribution.setAttributionString("__MOZCUSTOM__" + code);
+
+ // Read attribution code from xattr
+ AttributionCode._clearCache();
+ let result = await AttributionCode.getAttrDataAsync();
+ Assert.deepEqual(result, {}, "Code should have failed to parse: " + code);
+ }
+});
diff --git a/browser/components/attribution/test/xpcshell/test_attribution_parsing.js b/browser/components/attribution/test/xpcshell/test_attribution_parsing.js
new file mode 100644
index 0000000000..abf2456ce4
--- /dev/null
+++ b/browser/components/attribution/test/xpcshell/test_attribution_parsing.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+/**
+ * This test file exists to be run on any platform during development,
+ * whereas the test_AttributionCode.js will test the attribution file
+ * in the app local data dir on Windows. It will only run under
+ * Windows on try.
+ */
+
+/**
+ * Test validation of attribution codes.
+ */
+add_task(async function testValidAttrCodes() {
+ for (let entry of validAttrCodes) {
+ let result = AttributionCode.parseAttributionCode(entry.code);
+ Assert.deepEqual(
+ result,
+ entry.parsed,
+ "Parsed code should match expected value, code was: " + entry.code
+ );
+
+ result = AttributionCode.serializeAttributionData(entry.parsed);
+ if (!entry.doesNotRoundtrip) {
+ Assert.deepEqual(
+ result,
+ entry.code,
+ "Serialized data should match expected value, code was: " + entry.code
+ );
+ }
+ }
+});
+
+/**
+ * Make sure codes with various formatting errors are not seen as valid.
+ */
+add_task(async function testInvalidAttrCodes() {
+ for (let code of invalidAttrCodes) {
+ let result = AttributionCode.parseAttributionCode(code);
+ Assert.deepEqual(result, {}, "Code should have failed to parse: " + code);
+ }
+});
diff --git a/browser/components/attribution/test/xpcshell/xpcshell.toml b/browser/components/attribution/test/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..5013d43519
--- /dev/null
+++ b/browser/components/attribution/test/xpcshell/xpcshell.toml
@@ -0,0 +1,14 @@
+[DEFAULT]
+firefox-appdir = "browser"
+run-if = [
+ "os == 'win'",
+ "os == 'mac'",
+]
+head = "head.js"
+
+["test_AttributionCode.js"]
+
+["test_MacAttribution.js"]
+run-if = ["os == 'mac'"] # osx specific tests
+
+["test_attribution_parsing.js"]