コード例 #1
0
 def test_preference_experiments_unique_branch_values(self):
     action = ActionFactory(name="preference-experiment")
     arguments = {
         "slug":
         "test",
         "branches": [
             {
                 "slug": "a",
                 "value": "unique"
             },
             {
                 "slug": "b",
                 "value": "duplicate"
             },
             {
                 "slug": "c",
                 "value": "duplicate"
             },
         ],
     }
     with pytest.raises(serializers.ValidationError) as exc_info:
         action.validate_arguments(arguments, RecipeRevisionFactory())
     error = action.errors["duplicate_branch_value"]
     assert exc_info.value.detail == {
         "arguments": {
             "branches": {
                 2: {
                     "value": error
                 }
             }
         }
     }
コード例 #2
0
    def test_enable_rollback_enable_rollout_invariance(self):
        rollout_recipe = RecipeFactory(
            name="Rollout",
            approver=UserFactory(),
            enabler=UserFactory(),
            action=ActionFactory(name="preference-rollout"),
            arguments={"slug": "myslug"},
        )
        assert rollout_recipe.enabled

        rollback_recipe = RecipeFactory(
            name="Rollback",
            action=ActionFactory(name="preference-rollback"),
            arguments={"rolloutSlug": "myslug"},
        )
        approval_request = rollback_recipe.latest_revision.request_approval(
            creator=UserFactory())
        approval_request.approve(approver=UserFactory(), comment="r+")

        with pytest.raises(ValidationError) as exc_info:
            rollback_recipe.approved_revision.enable(user=UserFactory())
        assert exc_info.value.message == "Rollout recipe 'Rollout' is currently enabled"

        rollout_recipe.approved_revision.disable(user=UserFactory())
        assert not rollout_recipe.enabled
        # Now it should be possible to enable the rollback recipe.
        rollback_recipe.approved_revision.enable(user=UserFactory())
        assert rollback_recipe.enabled

        # Can't make up your mind. Now try to enable the rollout recipe again even though
        # the rollback recipe is enabled.
        with pytest.raises(ValidationError) as exc_info:
            rollout_recipe.approved_revision.enable(user=UserFactory())
        assert exc_info.value.message == "Rollback recipe 'Rollback' is currently enabled"
コード例 #3
0
ファイル: test_commands.py プロジェクト: mythmon/normandy
 def test_it_does_not_resign_up_to_date_actions(self, settings, mocked_autograph):
     a = ActionFactory(signed=True)
     a.signature.signature = "original signature"
     a.signature.save()
     call_command("update_action_signatures")
     a.refresh_from_db()
     assert a.signature.signature == "original signature"
コード例 #4
0
ファイル: test_models.py プロジェクト: leplatrem/normandy
 def test_preference_exeriments_unique_branch_values(self):
     action = ActionFactory(name='preference-experiment')
     arguments = {
         'slug':
         'test',
         'branches': [{
             'slug': 'a',
             'value': 'unique'
         }, {
             'slug': 'b',
             'value': 'duplicate'
         }, {
             'slug': 'c',
             'value': 'duplicate'
         }]
     }
     with pytest.raises(serializers.ValidationError) as exc_info:
         action.validate_arguments(arguments)
     error = action.errors['duplicate_branch_value']
     assert exc_info.value.detail == {
         'arguments': {
             'branches': {
                 2: {
                     'value': error
                 }
             }
         }
     }
コード例 #5
0
ファイル: test_commands.py プロジェクト: leplatrem/normandy
 def test_it_resigns_signed_actions_with_force(self, mocked_autograph):
     a = ActionFactory(signed=True)
     a.signature.signature = 'old signature'
     a.signature.save()
     call_command('update_action_signatures', '--force')
     a.refresh_from_db()
     assert a.signature.signature != 'old signature'
コード例 #6
0
ファイル: test_commands.py プロジェクト: chartjes/normandy
    def test_it_ignores_missing_actions(self, mock_action):
        dont_update_action = ActionFactory(name='dont-update-action', implementation='old')
        mock_action(dont_update_action.name, 'new', dont_update_action.arguments_schema)

        call_command('update_actions', 'missing-action')
        dont_update_action.refresh_from_db()
        assert dont_update_action.implementation == 'old'
コード例 #7
0
ファイル: test_commands.py プロジェクト: amreshk005/normandy
 def test_it_sends_metrics(self, settings, mocked_autograph):
     ActionFactory.create_batch(3, signed=False)
     with MetricsMock() as mm:
         call_command("update_action_signatures")
         mm.print_records()
         assert mm.has_record(GAUGE,
                              stat="normandy.signing.actions.signed",
                              value=3)
コード例 #8
0
ファイル: test_commands.py プロジェクト: mythmon/normandy
 def test_it_signs_out_of_date_actions(self, settings, mocked_autograph):
     a = ActionFactory(signed=True)
     a.signature.timestamp -= timedelta(seconds=settings.AUTOGRAPH_SIGNATURE_MAX_AGE * 2)
     a.signature.signature = "old signature"
     a.signature.save()
     call_command("update_action_signatures")
     a.refresh_from_db()
     assert a.signature.signature != "old signature"
コード例 #9
0
        def test_no_errors(self):
            rollback_action = ActionFactory(name="preference-rollback")
            rollout_action = ActionFactory(name="preference-rollout")
            rollout_recipe = RecipeFactory(action=rollout_action,
                                           arguments={"slug": "test-rollout"})

            # does not throw when saving the revision
            arguments = {"rolloutSlug": rollout_recipe.arguments["slug"]}
            RecipeFactory(action=rollback_action, arguments=arguments)
コード例 #10
0
ファイル: test_commands.py プロジェクト: leplatrem/normandy
    def test_it_ignores_missing_actions(self, mock_action):
        dont_update_action = ActionFactory(name='dont-update-action',
                                           implementation='old')
        mock_action(dont_update_action.name, 'new',
                    dont_update_action.arguments_schema)

        call_command('update_actions', 'missing-action')
        dont_update_action.refresh_from_db()
        assert dont_update_action.implementation == 'old'
コード例 #11
0
ファイル: test_models.py プロジェクト: micdurodola/normandy
 def test_unique_branch_slugs(self):
     action = ActionFactory(name="multi-preference-experiment")
     arguments = MultiPreferenceExperimentArgumentsFactory(
         branches=[{"slug": "unique"}, {"slug": "duplicate"}, {"slug": "duplicate"}],
     )
     with pytest.raises(serializers.ValidationError) as exc_info:
         action.validate_arguments(arguments, RecipeRevisionFactory())
     error = action.errors["duplicate_branch_slug"]
     assert exc_info.value.detail == {"arguments": {"branches": {2: {"slug": error}}}}
コード例 #12
0
ファイル: test_commands.py プロジェクト: mythmon/normandy
    def test_it_updates_existing_actions(self, mock_action):
        action = ActionFactory(name="test-action", implementation="old_impl", arguments_schema={})
        mock_action(action.name, {"type": "int"}, "new_impl")

        call_command("update_actions")
        assert Action.objects.count() == 1

        action.refresh_from_db()
        assert action.implementation == "new_impl"
        assert action.arguments_schema == {"type": "int"}
コード例 #13
0
ファイル: test_models.py プロジェクト: micdurodola/normandy
        def test_no_errors(self):
            rollback_action = ActionFactory(name="preference-rollback")
            assert rollback_action.arguments_schema != {}
            rollout_action = ActionFactory(name="preference-rollout")
            assert rollout_action.arguments_schema != {}

            rollout_recipe = RecipeFactory(action=rollout_action)

            # does not throw when saving the revision
            arguments = {"rolloutSlug": rollout_recipe.latest_revision.arguments["slug"]}
            RecipeFactory(action=rollback_action, arguments=arguments)
コード例 #14
0
ファイル: test_commands.py プロジェクト: chartjes/normandy
    def test_it_updates_existing_actions(self, mock_action):
        action = ActionFactory(
            name='test-action',
            implementation='old_impl',
            arguments_schema={},
        )
        mock_action(action.name, 'new_impl', {'type': 'int'})

        call_command('update_actions')
        assert Action.objects.count() == 1

        action.refresh_from_db()
        assert action.implementation == 'new_impl'
        assert action.arguments_schema == {'type': 'int'}
コード例 #15
0
ファイル: test_models.py プロジェクト: rehandalal/normandy
 def test_preference_exeriments_unique_branch_values(self):
     action = ActionFactory(name="preference-experiment")
     arguments = {
         "slug": "test",
         "branches": [
             {"slug": "a", "value": "unique"},
             {"slug": "b", "value": "duplicate"},
             {"slug": "c", "value": "duplicate"},
         ],
     }
     with pytest.raises(serializers.ValidationError) as exc_info:
         action.validate_arguments(arguments, RecipeRevisionFactory())
     error = action.errors["duplicate_branch_value"]
     assert exc_info.value.detail == {"arguments": {"branches": {2: {"value": error}}}}
コード例 #16
0
 def test_canonical_json(self):
     action = ActionFactory(name="test-action",
                            implementation="console.log(true)")
     # Yes, this is ugly, but it needs to compare an exact byte
     # sequence, since this is used for hashing and signing
     expected = (
         "{"
         '"arguments_schema":{},'
         '"implementation_url":"/api/v1/action/test-action/implementation'
         '/sha384-ZRkmoh4lizeQ_jdtJBOQZmPzc3x09DKCA4gkdJmwEnO31F7Ttl8RyXkj3wG93lAP/",'
         '"name":"test-action"'
         "}")
     expected = expected.encode()
     assert action.canonical_json() == expected
コード例 #17
0
ファイル: test_commands.py プロジェクト: leplatrem/normandy
    def test_it_updates_existing_actions(self, mock_action):
        action = ActionFactory(
            name='test-action',
            implementation='old_impl',
            arguments_schema={},
        )
        mock_action(action.name, 'new_impl', {'type': 'int'})

        call_command('update_actions')
        assert Action.objects.count() == 1

        action.refresh_from_db()
        assert action.implementation == 'new_impl'
        assert action.arguments_schema == {'type': 'int'}
コード例 #18
0
ファイル: test_models.py プロジェクト: rehandalal/normandy
 def test_canonical_json(self):
     action = ActionFactory(name="test-action", implementation="console.log(true)")
     # Yes, this is ugly, but it needs to compare an exact byte
     # sequence, since this is used for hashing and signing
     expected = (
         "{"
         '"arguments_schema":{},'
         '"implementation_url":"/api/v1/action/test-action/implementation'
         '/sha384-ZRkmoh4lizeQ_jdtJBOQZmPzc3x09DKCA4gkdJmwEnO31F7Ttl8RyXkj3wG93lAP/",'
         '"name":"test-action"'
         "}"
     )
     expected = expected.encode()
     assert action.canonical_json() == expected
コード例 #19
0
 def test_recipe_revise_partial(self):
     a1 = ActionFactory()
     recipe = RecipeFactory(
         name="unchanged",
         action=a1,
         arguments={"message": "something"},
         extra_filter_expression="something !== undefined",
         filter_object_json=None,
     )
     a2 = ActionFactory()
     recipe.revise(name="changed", action=a2)
     assert recipe.action == a2
     assert recipe.name == "changed"
     assert recipe.arguments == {"message": "something"}
     assert recipe.filter_expression == "something !== undefined"
コード例 #20
0
ファイル: test_models.py プロジェクト: rehandalal/normandy
    def test_update_signature(self, mocker, mock_logger):
        # Mock the Autographer
        mock_autograph = mocker.patch("normandy.recipes.models.Autographer")
        mock_autograph.return_value.sign_data.return_value = [{"signature": "fake signature"}]

        action = ActionFactory(signed=False)
        action.update_signature()
        mock_logger.info.assert_called_with(
            Whatever.contains(action.name),
            extra={"code": INFO_REQUESTING_ACTION_SIGNATURES, "action_names": [action.name]},
        )

        action.save()
        assert action.signature is not None
        assert action.signature.signature == "fake signature"
コード例 #21
0
    def test_only_signed_when_approved_and_enabled(self, mocked_autograph):
        sign_data_mock = mocked_autograph.return_value.sign_data
        # This uses the signer, so do it first
        action = ActionFactory()
        sign_data_mock.reset_mock()

        sign_data_mock.side_effect = Exception("Can't sign yet")
        recipe = RecipeFactory(name="unchanged", action=action)
        assert not recipe.enabled
        assert not recipe.is_approved
        assert recipe.signature is None

        # Updating does not generate a signature
        recipe.revise(name="changed")
        assert recipe.signature is None

        # Approving does not sign the recipe
        rev = recipe.latest_revision
        approval_request = rev.request_approval(UserFactory())
        approval_request.approve(UserFactory(), "r+")
        recipe.refresh_from_db()
        assert recipe.signature is None
        mocked_autograph.return_value.sign_data.assert_not_called()

        # Enabling signs the recipe
        mocked_autograph.return_value.sign_data.side_effect = fake_sign
        rev.enable(UserFactory())
        recipe.refresh_from_db()
        expected_sig = fake_sign([recipe.canonical_json()])[0]["signature"]
        assert recipe.signature.signature == expected_sig
        assert mocked_autograph.return_value.sign_data.called_once()
コード例 #22
0
ファイル: test_models.py プロジェクト: micdurodola/normandy
 def test_unique_experiment_slug_no_collision(self):
     action = ActionFactory(name="preference-experiment")
     arguments_a = PreferenceExperimentArgumentsFactory()
     arguments_b = PreferenceExperimentArgumentsFactory()
     # Does not throw when saving revisions
     RecipeFactory(action=action, arguments=arguments_a)
     RecipeFactory(action=action, arguments=arguments_b)
コード例 #23
0
 def test_canonical_json(self):
     recipe = RecipeFactory(
         action=ActionFactory(name="action"),
         arguments_json='{"foo": 1, "bar": 2}',
         extra_filter_expression="2 + 2 == 4",
         name="canonical",
         filter_object_json=None,
     )
     # Yes, this is really ugly, but we really do need to compare an exact
     # byte sequence, since this is used for hashing and signing
     filter_expression = "2 + 2 == 4"
     expected = ("{"
                 '"action":"action",'
                 '"arguments":{"bar":2,"foo":1},'
                 '"filter_expression":"%(filter_expression)s",'
                 '"id":%(id)s,'
                 '"name":"canonical",'
                 '"revision_id":"%(revision_id)s"'
                 "}") % {
                     "id": recipe.id,
                     "revision_id": recipe.revision_id,
                     "filter_expression": filter_expression,
                 }
     expected = expected.encode()
     assert recipe.canonical_json() == expected
コード例 #24
0
 def test_repeated_identical_survey_ids(self):
     action = ActionFactory(name="show-heartbeat")
     arguments = {
         "repeatOption": "nag",
         "surveyId": "001",
         "message": "Message!",
         "learnMoreMessage": "More!?!",
         "learnMoreUrl": "https://example.com/learnmore",
         "engagementButtonLabel": "Label!",
         "thanksMessage": "Thanks!",
         "postAnswerUrl": "https://example.com/answer",
         "includeTelemetryUUID": True,
     }
     RecipeFactory(action=action, arguments=arguments)
     # Reusing the same "surveyId" should cause a ValidationError.
     # But you can change other things.
     arguments["message"] += " And this!"
     with pytest.raises(serializers.ValidationError) as exc_info:
         RecipeFactory(action=action, arguments=arguments)
     expected_error = action.errors["duplicate_survey_id"]
     assert exc_info.value.detail == {
         "arguments": {
             "surveyId": expected_error
         }
     }
コード例 #25
0
    def test_no_error_distinctly_different_survey_ids(self):
        action = ActionFactory(name="show-heartbeat")
        arguments = {
            "repeatOption": "nag",
            "surveyId": "001",
            "message": "Message!",
            "learnMoreMessage": "More!?!",
            "learnMoreUrl": "https://example.com/learnmore",
            "engagementButtonLabel": "Label!",
            "thanksMessage": "Thanks!",
            "postAnswerUrl": "https://example.com/answer",
            "includeTelemetryUUID": True,
        }
        # does not throw when saving the revision
        recipe = RecipeFactory(action=action, arguments=arguments)

        # Approve and enable the revision
        rev = recipe.latest_revision
        approval_request = rev.request_approval(UserFactory())
        approval_request.approve(UserFactory(), "r+")
        rev.enable(UserFactory())
        assert rev.arguments["surveyId"] == "001"

        arguments["surveyId"] = "002"
        recipe = RecipeFactory(action=action, arguments=arguments)
        rev = recipe.latest_revision
        assert rev.arguments["surveyId"] == "002"
コード例 #26
0
 def test_slug_must_match_a_rollout(self):
     rollback_action = ActionFactory(name="preference-rollback")
     arguments = {"rolloutSlug": "does-not-exist"}
     with pytest.raises(serializers.ValidationError) as exc_info:
         RecipeFactory(action=rollback_action, arguments=arguments)
     error = rollback_action.errors["rollout_slug_not_found"]
     assert exc_info.value.detail == {"arguments": {"slug": error}}
コード例 #27
0
ファイル: test_commands.py プロジェクト: amreshk005/normandy
    def test_it_works_for_multiple_extensions(self, storage):
        extension1 = ExtensionFactory(name="1.xpi")
        extension2 = ExtensionFactory(name="2.xpi")

        fake_old_url1 = extension1.xpi.url.replace("/media/", "/media-old/")
        fake_old_url2 = extension2.xpi.url.replace("/media/", "/media-old/")

        action = ActionFactory(name="opt-out-study")
        recipe1 = RecipeFactory(action=action,
                                arguments={
                                    "name": "1",
                                    addonUrl: fake_old_url1
                                })
        recipe2 = RecipeFactory(action=action,
                                arguments={
                                    "name": "2",
                                    addonUrl: fake_old_url2
                                })
        call_command("update_addon_urls")

        # For reasons that I don't understand, recipe.update_from_db() doesn't work here.
        recipe1 = Recipe.objects.get(id=recipe1.id)
        recipe2 = Recipe.objects.get(id=recipe2.id)

        assert recipe1.arguments[addonUrl] == extension1.xpi.url
        assert recipe2.arguments[addonUrl] == extension2.xpi.url
コード例 #28
0
 def test_unique_experiment_slug_no_collision(self):
     action = ActionFactory(name="preference-experiment")
     arguments_a = {"slug": "a", "branches": []}
     arguments_b = {"slug": "b", "branches": []}
     # Does not throw when saving revisions
     RecipeFactory(action=action, arguments=arguments_a)
     RecipeFactory(action=action, arguments=arguments_b)
コード例 #29
0
ファイル: test_models.py プロジェクト: MFBrewster/normandy
    def test_recipes_used_by(self):
        recipe = RecipeFactory(enabled=True)
        assert [recipe] == list(recipe.action.recipes_used_by)

        action = ActionFactory()
        recipes = RecipeFactory.create_batch(2, action=action, enabled=True)
        assert set(action.recipes_used_by) == set(recipes)
コード例 #30
0
 def test_signed_listing_works(self, api_client):
     a1 = ActionFactory(signed=True)
     res = api_client.get('/api/v1/action/signed/')
     assert res.status_code == 200
     assert len(res.data) == 1
     assert res.data[0]['action']['name'] == a1.name
     assert res.data[0]['signature']['signature'] == a1.signature.signature
コード例 #31
0
 def test_detail_view_includes_cache_headers(self, api_client):
     action = ActionFactory()
     res = api_client.get('/api/v1/action/{name}/'.format(name=action.name))
     assert res.status_code == 200
     # It isn't important to assert a particular value for max-age
     assert 'max-age=' in res['Cache-Control']
     assert 'public' in res['Cache-Control']
コード例 #32
0
ファイル: test_api.py プロジェクト: amreshk005/normandy
 def test_signed_listing_works(self, api_client):
     a1 = ActionFactory(signed=True)
     res = api_client.get("/api/v1/action/signed/")
     assert res.status_code == 200
     assert len(res.data) == 1
     assert res.data[0]["action"]["name"] == a1.name
     assert res.data[0]["signature"]["signature"] == a1.signature.signature
コード例 #33
0
    def test_creation_when_arguments_are_invalid(self, api_client):
        ActionFactory(name='foobarbaz',
                      arguments_schema={
                          'type': 'object',
                          'properties': {
                              'message': {
                                  'type': 'string'
                              }
                          },
                          'required': ['message']
                      })
        res = api_client.post(
            '/api/v1/recipe/', {
                'name': 'Test Recipe',
                'enabled': True,
                'extra_filter_expression': 'true',
                'action': 'foobarbaz',
                'arguments': {
                    'message': ''
                }
            })
        assert res.status_code == 400

        recipes = Recipe.objects.all()
        assert recipes.count() == 0
コード例 #34
0
    def test_validation_with_invalid_filter_expression(self):
        ActionFactory(name="show-heartbeat", arguments_schema=ARGUMENTS_SCHEMA)

        serializer = RecipeSerializer(
            data={
                "name": "bar",
                "enabled": True,
                "extra_filter_expression": "inv(-alsid",
                "action": "show-heartbeat",
                "arguments": {
                    "surveyId":
                    "lorem-ipsum-dolor",
                    "surveys": [
                        {
                            "title": "adipscing",
                            "weight": 1
                        },
                        {
                            "title": "consequetar",
                            "weight": 1
                        },
                    ],
                },
            })

        assert not serializer.is_valid()
        assert serializer.errors["extra_filter_expression"] == [
            "Could not parse expression: inv(-alsid"
        ]
コード例 #35
0
    def test_validation_with_invalid_filter_expression(self):
        ActionFactory(name='show-heartbeat', arguments_schema=ARGUMENTS_SCHEMA)

        serializer = RecipeSerializer(
            data={
                'name': 'bar',
                'enabled': True,
                'extra_filter_expression': 'inv(-alsid',
                'action': 'show-heartbeat',
                'arguments': {
                    'surveyId':
                    'lorem-ipsum-dolor',
                    'surveys': [{
                        'title': 'adipscing',
                        'weight': 1
                    }, {
                        'title': 'consequetar',
                        'weight': 1
                    }]
                }
            })

        assert not serializer.is_valid()
        assert serializer.errors['extra_filter_expression'] == [
            'Could not parse expression: inv(-alsid'
        ]
コード例 #36
0
ファイル: test_models.py プロジェクト: rehandalal/normandy
 def test_cant_change_signature_and_other_fields(self, mocker):
     # Mock the Autographer
     mock_autograph = mocker.patch("normandy.recipes.models.Autographer")
     mock_autograph.return_value.sign_data.return_value = [{"signature": "fake signature"}]
     action = ActionFactory(name="unchanged", signed=False)
     action.update_signature()
     action.name = "changed"
     with pytest.raises(ValidationError) as exc_info:
         action.save()
     assert exc_info.value.message == "Signatures must change alone"
コード例 #37
0
ファイル: test_commands.py プロジェクト: chartjes/normandy
    def test_it_only_updates_given_actions(self, mock_action):
        update_action = ActionFactory(name='update-action', implementation='old')
        dont_update_action = ActionFactory(name='dont-update-action', implementation='old')

        mock_action(update_action.name, 'new', update_action.arguments_schema)
        mock_action(dont_update_action.name, 'new', dont_update_action.arguments_schema)

        call_command('update_actions', 'update-action')
        update_action.refresh_from_db()
        assert update_action.implementation == 'new'
        dont_update_action.refresh_from_db()
        assert dont_update_action.implementation == 'old'
コード例 #38
0
ファイル: test_models.py プロジェクト: rehandalal/normandy
 def test_it_works(self):
     action = ActionFactory(name="nothing special")
     # does not raise an exception
     action.validate_arguments({}, RecipeRevisionFactory())
コード例 #39
0
ファイル: test_factories.py プロジェクト: chartjes/normandy
 def test_it_gets_the_right_hash(self):
     a = ActionFactory.build()
     old_hash = a.implementation_hash
     a.save()
     assert a.implementation_hash == old_hash