Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/browser/components/asrouter/tests/unit/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 92 kB image not shown  

Quelle  ASRouter.test.js   Sprache: JAVA

 
import { _ASRouter, MessageLoaderUtils } from "modules/ASRouter.sys.mjs";
import { QueryCache } from "modules/ASRouterTargeting.sys.mjs";
import {
  FAKE_LOCAL_MESSAGES,
  FAKE_LOCAL_PROVIDER,
  FAKE_LOCAL_PROVIDERS,
  FAKE_REMOTE_MESSAGES,
  FAKE_REMOTE_PROVIDER,
  FAKE_REMOTE_SETTINGS_PROVIDER,
} from "./constants";
import {
  ASRouterPreferences,
  TARGETING_PREFERENCES,
} from "modules/ASRouterPreferences.sys.mjs";
import { ASRouterTriggerListeners } from "modules/ASRouterTriggerListeners.sys.mjs";
import { CFRPageActions } from "modules/CFRPageActions.sys.mjs";
import { GlobalOverrider } from "tests/unit/utils";
import { PanelTestProvider } from "modules/PanelTestProvider.sys.mjs";
import ProviderResponseSchema from "content-src/schemas/provider-response.schema.json";

const MESSAGE_PROVIDER_PREF_NAME =
  "browser.newtabpage.activity-stream.asrouter.providers.cfr";
const FAKE_PROVIDERS = [
  FAKE_LOCAL_PROVIDER,
  FAKE_REMOTE_PROVIDER,
  FAKE_REMOTE_SETTINGS_PROVIDER,
];
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
const FAKE_RESPONSE_HEADERS = { get() {} };
const FAKE_BUNDLE = [FAKE_LOCAL_MESSAGES[1], FAKE_LOCAL_MESSAGES[2]];

const USE_REMOTE_L10N_PREF =
  "browser.newtabpage.activity-stream.asrouter.useRemoteL10n";

// eslint-disable-next-line max-statements
describe("ASRouter", () => {
  let Router;
  let globals;
  let sandbox;
  let initParams;
  let messageBlockList;
  let providerBlockList;
  let messageImpressions;
  let groupImpressions;
  let previousSessionEnd;
  let fetchStub;
  let clock;
  let fakeAttributionCode;
  let fakeTargetingContext;
  let FakeToolbarBadgeHub;
  let FakeMomentsPageHub;
  let ASRouterTargeting;
  let gBrowser;
  let screenImpressions;

  function setMessageProviderPref(value) {
    sandbox.stub(ASRouterPreferences, "providers").get(() => value);
  }

  function initASRouter(router) {
    const getStub = sandbox.stub();
    getStub.returns(Promise.resolve());
    getStub
      .withArgs("messageBlockList")
      .returns(Promise.resolve(messageBlockList));
    getStub
      .withArgs("providerBlockList")
      .returns(Promise.resolve(providerBlockList));
    getStub
      .withArgs("messageImpressions")
      .returns(Promise.resolve(messageImpressions));
    getStub.withArgs("groupImpressions").resolves(groupImpressions);
    getStub
      .withArgs("previousSessionEnd")
      .returns(Promise.resolve(previousSessionEnd));
    getStub
      .withArgs("screenImpressions")
      .returns(Promise.resolve(screenImpressions));
    initParams = {
      storage: {
        get: getStub,
        set: sandbox.stub().returns(Promise.resolve()),
      },
      sendTelemetry: sandbox.stub().resolves(),
      clearChildMessages: sandbox.stub().resolves(),
      clearChildProviders: sandbox.stub().resolves(),
      updateAdminState: sandbox.stub().resolves(),
      dispatchCFRAction: sandbox.stub().resolves(),
    };
    sandbox.stub(router, "loadMessagesFromAllProviders").callThrough();
    return router.init(initParams);
  }

  async function createRouterAndInit(providers = FAKE_PROVIDERS) {
    setMessageProviderPref(providers);
    // `.freeze` to catch any attempts at modifying the object
    Router = new _ASRouter(Object.freeze(FAKE_LOCAL_PROVIDERS));
    await initASRouter(Router);
  }

  beforeEach(async () => {
    globals = new GlobalOverrider();
    messageBlockList = [];
    providerBlockList = [];
    messageImpressions = {};
    groupImpressions = {};
    previousSessionEnd = 100;
    screenImpressions = {};
    sandbox = sinon.createSandbox();
    ASRouterTargeting = {
      isMatch: sandbox.stub(),
      findMatchingMessage: sandbox.stub(),
      Environment: {
        locale: "en-US",
        localeLanguageCode: "en",
        browserSettings: {
          update: {
            channel: "default",
            enabled: true,
            autoDownload: true,
          },
        },
        attributionData: {},
        currentDate: "2000-01-01T10:00:0.001Z",
        profileAgeCreated: {},
        profileAgeReset: {},
        usesFirefoxSync: false,
        isFxAEnabled: true,
        isFxASignedIn: false,
        sync: {
          desktopDevices: 0,
          mobileDevices: 0,
          totalDevices: 0,
        },
        xpinstallEnabled: true,
        addonsInfo: {},
        searchEngines: {},
        isDefaultBrowser: false,
        devToolsOpenedCount: 5,
        topFrecentSites: {},
        recentBookmarks: {},
        pinnedSites: [
          {
            url: "https://amazon.com",
            host: "amazon.com",
            searchTopSite: true,
          },
        ],
        providerCohorts: {
          onboarding: "",
          cfr: "",
          "message-groups""",
          "messaging-experiments""",
        },
        totalBookmarksCount: {},
        firefoxVersion: 80,
        region: "US",
        needsUpdate: {},
        hasPinnedTabs: false,
        hasAccessedFxAPanel: false,
        userPrefs: {
          cfrFeatures: true,
          cfrAddons: true,
        },
        totalBlockedCount: {},
        blockedCountByType: {},
        attachedFxAOAuthClients: [],
        platformName: "macosx",
        scores: {},
        scoreThreshold: 5000,
        isChinaRepack: false,
        userId: "adsf",
      },
    };
    gBrowser = {
      selectedBrowser: {
        constructor: { name: "MozBrowser" },
        get ownerGlobal() {
          return { gBrowser };
        },
      },
    };

    ASRouterPreferences.specialConditions = {
      someCondition: true,
    };
    sandbox.spy(ASRouterPreferences, "init");
    sandbox.spy(ASRouterPreferences, "uninit");
    sandbox.spy(ASRouterPreferences, "addListener");
    sandbox.spy(ASRouterPreferences, "removeListener");

    clock = sandbox.useFakeTimers();
    fetchStub = sandbox
      .stub(global, "fetch")
      .withArgs("http://fake.com/endpoint")
      .resolves({
        ok: true,
        status: 200,
        json: () => Promise.resolve({ messages: FAKE_REMOTE_MESSAGES }),
        headers: FAKE_RESPONSE_HEADERS,
      });
    sandbox.stub(global.Services.prefs, "getStringPref");

    fakeAttributionCode = {
      allowedCodeKeys: ["foo""bar""baz"],
      _clearCache: () => sinon.stub(),
      getAttrDataAsync: () => Promise.resolve({ content: "addonID" }),
      deleteFileAsync: () => Promise.resolve(),
      writeAttributionFile: () => Promise.resolve(),
      getCachedAttributionData: sinon.stub(),
    };
    FakeToolbarBadgeHub = {
      init: sandbox.stub(),
      uninit: sandbox.stub(),
      registerBadgeNotificationListener: sandbox.stub(),
    };
    FakeMomentsPageHub = {
      init: sandbox.stub(),
      uninit: sandbox.stub(),
      executeAction: sandbox.stub(),
    };
    fakeTargetingContext = {
      combineContexts: sandbox.stub(),
      evalWithDefault: sandbox.stub().resolves(),
    };
    let fakeNimbusFeatures = [
      "cfr",
      "infobar",
      "spotlight",
      "moments-page",
      "pbNewtab",
    ].reduce((features, featureId) => {
      features[featureId] = {
        getAllVariables: sandbox.stub().returns(null),
        recordExposureEvent: sandbox.stub(),
      };
      return features;
    }, {});
    globals.set({
      // Testing framework doesn't know how to `defineESModuleGetters` so we're
      // importing these modules into the global scope ourselves.
      GroupsConfigurationProvider: { getMessages: () => Promise.resolve([]) },
      ASRouterPreferences,
      TARGETING_PREFERENCES,
      ASRouterTargeting,
      ASRouterTriggerListeners,
      QueryCache,
      gBrowser,
      gURLBar: {},
      isSeparateAboutWelcome: true,
      AttributionCode: fakeAttributionCode,
      PanelTestProvider,
      MacAttribution: { applicationPath: "" },
      ToolbarBadgeHub: FakeToolbarBadgeHub,
      MomentsPageHub: FakeMomentsPageHub,
      KintoHttpClient: class {
        bucket() {
          return this;
        }
        collection() {
          return this;
        }
        getRecord() {
          return Promise.resolve({ data: {} });
        }
      },
      Downloader: class {
        download() {
          return Promise.resolve("/path/to/download");
        }
      },
      NimbusFeatures: fakeNimbusFeatures,
      ExperimentAPI: {
        getExperimentMetaData: sandbox.stub().returns({
          slug: "experiment-slug",
          active: true,
          branch: { slug: "experiment-branch-slug" },
        }),
        getExperiment: sandbox.stub().returns({
          branch: {
            slug: "unit-slug",
            feature: {
              featureId: "foo",
              value: { id: "test-message" },
            },
          },
        }),
        getAllBranches: sandbox.stub().resolves([]),
        ready: sandbox.stub().resolves(),
      },
      SpecialMessageActions: {
        handleAction: sandbox.stub(),
      },
      TargetingContext: class {
        static combineContexts(...args) {
          return fakeTargetingContext.combineContexts.apply(sandbox, args);
        }

        evalWithDefault(expr) {
          return fakeTargetingContext.evalWithDefault(expr);
        }
      },
      RemoteL10n: {
        // This is just a subset of supported locales that happen to be used in
        // the test.
        isLocaleSupported: locale => ["en-US""ja-JP-mac"].includes(locale),
      },
    });
    await createRouterAndInit();
  });
  afterEach(() => {
    Router.uninit();
    ASRouterPreferences.uninit();
    sandbox.restore();
    globals.restore();
  });

  describe(".state", () => {
    it("should throw if an attempt to set .state was made", () => {
      assert.throws(() => {
        Router.state = {};
      });
    });
  });

  describe("#init", () => {
    it("should only be called once", async () => {
      Router = new _ASRouter();
      let state = await initASRouter(Router);

      assert.equal(state, Router.state);

      assert.isNull(await Router.init({}));
    });
    it("should only be called once", async () => {
      Router = new _ASRouter();
      initASRouter(Router);
      let secondCall = await Router.init({});

      assert.isNull(
        secondCall,
        "Should not init twice, it should exit early with null"
      );
    });
    it("should set state.messageBlockList to the block list in persistent storage", async () => {
      messageBlockList = ["foo"];
      Router = new _ASRouter();
      await initASRouter(Router);

      assert.deepEqual(Router.state.messageBlockList, ["foo"]);
    });
    it("should initialize all the hub providers", async () => {
      // ASRouter init called in `beforeEach` block above

      assert.calledOnce(FakeToolbarBadgeHub.init);
      assert.calledOnce(FakeMomentsPageHub.init);

      assert.calledWithExactly(
        FakeToolbarBadgeHub.init,
        Router.waitForInitialized,
        {
          handleMessageRequest: Router.handleMessageRequest,
          addImpression: Router.addImpression,
          blockMessageById: Router.blockMessageById,
          sendTelemetry: Router.sendTelemetry,
          unblockMessageById: Router.unblockMessageById,
        }
      );

      assert.calledWithExactly(
        FakeMomentsPageHub.init,
        Router.waitForInitialized,
        {
          handleMessageRequest: Router.handleMessageRequest,
          addImpression: Router.addImpression,
          blockMessageById: Router.blockMessageById,
          sendTelemetry: Router.sendTelemetry,
        }
      );
    });
    it("should set state.messageImpressions to the messageImpressions object in persistent storage", async () => {
      // Note that messageImpressions are only kept if a message exists in router and has a .frequency property,
      // otherwise they will be cleaned up by .cleanupImpressions()
      const testMessage = { id: "foo", frequency: { lifetimeCap: 10 } };
      messageImpressions = { foo: [0, 1, 2] };
      setMessageProviderPref([
        { id: "onboarding", type: "local", messages: [testMessage] },
      ]);
      Router = new _ASRouter();
      await initASRouter(Router);

      assert.deepEqual(Router.state.messageImpressions, messageImpressions);
    });
    it("should set state.screenImpressions to the screenImpressions object in persistent storage", async () => {
      screenImpressions = { test: 123 };

      Router = new _ASRouter();
      await initASRouter(Router);

      assert.deepEqual(Router.state.screenImpressions, screenImpressions);
    });
    it("should clear impressions for groups that are not active", async () => {
      groupImpressions = { foo: [0, 1, 2] };
      Router = new _ASRouter();
      await initASRouter(Router);

      assert.notProperty(Router.state.groupImpressions, "foo");
    });
    it("should keep impressions for groups that are active", async () => {
      Router = new _ASRouter();
      await initASRouter(Router);
      await Router.setState(() => {
        return {
          groups: [
            {
              id: "foo",
              enabled: true,
              frequency: {
                custom: [{ period: ONE_DAY_IN_MS, cap: 10 }],
                lifetime: Infinity,
              },
            },
          ],
          groupImpressions: { foo: [Date.now()] },
        };
      });
      Router.cleanupImpressions();

      assert.property(Router.state.groupImpressions, "foo");
      assert.lengthOf(Router.state.groupImpressions.foo, 1);
    });
    it("should remove old impressions for a group", async () => {
      Router = new _ASRouter();
      await initASRouter(Router);
      await Router.setState(() => {
        return {
          groups: [
            {
              id: "foo",
              enabled: true,
              frequency: {
                custom: [{ period: ONE_DAY_IN_MS, cap: 10 }],
              },
            },
          ],
          groupImpressions: {
            foo: [Date.now() - ONE_DAY_IN_MS - 1, Date.now()],
          },
        };
      });
      Router.cleanupImpressions();

      assert.property(Router.state.groupImpressions, "foo");
      assert.lengthOf(Router.state.groupImpressions.foo, 1);
    });
    it("should await .loadMessagesFromAllProviders() and add messages from providers to state.messages", async () => {
      Router = new _ASRouter(Object.freeze(FAKE_LOCAL_PROVIDERS));

      await initASRouter(Router);

      assert.calledOnce(Router.loadMessagesFromAllProviders);
      assert.isArray(Router.state.messages);
      assert.lengthOf(
        Router.state.messages,
        FAKE_LOCAL_MESSAGES.length + FAKE_REMOTE_MESSAGES.length
      );
    });
    it("should set state.previousSessionEnd from IndexedDB", async () => {
      previousSessionEnd = 200;
      await createRouterAndInit();

      assert.equal(Router.state.previousSessionEnd, previousSessionEnd);
    });
    it("should assign ASRouterPreferences.specialConditions to state", async () => {
      assert.isTrue(ASRouterPreferences.specialConditions.someCondition);
      assert.isTrue(Router.state.someCondition);
    });
    it("should add observer for `intl:app-locales-changed`", async () => {
      sandbox.spy(global.Services.obs, "addObserver");
      await createRouterAndInit();

      assert.calledWithExactly(
        global.Services.obs.addObserver,
        Router._onLocaleChanged,
        "intl:app-locales-changed"
      );
    });
    it("should add a pref observer", async () => {
      sandbox.spy(global.Services.prefs, "addObserver");
      await createRouterAndInit();

      assert.calledOnce(global.Services.prefs.addObserver);
      assert.calledWithExactly(
        global.Services.prefs.addObserver,
        USE_REMOTE_L10N_PREF,
        Router
      );
    });
    describe("lazily loading local test providers", () => {
      let justIdAndContent = ({ id, content }) => ({ id, content });
      afterEach(() => Router.uninit());

      it("should add the local test providers on init if devtools are enabled", async () => {
        sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);

        await createRouterAndInit();

        assert.property(Router._localProviders, "PanelTestProvider");
      });
      it("should not add the local test providers on init if devtools are disabled", async () => {
        sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => false);

        await createRouterAndInit();

        assert.notProperty(Router._localProviders, "PanelTestProvider");
      });
      it("should flatten experiment translated messages from local test providers if devtools are enabled...", async () => {
        sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);

        await createRouterAndInit();

        assert.property(Router._localProviders, "PanelTestProvider");

        expect(
          Router.state.messages.map(justIdAndContent)
        ).to.deep.include.members([
          { id: "experimentL10n", content: { text: "UniqueText" } },
        ]);
      });
      it("...but not if devtools are disabled", async () => {
        sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => false);

        await createRouterAndInit();

        assert.notProperty(Router._localProviders, "PanelTestProvider");

        let justIdAndContentMessages =
          Router.state.messages.map(justIdAndContent);
        expect(justIdAndContentMessages).not.to.deep.include.members([
          { id: "experimentL10n", content: { text: "UniqueText" } },
        ]);
        expect(justIdAndContentMessages).to.deep.include.members([
          {
            id: "experimentL10n",
            content: { text: { $l10n: { text: "UniqueText" } } },
          },
        ]);
      });
    });
  });

  describe("preference changes", () => {
    it("should call ASRouterPreferences.init and add a listener on init", () => {
      assert.calledOnce(ASRouterPreferences.init);
      assert.calledWith(ASRouterPreferences.addListener, Router.onPrefChange);
    });
    it("should call ASRouterPreferences.uninit and remove the listener on uninit", () => {
      Router.uninit();
      assert.calledOnce(ASRouterPreferences.uninit);
      assert.calledWith(
        ASRouterPreferences.removeListener,
        Router.onPrefChange
      );
    });
    it("should call clearChildMessages (does nothing, see bug 1899028)", async () => {
      const messageTargeted = {
        id: "1",
        campaign: "foocampaign",
        targeting: "true",
        groups: ["cfr"],
        provider: "cfr",
      };
      const messageNotTargeted = {
        id: "2",
        campaign: "foocampaign",
        groups: ["cfr"],
        provider: "cfr",
      };
      await Router.setState({
        messages: [messageTargeted, messageNotTargeted],
        providers: [{ id: "cfr" }],
      });
      fakeTargetingContext.evalWithDefault.resolves(false);

      await Router.onPrefChange("services.sync.username");

      assert.calledOnce(initParams.clearChildMessages);
      assert.calledWith(initParams.clearChildMessages, [messageTargeted.id]);
    });
    it("should call loadMessagesFromAllProviders on pref change", () => {
      ASRouterPreferences.observe(nullnull, MESSAGE_PROVIDER_PREF_NAME);
      assert.calledOnce(Router.loadMessagesFromAllProviders);
    });
    it("should update groups state if a user pref changes", async () => {
      await Router.setState({
        groups: [{ id: "foo", userPreferences: ["bar"], enabled: true }],
      });
      sandbox.stub(ASRouterPreferences, "getUserPreference");

      await Router.onPrefChange(MESSAGE_PROVIDER_PREF_NAME);

      assert.calledWithExactly(ASRouterPreferences.getUserPreference, "bar");
    });
    it("should update the list of providers on pref change", async () => {
      const modifiedRemoteProvider = Object.assign({}, FAKE_REMOTE_PROVIDER, {
        url: "baz.com",
      });
      setMessageProviderPref([
        FAKE_LOCAL_PROVIDER,
        modifiedRemoteProvider,
        FAKE_REMOTE_SETTINGS_PROVIDER,
      ]);

      const { length } = Router.state.providers;

      ASRouterPreferences.observe(nullnull, MESSAGE_PROVIDER_PREF_NAME);
      await Router._updateMessageProviders();

      const provider = Router.state.providers.find(p => p.url === "baz.com");
      assert.lengthOf(Router.state.providers, length);
      assert.isDefined(provider);
    });
    it("should clear disabled providers on pref change", async () => {
      const TEST_PROVIDER_ID = "some_provider_id";
      await Router.setState({
        providers: [{ id: TEST_PROVIDER_ID }],
      });
      const modifiedRemoteProvider = Object.assign({}, FAKE_REMOTE_PROVIDER, {
        id: TEST_PROVIDER_ID,
        enabled: false,
      });
      setMessageProviderPref([
        FAKE_LOCAL_PROVIDER,
        modifiedRemoteProvider,
        FAKE_REMOTE_SETTINGS_PROVIDER,
      ]);
      await Router.onPrefChange(MESSAGE_PROVIDER_PREF_NAME);

      assert.calledOnce(initParams.clearChildProviders);
      assert.calledWith(initParams.clearChildProviders, [TEST_PROVIDER_ID]);
    });
  });

  describe("setState", () => {
    it("should broadcast a message to update the admin tool on a state change if the asrouter.devtoolsEnabled pref is", async () => {
      sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => true);
      sandbox.stub(Router, "getTargetingParameters").resolves({});
      const state = await Router.setState({ foo: 123 });

      assert.calledOnce(initParams.updateAdminState);
      assert.deepEqual(state.providerPrefs, ASRouterPreferences.providers);
      assert.deepEqual(
        state.userPrefs,
        ASRouterPreferences.getAllUserPreferences()
      );
      assert.deepEqual(state.targetingParameters, {});
      assert.deepEqual(state.errors, Router.errors);
    });
    it("should not send a message on a state change asrouter.devtoolsEnabled pref is on", async () => {
      sandbox.stub(ASRouterPreferences, "devtoolsEnabled").get(() => false);
      await Router.setState({ foo: 123 });

      assert.notCalled(initParams.updateAdminState);
    });
  });

  describe("getTargetingParameters", () => {
    it("should return the targeting parameters", async () => {
      const stub = sandbox.stub().resolves("foo");
      const obj = { foo: 1 };
      sandbox.stub(obj, "foo").get(stub);
      const result = await Router.getTargetingParameters(obj, obj);

      assert.calledTwice(stub);
      assert.propertyVal(result, "foo""foo");
    });
  });

  describe("evaluateExpression", () => {
    it("should call ASRouterTargeting to evaluate", async () => {
      fakeTargetingContext.evalWithDefault.resolves("foo");
      const response = await Router.evaluateExpression({});
      assert.equal(response.evaluationStatus.result, "foo");
      assert.isTrue(response.evaluationStatus.success);
    });
    it("should catch evaluation errors", async () => {
      fakeTargetingContext.evalWithDefault.returns(
        Promise.reject(new Error("fake error"))
      );
      const response = await Router.evaluateExpression({});
      assert.isFalse(response.evaluationStatus.success);
    });
  });

  describe("#routeCFRMessage", () => {
    let browser;
    beforeEach(() => {
      sandbox.stub(CFRPageActions, "forceRecommendation");
      sandbox.stub(CFRPageActions, "addRecommendation");
      browser = {};
    });
    it("should route moments messages to the right hub", () => {
      Router.routeCFRMessage({ template: "update_action" }, browser, ""true);

      assert.calledOnce(FakeMomentsPageHub.executeAction);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(CFRPageActions.addRecommendation);
      assert.notCalled(CFRPageActions.forceRecommendation);
    });
    it("should route toolbar_badge message to the right hub", () => {
      Router.routeCFRMessage({ template: "toolbar_badge" }, browser);

      assert.calledOnce(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(CFRPageActions.addRecommendation);
      assert.notCalled(CFRPageActions.forceRecommendation);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
    it("should route milestone_message to the right hub", () => {
      Router.routeCFRMessage(
        { template: "milestone_message" },
        browser,
        "",
        false
      );

      assert.calledOnce(CFRPageActions.addRecommendation);
      assert.notCalled(CFRPageActions.forceRecommendation);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
    it("should route cfr_doorhanger message to the right hub force = false", () => {
      Router.routeCFRMessage(
        { template: "cfr_doorhanger" },
        browser,
        { param: {} },
        false
      );

      assert.calledOnce(CFRPageActions.addRecommendation);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(CFRPageActions.forceRecommendation);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
    it("should route cfr_doorhanger message to the right hub force = true", () => {
      Router.routeCFRMessage({ template: "cfr_doorhanger" }, browser, {}, true);

      assert.calledOnce(CFRPageActions.forceRecommendation);
      assert.notCalled(CFRPageActions.addRecommendation);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
    it("should route cfr_urlbar_chiclet message to the right hub force = false", () => {
      Router.routeCFRMessage(
        { template: "cfr_urlbar_chiclet" },
        browser,
        { param: {} },
        false
      );

      assert.calledOnce(CFRPageActions.addRecommendation);
      const { args } = CFRPageActions.addRecommendation.firstCall;
      // Host should be null
      assert.isNull(args[1]);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(CFRPageActions.forceRecommendation);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
    it("should route cfr_urlbar_chiclet message to the right hub force = true", () => {
      Router.routeCFRMessage(
        { template: "cfr_urlbar_chiclet" },
        browser,
        {},
        true
      );

      assert.calledOnce(CFRPageActions.forceRecommendation);
      assert.notCalled(CFRPageActions.addRecommendation);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
    it("should route default to sending to content", () => {
      Router.routeCFRMessage(
        { template: "some_other_template" },
        browser,
        {},
        true
      );

      assert.notCalled(CFRPageActions.forceRecommendation);
      assert.notCalled(CFRPageActions.addRecommendation);
      assert.notCalled(FakeToolbarBadgeHub.registerBadgeNotificationListener);
      assert.notCalled(FakeMomentsPageHub.executeAction);
    });
  });

  describe("#loadMessagesFromAllProviders", () => {
    function assertRouterContainsMessages(messages) {
      const messageIdsInRouter = Router.state.messages.map(m => m.id);
      for (const message of messages) {
        assert.include(messageIdsInRouter, message.id);
      }
    }

    it("should not trigger an update if not enough time has passed for a provider", async () => {
      await createRouterAndInit([
        {
          id: "remotey",
          type: "remote",
          enabled: true,
          url: "http://fake.com/endpoint",
          updateCycleInMs: 300,
        },
      ]);

      const previousState = Router.state;

      // Since we've previously gotten messages during init and we haven't advanced our fake timer,
      // no updates should be triggered.
      await Router.loadMessagesFromAllProviders();
      assert.deepEqual(Router.state, previousState);
    });
    it("should not trigger an update if we only have local providers", async () => {
      await createRouterAndInit([
        {
          id: "foo",
          type: "local",
          enabled: true,
          messages: FAKE_LOCAL_MESSAGES,
        },
      ]);

      const previousState = Router.state;
      const stub = sandbox.stub(MessageLoaderUtils, "loadMessagesForProvider");

      clock.tick(300);

      await Router.loadMessagesFromAllProviders();

      assert.deepEqual(Router.state, previousState);
      assert.notCalled(stub);
    });
    it("should update messages for a provider if enough time has passed, without removing messages for other providers", async () => {
      const NEW_MESSAGES = [{ id: "new_123" }];
      await createRouterAndInit([
        {
          id: "remotey",
          type: "remote",
          url: "http://fake.com/endpoint",
          enabled: true,
          updateCycleInMs: 300,
        },
        {
          id: "alocalprovider",
          type: "local",
          enabled: true,
          messages: FAKE_LOCAL_MESSAGES,
        },
      ]);
      fetchStub.withArgs("http://fake.com/endpoint").resolves({
        ok: true,
        status: 200,
        json: () => Promise.resolve({ messages: NEW_MESSAGES }),
        headers: FAKE_RESPONSE_HEADERS,
      });

      clock.tick(301);
      await Router.loadMessagesFromAllProviders();

      // These are the new messages
      assertRouterContainsMessages(NEW_MESSAGES);
      // These are the local messages that should not have been deleted
      assertRouterContainsMessages(FAKE_LOCAL_MESSAGES);
    });
    it("should parse the triggers in the messages and register the trigger listeners", async () => {
      sandbox.spy(
        ASRouterTriggerListeners.get("openURL"),
        "init"
      ); /* eslint-disable object-property-newline */

      /* eslint-disable object-curly-newline */ await createRouterAndInit([
        {
          id: "foo",
          type: "local",
          enabled: true,
          messages: [
            {
              id: "foo",
              template: "simple_template",
              trigger: { id: "firstRun" },
              content: { title: "Foo", body: "Foo123" },
            },
            {
              id: "bar1",
              template: "simple_template",
              trigger: {
                id: "openURL",
                params: ["www.mozilla.org""www.mozilla.com"],
              },
              content: { title: "Bar1", body: "Bar123" },
            },
            {
              id: "bar2",
              template: "simple_template",
              trigger: { id: "openURL", params: ["www.example.com"] },
              content: { title: "Bar2", body: "Bar123" },
            },
          ],
        },
      ]); /* eslint-enable object-property-newline */
      /* eslint-enable object-curly-newline */ assert.calledTwice(
        ASRouterTriggerListeners.get("openURL").init
      );
      assert.calledWithExactly(
        ASRouterTriggerListeners.get("openURL").init,
        Router._triggerHandler,
        ["www.mozilla.org""www.mozilla.com"],
        undefined
      );
      assert.calledWithExactly(
        ASRouterTriggerListeners.get("openURL").init,
        Router._triggerHandler,
        ["www.example.com"],
        undefined
      );
    });
    it("should parse the message's messagesLoaded trigger and immediately fire trigger", async () => {
      setMessageProviderPref([
        {
          id: "foo",
          type: "local",
          enabled: true,
          messages: [
            {
              id: "bar3",
              template: "simple_template",
              trigger: { id: "messagesLoaded" },
              content: { title: "Bar3", body: "Bar123" },
            },
          ],
        },
      ]);
      Router = new _ASRouter(Object.freeze(FAKE_LOCAL_PROVIDERS));
      sandbox.spy(Router, "sendTriggerMessage");
      await initASRouter(Router);
      assert.calledOnce(Router.sendTriggerMessage);
      assert.calledWith(
        Router.sendTriggerMessage,
        sandbox.match({ id: "messagesLoaded" }),
        true
      );
    });
    it("should gracefully handle messages loading before a window or browser exists", async () => {
      sandbox.stub(global, "gBrowser").value(undefined);
      sandbox
        .stub(global.Services.wm, "getMostRecentBrowserWindow")
        .returns(undefined);
      setMessageProviderPref([
        {
          id: "foo",
          type: "local",
          enabled: true,
          messages: [
            "cfr_doorhanger",
            "toolbar_badge",
            "update_action",
            "infobar",
            "spotlight",
            "toast_notification",
          ].map((template, i) => {
            return {
              id: `foo${i}`,
              template,
              trigger: { id: "messagesLoaded" },
              content: { title: `Foo${i}`, body: "Bar123" },
            };
          }),
        },
      ]);
      Router = new _ASRouter(Object.freeze(FAKE_LOCAL_PROVIDERS));
      sandbox.spy(Router, "sendTriggerMessage");
      await initASRouter(Router);
      assert.calledWith(
        Router.sendTriggerMessage,
        sandbox.match({ id: "messagesLoaded" }),
        true
      );
    });
    it("should gracefully handle RemoteSettings blowing up and dispatch undesired event", async () => {
      sandbox
        .stub(MessageLoaderUtils, "_getRemoteSettingsMessages")
        .rejects("fake error");
      await createRouterAndInit();
      assert.calledWith(initParams.dispatchCFRAction, {
        type: "AS_ROUTER_TELEMETRY_USER_EVENT",
        data: {
          action: "asrouter_undesired_event",
          message_id: "n/a",
          event: "ASR_RS_ERROR",
          event_context: "remotey-settingsy",
        },
      });
    });
    it("should dispatch undesired event if RemoteSettings returns no messages", async () => {
      sandbox
        .stub(MessageLoaderUtils, "_getRemoteSettingsMessages")
        .resolves([]);
      assert.calledWith(initParams.dispatchCFRAction, {
        type: "AS_ROUTER_TELEMETRY_USER_EVENT",
        data: {
          action: "asrouter_undesired_event",
          message_id: "n/a",
          event: "ASR_RS_NO_MESSAGES",
          event_context: "remotey-settingsy",
        },
      });
    });
    it("should download the attachment if RemoteSettings returns some messages", async () => {
      sandbox
        .stub(global.Services.locale, "appLocaleAsBCP47")
        .get(() => "en-US");
      sandbox
        .stub(MessageLoaderUtils, "_getRemoteSettingsMessages")
        .resolves([{ id: "message_1" }]);
      const spy = sandbox.spy();
      global.Downloader.prototype.downloadToDisk = spy;
      const provider = {
        id: "cfr",
        enabled: true,
        type: "remote-settings",
        collection: "cfr",
      };
      await createRouterAndInit([provider]);

      assert.calledOnce(spy);
    });
    it("should dispatch undesired event if the ms-language-packs returns no messages", async () => {
      sandbox
        .stub(global.Services.locale, "appLocaleAsBCP47")
        .get(() => "en-US");
      sandbox
        .stub(MessageLoaderUtils, "_getRemoteSettingsMessages")
        .resolves([{ id: "message_1" }]);
      sandbox
        .stub(global.KintoHttpClient.prototype, "getRecord")
        .resolves(null);
      const provider = {
        id: "cfr",
        enabled: true,
        type: "remote-settings",
        collection: "cfr",
      };
      await createRouterAndInit([provider]);

      assert.calledWith(initParams.dispatchCFRAction, {
        type: "AS_ROUTER_TELEMETRY_USER_EVENT",
        data: {
          action: "asrouter_undesired_event",
          message_id: "n/a",
          event: "ASR_RS_NO_MESSAGES",
          event_context: "ms-language-packs",
        },
      });
    });
  });

  describe("#_updateMessageProviders", () => {
    it("should correctly replace %STARTPAGE_VERSION% in remote provider urls", async () => {
      // If this test fails, you need to update the constant STARTPAGE_VERSION in
      // ASRouter.sys.mjs to match the `version` property of provider-response-schema.json
      const expectedStartpageVersion = ProviderResponseSchema.version;
      const provider = {
        id: "foo",
        enabled: true,
        type: "remote",
        url: "https://www.mozilla.org/%STARTPAGE_VERSION%/",
      };
      setMessageProviderPref([provider]);
      await Router._updateMessageProviders();
      assert.equal(
        Router.state.providers[0].url,
        `https://www.mozilla.org/${parseInt(expectedStartpageVersion, 10)}/`
      );
    });
    it("should replace other params in remote provider urls by calling Services.urlFormater.formatURL", async () => {
      const url = "https://www.example.com/";
      const replacedUrl = "https://www.foo.bar/";
      const stub = sandbox
        .stub(global.Services.urlFormatter, "formatURL")
        .withArgs(url)
        .returns(replacedUrl);
      const provider = { id: "foo", enabled: true, type: "remote", url };
      setMessageProviderPref([provider]);
      await Router._updateMessageProviders();
      assert.calledOnce(stub);
      assert.calledWithExactly(stub, url);
      assert.equal(Router.state.providers[0].url, replacedUrl);
    });
    it("should only add the providers that are enabled", async () => {
      const providers = [
        {
          id: "foo",
          enabled: false,
          type: "remote",
          url: "https://www.foo.com/",
        },
        {
          id: "bar",
          enabled: true,
          type: "remote",
          url: "https://www.bar.com/",
        },
      ];
      setMessageProviderPref(providers);
      await Router._updateMessageProviders();
      assert.equal(Router.state.providers.length, 1);
      assert.equal(Router.state.providers[0].id, providers[1].id);
    });
  });

  describe("#handleMessageRequest", () => {
    beforeEach(async () => {
      await Router.setState(() => ({
        providers: [{ id: "cfr" }, { id: "badge" }],
      }));
    });
    it("should not return a blocked message", async () => {
      // Block all messages except the first
      await Router.setState(() => ({
        messages: [
          { id: "foo", provider: "cfr", groups: ["cfr"] },
          { id: "bar", provider: "cfr", groups: ["cfr"] },
        ],
        messageBlockList: ["foo"],
      }));
      await Router.handleMessageRequest({
        provider: "cfr",
      });
      assert.calledWithMatch(ASRouterTargeting.findMatchingMessage, {
        messages: [{ id: "bar", provider: "cfr", groups: ["cfr"] }],
      });
    });
    it("should not return a message from a disabled group", async () => {
      ASRouterTargeting.findMatchingMessage.callsFake(
        ({ messages }) => messages[0]
      );
      // Block all messages except the first
      await Router.setState(() => ({
        messages: [
          { id: "foo", provider: "cfr", groups: ["cfr"] },
          { id: "bar", provider: "cfr", groups: ["cfr"] },
        ],
        groups: [{ id: "cfr", enabled: false }],
      }));
      const result = await Router.handleMessageRequest({
        provider: "cfr",
      });
      assert.isNull(result);
    });
    it("should not return a message from a blocked campaign", async () => {
      // Block all messages except the first
      await Router.setState(() => ({
        messages: [
          {
            id: "foo",
            provider: "cfr",
            campaign: "foocampaign",
            groups: ["cfr"],
          },
          { id: "bar", provider: "cfr", groups: ["cfr"] },
        ],
        messageBlockList: ["foocampaign"],
      }));

      await Router.handleMessageRequest({
        provider: "cfr",
      });
      assert.calledWithMatch(ASRouterTargeting.findMatchingMessage, {
        messages: [{ id: "bar", provider: "cfr", groups: ["cfr"] }],
      });
    });
    it("should not return a message excluded by the provider", async () => {
      // There are only two providers; block the FAKE_LOCAL_PROVIDER, leaving
      // only FAKE_REMOTE_PROVIDER unblocked, which provides only one message
      await Router.setState(() => ({
        providers: [{ id: "cfr", exclude: ["foo"] }],
      }));

      await Router.setState(() => ({
        messages: [{ id: "foo", provider: "cfr" }],
        messageBlockList: ["foocampaign"],
      }));

      const result = await Router.handleMessageRequest({
        provider: "cfr",
      });
      assert.isNull(result);
    });
    it("should not return a message if the frequency cap has been hit", async () => {
      sandbox.stub(Router, "isBelowFrequencyCaps").returns(false);
      await Router.setState(() => ({
        messages: [{ id: "foo", provider: "cfr" }],
      }));
      const result = await Router.handleMessageRequest({
        provider: "cfr",
      });
      assert.isNull(result);
    });
    it("should get unblocked messages that match the trigger", async () => {
      const message1 = {
        id: "1",
        campaign: "foocampaign",
        trigger: { id: "foo" },
        groups: ["cfr"],
        provider: "cfr",
      };
      const message2 = {
        id: "2",
        campaign: "foocampaign",
        trigger: { id: "bar" },
        groups: ["cfr"],
        provider: "cfr",
      };
      await Router.setState({ messages: [message2, message1] });
      // Just return the first message provided as arg
      ASRouterTargeting.findMatchingMessage.callsFake(
        ({ messages }) => messages[0]
      );

      const result = Router.handleMessageRequest({ triggerId: "foo" });

      assert.deepEqual(result, message1);
    });
    it("should get unblocked messages that match trigger and template", async () => {
      const message1 = {
        id: "1",
        campaign: "foocampaign",
        template: "badge",
        trigger: { id: "foo" },
        groups: ["badge"],
        provider: "badge",
      };
      const message2 = {
        id: "2",
        campaign: "foocampaign",
        template: "test_template",
        trigger: { id: "foo" },
        groups: ["cfr"],
        provider: "cfr",
      };
      await Router.setState({ messages: [message2, message1] });
      // Just return the first message provided as arg
      ASRouterTargeting.findMatchingMessage.callsFake(
        ({ messages }) => messages[0]
      );

      const result = Router.handleMessageRequest({
        triggerId: "foo",
        template: "badge",
      });

      assert.deepEqual(result, message1);
    });
    it("should have messageImpressions in the message context", () => {
      assert.propertyVal(
        Router._getMessagesContext(),
        "messageImpressions",
        Router.state.messageImpressions
      );
    });
    it("should forward trigger param info", async () => {
      const trigger = {
        triggerId: "foo",
        triggerParam: "bar",
        triggerContext: "context",
      };
      const message1 = {
        id: "1",
        campaign: "foocampaign",
        trigger: { id: "foo" },
        groups: ["cfr"],
        provider: "cfr",
      };
      const message2 = {
        id: "2",
        campaign: "foocampaign",
        trigger: { id: "bar" },
        groups: ["badge"],
        provider: "badge",
      };
      await Router.setState({ messages: [message2, message1] });
      // Just return the first message provided as arg

      Router.handleMessageRequest(trigger);

      assert.calledOnce(ASRouterTargeting.findMatchingMessage);

      const [options] = ASRouterTargeting.findMatchingMessage.firstCall.args;
      assert.propertyVal(options.trigger, "id", trigger.triggerId);
      assert.propertyVal(options.trigger, "param", trigger.triggerParam);
      assert.propertyVal(options.trigger, "context", trigger.triggerContext);
    });
    it("should not cache badge messages", async () => {
      const trigger = {
        triggerId: "bar",
        triggerParam: "bar",
        triggerContext: "context",
      };
      const message1 = {
        id: "1",
        provider: "cfr",
        campaign: "foocampaign",
        trigger: { id: "foo" },
        groups: ["cfr"],
      };
      const message2 = {
        id: "2",
        campaign: "foocampaign",
        trigger: { id: "bar" },
        groups: ["badge"],
        provider: "badge",
      };
      await Router.setState({ messages: [message2, message1] });
      // Just return the first message provided as arg

      Router.handleMessageRequest(trigger);

      assert.calledOnce(ASRouterTargeting.findMatchingMessage);

      const [options] = ASRouterTargeting.findMatchingMessage.firstCall.args;
      assert.propertyVal(options, "shouldCache"false);
    });
    it("should filter out messages without a trigger (or different) when a triggerId is defined", async () => {
      const trigger = { triggerId: "foo" };
      const message1 = {
        id: "1",
        campaign: "foocampaign",
        trigger: { id: "foo" },
        groups: ["cfr"],
        provider: "cfr",
      };
      const message2 = {
        id: "2",
        campaign: "foocampaign",
        trigger: { id: "bar" },
        groups: ["cfr"],
        provider: "cfr",
      };
      const message3 = {
        id: "3",
        campaign: "bazcampaign",
        groups: ["cfr"],
        provider: "cfr",
      };
      await Router.setState({
        messages: [message2, message1, message3],
        groups: [{ id: "cfr", enabled: true }],
      });
      // Just return the first message provided as arg
      ASRouterTargeting.findMatchingMessage.callsFake(args => args.messages);

      const result = Router.handleMessageRequest(trigger);

      assert.lengthOf(result, 1);
      assert.deepEqual(result[0], message1);
    });
  });

  describe("#uninit", () => {
    it("should unregister the trigger listeners", () => {
      for (const listener of ASRouterTriggerListeners.values()) {
        sandbox.spy(listener, "uninit");
      }

      Router.uninit();

      for (const listener of ASRouterTriggerListeners.values()) {
        assert.calledOnce(listener.uninit);
      }
    });
    it("should set .dispatchCFRAction to null", () => {
      Router.uninit();
      assert.isNull(Router.dispatchCFRAction);
      assert.isNull(Router.clearChildMessages);
      assert.isNull(Router.sendTelemetry);
    });
    it("should save previousSessionEnd", () => {
      Router.uninit();

      assert.calledOnce(Router._storage.set);
      assert.calledWithExactly(
        Router._storage.set,
        "previousSessionEnd",
        sinon.match.number
      );
    });
    it("should remove the observer for `intl:app-locales-changed`", () => {
      sandbox.spy(global.Services.obs, "removeObserver");
      Router.uninit();

      assert.calledWithExactly(
        global.Services.obs.removeObserver,
        Router._onLocaleChanged,
        "intl:app-locales-changed"
      );
    });
    it("should remove the pref observer for `USE_REMOTE_L10N_PREF`", async () => {
      sandbox.spy(global.Services.prefs, "removeObserver");
      Router.uninit();

      // Grab the last call as #uninit() also involves multiple calls of `Services.prefs.removeObserver`.
      const call = global.Services.prefs.removeObserver.lastCall;
      assert.calledWithExactly(call, USE_REMOTE_L10N_PREF, Router);
    });
  });

  describe("#setMessageById", async () => {
    it("should send an empty message if provided id did not resolve to a message", async () => {
      let response = await Router.setMessageById({ id: -1 }, true, {});
      assert.deepEqual(response.message, {});
    });
  });

  describe("#isUnblockedMessage", () => {
    it("should block a message if the group is blocked", async () => {
      const msg = { id: "msg1", groups: ["foo"], provider: "unit-test" };
      await Router.setState({
        groups: [{ id: "foo", enabled: false }],
        messages: [msg],
        providers: [{ id: "unit-test" }],
      });
      assert.isFalse(Router.isUnblockedMessage(msg));

      await Router.setState({ groups: [{ id: "foo", enabled: true }] });

      assert.isTrue(Router.isUnblockedMessage(msg));
    });
    it("should block a message if at least one group is blocked", async () => {
      const msg = {
        id: "msg1",
        groups: ["foo""bar"],
        provider: "unit-test",
      };
      await Router.setState({
        groups: [
          { id: "foo", enabled: false },
          { id: "bar", enabled: false },
        ],
        messages: [msg],
        providers: [{ id: "unit-test" }],
      });
      assert.isFalse(Router.isUnblockedMessage(msg));

      await Router.setState({
        groups: [
          { id: "foo", enabled: true },
          { id: "bar", enabled: false },
        ],
      });

      assert.isFalse(Router.isUnblockedMessage(msg));
    });
  });

  describe("#blockMessageById", () => {
    it("should add the id to the messageBlockList", async () => {
      await Router.blockMessageById("foo");
      assert.isTrue(Router.state.messageBlockList.includes("foo"));
    });
    it("should add the campaign to the messageBlockList instead of id if .campaign is specified and not select messages of that campaign again", async () => {
      await Router.setState({
        messages: [
          { id: "1", campaign: "foocampaign" },
          { id: "2", campaign: "foocampaign" },
        ],
      });
      await Router.blockMessageById("1");

      assert.isTrue(Router.state.messageBlockList.includes("foocampaign"));
      assert.isEmpty(Router.state.messages.filter(Router.isUnblockedMessage));
    });
    it("should be able to add multiple items to the messageBlockList", async () => {
      await Router.blockMessageById(FAKE_BUNDLE.map(b => b.id));
      assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[0].id));
      assert.isTrue(Router.state.messageBlockList.includes(FAKE_BUNDLE[1].id));
    });
    it("should save the messageBlockList", async () => {
      await Router.blockMessageById(FAKE_BUNDLE.map(b => b.id));
      assert.calledWithExactly(Router._storage.set, "messageBlockList", [
        FAKE_BUNDLE[0].id,
        FAKE_BUNDLE[1].id,
      ]);
    });
  });

  describe("#unblockMessageById", () => {
    it("should remove the id from the messageBlockList", async () => {
      await Router.blockMessageById("foo");
      assert.isTrue(Router.state.messageBlockList.includes("foo"));
      await Router.unblockMessageById("foo");
      assert.isFalse(Router.state.messageBlockList.includes("foo"));
    });
    it("should remove the campaign from the messageBlockList if it is defined", async () => {
      await Router.setState({ messages: [{ id: "1", campaign: "foo" }] });
      await Router.blockMessageById("1");
      assert.isTrue(
        Router.state.messageBlockList.includes("foo"),
        "blocklist has campaign id"
      );
      await Router.unblockMessageById("1");
      assert.isFalse(
        Router.state.messageBlockList.includes("foo"),
        "campaign id removed from blocklist"
      );
    });
    it("should save the messageBlockList", async () => {
      await Router.unblockMessageById("foo");
      assert.calledWithExactly(Router._storage.set, "messageBlockList", []);
    });
  });

  describe("#routeCFRMessage", () => {
    it("should allow for echoing back message modifications", () => {
      const message = { somekey: "some value" };
      const data = { content: message };
      const browser = {};
      let msg = Router.routeCFRMessage(data.content, browser, data, false);
      assert.deepEqual(msg.message, message);
    });
    it("should call CFRPageActions.forceRecommendation if the template is cfr_action and force is true", async () => {
      sandbox.stub(CFRPageActions, "forceRecommendation");
      const testMessage = { id: "foo", template: "cfr_doorhanger" };
      await Router.setState({ messages: [testMessage] });
      Router.routeCFRMessage(testMessage, {}, nulltrue);

      assert.calledOnce(CFRPageActions.forceRecommendation);
    });
    it("should call CFRPageActions.addRecommendation if the template is cfr_action and force is false", async () => {
      sandbox.stub(CFRPageActions, "addRecommendation");
      const testMessage = { id: "foo", template: "cfr_doorhanger" };
      await Router.setState({ messages: [testMessage] });
      Router.routeCFRMessage(testMessage, {}, {}, false);
      assert.calledOnce(CFRPageActions.addRecommendation);
    });
  });

  describe("#updateTargetingParameters", () => {
    it("should return an object containing the whole state", async () => {
      sandbox.stub(Router, "getTargetingParameters").resolves({});
      let msg = await Router.updateTargetingParameters();
      let expected = Object.assign({}, Router.state, {
        providerPrefs: ASRouterPreferences.providers,
        userPrefs: ASRouterPreferences.getAllUserPreferences(),
        targetingParameters: {},
        errors: Router.errors,
        devtoolsEnabled: ASRouterPreferences.devtoolsEnabled,
      });

      assert.deepEqual(msg, expected);
    });
  });

  describe("#reachEvent", () => {
    let experimentAPIStub;
    let featureIds = ["cfr""moments-page""infobar""spotlight"];
    beforeEach(() => {
      let getExperimentMetaDataStub = sandbox.stub();
      let getAllBranchesStub = sandbox.stub();
      featureIds.forEach(feature => {
        global.NimbusFeatures[feature].getAllVariables.returns({
          id: `message-${feature}`,
        });
        getExperimentMetaDataStub.withArgs({ featureId: feature }).returns({
          slug: `slug-${feature}`,
          branch: {
            slug: `branch-${feature}`,
          },
        });
        getAllBranchesStub.withArgs(`slug-${feature}`).resolves([
          {
            slug: `other-branch-${feature}`,
            [feature]: { value: { trigger: "unit-test" } },
          },
        ]);
      });
      experimentAPIStub = {
        getExperimentMetaData: getExperimentMetaDataStub,
        getAllBranches: getAllBranchesStub,
      };
      globals.set("ExperimentAPI", experimentAPIStub);
    });
    afterEach(() => {
      sandbox.restore();
    });
    it("should tag `forReachEvent` for all the expected message types", async () => {
      // This should match the `providers.messaging-experiments`
      let response = await MessageLoaderUtils.loadMessagesForProvider({
        type: "remote-experiments",
        featureIds,
      });

      // 1 message for reach 1 for expose
      assert.property(response, "messages");
      assert.lengthOf(response.messages, featureIds.length * 2);
      assert.lengthOf(
        response.messages.filter(m => m.forReachEvent),
        featureIds.length
      );
    });
  });

  describe("#sendTriggerMessage", () => {
    it("should pass the trigger to ASRouterTargeting when sending trigger message", async () => {
      await Router.setState({
        messages: [
          {
            id: "foo1",
            provider: "onboarding",
            template: "onboarding",
            trigger: { id: "firstRun" },
            content: { title: "Foo1", body: "Foo123-1" },
            groups: ["onboarding"],
          },
        ],
        providers: [{ id: "onboarding" }],
      });

      Router.loadMessagesFromAllProviders.resetHistory();
      Router.loadMessagesFromAllProviders.onFirstCall().resolves();

      await Router.sendTriggerMessage({
        tabId: 0,
        browser: gBrowser.selectedBrowser,
        id: "firstRun",
      });

      assert.calledOnce(ASRouterTargeting.findMatchingMessage);
      assert.deepEqual(
        ASRouterTargeting.findMatchingMessage.firstCall.args[0].trigger,
        {
          id: "firstRun",
          param: undefined,
          context: { browserIsSelected: true },
        }
      );
    });
    it("should record telemetry information", async () => {
      const startTelemetryStopwatch = sandbox.stub(
        global.TelemetryStopwatch,
        "start"
      );
      const finishTelemetryStopwatch = sandbox.stub(
        global.TelemetryStopwatch,
        "finish"
      );

      const tabId = 123;

      await Router.sendTriggerMessage({
        tabId,
        browser: {},
        id: "firstRun",
      });

      assert.calledTwice(startTelemetryStopwatch);
      assert.calledWithExactly(
        startTelemetryStopwatch,
        "MS_MESSAGE_REQUEST_TIME_MS",
        { tabId }
      );
      assert.calledTwice(finishTelemetryStopwatch);
      assert.calledWithExactly(
        finishTelemetryStopwatch,
        "MS_MESSAGE_REQUEST_TIME_MS",
        { tabId }
      );
    });
    it("should have previousSessionEnd in the message context", () => {
      assert.propertyVal(
        Router._getMessagesContext(),
        "previousSessionEnd",
        100
      );
    });
    it("should record the Reach event if found any", async () => {
      let messages = [
        {
          id: "foo1",
          forReachEvent: { sent: false, group: "cfr" },
          experimentSlug: "exp01",
          branchSlug: "branch01",
          template: "simple_template",
          trigger: { id: "foo" },
          content: { title: "Foo1", body: "Foo123-1" },
        },
        {
          id: "foo2",
          template: "simple_template",
          trigger: { id: "bar" },
          content: { title: "Foo2", body: "Foo123-2" },
          provider: "onboarding",
        },
        {
          id: "foo3",
          forReachEvent: { sent: false, group: "cfr" },
          experimentSlug: "exp02",
          branchSlug: "branch02",
          template: "simple_template",
          trigger: { id: "foo" },
          content: { title: "Foo1", body: "Foo123-1" },
        },
      ];
      sandbox.stub(Router, "handleMessageRequest").resolves(messages);
      sandbox.spy(Glean.messagingExperiments.reachCfr, "record");

      await Router.sendTriggerMessage({
        tabId: 0,
        browser: {},
        id: "foo",
      });

      assert.calledTwice(Glean.messagingExperiments.reachCfr.record);
    });
    it("should not record the Reach event if it's already sent", async () => {
      let messages = [
        {
          id: "foo1",
          forReachEvent: { sent: true, group: "cfr" },
          experimentSlug: "exp01",
          branchSlug: "branch01",
          template: "simple_template",
          trigger: { id: "foo" },
          content: { title: "Foo1", body: "Foo123-1" },
        },
      ];
      sandbox.stub(Router, "handleMessageRequest").resolves(messages);
      sandbox.spy(Glean.messagingExperiments.reachCfr, "record");

      await Router.sendTriggerMessage({
        tabId: 0,
        browser: {},
        id: "foo",
      });
      assert.notCalled(Glean.messagingExperiments.reachCfr.record);
    });
    it("should record the Exposure event for each valid feature", async () => {
      ["cfr_doorhanger""update_action""infobar""spotlight"].forEach(
        async template => {
          let featureMap = {
            cfr_doorhanger: "cfr",
            spotlight: "spotlight",
            infobar: "infobar",
            update_action: "moments-page",
          };
          assert.notCalled(
            global.NimbusFeatures[featureMap[template]].recordExposureEvent
          );

          let messages = [
            {
              id: "foo1",
              template,
              trigger: { id: "foo" },
              content: { title: "Foo1", body: "Foo123-1" },
            },
          ];
          sandbox.stub(Router, "handleMessageRequest").resolves(messages);

          await Router.sendTriggerMessage({
            tabId: 0,
            browser: {},
            id: "foo",
          });

          assert.calledOnce(
            global.NimbusFeatures[featureMap[template]].recordExposureEvent
          );
        }
      );
    });
  });

  describe("forceAttribution", () => {
    let setAttributionString;
    beforeEach(() => {
      setAttributionString = sandbox.spy(Router, "setAttributionString");
      sandbox.stub(global.Services.env, "set");
    });
    afterEach(() => {
      sandbox.reset();
    });
    it("should double encode on windows", async () => {
      sandbox.stub(fakeAttributionCode, "writeAttributionFile");

      Router.forceAttribution({ foo: "FOO!", eh: "NOPE", bar: "BAR?" });

      assert.notCalled(setAttributionString);
      assert.calledWithMatch(
        fakeAttributionCode.writeAttributionFile,
        "foo%3DFOO!%26bar%3DBAR%253F"
      );
    });
    it("should set attribution string on mac", async () => {
      sandbox.stub(global.AppConstants, "platform").value("macosx");

      Router.forceAttribution({ foo: "FOO!", eh: "NOPE", bar: "BAR?" });

      assert.calledOnce(setAttributionString);
      assert.calledWithMatch(
        setAttributionString,
        "foo%3DFOO!%26bar%3DBAR%253F"
      );
    });
  });

  describe("_triggerHandler", () => {
    it("should call #sendTriggerMessage with the correct trigger", () => {
      const getter = sandbox.stub();
      getter.returns(false);
      sandbox.stub(global.BrowserHandler, "kiosk").get(getter);
      sinon.spy(Router, "sendTriggerMessage");
      const browser = {};
      const trigger = { id: "FAKE_TRIGGER", param: "some fake param" };
      Router._triggerHandler(browser, trigger);
      assert.calledOnce(Router.sendTriggerMessage);
      assert.calledWith(
        Router.sendTriggerMessage,
        sandbox.match({
          id: "FAKE_TRIGGER",
          param: "some fake param",
        })
      );
    });
  });

  describe("_triggerHandler_kiosk", () => {
    it("should not call #sendTriggerMessage", () => {
      const getter = sandbox.stub();
      getter.returns(true);
      sandbox.stub(global.BrowserHandler, "kiosk").get(getter);
      sinon.spy(Router, "sendTriggerMessage");
      const browser = {};
      const trigger = { id: "FAKE_TRIGGER", param: "some fake param" };
      Router._triggerHandler(browser, trigger);
      assert.notCalled(Router.sendTriggerMessage);
    });
  });

  describe("valid preview endpoint", () => {
    it("should report an error if url protocol is not https", () => {
      sandbox.stub(console, "error");

      assert.equal(false, Router._validPreviewEndpoint("http://foo.com"));
      assert.calledTwice(console.error);
    });
  });

  describe("impressions", () => {
    describe("#addImpression for groups", () => {
      it("should save an impression in each group-with-frequency in a message", async () => {
        const fooMessageImpressions = [0];
        const aGroupImpressions = [0, 1, 2];
        const bGroupImpressions = [3, 4, 5];
        const cGroupImpressions = [6, 7, 8];

        const message = {
          id: "foo",
          provider: "bar",
          groups: ["a""b""c"],
        };
        const groups = [
          { id: "a", frequency: { lifetime: 3 } },
          { id: "b", frequency: { lifetime: 4 } },
          { id: "c", frequency: { lifetime: 5 } },
        ];
        await Router.setState(state => {
          // Add provider
          const providers = [...state.providers];
          // Add fooMessageImpressions
          // eslint-disable-next-line no-shadow
          const messageImpressions = Object.assign(
            {},
            state.messageImpressions
          );
          let gImpressions = {};
          gImpressions.a = aGroupImpressions;
          gImpressions.b = bGroupImpressions;
          gImpressions.c = cGroupImpressions;
          messageImpressions.foo = fooMessageImpressions;
          return {
            providers,
            messageImpressions,
            groups,
            groupImpressions: gImpressions,
          };
        });

        await Router.addImpression(message);

        assert.deepEqual(
          Router.state.groupImpressions.a,
          [0, 1, 2, 0],
          "a impressions"
        );
        assert.deepEqual(
          Router.state.groupImpressions.b,
          [3, 4, 5, 0],
          "b impressions"
        );
        assert.deepEqual(
          Router.state.groupImpressions.c,
          [6, 7, 8, 0],
          "c impressions"
        );
      });
    });

    describe("#isBelowFrequencyCaps", () => {
      it("should call #_isBelowItemFrequencyCap for the message and for the provider with the correct impressions and arguments", async () => {
        sinon.spy(Router, "_isBelowItemFrequencyCap");

        const MAX_MESSAGE_LIFETIME_CAP = 100; // Defined in ASRouter
        const fooMessageImpressions = [0, 1];
        const barGroupImpressions = [0, 1, 2];

        const message = {
          id: "foo",
          provider: "bar",
          groups: ["bar"],
          frequency: { lifetime: 3 },
        };
        const groups = [{ id: "bar", frequency: { lifetime: 5 } }];

        await Router.setState(state => {
          // Add provider
          const providers = [...state.providers];
          // Add fooMessageImpressions
          // eslint-disable-next-line no-shadow
          const messageImpressions = Object.assign(
            {},
            state.messageImpressions
          );
          let gImpressions = {};
          gImpressions.bar = barGroupImpressions;
          messageImpressions.foo = fooMessageImpressions;
          return {
            providers,
            messageImpressions,
            groups,
            groupImpressions: gImpressions,
          };
        });

        await Router.isBelowFrequencyCaps(message);

        assert.calledTwice(Router._isBelowItemFrequencyCap);
        assert.calledWithExactly(
          Router._isBelowItemFrequencyCap,
          message,
          fooMessageImpressions,
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=89 G=92

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.