Beispiel #1
0
    def test_delete_pending_approval_request_on_revise(self):
        recipe = RecipeFactory(name="old")
        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        recipe.revise(name="new")

        with pytest.raises(ApprovalRequest.DoesNotExist):
            ApprovalRequest.objects.get(pk=approval.pk)
Beispiel #2
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()
Beispiel #3
0
    def test_delete_pending_approval_request_on_revise(self):
        recipe = RecipeFactory(name="old")
        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        recipe.revise(name="new")

        with pytest.raises(ApprovalRequest.DoesNotExist):
            ApprovalRequest.objects.get(pk=approval.pk)
Beispiel #4
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()
Beispiel #5
0
 def test_enabled_state_carried_over_on_approval(self):
     recipe = RecipeFactory(approver=UserFactory(), enabler=UserFactory())
     carryover_from = recipe.approved_revision.enabled_state
     recipe.revise(name="New name")
     approval_request = recipe.latest_revision.request_approval(UserFactory())
     approval_request.approve(UserFactory(), "r+")
     assert recipe.enabled
     assert recipe.approved_revision.enabled_state.carryover_from == carryover_from
Beispiel #6
0
 def test_enabled_state_carried_over_on_approval(self):
     recipe = RecipeFactory(approver=UserFactory(), enabler=UserFactory())
     carryover_from = recipe.approved_revision.enabled_state
     recipe.revise(name="New name")
     approval_request = recipe.latest_revision.request_approval(UserFactory())
     approval_request.approve(UserFactory(), "r+")
     assert recipe.approved_revision.enabled
     assert recipe.approved_revision.enabled_state.carryover_from == carryover_from
Beispiel #7
0
    def test_recipe_doesnt_revise_when_clean(self):
        recipe = RecipeFactory(name="my name")

        revision_id = recipe.revision_id
        last_updated = recipe.last_updated

        recipe.revise(name="my name")
        assert revision_id == recipe.revision_id
        assert last_updated == recipe.last_updated
Beispiel #8
0
 def test_can_delete_extensions_no_longer_in_use(self, api_client, storage):
     xpi = WebExtensionFileFactory()
     e = ExtensionFactory(xpi__from_func=xpi.open)
     a = ActionFactory(name="opt-out-study")
     r = RecipeFactory(action=a, arguments={"extensionId": e.id})
     r.revise(arguments=OptOutStudyArgumentsFactory(extensionId=e.id + 1))
     res = api_client.delete(f"/api/v3/extension/{e.id}/")
     assert res.status_code == 204
     assert Extension.objects.count() == 0
Beispiel #9
0
    def test_recipe_doesnt_revise_when_clean(self):
        recipe = RecipeFactory(name="my name")

        revision_id = recipe.revision_id
        last_updated = recipe.last_updated

        recipe.revise(name="my name")
        assert revision_id == recipe.revision_id
        assert last_updated == recipe.last_updated
Beispiel #10
0
 def test_can_update_extensions_no_longer_in_use(self, api_client, storage):
     xpi = WebExtensionFileFactory()
     e = ExtensionFactory(xpi__from_func=xpi.open)
     a = ActionFactory(name="opt-out-study")
     r = RecipeFactory(action=a, arguments={"extensionId": e.id})
     r.revise(arguments={"extensionId": 0})
     res = api_client.patch(f"/api/v3/extension/{e.id}/", {"name": "new name"})
     assert res.status_code == 200
     assert res.data["name"] == "new name"
Beispiel #11
0
 def test_can_delete_extensions_no_longer_in_use(self, api_client, storage):
     xpi = WebExtensionFileFactory()
     e = ExtensionFactory(xpi__from_func=xpi.open)
     a = ActionFactory(name="opt-out-study")
     r = RecipeFactory(action=a, arguments={"extensionId": e.id})
     r.revise(arguments={"extensionId": 0})
     res = api_client.delete(f"/api/v3/extension/{e.id}/")
     assert res.status_code == 204
     assert Extension.objects.count() == 0
 def test_can_update_extensions_no_longer_in_use(self, api_client, storage):
     xpi = WebExtensionFileFactory()
     e = ExtensionFactory(xpi__from_func=xpi.open)
     a = ActionFactory(name="opt-out-study")
     r = RecipeFactory(action=a, arguments={"extensionId": e.id})
     r.revise(arguments={"extensionId": 0})
     res = api_client.patch(f"/api/v3/extension/{e.id}/", {"name": "new name"})
     assert res.status_code == 200
     assert res.data["name"] == "new name"
Beispiel #13
0
    def test_recipe_doesnt_revise_when_clean(self):
        channel = ChannelFactory()
        recipe = RecipeFactory(name='my name', channels=[channel])

        revision_id = recipe.revision_id
        last_updated = recipe.last_updated

        recipe.revise(name='my name', channels=[channel])
        assert revision_id == recipe.revision_id
        assert last_updated == recipe.last_updated
Beispiel #14
0
    def test_history(self, api_client):
        recipe = RecipeFactory(name='version 1')
        recipe.revise(name='version 2')
        recipe.revise(name='version 3')

        res = api_client.get('/api/v1/recipe/%s/history/' % recipe.id)

        assert res.data[0]['recipe']['name'] == 'version 3'
        assert res.data[1]['recipe']['name'] == 'version 2'
        assert res.data[2]['recipe']['name'] == 'version 1'
Beispiel #15
0
        def test_history(self, api_client):
            recipe = RecipeFactory(name="version 1")
            recipe.revise(name="version 2")
            recipe.revise(name="version 3")

            res = api_client.get("/api/v1/recipe/%s/history/" % recipe.id)

            assert res.data[0]["recipe"]["name"] == "version 3"
            assert res.data[1]["recipe"]["name"] == "version 2"
            assert res.data[2]["recipe"]["name"] == "version 1"
Beispiel #16
0
    def test_signature_is_cleared_if_autograph_unavailable(self, mocker):
        # Mock the Autographer to return an error
        mock_autograph = mocker.patch("normandy.recipes.models.Autographer")
        mock_autograph.side_effect = ImproperlyConfigured

        recipe = RecipeFactory(name="unchanged", signed=True)
        original_signature = recipe.signature
        recipe.revise(name="changed")
        assert recipe.name == "changed"
        assert recipe.signature is not original_signature
        assert recipe.signature is None
Beispiel #17
0
        def test_unique_name_update_collision(self):
            action = ActionFactory(name="opt-out-study")
            arguments_a = {"name": "foo"}
            arguments_b = {"name": "bar"}
            RecipeFactory(action=action, arguments=arguments_a)
            recipe = RecipeFactory(action=action, arguments=arguments_b)

            with pytest.raises(serializers.ValidationError) as exc_info1:
                recipe.revise(arguments=arguments_a)
            error = action.errors["duplicate_study_name"]
            assert exc_info1.value.detail == {"arguments": {"name": error}}
Beispiel #18
0
        def test_unique_name_update_collision(self):
            action = ActionFactory(name="opt-out-study")
            arguments_a = {"name": "foo"}
            arguments_b = {"name": "bar"}
            RecipeFactory(action=action, arguments=arguments_a)
            recipe = RecipeFactory(action=action, arguments=arguments_b)

            with pytest.raises(serializers.ValidationError) as exc_info1:
                recipe.revise(arguments=arguments_a)
            error = action.errors["duplicate_study_name"]
            assert exc_info1.value.detail == {"arguments": {"name": error}}
Beispiel #19
0
    def test_signature_is_updated_if_autograph_available(self, mocked_autograph):
        recipe = RecipeFactory(name="unchanged", approver=UserFactory(), enabler=UserFactory())
        original_signature = recipe.signature
        assert original_signature is not None

        recipe.revise(name="changed")

        assert recipe.latest_revision.name == "changed"
        assert recipe.signature is not original_signature
        expected_sig = fake_sign([recipe.canonical_json()])[0]["signature"]
        assert recipe.signature.signature == expected_sig
Beispiel #20
0
    def test_signature_is_updated_if_autograph_available(self, mocked_autograph):
        recipe = RecipeFactory(name="unchanged", approver=UserFactory(), enabler=UserFactory())
        original_signature = recipe.signature
        assert original_signature is not None

        recipe.revise(name="changed")

        assert recipe.latest_revision.name == "changed"
        assert recipe.signature is not original_signature
        expected_sig = fake_sign([recipe.canonical_json()])[0]["signature"]
        assert recipe.signature.signature == expected_sig
Beispiel #21
0
    def test_signature_is_cleared_if_autograph_unavailable(self, mocker):
        # Mock the Autographer to return an error
        mock_autograph = mocker.patch("normandy.recipes.models.Autographer")
        mock_autograph.side_effect = ImproperlyConfigured

        recipe = RecipeFactory(name="unchanged", signed=True)
        original_signature = recipe.signature
        recipe.revise(name="changed")
        assert recipe.name == "changed"
        assert recipe.signature is not original_signature
        assert recipe.signature is None
Beispiel #22
0
    def test_recipe_is_approved(self):
        recipe = RecipeFactory(name="old")
        assert not recipe.is_approved

        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        approval.approve(UserFactory(), "r+")
        assert recipe.is_approved
        assert recipe.approved_revision == recipe.latest_revision

        recipe.revise(name="new")
        assert recipe.is_approved
        assert recipe.approved_revision != recipe.latest_revision
Beispiel #23
0
    def test_signature_is_updated_if_autograph_available(
            self, mocked_autograph):
        recipe = RecipeFactory(name='unchanged')
        original_signature = recipe.signature
        assert original_signature is not None

        recipe.revise(name='changed')

        assert recipe.name == 'changed'
        assert recipe.signature is not original_signature
        expected_sig = hashlib.sha256(recipe.canonical_json()).hexdigest()
        assert recipe.signature.signature == expected_sig
Beispiel #24
0
    def test_recipe_is_approved(self):
        recipe = RecipeFactory(name="old")
        assert not recipe.is_approved

        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        approval.approve(UserFactory(), "r+")
        assert recipe.is_approved
        assert recipe.approved_revision == recipe.latest_revision

        recipe.revise(name="new")
        assert recipe.is_approved
        assert recipe.approved_revision != recipe.latest_revision
Beispiel #25
0
        def test_it_publishes_new_revisions_if_enabled(self, mocked_remotesettings):
            recipe = RecipeFactory(name="Test", approver=UserFactory(), enabler=UserFactory())
            assert mocked_remotesettings.return_value.publish.call_count == 1

            recipe.revise(name="Modified")
            approval_request = recipe.latest_revision.request_approval(creator=UserFactory())
            approval_request.approve(approver=UserFactory(), comment="r+")

            assert mocked_remotesettings.return_value.publish.call_count == 2
            second_call_args, _ = mocked_remotesettings.return_value.publish.call_args_list[1]
            (modified_recipe,) = second_call_args
            assert modified_recipe.latest_revision.name == "Modified"
Beispiel #26
0
        def test_unique_experiment_slug_update_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)
            recipe = RecipeFactory(action=action, arguments=arguments_b)

            with pytest.raises(serializers.ValidationError) as exc_info1:
                recipe.revise(arguments=arguments_a)
            error = action.errors["duplicate_experiment_slug"]
            assert exc_info1.value.detail == {"arguments": {"slug": error}}
Beispiel #27
0
    def test_it_publishes_new_revisions_if_enabled(self, mocked_remotesettings):
        recipe = RecipeFactory(name="Test", approver=UserFactory(), enabler=UserFactory())
        assert mocked_remotesettings.return_value.publish.call_count == 1

        recipe.revise(name="Modified")
        approval_request = recipe.latest_revision.request_approval(creator=UserFactory())
        approval_request.approve(approver=UserFactory(), comment="r+")

        assert mocked_remotesettings.return_value.publish.call_count == 2
        second_call_args, _ = mocked_remotesettings.return_value.publish.call_args_list[1]
        modified_recipe, = second_call_args
        assert modified_recipe.name == "Modified"
Beispiel #28
0
        def test_unique_experiment_slug_update_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)
            recipe = RecipeFactory(action=action, arguments=arguments_b)

            with pytest.raises(serializers.ValidationError) as exc_info1:
                recipe.revise(arguments=arguments_a)
            error = action.errors["duplicate_experiment_slug"]
            assert exc_info1.value.detail == {"arguments": {"slug": error}}
Beispiel #29
0
        def test_unique_experiment_slug_update_collision(self):
            """A recipe can't be updated to have the same slug as another existing recipe"""
            action = ActionFactory(name="multi-preference-experiment")
            arguments_a = MultiPreferenceExperimentArgumentsFactory()
            arguments_b = MultiPreferenceExperimentArgumentsFactory()
            # Does not throw when saving revisions
            RecipeFactory(action=action, arguments=arguments_a)
            recipe = RecipeFactory(action=action, arguments=arguments_b)

            with pytest.raises(serializers.ValidationError) as exc_info1:
                recipe.revise(arguments=arguments_a)
            error = action.errors["duplicate_experiment_slug"]
            assert exc_info1.value.detail == {"arguments": {"slug": error}}
Beispiel #30
0
    def test_preference_experiments_unique_experiment_slug_update_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)
        recipe = RecipeFactory(action=action, arguments=arguments_b)

        with pytest.raises(serializers.ValidationError) as exc_info1:
            recipe.revise(arguments=arguments_a)
        error = action.errors['duplicate_experiment_slug']
        assert exc_info1.value.detail == {'arguments': {'slug': error}}
Beispiel #31
0
    def test_disable(self):
        recipe = RecipeFactory(name="Test", approver=UserFactory(), enabler=UserFactory())
        assert recipe.enabled

        recipe.approved_revision.disable(user=UserFactory())
        assert not recipe.enabled

        with pytest.raises(EnabledState.NotActionable):
            recipe.approved_revision.disable(user=UserFactory())

        recipe.revise(name="New name")

        with pytest.raises(EnabledState.NotActionable):
            recipe.latest_revision.disable(user=UserFactory())
Beispiel #32
0
    def test_disable(self):
        recipe = RecipeFactory(name="Test", approver=UserFactory(), enabler=UserFactory())
        assert recipe.approved_revision.enabled

        recipe.approved_revision.disable(user=UserFactory())
        assert not recipe.approved_revision.enabled

        with pytest.raises(EnabledState.NotActionable):
            recipe.approved_revision.disable(user=UserFactory())

        recipe.revise(name="New name")

        with pytest.raises(EnabledState.NotActionable):
            recipe.latest_revision.disable(user=UserFactory())
Beispiel #33
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"
Beispiel #34
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"
Beispiel #35
0
    def test_it_works(self):
        author = UserFactory()
        reviewer = UserFactory()

        recipe = RecipeFactory(name="first", user=author)
        revision1 = recipe.latest_revision
        approval_request = revision1.request_approval(author)
        approval_request.approve(reviewer, "r+")
        revision1.enable(author)
        state1 = revision1.enabled_state
        assert state1 is not None

        recipe.revise("second", user=author)
        revision2 = recipe.latest_revision
        approval_request = revision2.request_approval(author)
        approval_request.approve(reviewer, "r+")
        state2 = revision2.enabled_state
        assert state2 is not None

        serializer = EnabledStateSerializer(state1)
        assert serializer.data == {
            "id": state1.id,
            "carryover_from": None,
            "created": Whatever.iso8601(),
            "creator": {
                "id": author.id,
                "first_name": author.first_name,
                "last_name": author.last_name,
                "email": author.email,
            },
            "enabled": True,
            "revision_id": revision1.id,
        }

        serializer = EnabledStateSerializer(state2)
        assert serializer.data == {
            "id": state2.id,
            "carryover_from": state1.id,
            "created": Whatever.iso8601(),
            "creator": {
                "id": reviewer.id,
                "first_name": reviewer.first_name,
                "last_name": reviewer.last_name,
                "email": reviewer.email,
            },
            "enabled": True,
            "revision_id": revision2.id,
        }
Beispiel #36
0
    def test_it_works(self):
        author = UserFactory()
        reviewer = UserFactory()

        recipe = RecipeFactory(name="first", user=author)
        revision1 = recipe.latest_revision
        approval_request = revision1.request_approval(author)
        approval_request.approve(reviewer, "r+")
        revision1.enable(author)
        state1 = revision1.enabled_state
        assert state1 is not None

        recipe.revise("second", user=author)
        revision2 = recipe.latest_revision
        approval_request = revision2.request_approval(author)
        approval_request.approve(reviewer, "r+")
        state2 = revision2.enabled_state
        assert state2 is not None

        serializer = EnabledStateSerializer(state1)
        assert serializer.data == {
            "id": state1.id,
            "carryover_from": None,
            "created": Whatever.iso8601(),
            "creator": {
                "id": author.id,
                "first_name": author.first_name,
                "last_name": author.last_name,
                "email": author.email,
            },
            "enabled": True,
            "revision_id": revision1.id,
        }

        serializer = EnabledStateSerializer(state2)
        assert serializer.data == {
            "id": state2.id,
            "carryover_from": state1.id,
            "created": Whatever.iso8601(),
            "creator": {
                "id": reviewer.id,
                "first_name": reviewer.first_name,
                "last_name": reviewer.last_name,
                "email": reviewer.email,
            },
            "enabled": True,
            "revision_id": revision2.id,
        }
Beispiel #37
0
 def test_recipe_revise_partial(self):
     a1 = ActionFactory()
     recipe = RecipeFactory(
         name='unchanged',
         action=a1,
         arguments={'message': 'something'},
         extra_filter_expression='something !== undefined')
     a2 = ActionFactory()
     c = ChannelFactory(slug='beta')
     recipe.revise(name='changed', action=a2, channels=[c])
     assert recipe.action == a2
     assert recipe.name == 'changed'
     assert recipe.arguments == {'message': 'something'}
     assert recipe.filter_expression == (
         "(normandy.channel in ['beta']) && "
         "(something !== undefined)")
Beispiel #38
0
        def test_no_duplicates(self):
            action = ActionFactory(name="preference-rollout")
            arguments_a = {"slug": "a", "preferences": [{"preferenceName": "a", "value": "a"}]}
            arguments_b = {"slug": "b", "preferences": [{"preferenceName": "b", "value": "b"}]}
            RecipeFactory(action=action, arguments=arguments_a)
            recipe_b = RecipeFactory(action=action, arguments=arguments_b)
            expected_error = action.errors["duplicate_rollout_slug"]

            # Creating a new recipe fails
            with pytest.raises(serializers.ValidationError) as exc_info1:
                RecipeFactory(action=action, arguments=arguments_a)
            assert exc_info1.value.detail == {"arguments": {"slug": expected_error}}

            # Revising an existing recipe fails
            with pytest.raises(serializers.ValidationError) as exc_info2:
                recipe_b.revise(arguments=arguments_a)
            assert exc_info2.value.detail == {"arguments": {"slug": expected_error}}
Beispiel #39
0
        def test_no_duplicates(self):
            action = ActionFactory(name="preference-rollout")
            arguments_a = {"slug": "a", "preferences": [{"preferenceName": "a", "value": "a"}]}
            arguments_b = {"slug": "b", "preferences": [{"preferenceName": "b", "value": "b"}]}
            RecipeFactory(action=action, arguments=arguments_a)
            recipe_b = RecipeFactory(action=action, arguments=arguments_b)
            expected_error = action.errors["duplicate_rollout_slug"]

            # Creating a new recipe fails
            with pytest.raises(serializers.ValidationError) as exc_info1:
                RecipeFactory(action=action, arguments=arguments_a)
            assert exc_info1.value.detail == {"arguments": {"slug": expected_error}}

            # Revising an existing recipe fails
            with pytest.raises(serializers.ValidationError) as exc_info2:
                recipe_b.revise(arguments=arguments_a)
            assert exc_info2.value.detail == {"arguments": {"slug": expected_error}}
Beispiel #40
0
    def test_current_revision_property(self):
        """Ensure current revision properties work as expected."""
        recipe = RecipeFactory(name="first")
        assert recipe.name == "first"

        recipe.revise(name="second")
        assert recipe.name == "second"

        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        approval.approve(UserFactory(), "r+")
        assert recipe.name == "second"

        # When `revise` is called on a recipe with at least one approved revision, the new revision
        # is treated as a draft and as such the `name` property of the recipe should return the
        # `name` from the `approved_revision` not the `latest_revision`.
        recipe.revise(name="third")
        assert recipe.latest_revision.name == "third"  # The latest revision ("draft") is updated
        assert recipe.name == "second"  # The current revision is unchanged
Beispiel #41
0
    def test_current_revision_property(self):
        """Ensure current revision properties work as expected."""
        recipe = RecipeFactory(name="first")
        assert recipe.name == "first"

        recipe.revise(name="second")
        assert recipe.name == "second"

        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        approval.approve(UserFactory(), "r+")
        assert recipe.name == "second"

        # When `revise` is called on a recipe with at least one approved revision, the new revision
        # is treated as a draft and as such the `name` property of the recipe should return the
        # `name` from the `approved_revision` not the `latest_revision`.
        recipe.revise(name="third")
        assert recipe.latest_revision.name == "third"  # The latest revision ("draft") is updated
        assert recipe.name == "second"  # The current revision is unchanged
Beispiel #42
0
    def test_enable(self):
        recipe = RecipeFactory(name="Test")
        with pytest.raises(EnabledState.NotActionable):
            recipe.latest_revision.enable(user=UserFactory())

        approval_request = recipe.latest_revision.request_approval(creator=UserFactory())
        approval_request.approve(approver=UserFactory(), comment="r+")

        recipe.revise(name="New name")
        with pytest.raises(EnabledState.NotActionable):
            recipe.latest_revision.enable(user=UserFactory())

        recipe.approved_revision.enable(user=UserFactory())
        assert recipe.enabled

        with pytest.raises(EnabledState.NotActionable):
            recipe.approved_revision.enable(user=UserFactory())

        approval_request = recipe.latest_revision.request_approval(creator=UserFactory())
        approval_request.approve(approver=UserFactory(), comment="r+")
        assert recipe.enabled
Beispiel #43
0
    def test_enable(self):
        recipe = RecipeFactory(name="Test")
        with pytest.raises(EnabledState.NotActionable):
            recipe.latest_revision.enable(user=UserFactory())

        approval_request = recipe.latest_revision.request_approval(creator=UserFactory())
        approval_request.approve(approver=UserFactory(), comment="r+")

        recipe.revise(name="New name")
        with pytest.raises(EnabledState.NotActionable):
            recipe.latest_revision.enable(user=UserFactory())

        recipe.approved_revision.enable(user=UserFactory())
        assert recipe.approved_revision.enabled

        with pytest.raises(EnabledState.NotActionable):
            recipe.approved_revision.enable(user=UserFactory())

        approval_request = recipe.latest_revision.request_approval(creator=UserFactory())
        approval_request.approve(approver=UserFactory(), comment="r+")
        assert recipe.approved_revision.enabled
Beispiel #44
0
    def test_error_during_approval_rolls_back_changes(self, mocker):
        recipe = RecipeFactory(approver=UserFactory(), enabler=UserFactory())
        old_approved_revision = recipe.approved_revision
        recipe.revise(name="New name")
        latest_revision = recipe.latest_revision
        approval_request = recipe.latest_revision.request_approval(UserFactory())

        # Simulate an error during signing
        mocked_update_signature = mocker.patch.object(recipe, "update_signature")
        mocked_update_signature.side_effect = Exception

        with pytest.raises(Exception):
            approval_request.approve(UserFactory(), "r+")

        # Ensure the changes to the approval request and the recipe are rolled back and the recipe
        # is still enabled
        recipe.refresh_from_db()
        approval_request.refresh_from_db()
        assert approval_request.approved is None
        assert recipe.approved_revision == old_approved_revision
        assert recipe.latest_revision == latest_revision
        assert recipe.approved_revision.enabled
Beispiel #45
0
    def test_filter_expression(self):
        channel1 = ChannelFactory(slug='beta', name='Beta')
        channel2 = ChannelFactory(slug='release', name='Release')
        country1 = CountryFactory(code='US', name='USA')
        country2 = CountryFactory(code='CA', name='Canada')
        locale1 = LocaleFactory(code='en-US', name='English (US)')
        locale2 = LocaleFactory(code='fr-CA', name='French (CA)')

        r = RecipeFactory()
        assert r.filter_expression == ''

        r = RecipeFactory(channels=[channel1])
        assert r.filter_expression == "normandy.channel in ['beta']"

        r.revise(channels=[channel1, channel2])
        assert r.filter_expression == "normandy.channel in ['beta', 'release']"

        r = RecipeFactory(countries=[country1])
        assert r.filter_expression == "normandy.country in ['US']"

        r.revise(countries=[country1, country2])
        assert r.filter_expression == "normandy.country in ['CA', 'US']"

        r = RecipeFactory(locales=[locale1])
        assert r.filter_expression == "normandy.locale in ['en-US']"

        r.revise(locales=[locale1, locale2])
        assert r.filter_expression == "normandy.locale in ['en-US', 'fr-CA']"

        r = RecipeFactory(extra_filter_expression='2 + 2 == 4')
        assert r.filter_expression == '2 + 2 == 4'

        r.revise(channels=[channel1], countries=[country1], locales=[locale1])
        assert r.filter_expression == ("(normandy.locale in ['en-US']) && "
                                       "(normandy.country in ['US']) && "
                                       "(normandy.channel in ['beta']) && "
                                       "(2 + 2 == 4)")
Beispiel #46
0
    def test_recipe_revise_locales(self):
        l1 = LocaleFactory(code='en-US')
        recipe = RecipeFactory(locales=[l1])

        l2 = LocaleFactory(code='fr-CA')
        recipe.revise(locales=[l2])
        assert recipe.locales.count() == 1
        assert list(recipe.locales.all()) == [l2]

        recipe.revise(locales=[l1, l2])
        locales = list(recipe.locales.all())
        assert recipe.locales.count() == 2
        assert l1 in locales
        assert l2 in locales

        recipe.revise(locales=[])
        assert recipe.locales.count() == 0
Beispiel #47
0
    def test_recipe_revise_channels(self):
        c1 = ChannelFactory(slug='beta')
        recipe = RecipeFactory(channels=[c1])

        c2 = ChannelFactory(slug='release')
        recipe.revise(channels=[c2])
        assert recipe.channels.count() == 1
        assert list(recipe.channels.all()) == [c2]

        recipe.revise(channels=[c1, c2])
        channels = list(recipe.channels.all())
        assert recipe.channels.count() == 2
        assert c1 in channels
        assert c2 in channels

        recipe.revise(channels=[])
        assert recipe.channels.count() == 0
Beispiel #48
0
    def test_recipe_revise_countries(self):
        c1 = CountryFactory(code='CA')
        recipe = RecipeFactory(countries=[c1])

        c2 = CountryFactory(code='US')
        recipe.revise(countries=[c2])
        assert recipe.countries.count() == 1
        assert list(recipe.countries.all()) == [c2]

        recipe.revise(countries=[c1, c2])
        countries = list(recipe.countries.all())
        assert recipe.countries.count() == 2
        assert c1 in countries
        assert c2 in countries

        recipe.revise(countries=[])
        assert recipe.countries.count() == 0
Beispiel #49
0
 def test_recipe_revise_arguments(self):
     recipe = RecipeFactory(arguments_json="{}")
     recipe.revise(arguments={"something": "value"})
     assert recipe.arguments_json == '{"something": "value"}'
Beispiel #50
0
 def test_recipe_force_revise(self):
     recipe = RecipeFactory(name="my name")
     revision_id = recipe.revision_id
     recipe.revise(name="my name", force=True)
     assert revision_id != recipe.revision_id
Beispiel #51
0
 def test_update_logging(self, mock_logger):
     recipe = RecipeFactory(name="my name")
     recipe.revise(name="my name", force=True)
     mock_logger.info.assert_called_with(
         Whatever.contains(str(recipe.id)), extra={"code": INFO_CREATE_REVISION}
     )
Beispiel #52
0
 def test_revision_id_changes(self):
     """Ensure that the revision id is incremented on each save"""
     recipe = RecipeFactory()
     revision_id = recipe.revision_id
     recipe.revise(action=ActionFactory())
     assert recipe.revision_id != revision_id
Beispiel #53
0
 def test_revise_arguments(self):
     recipe = RecipeFactory(arguments_json="[]")
     recipe.revise(arguments=[{"id": 1}])
     assert recipe.arguments_json == '[{"id": 1}]'
Beispiel #54
0
 def test_cant_change_signature_and_other_fields(self):
     recipe = RecipeFactory(name="unchanged", signed=False)
     recipe.signature = SignatureFactory()
     with pytest.raises(ValidationError) as exc_info:
         recipe.revise(name="changed")
     assert exc_info.value.message == "Signatures must change alone"