/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

const { ExperimentAPI, _ExperimentFeature: ExperimentFeature } =
  ChromeUtils.importESModule("resource://nimbus/ExperimentAPI.sys.mjs");
const { PrefUtils } = ChromeUtils.importESModule(
  "resource://normandy/lib/PrefUtils.sys.mjs"
);
const { JsonSchema } = ChromeUtils.importESModule(
  "resource://gre/modules/JsonSchema.sys.mjs"
);
const { TelemetryTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/TelemetryTestUtils.sys.mjs"
);
const { TelemetryEvents } = ChromeUtils.importESModule(
  "resource://normandy/lib/TelemetryEvents.sys.mjs"
);

const USER = "user";
const DEFAULT = "default";

const STRING_PREF = "test.nimbus.prefFlips.string";
const INT_PREF = "test.nimbus.prefFlips.int";
const BOOL_PREF = "test.nimbus.prefFlips.boolean";

const FEATURE_ID = "prefFlips";

const SET_BEFORE_VALUE = "set-before-value";
const USER_VALUE = "user-value";
const DEFAULT_VALUE = "default-value";

const PREF_FEATURES = {
  [USER]: new ExperimentFeature("test-set-pref-user-1", {
    description: "Test feature that sets prefs on the user branch via setPref",
    owner: "test@test.test",
    hasExposure: false,
    variables: {
      foo: {
        type: "string",
        description: "test variable",
        setPref: {
          branch: USER,
          pref: "nimbus.test-only.foo",
        },
      },
    },
  }),
  [DEFAULT]: new ExperimentFeature("test-set-pref-default-1", {
    description:
      "Test feature that sets prefs on the default branch via setPref",
    owner: "test@test.test",
    hasExposure: false,
    variables: {
      foo: {
        type: "string",
        description: "test variable",
        setPref: {
          branch: DEFAULT,
          pref: "nimbus.test-only.foo",
        },
      },
    },
  }),
};

function assertNoObservers(manager) {
  Assert.equal(
    manager._prefs.size,
    0,
    "There should be no active pref observers on ExperimentManager"
  );
  Assert.equal(
    manager._prefsBySlug.size,
    0,
    "There should be no active pref observers on ExperimentManager"
  );
  Assert.equal(
    manager._prefFlips._prefs.size,
    0,
    "There should be no prefFlips feature observers"
  );
}

function setPrefs(prefs) {
  for (const [name, { userBranchValue, defaultBranchValue }] of Object.entries(
    prefs
  )) {
    // If the different prefs have the same value, we must set the user branch
    // value first. Otherwise when we try to set the user branch value after
    // the default value, it will see the value already set for the user
    // branch (because it falls back to the default branch value) and will not
    // set it, leaving only a default branch pref.
    if (typeof userBranchValue !== "undefined") {
      PrefUtils.setPref(name, userBranchValue);
    }

    if (typeof defaultBranchValue !== "undefined") {
      PrefUtils.setPref(name, defaultBranchValue, { branch: DEFAULT });
    }
  }
}

function cleanupPrefs(prefs) {
  for (const name of Object.keys(prefs)) {
    Services.prefs.deleteBranch(name);
  }
}

function checkExpectedPrefs(prefs) {
  for (const [name, value] of Object.entries(prefs)) {
    Assert.equal(PrefUtils.getPref(name), value);
  }
}

function checkExpectedPrefBranches(prefs) {
  for (const [
    name,
    { defaultBranchValue = null, userBranchValue = null },
  ] of Object.entries(prefs)) {
    if (userBranchValue === null) {
      Assert.ok(
        !Services.prefs.prefHasUserValue(name),
        `Pref ${name} has no value on user branch`
      );
    } else {
      Assert.equal(
        PrefUtils.getPref(name, { branch: USER }),
        userBranchValue,
        `Pref ${name} has correct value on user branch`
      );
    }

    if (defaultBranchValue === null) {
      Assert.ok(
        !Services.prefs.prefHasDefaultValue(name),
        `Pref ${name} has no value on default branch`
      );
    } else {
      Assert.equal(
        PrefUtils.getPref(name, { branch: DEFAULT }),
        defaultBranchValue,
        `Pref ${name} has correct value on default branch`
      );
    }
  }
}

add_setup(function setup() {
  do_get_profile();
  Services.fog.initializeFOG();
  TelemetryEvents.init();

  const cleanupFeatures = ExperimentTestUtils.addTestFeatures(
    PREF_FEATURES[USER],
    PREF_FEATURES[DEFAULT]
  );

  registerCleanupFunction(cleanupFeatures);
});

add_task(async function test_schema() {
  const schema = await fetch(
    "resource://nimbus/schemas/PrefFlipsFeature.schema.json"
  ).then(rsp => rsp.json());
  const validator = new JsonSchema.Validator(schema);

  const ALLOWED_TEST_CASES = [
    { prefs: {} },
    {
      prefs: {
        "foo.string": {
          branch: USER,
          value: "value",
        },
        "foo.int": {
          branch: USER,
          value: 123,
        },
        "foo.bool": {
          branch: USER,
          value: true,
        },
        "bar.string": {
          branch: DEFAULT,
          value: "value",
        },
        "bar.int": {
          branch: DEFAULT,
          value: 345,
        },
        "bar.bool": {
          branch: DEFAULT,
          value: false,
        },
      },
    },
  ];

  for (const obj of ALLOWED_TEST_CASES) {
    const result = validator.validate(obj);
    Assert.ok(
      result.valid,
      `validated: ${JSON.stringify(result.errors, null, 2)}`
    );
  }

  const DISALLOWED_TEST_CASES = [
    {},
    {
      prefs: {
        "foo.bar.baz": {
          branch: "other",
          value: "value",
        },
      },
    },
    {
      prefs: {
        "foo.bar.baz": {},
      },
    },
    {
      prefs: {
        "foo.bar.baz": {
          branch: USER,
        },
      },
    },
    {
      prefs: {
        "foo.bar.baz": {
          branch: DEFAULT,
        },
      },
    },
    {
      prefs: {
        "foo.bar.baz": {
          value: "value",
        },
      },
    },
    {
      prefs: {
        "foo.bar.baz": {
          branch: DEFAULT,
          value: null,
        },
      },
    },
  ];

  for (const obj of DISALLOWED_TEST_CASES) {
    const result = validator.validate(obj);
    Assert.ok(!result.valid);
  }
});

add_task(async function test_prefFlips() {
  const setUserPrefs = {
    prefs: {
      [STRING_PREF]: {
        branch: USER,
        value: "hello, world",
      },
      [INT_PREF]: {
        branch: USER,
        value: 123,
      },
      [BOOL_PREF]: {
        branch: USER,
        value: true,
      },
    },
  };
  const setDefaultPrefs = {
    prefs: {
      [STRING_PREF]: {
        branch: DEFAULT,
        value: "hello, world",
      },
      [INT_PREF]: {
        branch: DEFAULT,
        value: 123,
      },
      [BOOL_PREF]: {
        branch: DEFAULT,
        value: true,
      },
    },
  };

  const clearUserPrefs = {
    prefs: {
      [STRING_PREF]: {
        branch: USER,
        value: null,
      },
      [INT_PREF]: {
        branch: USER,
        value: null,
      },
      [BOOL_PREF]: {
        branch: USER,
        value: null,
      },
    },
  };

  const PRE_SET_PREFS = {
    [USER]: {
      [STRING_PREF]: { userBranchValue: "goodbye, world" },
      [INT_PREF]: { userBranchValue: 234 },
      [BOOL_PREF]: { userBranchValue: false },
    },
    [DEFAULT]: {
      [STRING_PREF]: { defaultBranchValue: "goodbye, world" },
      [INT_PREF]: { defaultBranchValue: 234 },
      [BOOL_PREF]: { defaultBranchValue: false },
    },
    BOTH_BRANCHES: {
      [STRING_PREF]: {
        userBranchValue: USER_VALUE,
        defaultBranchValue: DEFAULT_VALUE,
      },
      [INT_PREF]: { userBranchValue: 2, defaultBranchValue: 3 },
      [BOOL_PREF]: { userBranchValue: false, defaultBranchValue: false },
    },
  };

  const TEST_CASES = [
    {
      name: "Set prefs on the user branch",
      featureValue: setUserPrefs,
    },
    {
      name: "Set prefs on the user branch with pre-existing values on the user branch",
      featureValue: setUserPrefs,
      setPrefsBefore: PRE_SET_PREFS[USER],
    },
    {
      name: "Set prefs on the user branch with pre-existing values on the default branch",
      featureValue: setUserPrefs,
      setPrefsBefore: PRE_SET_PREFS[DEFAULT],
    },
    {
      name: "Set prefs on the user branch with pre-existing values on both branches",
      featureValue: setUserPrefs,
      setPrefsBefore: PRE_SET_PREFS.BOTH_BRANCHES,
    },
    {
      name: "Set prefs on the default branch",
      featureValue: setDefaultPrefs,
    },
    {
      name: "Set prefs on the default branch with pre-existing values on the default branch",
      featureValue: setDefaultPrefs,
      setPrefsBefore: PRE_SET_PREFS[DEFAULT],
    },
    {
      name: "Set prefs on the default branch with pre-existing values on the user branch",
      featureValue: setDefaultPrefs,
      setPrefsBefore: PRE_SET_PREFS[USER],
      expectedPrefs: {
        [STRING_PREF]: PRE_SET_PREFS[USER][STRING_PREF].userBranchValue,
        [INT_PREF]: PRE_SET_PREFS[USER][INT_PREF].userBranchValue,
        [BOOL_PREF]: PRE_SET_PREFS[USER][BOOL_PREF].userBranchValue,
      },
    },
    {
      name: "Set prefs on the default branch with pre-existing values on both branches",
      featureValue: setDefaultPrefs,
      setPrefsBefore: PRE_SET_PREFS.BOTH_BRANCHES,
      expectedPrefs: {
        [STRING_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[STRING_PREF].userBranchValue,
        [INT_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[INT_PREF].userBranchValue,
        [BOOL_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[BOOL_PREF].userBranchValue,
      },
    },
    {
      name: "Clearing prefs on the user branch (with value null) without pre-existing values",
      featureValue: clearUserPrefs,
    },
    {
      name: "Clearing prefs on the user branch (with value null) with pre-existing values on the user branch",
      featureValue: clearUserPrefs,
      setPrefsBefore: PRE_SET_PREFS[USER],
    },
    {
      name: "Clearing prefs on the user branch (with value null) with pre-existing values on the default branch",
      featureValue: clearUserPrefs,
      setPrefsBefore: PRE_SET_PREFS[DEFAULT],
      // This will not affect the default branch prefs.
      expectedPrefs: {
        [STRING_PREF]: PRE_SET_PREFS[DEFAULT][STRING_PREF].defaultBranchValue,
        [INT_PREF]: PRE_SET_PREFS[DEFAULT][INT_PREF].defaultBranchValue,
        [BOOL_PREF]: PRE_SET_PREFS[DEFAULT][BOOL_PREF].defaultBranchValue,
      },
    },
    {
      name: "Clearing prefs on the user branch (with value null) with pre-existing values on both branches",
      featureValue: clearUserPrefs,
      setPrefsBefore: PRE_SET_PREFS.BOTH_BRANCHES,
      expectedPrefs: {
        [STRING_PREF]:
          PRE_SET_PREFS.BOTH_BRANCHES[STRING_PREF].defaultBranchValue,
        [INT_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[INT_PREF].defaultBranchValue,
        [BOOL_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[BOOL_PREF].defaultBranchValue,
      },
    },
  ];

  for (const [i, { name, ...testCase }] of TEST_CASES.entries()) {
    info(`Running test case ${i}: ${name}`);

    const sandbox = sinon.createSandbox();

    const {
      // The feature config to enroll.
      featureValue,

      // Prefs that should be set before enrollment. These will be undone after
      // each test case.
      setPrefsBefore = {},

      // Additional prefs to check after enrollment. They will be checked on the
      // user branch.
      expectedPrefs = {},
    } = testCase;

    const manager = ExperimentFakes.manager();
    sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

    await manager.onStartup();

    info("Setting initial values of prefs...");
    setPrefs(setPrefsBefore);

    // Collect the values of any prefs that will be set by the enrollment so we
    // can compare their values after unenrollment.
    const prefValuesBeforeEnrollment = Object.fromEntries(
      Object.keys(featureValue.prefs).map(prefName => [
        prefName,
        PrefUtils.getPref(prefName),
      ])
    );

    info("Enrolling...");
    const cleanup = await ExperimentFakes.enrollWithFeatureConfig(
      {
        featureId: FEATURE_ID,
        value: featureValue,
      },
      {
        manager,
        isRollout: true,
      }
    );

    info("Checking prefs were set by enrollment...");
    for (const [prefName, { branch, value }] of Object.entries(
      featureValue.prefs
    )) {
      if (typeof value === "undefined" || value === null) {
        if (branch === USER) {
          Assert.ok(
            !Services.prefs.prefHasUserValue(prefName),
            `${prefName} was cleared on the user branch`
          );
        } else if (prefValuesBeforeEnrollment[prefName] !== null) {
          // Can't clear the user branch.
          Assert.equal(
            PrefUtils.getPref(prefName, { branch }),
            prefValuesBeforeEnrollment
          );
        } else {
          Assert.equal(PrefUtils.getPref(prefName, { branch }), value);
        }
      } else {
        Assert.equal(PrefUtils.getPref(prefName, { branch }), value);
      }
    }

    if (expectedPrefs) {
      info("Checking expected prefs...");
      checkExpectedPrefs(expectedPrefs);
    }

    info("Unenrolling...");
    await cleanup();

    info("Checking prefs were restored after unenrollment...");
    // After unenrollment, the prefs should have been restored to their values
    // before enrollment.
    for (const [prefName, originalValue] of Object.entries(
      prefValuesBeforeEnrollment
    )) {
      // If the pref was set on the default branch, it won't be cleared. It will
      // persist until the next restart.
      const expectedValue =
        featureValue.prefs[prefName].branch === "default" &&
        originalValue === null
          ? featureValue.prefs[prefName].value
          : originalValue;
      Assert.equal(PrefUtils.getPref(prefName), expectedValue);
    }

    info("Cleaning up...");
    // Clear all the prefs we specified in `setPrefsBefore`.
    cleanupPrefs(setPrefsBefore);

    // Clear all prefs specified by the enrollment.
    for (const prefName of Object.keys(featureValue.prefs)) {
      Services.prefs.deleteBranch(prefName);
    }

    await assertEmptyStore(manager.store);
    assertNoObservers(manager);

    sandbox.restore();
  }
});

add_task(async function test_prefFlips_unenrollment() {
  const PREF = "nimbus.test-only.foo";
  const PREF2 = "nimbus.test-only.bar";

  const PREF_FLIPS_USER_1 = "pref-flips-user-1";
  const PREF_FLIPS_USER_2 = "pref-flips-user-2";
  const PREF_FLIPS_USER_MULTI = "pref-flips-user-multi";

  const PREF_FLIPS_DEFAULT_1 = "pref-flips-default-1";
  const PREF_FLIPS_DEFAULT_2 = "pref-flips-default-2";
  const PREF_FLIPS_DEFAULT_MULTI = "pref-flips-default-multi";

  const SET_PREF_USER_1 = "set-pref-user-1";
  const SET_PREF_USER_2 = "set-pref-user-2";

  const SET_PREF_DEFAULT_1 = "set-pref-default-1";
  const SET_PREF_DEFAULT_2 = "set-pref-default-2";

  const FEATURE_CONFIGS = {
    [PREF_FLIPS_USER_1]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: USER,
            value: PREF_FLIPS_USER_1,
          },
        },
      },
    },
    [PREF_FLIPS_USER_2]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: USER,
            value: PREF_FLIPS_USER_2,
          },
        },
      },
    },
    [PREF_FLIPS_USER_MULTI]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: USER,
            value: PREF_FLIPS_USER_MULTI,
          },
          [PREF2]: {
            branch: USER,
            value: PREF_FLIPS_USER_MULTI,
          },
        },
      },
    },
    [PREF_FLIPS_DEFAULT_1]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: DEFAULT,
            value: PREF_FLIPS_DEFAULT_1,
          },
        },
      },
    },
    [PREF_FLIPS_DEFAULT_2]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: DEFAULT,
            value: PREF_FLIPS_DEFAULT_2,
          },
        },
      },
    },
    [PREF_FLIPS_DEFAULT_MULTI]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: DEFAULT,
            value: PREF_FLIPS_DEFAULT_MULTI,
          },
          [PREF2]: {
            branch: DEFAULT,
            value: PREF_FLIPS_DEFAULT_MULTI,
          },
        },
      },
    },
    [SET_PREF_USER_1]: {
      featureId: PREF_FEATURES[USER].featureId,
      value: {
        foo: SET_PREF_USER_1,
      },
    },
    [SET_PREF_USER_2]: {
      featureId: PREF_FEATURES[USER].featureId,
      value: {
        foo: SET_PREF_USER_2,
      },
    },
    [SET_PREF_DEFAULT_1]: {
      featureId: PREF_FEATURES[DEFAULT].featureId,
      value: {
        foo: SET_PREF_DEFAULT_1,
      },
    },
    [SET_PREF_DEFAULT_2]: {
      featureId: PREF_FEATURES[DEFAULT].featureId,
      value: {
        foo: SET_PREF_DEFAULT_2,
      },
    },
  };

  const TEST_CASES = [
    // Single enrollment case (experiments)
    {
      name: "set pref on the user branch with a prefFlips experiment and change that pref on the user branch",
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_1 }],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the user branch with a prefFlips experiment and change that pref on the default branch",
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_1 }],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "set pref on the default branch with a prefFlips experiment and change that pref on the user branch",
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_1 }],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [{ slug: PREF_FLIPS_USER_1 }],
    },
    {
      name: "set pref on the default branch with a prefFlips experiment and change that pref on the default branch",
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_1 }],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    // Single enrollment case, multiple prefs being reset
    {
      name: "set prefs on the user branch with a prefFlips experiment and change one pref on the user branch",
      setPrefsBefore: { [PREF]: { userBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_MULTI }],
      setPrefsAfter: { [PREF2]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [{ slug: PREF_FLIPS_USER_MULTI }],
      expectedPrefs: { [PREF]: SET_BEFORE_VALUE, [PREF2]: USER_VALUE },
    },
    {
      name: "set prefs on the user branch with a prefFlips experiment and change one pref on the default branch",
      setPrefsBefore: { [PREF]: { userBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_MULTI }],
      setPrefsAfter: { [PREF2]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_MULTI }],
      expectedPrefs: {
        [PREF]: PREF_FLIPS_USER_MULTI,
        [PREF2]: PREF_FLIPS_USER_MULTI,
      },
    },
    {
      name: "set prefs on the default branch with a prefFlips experiment and change one pref on the user branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [{ slug: PREF_FLIPS_DEFAULT_MULTI }],
      setPrefsAfter: { [PREF2]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [{ slug: PREF_FLIPS_DEFAULT_MULTI }],
      expectedPrefs: { [PREF]: SET_BEFORE_VALUE, [PREF2]: USER_VALUE },
    },
    {
      name: "set prefs on the default branch with a prefFlips experiment and change one pref on the default branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [{ slug: PREF_FLIPS_DEFAULT_MULTI }],
      setPrefsAfter: { [PREF2]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedUnenrollments: [{ slug: PREF_FLIPS_DEFAULT_MULTI }],
      expectedPrefs: { [PREF]: SET_BEFORE_VALUE, [PREF2]: DEFAULT_VALUE },
    },
    // Multiple enrollment cases
    // * change pref that would be controlled by a rollout while an experiment is active
    {
      name: "set pref on the user branch with a prefFlips experiment and rollout and then change rollout pref on the user branch",
      setPrefsBefore: { [PREF]: { userBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        // The rollout won't set any prefs because there is an active experiment.
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      setPrefsAfter: { [PREF2]: { userBranchValue: USER_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        // The rollout won't unenroll because the pref flipper doesn't know
        // about its prefs while the experiment is active.
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1, [PREF2]: USER_VALUE },
    },
    {
      name: "set pref on the user branch with a prefFlips experiment and rollout and then change rollout pref on the default branch",
      setPrefsBefore: { [PREF]: { userBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      setPrefsAfter: { [PREF2]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1, [PREF2]: DEFAULT_VALUE },
    },
    {
      name: "set pref on the default branch with a prefFlips experiment and rollout and then change rollout pref on the user branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      setPrefsAfter: { [PREF2]: { userBranchValue: USER_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1, [PREF2]: USER_VALUE },
    },
    {
      name: "set pref on the default branch with a prefFlips experiment and rollout and then change rollout pref on the default branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: SET_BEFORE_VALUE } },
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      setPrefsAfter: { [PREF2]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_MULTI, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1, [PREF2]: DEFAULT_VALUE },
    },
    // * prefFlips experiment (user) -> prefFlips rollout (user)
    {
      name: "set pref on the user branch with an experiment and then a rollout",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "set pref on the user branch with an experiment and then a rollout, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the user branch with an experiment and then a rollout, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchvalue: DEFAULT_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    // * prefFlips rollout (user) -> prefFlips experiment (user)
    {
      name: "set pref on the user branch with a rollout and then an experiment",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_2 },
    },
    {
      name: "set pref on the user branch with a rollout and then an experiment, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the user branch with a rollout and then an experiment, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_2 },
    },
    // * prefFlips experiment (user) -> prefFlips rollout (default)
    {
      name: "set pref on the user branch with an experiment and on the default branch with a rollout",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "set pref on the user branch with an experiment and on the default branch with a rollout, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the user branch with an experiment and on the default branch with a rollout, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    // * prefFlips rollout (user) -> prefFlips experiment (default)
    {
      name: "set pref on the user branch with a rollout and on the default branch with an experiment",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    {
      name: "set pref on the user branch with a rollout and on the default branch with an experiment, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the user branch with a rollout and on the default branch with an experiment, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedPrefs: { [PREF]: DEFAULT_VALUE },
    },
    // * prefFlips experiment (default) -> prefFlips rollout (user)
    {
      name: "set pref on the default branch with an experiment and on the user branch with a rollout",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    {
      name: "set pref on the default branch with an experiment and on the user branch with a rollout, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the default branch with an experiment and on the user branch with a rollout, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      expectedPrefs: { [PREF]: DEFAULT_VALUE },
    },
    // * prefFlips rollout (default) -> prefFlips experiment (user)
    {
      name: "set pref on the default branch with a rollout and on the user branch with an experiment",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "set pref on the default branch with a rollout and on the user branch with an experiment, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the default branch with a rollout and on the user branch with an experiment, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedEnrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    // * prefFlips experiment (default) -> prefFlips rollout (default)
    {
      name: "set pref on the default branch with an experiment and then a rollout",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    {
      name: "set pref on the default branch with an experiment and then a rollout, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the default branch with an experiment and then a rollout, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      expectedPrefs: { [PREF]: DEFAULT_VALUE },
    },
    // * prefFlips rollout (default) -> prefFlips experiment (default)
    {
      name: "set pref on the default branch with a rollout and then an experiment",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      expectedEnrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
    },
    {
      name: "set pref on the default branch with a rollout and then an experiment, then change that pref on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      setPrefsAfter: { [PREF]: { userBranchValue: USER_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      expectedPrefs: { [PREF]: USER_VALUE },
    },
    {
      name: "set pref on the default branch with a rollout and then an experiment, then change that pref on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      setPrefsAfter: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      expectedPrefs: { [PREF]: DEFAULT_VALUE },
    },

    // Multiple enrollment cases (prefFlips -> setPref)

    // NB: We don't need to test setPref experiments/rollouts on both branches
    // for the same pref because that configuration is prohibited by
    // gen_feature_manifests.py

    // NB: prefFlip experiments/rollouts will stay enrolled over setPref
    // experiment/rollouts, no matter the enrollment order.

    // NB: If there is a prefFlips experiment/rollout controlling a pref and the
    // client would enroll in a setPref experiment for that same pref, the
    // setPref experiment will not be enrolled.

    // * prefFlip experiment -> setPref experiment

    // TODO: These need to be rewritten
    {
      name: "enroll in a prefFlips experiment on the user branch and then a setPref experiment on the user branch",
      enrollmentOrder: [{ slug: PREF_FLIPS_USER_1 }, { slug: SET_PREF_USER_1 }],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedUnenrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the user branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the default branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the default branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },

    // * prefFlip experiment -> prefFlip rollout -> setPref experiment
    {
      name: "enroll in a prefFlips experiment on the user branch and rollout on the user branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the user branch and rollout on the user branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_USER_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the user branch and rollout on the default branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the user branch and rollout on the default branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the default branch and rollout on the user branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the default branch and rollout on the user branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the default branch and rollout on the default branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips experiment on the default branch and rollout on the default branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },

    // * prefFlip rollout -> prefFlip experiment -> setPref experiment
    {
      name: "enroll in a prefFlips rollout on the user branch and experiment on the user branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the user branch and experiment on the user branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the user branch and experiment on the default branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_USER_2 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the user branch and experiment on the default branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_USER_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the default branch and experiment on the user branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the default branch and experiment on the user branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the default branch and experiment on the default branch and then a setPref experiment on the user branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
        { slug: SET_PREF_USER_1 },
      ],
      expectedUnnrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_USER_1 }],
      expectedPrefs: { [PREF]: SET_PREF_USER_1 },
    },
    {
      name: "enroll in a prefFlips rollout on the default branch and experiment on the default branch and then a setPref experiment on the default branch",
      enrollmentOrder: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
        { slug: SET_PREF_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: PREF_FLIPS_DEFAULT_1, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_2 },
      ],
      expectedEnrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedPrefs: { [PREF]: SET_PREF_DEFAULT_1 },
    },
    // Multiple enrollment cases (setPref -> prefFlips)
    // * setPref experiment -> prefFLip experiment:
    {
      name: "enroll in a setPref experiment on the user branch and then a prefFlip experiment on the user branch",
      enrollmentOrder: [{ slug: SET_PREF_USER_1 }, { slug: PREF_FLIPS_USER_1 }],
      expectedUnenrollments: [{ slug: SET_PREF_USER_1 }],
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "enroll in a setPref experiment on the user branch and then a prefFlip experiment on the default branch",
      enrollmentOrder: [
        { slug: SET_PREF_USER_1 },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedUnenrollments: [{ slug: SET_PREF_USER_1 }],
      expectedEnrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    {
      name: "enroll in a setPref experiment on the default branch and then a prefFlip experiment on the user branch",
      enrollmentOrder: [
        { slug: SET_PREF_DEFAULT_1 },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedUnenrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "enroll in a setPref experiment on the default branch and then a prefFlip experiment on the default branch",
      enrollmentOrder: [
        { slug: SET_PREF_DEFAULT_1 },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedUnenrollments: [{ slug: SET_PREF_DEFAULT_1 }],
      expectedEnrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    // * setPref experiment -> setPref rollout -> prefFlip experiment
    {
      name: "enroll in a setPref experiment and rollout on the user branch and then a prefFlips experiment on the user branch",
      enrollmentOrder: [
        { slug: SET_PREF_USER_1 },
        { slug: SET_PREF_USER_2, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_USER_1 },
        { slug: SET_PREF_USER_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "enroll in a setPref experiment and rollout on the user branch and then a prefFlips experiment on the default branch",
      enrollmentOrder: [
        { slug: SET_PREF_USER_1 },
        { slug: SET_PREF_USER_2, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_USER_1 },
        { slug: SET_PREF_USER_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    {
      name: "enroll in a setPref experiment and rollout on the default branch and then a prefFlips experiment on the user branch",
      enrollmentOrder: [
        { slug: SET_PREF_DEFAULT_1 },
        { slug: SET_PREF_DEFAULT_2, isRollout: true },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_DEFAULT_1 },
        { slug: SET_PREF_DEFAULT_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "enroll in a setPref experiment and rollout on the default branch and then a prefFlips experiment on the default branch",
      enrollmentOrder: [
        { slug: SET_PREF_DEFAULT_1 },
        { slug: SET_PREF_DEFAULT_2, isRollout: true },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_DEFAULT_1 },
        { slug: SET_PREF_DEFAULT_2, isRollout: true },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    // * setPref rollout -> setPref experiment -> prefFlip experiment
    {
      name: "enroll in a setPref rollout on the user branch and experiment on the user branch and then a prefFlip experiment on the user branch",
      enrollmentOrder: [
        { slug: SET_PREF_USER_1, isRollout: true },
        { slug: SET_PREF_USER_2 },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_USER_1, isRollout: true },
        { slug: SET_PREF_USER_2 },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "enroll in a setPref rollout on the user branch and experiment on the user branch and then a prefFlip experiment on the default branch",
      enrollmentOrder: [
        { slug: SET_PREF_USER_1, isRollout: true },
        { slug: SET_PREF_USER_2 },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_USER_1, isRollout: true },
        { slug: SET_PREF_USER_2 },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
    {
      name: "enroll in a setPref rollout on the default branch and experiment on the user branch and then a prefFlip experiment on the user branch",
      enrollmentOrder: [
        { slug: SET_PREF_DEFAULT_1, isRollout: true },
        { slug: SET_PREF_DEFAULT_2 },
        { slug: PREF_FLIPS_USER_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_DEFAULT_1, isRollout: true },
        { slug: SET_PREF_DEFAULT_2 },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_USER_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_USER_1 },
    },
    {
      name: "enroll in a setPref rollout on the default branch and experiment on the user branch and then a prefFlip experiment on the default branch",
      enrollmentOrder: [
        { slug: SET_PREF_DEFAULT_1, isRollout: true },
        { slug: SET_PREF_DEFAULT_2 },
        { slug: PREF_FLIPS_DEFAULT_1 },
      ],
      expectedUnenrollments: [
        { slug: SET_PREF_DEFAULT_1, isRollout: true },
        { slug: SET_PREF_DEFAULT_2 },
      ],
      expectedEnrollments: [{ slug: PREF_FLIPS_DEFAULT_1 }],
      expectedPrefs: { [PREF]: PREF_FLIPS_DEFAULT_1 },
    },
  ];

  for (const [i, { name, ...testCase }] of TEST_CASES.entries()) {
    info(`Running test case ${i}: ${name}`);

    const sandbox = sinon.createSandbox();

    const {
      // Prefs that should be set after enrollment. These will be undone after
      // each test case.
      setPrefsBefore = {},
      // The slugs to enroll in the order they should be enrolled in, and
      // whether or not they should enroll as rollouts.
      enrollmentOrder,
      // Prefs that should be set after enrollment. These will be undone
      // after each test case.
      setPrefsAfter = {},
      // The expected active enrollments after all enrollments have finished.
      expectedEnrollments = [],
      // The expected inactive enrollments after all enrollments have finished.
      expectedUnenrollments = [],
      // Prefs to check after enrollment. They will be checked on the user
      // branch.
      expectedPrefs,
    } = testCase;

    info("Setting prefs before enrollment...");
    setPrefs(setPrefsBefore);

    const manager = ExperimentFakes.manager();
    sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

    await manager.onStartup();

    info("Enrolling...");
    for (const { slug, isRollout = false } of enrollmentOrder) {
      await ExperimentFakes.enrollWithFeatureConfig(FEATURE_CONFIGS[slug], {
        slug: `${slug}-${isRollout ? "rollout" : "experiment"}`,
        manager,
        isRollout,
      });
    }

    info("Setting prefs after enrollment...");
    setPrefs(setPrefsAfter);

    info("Checking expected enrollments...");
    for (const { slug, isRollout = false } of expectedEnrollments) {
      const computedSlug = `${slug}-${isRollout ? "rollout" : "experiment"}`;
      const enrollment = manager.store.get(computedSlug);

      Assert.ok(
        enrollment !== null && typeof enrollment !== "undefined",
        `An enrollment for ${computedSlug} should exist`
      );
      Assert.ok(enrollment.active, `It should still be active`);
    }

    info("Checking expected unenrollments...");
    for (const { slug, isRollout = false } of expectedUnenrollments) {
      const computedSlug = `${slug}-${isRollout ? "rollout" : "experiment"}`;
      const enrollment = manager.store.get(computedSlug);

      Assert.ok(
        enrollment !== null,
        `An enrollment for ${computedSlug} should exist`
      );
      Assert.ok(!enrollment.active, "It should no longer be active");
    }

    if (expectedPrefs) {
      info("Checking expected prefs...");
      checkExpectedPrefs(expectedPrefs);
    }

    info("Unenrolling from active experiments...");
    for (const { slug, isRollout = false } of expectedEnrollments) {
      const computedSlug = `${slug}-${isRollout ? "rollout" : "experiment"}`;
      info(`Unenrolling from ${computedSlug}\n`);
      manager.unenroll(computedSlug, "cleanup");
    }
    await assertEmptyStore(manager.store);
    assertNoObservers(manager);

    info("Cleaning up prefs...");
    Services.prefs.deleteBranch(PREF);
    Services.prefs.deleteBranch(PREF2);

    sandbox.restore();
  }
});

add_task(async function test_prefFlip_setPref_restore() {
  const PREF = "nimbus.test-only.foo";

  const SET_PREF_USER = "set-pref-user";
  const SET_PREF_DEFAULT = "set-pref-default";
  const PREF_FLIPS_USER = "pref-flips-user";
  const PREF_FLIPS_DEFAULT = "pref-flips-default";

  const FEATURE_CONFIGS = {
    [SET_PREF_USER]: {
      featureId: PREF_FEATURES[USER].featureId,
      value: {
        foo: SET_PREF_USER,
      },
    },
    [SET_PREF_DEFAULT]: {
      featureId: PREF_FEATURES[DEFAULT].featureId,
      value: {
        foo: SET_PREF_DEFAULT,
      },
    },
    [PREF_FLIPS_USER]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: USER,
            value: PREF_FLIPS_USER,
          },
        },
      },
    },
    [PREF_FLIPS_DEFAULT]: {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          [PREF]: {
            branch: DEFAULT,
            value: PREF_FLIPS_DEFAULT,
          },
        },
      },
    },
  };

  const TEST_CASES = [
    // 1. No prefs set beforehand.
    // - setPref first
    {
      name: "enroll in setPref on user branch and prefFlips on user branch",
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_USER],
      expectedPrefs: { [PREF]: {} },
    },
    {
      name: "enroll in setPref on user branch and prefFlips on default branch",
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: PREF_FLIPS_DEFAULT } },
    },
    {
      name: "enroll in setPref on default branch and prefFlips on user branch",
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_USER],
      expectedPrefs: { [PREF]: { defaultBranchValue: SET_PREF_DEFAULT } },
    },
    {
      name: "enroll in setPref on default branch and prefFlips on default branch",
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: SET_PREF_DEFAULT } },
    },
    // - prefFlips first
    {
      name: "enroll in prefFlips on user branch and setPref on user branch",
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_USER],
      expectedPrefs: { [PREF]: {} },
    },
    {
      name: "enroll in prefFlips on user branch and setPref on default branch",
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: SET_PREF_DEFAULT } },
    },
    {
      name: "enroll in prefFlips on default branch and setPref on user branch",
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_USER],
      expectedPrefs: { [PREF]: { defaultBranchValue: PREF_FLIPS_DEFAULT } },
    },
    {
      name: "enroll in prefFlips on default branch and setPref on default branch",
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: SET_PREF_DEFAULT } },
    },
    // 2. User branch prefs set beforehand.
    // - setPref first
    {
      name: "set prefs on user branch and enroll in setPref on user branch and prefFlips on user branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_USER],
      expectedPrefs: { [PREF]: { userBranchValue: USER_VALUE } },
    },
    {
      name: "set prefs on user branch and enroll in setPref on user branch and prefFlips on default branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: PREF_FLIPS_DEFAULT,
        },
      },
    },
    {
      name: "set prefs on user branch and enroll in setPref on default branch and prefFlips on user branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_USER],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: SET_PREF_DEFAULT,
        },
      },
    },
    {
      name: "set prefs on user branch and enroll in setPref on default branch and prefFlips on default branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: SET_PREF_DEFAULT,
        },
      },
    },
    // - prefFlips first
    {
      name: "set prefs on user branch and enroll in prefFlips on user branch and setPref on user branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_USER],
      expectedPrefs: { [PREF]: { userBranchValue: USER_VALUE } },
    },
    {
      name: "set prefs on user branch and enroll in prefFlips on user branch and setPref on default branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: SET_PREF_DEFAULT,
        },
      },
    },
    {
      name: "set prefs on user branch and enroll in prefFlips on default branch and setPref on user branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_USER],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: PREF_FLIPS_DEFAULT,
        },
      },
    },
    {
      name: "set prefs on user branch and enroll in prefFlips on default branch and setPref on default branch",
      setPrefsBefore: { [PREF]: { userBranchValue: USER_VALUE } },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: SET_PREF_DEFAULT,
        },
      },
    },
    // 3. Default branch prefs set beforehand.
    // - setPref first
    {
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      name: "set prefs on default branch and enroll branch in setPref on user branch and prefFlips on user branch",
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_USER],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      name: "set prefs on default branch and enroll branch in setPref on user branch and prefFlips on default branch",
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      name: "set prefs on default branch and enroll branch in setPref on default branch and prefFlips on user branch",
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_USER],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      name: "set prefs on default branch and enroll branch in setPref on default branch and prefFlips on default branch",
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    // - prefFlips first
    {
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      name: "set prefs on default branch and enroll branch in prefFlips on user branch and setPref on user branch",
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_USER],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      name: "set prefs on default branch and enroll branch in prefFlips on user branch and setPref on default branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      name: "set prefs on default branch and enroll branch in prefFlips on default branch and setPref on user branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_USER],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      name: "set prefs on default branch and enroll branch in prefFlips on default branch and setPref on default branch",
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_DEFAULT],
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    {
      name: "set prefs on default branch and enroll branch in prefFlips on default branch and setPref on default branch, unenrolling in reverse order",
      setPrefsBefore: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_DEFAULT],
      unenrollInReverseOrder: true,
      expectedPrefs: { [PREF]: { defaultBranchValue: DEFAULT_VALUE } },
    },
    // 4. Both user and default branch prefs set beforehand.
    // - setPref first
    {
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      name: "set prefs on both branches and enroll branch in setPref on user branch and prefFlips on user branch",
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_USER],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    {
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      name: "set prefs on both branches and enroll branch in setPref on user branch and prefFlips on default branch",
      enrollmentOrder: [SET_PREF_USER, PREF_FLIPS_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    {
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      name: "set prefs on both branches and enroll branch in setPref on default branch and prefFlips on user branch",
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_USER],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    {
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      name: "set prefs on both branches and enroll branch in setPref on default branch and prefFlips on default branch",
      enrollmentOrder: [SET_PREF_DEFAULT, PREF_FLIPS_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    // - prefFlips first
    {
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      name: "set prefs on both branches and enroll branch in prefFlips on user branch and setPref on user branch",
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_USER],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    {
      name: "set prefs on both branches and enroll branch in prefFlips on user branch and setPref on default branch",
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      enrollmentOrder: [PREF_FLIPS_USER, SET_PREF_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    {
      name: "set prefs on both branches and enroll branch in prefFlips on default branch and setPref on user branch",
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_USER],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
    {
      name: "set prefs on both branches and enroll branch in prefFlips on default branch and setPref on default branch",
      setPrefsBefore: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
      enrollmentOrder: [PREF_FLIPS_DEFAULT, SET_PREF_DEFAULT],
      expectedPrefs: {
        [PREF]: {
          userBranchValue: USER_VALUE,
          defaultBranchValue: DEFAULT_VALUE,
        },
      },
    },
  ];

  for (const [i, { name, ...testCase }] of TEST_CASES.entries()) {
    Services.fog.testResetFOG();
    Services.telemetry.snapshotEvents(
      Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
      /* clear = */ true
    );

    info(`Running test case ${i}: ${name}`);

    const sandbox = sinon.createSandbox();

    const { setPrefsBefore = {}, enrollmentOrder, expectedPrefs } = testCase;

    info("Setting prefs before enrollment...");
    setPrefs(setPrefsBefore);

    const manager = ExperimentFakes.manager();
    sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

    await manager.onStartup();

    info("Enrolling...");
    for (const slug of enrollmentOrder) {
      await ExperimentFakes.enrollWithFeatureConfig(FEATURE_CONFIGS[slug], {
        manager,
        slug,
      });
    }

    info("Checking expected enrollments...");
    {
      const enrollment = manager.store.get(enrollmentOrder[0]);
      Assert.ok(
        enrollment !== null,
        `An enrollment for ${enrollmentOrder[0]} should exist`
      );
      Assert.ok(!enrollment.active, "It should no longer be active.");
    }
    {
      const enrollment = manager.store.get(enrollmentOrder[1]);
      Assert.ok(
        enrollment !== null,
        `An enrollment for ${enrollmentOrder[1]} should exist`
      );
      Assert.ok(enrollment.active, "It should be active.");
    }

    info("Checking submitted telemetry...");
    TelemetryTestUtils.assertEvents(
      [
        {
          value: enrollmentOrder[0],
          extra: {
            reason: "prefFlips-conflict",
            conflictingSlug: enrollmentOrder[1],
          },
        },
      ],
      {
        category: "normandy",
        object: "nimbus_experiment",
        method: "unenroll",
      }
    );
    Assert.deepEqual(
      Glean.nimbusEvents.unenrollment.testGetValue().map(event => ({
        reason: event.extra.reason,
        experiment: event.extra.experiment,
        conflicting_slug: event.extra.conflicting_slug,
      })),
      [
        {
          reason: "prefFlips-conflict",
          experiment: enrollmentOrder[0],
          conflicting_slug: enrollmentOrder[1],
        },
      ]
    );

    info("Unenrolling...");
    manager.unenroll(enrollmentOrder[1], "test-cleanup");

    info("Checking expected prefs...");
    checkExpectedPrefBranches(expectedPrefs);

    await assertEmptyStore(manager.store);
    assertNoObservers(manager);

    info("Cleaning up prefs...");
    Services.prefs.deleteBranch(PREF);
  }
});

add_task(async function test_prefFlips_cacheOriginalValues() {
  const recipe = ExperimentFakes.recipe("prefFlips-test", {
    bucketConfig: {
      ...ExperimentFakes.recipe.bucketConfig,
      count: 1000,
    },
    branches: [
      {
        ...ExperimentFakes.recipe.branches[0],
        features: [
          {
            featureId: FEATURE_ID,
            value: {
              prefs: {
                "test.pref.please.ignore": {
                  branch: "user",
                  value: "test-value",
                },
              },
            },
          },
        ],
      },
    ],
  });

  const sandbox = sinon.createSandbox();
  const manager = ExperimentFakes.manager();

  sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  await manager.onStartup();
  await manager.enroll(recipe, "test");

  const activeEnrollment = manager.store.getExperimentForFeature(FEATURE_ID);

  Assert.deepEqual(activeEnrollment.prefFlips, {
    originalValues: {
      "test.pref.please.ignore": null,
    },
  });

  // Force the store to save to disk
  await manager.store._store._save();

  const storeContents = await IOUtils.readJSON(manager.store._store.path);

  Assert.ok(
    Object.hasOwn(storeContents, "prefFlips-test"),
    "enrollment present in serialized store"
  );
  Assert.ok(
    Object.hasOwn(storeContents["prefFlips-test"], "prefFlips"),
    "prefFlips cache preset in serialized enrollment"
  );

  Assert.deepEqual(
    storeContents["prefFlips-test"].prefFlips,
    {
      originalValues: {
        "test.pref.please.ignore": null,
      },
    },
    "originalValues cached on serialized enrollment"
  );

  manager.unenroll(recipe.slug, "test");
  Assert.ok(
    !Services.prefs.prefHasUserValue("test.pref.please.ignore"),
    "pref unset after unenrollment"
  );
  await assertEmptyStore(manager.store, { cleanup: true });

  sandbox.restore();
});

add_task(async function test_prefFlips_restore_unenroll() {
  const recipe = ExperimentFakes.recipe("prefFlips-test", {
    bucketConfig: {
      ...ExperimentFakes.recipe.bucketConfig,
      count: 1000,
    },
    branches: [
      {
        ...ExperimentFakes.recipe.branches[0],
        features: [
          {
            featureId: FEATURE_ID,
            value: {
              prefs: {
                "test.pref.please.ignore": {
                  branch: "user",
                  value: "test-value",
                },
              },
            },
          },
        ],
      },
    ],
  });

  // Set up a previous ExperimentStore on disk.
  {
    const enrollment = {
      slug: recipe.slug,
      branch: recipe.branches[0],
      active: true,
      experimentType: "nimbus",
      userFacingName: recipe.userFacingName,
      userFacingDescription: recipe.userFacingDescription,
      featureIds: recipe.featureIds,
      isRollout: recipe.isRollout,
      localizations: recipe.localizations,
      source: "rs-loader",
      prefFlips: {
        originalValues: {
          "test.pref.please.ignore": null,
        },
      },
    };

    const store = ExperimentFakes.store();
    await store.init();
    await store.ready();
    store.set(enrollment.slug, enrollment);
    store._store.saveSoon();
    await store._store.finalize();
  }

  // Set the pref controlled by the experiment.
  Services.prefs.setStringPref("test.pref.please.ignore", "test-value");

  const sandbox = sinon.createSandbox();

  const manager = ExperimentFakes.manager();

  sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  await manager.onStartup();

  const activeEnrollment = manager.store.getExperimentForFeature(FEATURE_ID);
  Assert.equal(activeEnrollment.slug, recipe.slug, "enrollment restored");

  Assert.equal(
    manager._prefFlips._prefs.get("test.pref.please.ignore").originalValue,
    null
  );

  manager.unenroll(recipe.slug, "test");
  Assert.ok(
    !Services.prefs.prefHasUserValue("test.pref.please.ignore"),
    "pref unset after unenrollment"
  );

  await assertEmptyStore(manager.store, { cleanup: true });

  sandbox.restore();
});

add_task(async function test_prefFlips_failed() {
  const PREF = "test.pref.please.ignore";

  Services.fog.testResetFOG();
  Services.telemetry.snapshotEvents(
    Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
    /* clear = */ true
  );

  Services.prefs.getDefaultBranch(null).setStringPref(PREF, "test-value");
  const sandbox = sinon.createSandbox();
  const manager = ExperimentFakes.manager();

  sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  await manager.onStartup();

  const recipe = ExperimentFakes.recipe("prefFlips-test", {
    branches: [
      {
        ...ExperimentFakes.recipe.branches[0],
        features: [
          {
            featureId: FEATURE_ID,
            value: {
              prefs: {
                [PREF]: { branch: "user", value: 123 },
              },
            },
          },
        ],
      },
    ],
    bucketConfig: {
      ...ExperimentFakes.recipe.bucketConfig,
      count: 1000,
    },
  });

  await manager.enroll(recipe);

  const enrollment = manager.store.get(recipe.slug);
  Assert.ok(!enrollment.active, "Experiment should not be active");

  Assert.equal(Services.prefs.getStringPref(PREF), "test-value");

  TelemetryTestUtils.assertEvents(
    [
      {
        value: recipe.slug,
        extra: {
          reason: "prefFlips-failed",
          prefName: PREF,
          prefType: "string",
        },
      },
    ],
    {
      category: "normandy",
      object: "nimbus_experiment",
      method: "unenroll",
    }
  );
  Assert.deepEqual(
    Glean.nimbusEvents.unenrollment.testGetValue().map(event => ({
      reason: event.extra.reason,
      experiment: event.extra.experiment,
      pref_name: event.extra.pref_name,
      pref_type: event.extra.pref_type,
    })),
    [
      {
        reason: "prefFlips-failed",
        experiment: recipe.slug,
        pref_name: PREF,
        pref_type: "string",
      },
    ]
  );

  Services.prefs.deleteBranch(PREF);

  await assertEmptyStore(manager.store);
});

add_task(async function test_prefFlips_failed_multiple_prefs() {
  const GOOD_PREF = "test.pref.please.ignore";
  const BAD_PREF = "this.one.too";

  Services.fog.testResetFOG();
  Services.telemetry.snapshotEvents(
    Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
    /* clear = */ true
  );

  Services.prefs.getDefaultBranch(null).setStringPref(BAD_PREF, "test-value");
  const sandbox = sinon.createSandbox();
  const manager = ExperimentFakes.manager();

  sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  const setPrefSpy = sandbox.spy(PrefUtils, "setPref");

  await manager.onStartup();

  const recipe = ExperimentFakes.recipe("prefFlips-test", {
    branches: [
      {
        ...ExperimentFakes.recipe.branches[0],
        features: [
          {
            featureId: FEATURE_ID,
            value: {
              prefs: {
                [GOOD_PREF]: { branch: USER, value: 123 },
                [BAD_PREF]: { branch: USER, value: 123 },
              },
            },
          },
        ],
      },
    ],
    bucketConfig: {
      ...ExperimentFakes.recipe.bucketConfig,
      count: 1000,
    },
  });

  await manager.enroll(recipe);

  const enrollment = manager.store.get(recipe.slug);
  Assert.ok(!enrollment.active, "Experiment should not be active");

  Assert.deepEqual(
    setPrefSpy.getCall(0).args,
    [GOOD_PREF, 123, { branch: USER }],
    `should set ${GOOD_PREF}`
  );
  Assert.deepEqual(
    setPrefSpy.getCall(1).args,
    [BAD_PREF, 123, { branch: USER }],
    `should have attempted to set ${BAD_PREF}`
  );
  Assert.ok(
    typeof setPrefSpy.getCall(1).exception !== "undefined",
    `Attempting to set ${BAD_PREF} threw`
  );
  Assert.deepEqual(
    setPrefSpy.getCall(2).args,
    [GOOD_PREF, null, { branch: USER }],
    `should reset ${GOOD_PREF}`
  );
  Assert.equal(
    setPrefSpy.callCount,
    3,
    "should have 3 calls to PrefUtils.setPref"
  );

  Assert.ok(
    !Services.prefs.prefHasUserValue(GOOD_PREF),
    `${GOOD_PREF} should not be set`
  );
  Assert.equal(Services.prefs.getStringPref(BAD_PREF), "test-value");

  Services.prefs.deleteBranch(GOOD_PREF);
  Services.prefs.deleteBranch(BAD_PREF);

  await assertEmptyStore(manager.store);
  sandbox.reset();
});

add_task(async function test_prefFlips_failed_experiment_and_rollout() {
  const ROLLOUT = "rollout";
  const EXPERIMENT = "experiment";

  const PREFS = {
    [ROLLOUT]: "test.nimbus.prefs.rollout",
    [EXPERIMENT]: "test.nimbus.prefs.experiment",
  };

  const VALUES = {
    [ROLLOUT]: "rollout-value",
    [EXPERIMENT]: "experiment-value",
  };

  const BOGUS_VALUE = 123;

  const TEST_CASES = [
    {
      name: "Enrolling in an experiment and then a rollout with errors",
      setPrefsBefore: {
        [PREFS[ROLLOUT]]: { defaultBranchValue: BOGUS_VALUE },
      },
      enrollmentOrder: [EXPERIMENT, ROLLOUT],
      expectedEnrollments: [EXPERIMENT, ROLLOUT],
      expectedUnenrollments: [],
      expectedPrefs: {
        [PREFS[EXPERIMENT]]: VALUES[EXPERIMENT],
        [PREFS[ROLLOUT]]: BOGUS_VALUE,
      },
    },
    {
      name: "Enrolling in a rollout and then an experiment with errors",
      setPrefsBefore: {
        [PREFS[EXPERIMENT]]: { defaultBranchValue: BOGUS_VALUE },
      },
      enrollmentOrder: [ROLLOUT, EXPERIMENT],
      expectedEnrollments: [ROLLOUT],
      expectedUnenrollments: [EXPERIMENT],
      expectedPrefs: {
        [PREFS[ROLLOUT]]: VALUES[ROLLOUT],
        [PREFS[EXPERIMENT]]: BOGUS_VALUE,
      },
    },
  ];

  const FEATURE_VALUES = {
    [EXPERIMENT]: {
      prefs: {
        [PREFS[EXPERIMENT]]: {
          value: VALUES[EXPERIMENT],
          branch: USER,
        },
      },
    },
    [ROLLOUT]: {
      prefs: {
        [PREFS[ROLLOUT]]: {
          value: VALUES[ROLLOUT],
          branch: USER,
        },
      },
    },
  };

  for (const [i, { name, ...testCase }] of TEST_CASES.entries()) {
    info(`Running test case ${i}: ${name}`);

    const {
      setPrefsBefore,
      enrollmentOrder,
      expectedEnrollments,
      expectedUnenrollments,
      expectedPrefs,
    } = testCase;

    const sandbox = sinon.createSandbox();

    const manager = ExperimentFakes.manager();
    sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
    sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

    await manager.onStartup();

    info("Setting initial values of prefs...");
    setPrefs(setPrefsBefore);

    info("Enrolling...");
    for (const slug of enrollmentOrder) {
      await ExperimentFakes.enrollWithFeatureConfig(
        {
          featureId: FEATURE_ID,
          value: FEATURE_VALUES[slug],
        },
        {
          manager,
          slug,
          isRollout: slug === ROLLOUT,
        }
      );
    }

    info("Checking expected enrollments...");
    for (const slug of expectedEnrollments) {
      const enrollment = manager.store.get(slug);
      Assert.ok(enrollment.active, "The enrollment is active.");
    }

    info("Checking expected unenrollments...");
    for (const slug of expectedUnenrollments) {
      const enrollment = manager.store.get(slug);
      Assert.ok(!enrollment.active, "The enrollment is no longer active.");
    }

    info("Checking expected prefs...");
    checkExpectedPrefs(expectedPrefs);

    info("Unenrolling...");
    if (expectedEnrollments.includes(ROLLOUT)) {
      manager.unenroll(ROLLOUT, "test-cleanup");
    }
    if (expectedEnrollments.includes(EXPERIMENT)) {
      manager.unenroll(EXPERIMENT, "test-cleanup");
    }

    info("Cleaning up...");
    Services.prefs.deleteBranch(PREFS[ROLLOUT]);
    Services.prefs.deleteBranch(PREFS[EXPERIMENT]);

    await assertEmptyStore(manager.store);
    assertNoObservers(manager);
    sandbox.restore();
  }
});

add_task(async function test_prefFlips_update_failure() {
  const sandbox = sinon.createSandbox();
  const manager = ExperimentFakes.manager();

  sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  await manager.onStartup();

  PrefUtils.setPref("pref.one", "default-value", { branch: DEFAULT });
  PrefUtils.setPref("pref.two", "default-value", { branch: DEFAULT });

  const doCleanup = await ExperimentFakes.enrollWithFeatureConfig(
    {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          "pref.one": { value: "one", branch: USER },
          "pref.two": { value: "two", branch: USER },
        },
      },
    },
    { manager, isRollout: true, slug: "rollout" }
  );

  Assert.equal(Services.prefs.getStringPref("pref.one"), "one");
  Assert.equal(Services.prefs.getStringPref("pref.two"), "two");

  await ExperimentFakes.enrollWithFeatureConfig(
    {
      featureId: FEATURE_ID,
      value: {
        prefs: {
          "pref.one": { value: "experiment-value", branch: USER },
          "pref.two": { value: 2, branch: USER },
        },
      },
    },
    { manager, slug: "experiment" }
  );

  const rolloutEnrollment = manager.store.get("rollout");
  const experimentEnrollment = manager.store.get("experiment");

  Assert.ok(rolloutEnrollment.active, "Rollout is active");
  Assert.ok(!experimentEnrollment.active, "Experiment is inactive");
  Assert.equal(experimentEnrollment.unenrollReason, "prefFlips-failed");

  Assert.equal(Services.prefs.getStringPref("pref.one"), "one");
  Assert.equal(Services.prefs.getStringPref("pref.two"), "two");

  Services.prefs.deleteBranch("pref.one");
  Services.prefs.deleteBranch("pref.two");

  doCleanup();
  await assertEmptyStore(manager.store);
  assertNoObservers(manager);

  sandbox.restore();
});

// Test the case where an experiment sets a default branch pref, but the user
// changed their user.js between restarts.
add_task(async function test_prefFlips_restore_failure() {
  const PREF = "foo.bar.baz";

  const recipe = ExperimentFakes.recipe("prefFlips-test", {
    branches: [
      {
        ...ExperimentFakes.recipe.branches[0],
        features: [
          {
            featureId: FEATURE_ID,
            value: {
              prefs: {
                [PREF]: {
                  branch: DEFAULT,
                  value: "recipe-value",
                },
              },
            },
          },
        ],
      },
    ],
    bucketConfig: {
      ...ExperimentFakes.recipe.bucketConfig,
      count: 1000,
    },
  });

  {
    const prevEnrollment = {
      slug: recipe.slug,
      branch: recipe.branches[0],
      active: true,
      experimentType: "nimbus",
      userFacingName: recipe.userFacingName,
      userFacingDescription: recipe.userFacingDescription,
      featureIds: recipe.featureIds,
      isRollout: recipe.isRollout,
      localizations: recipe.localizations,
      source: "rs-loader",
      prefFlips: {
        originalValues: {
          [PREF]: "original-value",
        },
      },
    };

    const store = ExperimentFakes.store();
    await store.init();
    await store.ready();
    store.set(prevEnrollment.slug, prevEnrollment);
    store._store.saveSoon();
    await store._store.finalize();
  }

  Services.prefs.setIntPref(PREF, 123);

  const sandbox = sinon.createSandbox();
  const manager = ExperimentFakes.manager();

  sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

  await manager.onStartup();

  const enrollment = manager.store.get(recipe.slug);

  Assert.ok(!enrollment.active, "Enrollment should be inactive");
  Assert.equal(enrollment.unenrollReason, "prefFlips-failed");

  Assert.ok(
    !Services.prefs.prefHasDefaultValue(PREF),
    "pref has no default value"
  );
  Assert.equal(Services.prefs.getIntPref(PREF), 123, "pref value unchanged");

  await assertEmptyStore(manager.store, { cleanup: true });
  assertNoObservers(manager);

  Services.prefs.deleteBranch(PREF);
});

add_task(
  async function test_prefFlips_reenroll_set_default_branch_wrong_type() {
    const PREF = "test.pref.please.ignore";

    const sandbox = sinon.createSandbox();
    const manager = ExperimentFakes.manager();

    const recipe = ExperimentFakes.recipe("invalid", {
      isRollout: true,
      bucketConfig: {
        ...ExperimentFakes.recipe.bucketConfig,
        count: 1000,
      },
      branches: [
        {
          ...ExperimentFakes.recipe.branches[0],
          features: [
            {
              featureId: FEATURE_ID,
              value: {
                prefs: {
                  [PREF]: { value: 123, branch: DEFAULT },
                },
              },
            },
          ],
        },
      ],
    });

    sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
    sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);

    PrefUtils.setPref(PREF, "default-value", { branch: DEFAULT });

    await manager.onStartup();
    await manager.enroll(recipe, "rs-loader");

    let enrollment = manager.store.get(recipe.slug);

    Assert.ok(!enrollment.active, "enrollment should not be active");
    Assert.equal(enrollment.unenrollReason, "prefFlips-failed");

    await manager.enroll(recipe, "rs-loader", { reenroll: true });
    enrollment = manager.store.get(recipe.slug);

    Assert.ok(!enrollment.active, "enrollment should not be active");
    Assert.equal(enrollment.unenrollReason, "prefFlips-failed");

    await assertEmptyStore(manager.store);
    assertNoObservers(manager);

    Services.prefs.deleteBranch(PREF);

    sandbox.restore();
  }
);
