def test_push_experiment_to_kinto_sends_desktop_experiment_data_and_sets_accepted( self, ): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.LAUNCH_APPROVE, application=NimbusExperiment.Application.DESKTOP, ) tasks.nimbus_push_experiment_to_kinto( settings.KINTO_COLLECTION_NIMBUS_DESKTOP, experiment.id) data = NimbusExperimentSerializer(experiment).data self.mock_kinto_client.create_record.assert_called_with( data=data, collection=settings.KINTO_COLLECTION_NIMBUS_DESKTOP, bucket=settings.KINTO_BUCKET_WORKSPACE, if_not_exists=True, ) experiment = NimbusExperiment.objects.get(id=experiment.id) self.assertEqual(experiment.publish_status, NimbusExperiment.PublishStatus.WAITING) self.assertTrue( experiment.changes.filter( old_publish_status=NimbusExperiment.PublishStatus.APPROVED, new_publish_status=NimbusExperiment.PublishStatus.WAITING, message=NimbusChangeLog.Messages.LAUNCHING_TO_KINTO, ).exists())
def test_publishes_preview_experiments_and_unpublishes_non_preview_experiments(self): should_publish_experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.PREVIEW, ) should_unpublish_experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.CREATED, ) self.setup_kinto_get_main_records([should_unpublish_experiment.slug]) tasks.nimbus_synchronize_preview_experiments_in_kinto() data = NimbusExperimentSerializer(should_publish_experiment).data self.mock_kinto_client.create_record.assert_called_with( data=data, collection=settings.KINTO_COLLECTION_NIMBUS_PREVIEW, bucket=settings.KINTO_BUCKET_WORKSPACE, if_not_exists=True, ) self.mock_kinto_client.delete_record.assert_called_with( id=should_unpublish_experiment.slug, collection=settings.KINTO_COLLECTION_NIMBUS_PREVIEW, bucket=settings.KINTO_BUCKET_WORKSPACE, )
def test_updates_experiment_record_in_kinto(self): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.PAUSING_APPROVE, application=NimbusExperiment.Application.DESKTOP, ) tasks.nimbus_update_experiment_in_kinto( settings.KINTO_COLLECTION_NIMBUS_DESKTOP, experiment.id) data = NimbusExperimentSerializer(experiment).data self.mock_kinto_client.update_record.assert_called_with( data=data, collection=settings.KINTO_COLLECTION_NIMBUS_DESKTOP, bucket=settings.KINTO_BUCKET_WORKSPACE, if_match='"0"', ) self.mock_kinto_client.patch_collection.assert_called_with( id=settings.KINTO_COLLECTION_NIMBUS_DESKTOP, data={"status": KINTO_REVIEW_STATUS}, bucket=settings.KINTO_BUCKET_WORKSPACE, ) experiment = NimbusExperiment.objects.get(id=experiment.id) self.assertEqual(experiment.publish_status, NimbusExperiment.PublishStatus.WAITING) self.assertTrue( experiment.changes.filter( old_publish_status=NimbusExperiment.PublishStatus.APPROVED, new_publish_status=NimbusExperiment.PublishStatus.WAITING, message=NimbusChangeLog.Messages.UPDATING_IN_KINTO, ).exists())
def test_recipe_json_returns_serialized_data(self): user_email = "*****@*****.**" experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.CREATED ) response = self.query( """ query experimentBySlug($slug: String!) { experimentBySlug(slug: $slug) { recipeJson } } """, variables={"slug": experiment.slug}, headers={settings.OPENIDC_EMAIL_HEADER: user_email}, ) content = json.loads(response.content) self.assertEqual(response.status_code, 200) experiment_data = content["data"]["experimentBySlug"] self.assertEqual( experiment_data["recipeJson"], json.dumps( NimbusExperimentSerializer(experiment).data, indent=2, sort_keys=True ), )
def test_push_experiment_to_kinto_sends_fenix_experiment_data(self): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.DRAFT, application=NimbusExperiment.Application.FENIX, population_percent=Decimal("50.0"), ) tasks.nimbus_push_experiment_to_kinto(experiment.id) self.assertEqual(experiment.bucket_range.start, 0) self.assertEqual(experiment.bucket_range.count, 5000) data = NimbusExperimentSerializer(experiment).data self.mock_kinto_client.create_record.assert_called_with( data=data, collection=settings.KINTO_COLLECTION_NIMBUS_MOBILE, bucket=settings.KINTO_BUCKET, if_not_exists=True, ) self.assertTrue( NimbusChangeLog.objects.filter( experiment=experiment, changed_by__email=settings.KINTO_DEFAULT_CHANGELOG_USER, old_status=NimbusExperiment.Status.DRAFT, new_status=NimbusExperiment.Status.ACCEPTED, ).exists())
def create_with_lifecycle(cls, lifecycle, with_random_timespan=False, **kwargs): experiment = cls.create(**kwargs) now = timezone.now() - datetime.timedelta( days=random.randint(100, 200)) for state in lifecycle.value: experiment.apply_lifecycle_state(state) if (experiment.status == experiment.Status.LIVE and experiment.status_next is None and "published_dto" not in kwargs): experiment.published_dto = NimbusExperimentSerializer( experiment).data experiment.save() if experiment.has_filter( experiment.Filters.SHOULD_ALLOCATE_BUCKETS): experiment.allocate_bucket_range() change = generate_nimbus_changelog( experiment, experiment.owner, f"set lifecycle {lifecycle} state {state}", ) if with_random_timespan: change.changed_on = now change.save() now += datetime.timedelta(days=random.randint(5, 20)) return NimbusExperiment.objects.get(id=experiment.id)
def nimbus_push_experiment_to_kinto(collection, experiment_id): """ An invoked task that given a single experiment id, query it in the db, serialize it, and push its data to the configured collection. If it fails for any reason, log the error and reraise it so it will be forwarded to sentry. """ metrics.incr("push_experiment_to_kinto.started") try: experiment = NimbusExperiment.objects.get(id=experiment_id) logger.info(f"Pushing {experiment.slug} to Kinto") kinto_client = KintoClient(collection) data = NimbusExperimentSerializer(experiment).data kinto_client.create_record(data) experiment.publish_status = NimbusExperiment.PublishStatus.WAITING experiment.save() generate_nimbus_changelog( experiment, get_kinto_user(), message=NimbusChangeLog.Messages.PUSHED_TO_KINTO, ) logger.info(f"{experiment.slug} pushed to Kinto") metrics.incr("push_experiment_to_kinto.completed") except Exception as e: metrics.incr("push_experiment_to_kinto.failed") logger.info( f"Pushing experiment {experiment.slug} to Kinto failed: {e}") raise e
def nimbus_update_experiment_in_kinto(collection, experiment_id): """ An invoked task that given a single experiment id, reserializes and updates the record. If it fails for any reason, log the error and reraise it so it will be forwarded to sentry. """ metrics.incr("update_experiment_in_kinto.started") try: experiment = NimbusExperiment.objects.get(id=experiment_id) logger.info(f"Updating {experiment.slug} in Kinto") kinto_client = KintoClient(collection) data = NimbusExperimentSerializer(experiment).data kinto_client.update_record(data) experiment.publish_status = NimbusExperiment.PublishStatus.WAITING experiment.save() generate_nimbus_changelog( experiment, get_kinto_user(), message=NimbusChangeLog.Messages.UPDATED_IN_KINTO, ) logger.info(f"{experiment.slug} updated in Kinto") metrics.incr("update_experiment_in_kinto.completed") except Exception as e: metrics.incr("update_experiment_in_kinto.failed") logger.info( f"Updating experiment {experiment.slug} in Kinto failed: {e}") raise e
def test_serializer_with_branches_no_feature(self): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.CREATED, feature_config=None, ) experiment.save() serializer = NimbusExperimentSerializer(experiment) self.assertIsNone(serializer.data["branches"][0]["feature"]["featureId"])
def test_serializer_outputs_targeting(self): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.LAUNCH_APPROVE, firefox_min_version=NimbusExperiment.Version.FIREFOX_83, targeting_config_slug=NimbusExperiment.TargetingConfig.ALL_ENGLISH, application=NimbusExperiment.Application.DESKTOP, channel=NimbusExperiment.Channel.NO_CHANNEL, ) serializer = NimbusExperimentSerializer(experiment) self.assertEqual(serializer.data["targeting"], experiment.targeting) check_schema("experiments/NimbusExperiment", serializer.data)
def test_serializers_with_feature_value_None(self): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.ACCEPTED, branches=[], ) experiment.reference_branch = NimbusBranchFactory( experiment=experiment, feature_value=None) experiment.save() serializer = NimbusExperimentSerializer(experiment) self.assertIsNone(serializer.data["branches"][0]["feature"]["value"]) check_schema("experiments/NimbusExperiment", serializer.data)
def test_serializer_outputs_empty_targeting(self): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.LAUNCH_APPROVE, publish_status=NimbusExperiment.PublishStatus.APPROVED, targeting_config_slug=NimbusExperiment.TargetingConfig.NO_TARGETING, application=NimbusExperiment.Application.FENIX, ) serializer = NimbusExperimentSerializer(experiment) self.assertEqual(serializer.data["targeting"], "true") check_schema("experiments/NimbusExperiment", serializer.data)
def test_sets_application_channel_for_fenix_experiment(self): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.ACCEPTED, application=NimbusExperiment.Application.FENIX, channel=NimbusExperiment.Channel.FENIX_NIGHTLY, ) serializer = NimbusExperimentSerializer(experiment) self.assertEqual(serializer.data["application"], NimbusExperiment.Channel.FENIX_NIGHTLY) self.assertEqual(serializer.data["channel"], NimbusExperiment.Channel.FENIX_NIGHTLY) check_schema("experiments/NimbusExperiment", serializer.data)
def test_serializers_with_missing_feature_value(self, application): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.LAUNCH_APPROVE, application=application, branches=[], ) experiment.reference_branch = NimbusBranchFactory( experiment=experiment, feature_value=None ) experiment.save() serializer = NimbusExperimentSerializer(experiment) self.assertEqual(serializer.data["branches"][0]["feature"]["value"], {}) check_schema("experiments/NimbusExperiment", serializer.data)
def test_get_nimbus_experiment_returns_expected_data(self): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.LIVE) response = self.client.get( reverse( "nimbus-experiment-rest-detail", kwargs={"slug": experiment.slug}, ), ) self.assertEqual(response.status_code, 200) json_data = json.loads(response.content) self.assertEqual( NimbusExperimentSerializer(experiment).data, json_data)
def test_serializer_outputs_targeting_for_experiment_without_firefox_min_version( self, ): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.ACCEPTED, firefox_min_version=None, targeting_config_slug=NimbusExperiment.TargetingConfig.ALL_ENGLISH, channel=NimbusExperiment.Channel.DESKTOP_NIGHTLY, ) serializer = NimbusExperimentSerializer(experiment) self.assertEqual( serializer.data["targeting"], ('browserSettings.update.channel == "nightly" ' "&& localeLanguageCode == 'en' " "&& 'app.shield.optoutstudies.enabled'|preferenceValue"), ) check_schema("experiments/NimbusExperiment", serializer.data)
def test_serializer_outputs_targeting_for_experiment_without_channels( self): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.ACCEPTED, firefox_min_version=NimbusExperiment.Version.FIREFOX_80, targeting_config_slug=NimbusExperiment.TargetingConfig.ALL_ENGLISH, application=NimbusExperiment.Application.DESKTOP, channel=None, ) serializer = NimbusExperimentSerializer(experiment) self.assertEqual( serializer.data["targeting"], ("version|versionCompare('80.!') >= 0 " "&& localeLanguageCode == 'en' " "&& 'app.shield.optoutstudies.enabled'|preferenceValue"), ) check_schema("experiments/NimbusExperiment", serializer.data)
def test_push_experiment_to_kinto_sends_fenix_experiment_data(self): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.LAUNCH_APPROVE, application=NimbusExperiment.Application.DESKTOP, ) tasks.nimbus_push_experiment_to_kinto( settings.KINTO_COLLECTION_NIMBUS_DESKTOP, experiment.id ) data = NimbusExperimentSerializer(experiment).data self.mock_kinto_client.create_record.assert_called_with( data=data, collection=settings.KINTO_COLLECTION_NIMBUS_DESKTOP, bucket=settings.KINTO_BUCKET_WORKSPACE, if_not_exists=True, )
def test_serializer_outputs_expected_schema_without_feature(self): experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.ACCEPTED, feature_config=None, ) serializer = NimbusExperimentSerializer(experiment) experiment_data = serializer.data.copy() branches_data = [dict(b) for b in experiment_data.pop("branches")] self.assertEqual(len(branches_data), 2) for branch in experiment.branches.all(): self.assertIn( { "slug": branch.slug, "ratio": branch.ratio }, branches_data, ) check_schema("experiments/NimbusExperiment", serializer.data)
def nimbus_push_experiment_to_kinto(experiment_id): """ An invoked task that given a single experiment id, query it in the db, serialize it, and push its data to the configured collection. If it fails for any reason, log the error and reraise it so it will be forwarded to sentry. """ metrics.incr("push_experiment_to_kinto.started") try: experiment = NimbusExperiment.objects.get(id=experiment_id) logger.info(f"Pushing {experiment} to Kinto") kinto_client = KintoClient( NimbusExperiment.KINTO_APPLICATION_COLLECTION[ experiment.application]) if not NimbusBucketRange.objects.filter( experiment=experiment).exists(): NimbusIsolationGroup.request_isolation_group_buckets( experiment.slug, experiment, int(experiment.population_percent / Decimal("100.0") * NimbusExperiment.BUCKET_TOTAL), ) data = NimbusExperimentSerializer(experiment).data kinto_client.push_to_kinto(data) experiment.status = NimbusExperiment.Status.ACCEPTED experiment.save() generate_nimbus_changelog(experiment, get_kinto_user()) logger.info(f"{experiment} pushed to Kinto") metrics.incr("push_experiment_to_kinto.completed") except Exception as e: metrics.incr("push_experiment_to_kinto.failed") logger.info( f"Pushing experiment id {experiment_id} to Kinto failed: {e}") raise e
def test_sets_app_id_name_channel_for_application( self, application, channel, expected_application, expected_appId, expected_appName, ): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.LAUNCH_APPROVE, application=application, channel=channel, ) serializer = NimbusExperimentSerializer(experiment) self.assertEqual(serializer.data["application"], expected_application) self.assertEqual(serializer.data["channel"], channel) self.assertEqual(serializer.data["appName"], expected_appName) self.assertEqual(serializer.data["appId"], expected_appId) check_schema("experiments/NimbusExperiment", serializer.data)
def nimbus_synchronize_preview_experiments_in_kinto(): """ A scheduled task that pushes any experiments with status PREVIEW to the preview collection and removes any experiments not with status PREVIEW from the preview collection. """ metrics.incr("nimbus_synchronize_preview_experiments_in_kinto.started") kinto_client = KintoClient(settings.KINTO_COLLECTION_NIMBUS_PREVIEW, review=False) try: published_preview_slugs = kinto_client.get_main_records().keys() should_publish_experiments = NimbusExperiment.objects.filter( status=NimbusExperiment.Status.PREVIEW).exclude( slug__in=published_preview_slugs) for experiment in should_publish_experiments: data = NimbusExperimentSerializer(experiment).data kinto_client.create_record(data) logger.info(f"{experiment.slug} is being pushed to preview") should_unpublish_experiments = NimbusExperiment.objects.filter( slug__in=published_preview_slugs).exclude( status=NimbusExperiment.Status.PREVIEW) for experiment in should_unpublish_experiments: kinto_client.delete_record(experiment.slug) logger.info(f"{experiment.slug} is being removed from preview") metrics.incr( "nimbus_synchronize_preview_experiments_in_kinto.completed") except Exception as e: metrics.incr("nimbus_synchronize_preview_experiments_in_kinto.failed") logger.info(f"Synchronizing preview experiments failed: {e}") raise e
def test_outputs_expected_schema_for_complete_experiment(self): application = NimbusExperiment.Application.DESKTOP feature_config = NimbusFeatureConfigFactory.create() project = ProjectFactory.create() primary_outcome = Outcomes.by_application(application)[0].slug secondary_outcome = Outcomes.by_application(application)[1].slug experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.ENDING_APPROVE_APPROVE, application=application, feature_config=feature_config, projects=[project], primary_outcomes=[primary_outcome], secondary_outcomes=[secondary_outcome], ) data = dict(NimbusExperimentChangeLogSerializer(experiment).data) branches_data = [dict(b) for b in data.pop("branches")] control_branch_data = dict(data.pop("reference_branch")) locales_data = data.pop("locales") countries_data = data.pop("countries") feature_config_data = data.pop("feature_config") published_dto_data = data.pop("published_dto") self.assertEqual( data, { "application": experiment.application, "channel": experiment.channel, "firefox_min_version": experiment.firefox_min_version, "hypothesis": experiment.hypothesis, "is_paused": experiment.is_paused, "name": experiment.name, "owner": experiment.owner.email, "population_percent": str(experiment.population_percent), "primary_outcomes": [primary_outcome], "projects": [project.slug], "proposed_duration": experiment.proposed_duration, "proposed_enrollment": experiment.proposed_enrollment, "public_description": experiment.public_description, "publish_status": experiment.publish_status, "results_data": None, "risk_brand": experiment.risk_brand, "risk_mitigation_link": experiment.risk_mitigation_link, "risk_partner_related": experiment.risk_partner_related, "risk_revenue": experiment.risk_revenue, "secondary_outcomes": [secondary_outcome], "slug": experiment.slug, "status": experiment.status, "status_next": experiment.status_next, "targeting_config_slug": experiment.targeting_config_slug, "total_enrolled_clients": experiment.total_enrolled_clients, }, ) self.assertEqual( published_dto_data.keys(), dict(NimbusExperimentSerializer(experiment).data).keys(), ) self.assertEqual( feature_config_data, { "name": feature_config.name, "slug": feature_config.slug, "description": feature_config.description, "application": feature_config.application, "owner_email": feature_config.owner_email, "schema": feature_config.schema, }, ) self.assertEqual( set(locales_data), set(experiment.locales.all().values_list("code", flat=True)), ) self.assertEqual( set(countries_data), set(experiment.countries.all().values_list("code", flat=True)), ) self.assertEqual( control_branch_data, { "description": experiment.reference_branch.description, "feature_enabled": experiment.reference_branch.feature_enabled, "feature_value": experiment.reference_branch.feature_value, "name": experiment.reference_branch.name, "ratio": experiment.reference_branch.ratio, "slug": experiment.reference_branch.slug, }, ) for branch in experiment.branches.all(): self.assertIn( { "description": branch.description, "feature_enabled": branch.feature_enabled, "feature_value": branch.feature_value, "name": branch.name, "ratio": branch.ratio, "slug": branch.slug, }, branches_data, )
def test_serializer_outputs_expected_schema_with_feature(self): probe_set = NimbusProbeSetFactory.create() experiment = NimbusExperimentFactory.create_with_status( NimbusExperiment.Status.COMPLETE, application=NimbusExperiment.Application.DESKTOP, firefox_min_version=NimbusExperiment.Version.FIREFOX_80, targeting_config_slug=NimbusExperiment.TargetingConfig.ALL_ENGLISH, channel=NimbusExperiment.Channel.DESKTOP_NIGHTLY, probe_sets=[probe_set], ) serializer = NimbusExperimentSerializer(experiment) all_experiment_data = serializer.data.copy() arguments_data = all_experiment_data.pop("arguments") for experiment_data in arguments_data, all_experiment_data: branches_data = [dict(b) for b in experiment_data.pop("branches")] self.assertEqual( experiment_data, { "application": experiment.application, "channel": experiment.channel, "bucketConfig": { "randomizationUnit": (experiment.bucket_range.isolation_group. randomization_unit), "namespace": experiment.bucket_range.isolation_group.namespace, "start": experiment.bucket_range.start, "count": experiment.bucket_range.count, "total": experiment.bucket_range.isolation_group.total, }, # DRF manually replaces the isoformat suffix so we have to do the same "endDate": experiment.end_date.isoformat().replace("+00:00", "Z"), "id": experiment.slug, "isEnrollmentPaused": False, "proposedDuration": experiment.proposed_duration, "proposedEnrollment": experiment.proposed_enrollment, "referenceBranch": experiment.reference_branch.slug, "schemaVersion": settings.NIMBUS_SCHEMA_VERSION, "slug": experiment.slug, # DRF manually replaces the isoformat suffix so we have to do the same "startDate": experiment.start_date.isoformat().replace("+00:00", "Z"), "targeting": ('browserSettings.update.channel == "nightly" ' "&& version|versionCompare('80.!') >= 0 " "&& localeLanguageCode == 'en' " "&& 'app.shield.optoutstudies.enabled'|preferenceValue"), "userFacingDescription": experiment.public_description, "userFacingName": experiment.name, "probeSets": [probe_set.slug], }, ) self.assertEqual(len(branches_data), 2) for branch in experiment.branches.all(): self.assertIn( { "slug": branch.slug, "ratio": branch.ratio, "feature": { "featureId": experiment.feature_config.slug, "enabled": branch.feature_enabled, "value": json.loads(branch.feature_value), }, }, branches_data, ) check_schema("experiments/NimbusExperiment", serializer.data)
def resolve_recipe_json(self, info): return json.dumps(NimbusExperimentSerializer(self).data, indent=2, sort_keys=True)
def test_serializer_outputs_expected_schema_with_feature(self): experiment = NimbusExperimentFactory.create_with_lifecycle( NimbusExperimentFactory.Lifecycles.ENDING_APPROVE_APPROVE, application=NimbusExperiment.Application.DESKTOP, firefox_min_version=NimbusExperiment.Version.FIREFOX_83, targeting_config_slug=NimbusExperiment.TargetingConfig.ALL_ENGLISH, channel=NimbusExperiment.Channel.NIGHTLY, primary_outcomes=["foo", "bar", "baz"], secondary_outcomes=["quux", "xyzzy"], ) serializer = NimbusExperimentSerializer(experiment) experiment_data = serializer.data.copy() bucket_data = dict(experiment_data.pop("bucketConfig")) branches_data = [dict(b) for b in experiment_data.pop("branches")] self.assertDictEqual( experiment_data, { "arguments": {}, "application": "firefox-desktop", "appName": "firefox_desktop", "appId": "firefox-desktop", "channel": "nightly", # DRF manually replaces the isoformat suffix so we have to do the same "endDate": experiment.end_date.isoformat().replace("+00:00", "Z"), "id": experiment.slug, "isEnrollmentPaused": True, "proposedDuration": experiment.proposed_duration, "proposedEnrollment": experiment.proposed_enrollment, "referenceBranch": experiment.reference_branch.slug, "schemaVersion": settings.NIMBUS_SCHEMA_VERSION, "slug": experiment.slug, # DRF manually replaces the isoformat suffix so we have to do the same "startDate": experiment.start_date.isoformat().replace("+00:00", "Z"), "targeting": ( 'browserSettings.update.channel == "nightly" ' "&& version|versionCompare('83.!') >= 0 " "&& 'app.shield.optoutstudies.enabled'|preferenceValue " "&& localeLanguageCode == 'en'" ), "userFacingDescription": experiment.public_description, "userFacingName": experiment.name, "probeSets": [], "outcomes": [ {"priority": "primary", "slug": "foo"}, {"priority": "primary", "slug": "bar"}, {"priority": "primary", "slug": "baz"}, {"priority": "secondary", "slug": "quux"}, {"priority": "secondary", "slug": "xyzzy"}, ], "featureIds": [experiment.feature_config.slug], }, ) self.assertEqual( bucket_data, { "randomizationUnit": ( experiment.bucket_range.isolation_group.randomization_unit ), "namespace": experiment.bucket_range.isolation_group.namespace, "start": experiment.bucket_range.start, "count": experiment.bucket_range.count, "total": experiment.bucket_range.isolation_group.total, }, ) self.assertEqual(len(branches_data), 2) for branch in experiment.branches.all(): self.assertIn( { "slug": branch.slug, "ratio": branch.ratio, "feature": { "featureId": experiment.feature_config.slug, "enabled": branch.feature_enabled, "value": json.loads(branch.feature_value), }, }, branches_data, ) check_schema("experiments/NimbusExperiment", serializer.data)