def test_cannot_approve_already_approved(self): u = UserFactory() req = ApprovalRequestFactory() req.approve(u, "r+") with pytest.raises(req.NotActionable): req.approve(u, "r+")
def test_cannot_reject_already_rejected(self): u = UserFactory() req = ApprovalRequestFactory() req.reject(u, "r-") with pytest.raises(req.NotActionable): req.reject(u, "r-")
def test_it_can_edit_approval_requests(self, api_client): approval_request = ApprovalRequestFactory(active=True) res = api_client.patch('/api/v1/approval_request/%s/' % approval_request.id, {'active': False}) assert res.status_code == 200 approval_request.refresh_from_db() assert not approval_request.active
def test_it_can_enable_recipes(self, api_client): recipe = RecipeFactory(enabled=False) approval_request = ApprovalRequestFactory(recipe=recipe) approval_request.approve(UserFactory()) res = api_client.post('/api/v1/recipe/%s/enable/' % recipe.id) assert res.status_code == 204 recipe = Recipe.objects.all()[0] assert recipe.enabled
def test_reject(self): u = UserFactory() req = ApprovalRequestFactory() req.reject(u, 'r-') assert not req.approved assert req.approver == u assert req.comment == 'r-' recipe = req.revision.recipe assert not recipe.is_approved
def test_reject_not_actionable(self, api_client): r = RecipeFactory() a = ApprovalRequestFactory(revision=r.latest_revision) a.approve(UserFactory(), 'r+') res = api_client.post( '/api/v1/approval_request/{}/reject/'.format(a.id), {'comment': '-r'}) assert res.status_code == 400 assert res.data[ 'error'] == 'This approval request has already been approved or rejected.'
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
def test_reject(self, mocker): u = UserFactory() req = ApprovalRequestFactory() mocker.patch.object(req, "verify_approver") req.reject(u, "r-") assert not req.approved assert req.approver == u assert req.comment == "r-" req.verify_approver.assert_called_with(u) recipe = req.revision.recipe assert not recipe.is_approved
def test_verify_approver_enforced(self, settings, mocker): settings.PEER_APPROVAL_ENFORCED = True creator = UserFactory() user = UserFactory() req = ApprovalRequestFactory(creator=creator) # Do not raise when creator and approver are different req.verify_approver(user) # Raise when creator and approver are the same with pytest.raises(req.CannotActOnOwnRequest): req.verify_approver(creator)
def test_approve(self, mocker): u = UserFactory() req = ApprovalRequestFactory() mocker.patch.object(req, 'verify_approver') req.approve(u, 'r+') assert req.approved assert req.approver == u assert req.comment == 'r+' req.verify_approver.assert_called_with(u) recipe = req.revision.recipe assert recipe.is_approved
def test_cannot_open_second_approval_request(self, api_client): recipe = RecipeFactory() ApprovalRequestFactory(revision=recipe.latest_revision) res = api_client.post( '/api/v1/recipe_revision/{}/request_approval/'.format( recipe.latest_revision.id)) assert res.status_code == 400
def test_it_works(self, rf): recipe = RecipeFactory(arguments={"foo": "bar"}) approval = ApprovalRequestFactory(revision=recipe.latest_revision) action = recipe.action serializer = RecipeSerializer(recipe, context={"request": rf.get("/")}) assert serializer.data == { "name": recipe.name, "id": recipe.id, "last_updated": Whatever(), "enabled": recipe.enabled, "extra_filter_expression": recipe.extra_filter_expression, "filter_expression": recipe.filter_expression, "revision_id": recipe.revision_id, "action": action.name, "arguments": { "foo": "bar" }, "is_approved": False, "latest_revision_id": recipe.latest_revision.id, "approved_revision_id": None, "approval_request": { "id": approval.id, "created": Whatever(), "creator": Whatever(), "approved": None, "approver": None, "comment": None, }, "identicon_seed": Whatever.startswith("v1:"), "capabilities": sorted(recipe.capabilities), }
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)
def test_reject_no_comment(self, api_client): r = RecipeFactory() a = ApprovalRequestFactory(revision=r.latest_revision) res = api_client.post('/api/v1/approval_request/{}/reject/'.format( a.id)) assert res.status_code == 400 assert res.data['comment'] == 'This field is required.'
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
def test_close(self, api_client): r = RecipeFactory() a = ApprovalRequestFactory(revision=r.latest_revision) res = api_client.post('/api/v1/approval_request/{}/close/'.format( a.id)) assert res.status_code == 204 with pytest.raises(ApprovalRequest.DoesNotExist): ApprovalRequest.objects.get(pk=a.pk)
def test_approval_status(self): recipe = RecipeFactory() revision = recipe.latest_revision assert revision.approval_status is None approval = ApprovalRequestFactory(revision=revision) revision = RecipeRevision.objects.get(pk=revision.pk) assert revision.approval_status == revision.PENDING approval.approve(UserFactory(), "r+") revision = RecipeRevision.objects.get(pk=revision.pk) assert revision.approval_status == revision.APPROVED approval.delete() approval = ApprovalRequestFactory(revision=revision) approval.reject(UserFactory(), "r-") revision = RecipeRevision.objects.get(pk=revision.pk) assert revision.approval_status == revision.REJECTED
def test_approve(self, api_client): r = RecipeFactory() a = ApprovalRequestFactory(revision=r.latest_revision) res = api_client.post( '/api/v1/approval_request/{}/approve/'.format(a.id), {'comment': 'r+'}) assert res.status_code == 200 r.refresh_from_db() assert r.is_approved assert r.approved_revision.approval_request.comment == 'r+'
def test_reject(self, api_client): r = RecipeFactory() a = ApprovalRequestFactory(revision=r.latest_revision) res = api_client.post( '/api/v1/approval_request/{}/reject/'.format(a.id), {'comment': 'r-'}) assert res.status_code == 200 r.latest_revision.approval_request.refresh_from_db() assert r.latest_revision.approval_status == r.latest_revision.REJECTED assert r.latest_revision.approval_request.comment == 'r-'
def test_approval_request_property(self): # Make sure it works when there is no approval request recipe = RecipeFactory(name="old") assert recipe.approval_request is None # Make sure it returns an approval request if it exists approval = ApprovalRequestFactory(revision=recipe.latest_revision) assert recipe.approval_request == approval # Check the edge case where there is no latest_revision recipe.latest_revision.delete() recipe.refresh_from_db() assert recipe.approval_request is None
def test_verify_approver_unenforced(self, settings, mocker): logger = mocker.patch("normandy.recipes.models.logger") settings.PEER_APPROVAL_ENFORCED = False creator = UserFactory() user = UserFactory() req = ApprovalRequestFactory(creator=creator) # Do not raise when creator and approver are different req.verify_approver(user) # Do not raise when creator and approver are the same since enforcement # is disabled. req.verify_approver(creator) logger.warning.assert_called_with( Whatever(), extra={ "code": WARNING_BYPASSING_PEER_APPROVAL, "approval_id": req.id, "approver": creator, }, )
def test_resolve_approval_request_by_id(self, gql_client): a = ApprovalRequestFactory() res = gql_client.execute(GQ().query.approvalRequest(id=a.id).fields( GQ().revision.fields("id"))) assert res == { "data": { "approvalRequest": { "revision": { "id": str(a.revision.id) } } } }
def test_it_works(self, rf): channel = ChannelFactory() country = CountryFactory() locale = LocaleFactory() recipe = RecipeFactory(arguments={'foo': 'bar'}, channels=[channel], countries=[country], locales=[locale]) approval = ApprovalRequestFactory(revision=recipe.latest_revision) action = recipe.action serializer = RecipeSerializer(recipe, context={'request': rf.get('/')}) assert serializer.data == { 'name': recipe.name, 'id': recipe.id, 'last_updated': Whatever(), 'enabled': recipe.enabled, 'extra_filter_expression': recipe.extra_filter_expression, 'filter_expression': recipe.filter_expression, 'action': { 'arguments_schema': {}, 'id': action.id, 'implementation_url': Whatever(), 'name': action.name, }, 'arguments': { 'foo': 'bar', }, 'channels': [channel.slug], 'countries': [country.code], 'locales': [locale.code], 'is_approved': False, 'latest_revision': RecipeRevisionSerializer(recipe.latest_revision).data, 'approved_revision': None, 'approval_request': { 'id': approval.id, 'created': Whatever(), 'creator': Whatever(), 'approved': None, 'approver': None, 'comment': None, }, }
def test_it_works(self, rf): recipe = RecipeFactory(arguments={"foo": "bar"}, bug_number=1436113) ApprovalRequestFactory(revision=recipe.latest_revision) request = rf.get("/") serializer = RecipeSerializer(recipe, context={"request": rf.get("/")}) assert serializer.data == { "id": recipe.id, "latest_revision": RecipeRevisionSerializer(recipe.latest_revision, context={ "request": request }).data, "approved_revision": None, "signature": None, }
def test_it_works(self, rf): recipe = RecipeFactory(arguments={"foo": "bar"}, experimenter_slug="some-experimenter-slug") ApprovalRequestFactory(revision=recipe.latest_revision) request = rf.get("/") serializer = RecipeSerializer(recipe, context={"request": rf.get("/")}) assert serializer.data == { "id": recipe.id, "latest_revision": RecipeRevisionSerializer(recipe.latest_revision, context={ "request": request }).data, "approved_revision": None, "signature": None, "uses_only_baseline_capabilities": False, }
def test_it_works(self, rf): recipe = RecipeFactory(arguments={"foo": "bar"}, filter_object_json=None) approval = ApprovalRequestFactory(revision=recipe.latest_revision) action = recipe.action serializer = RecipeSerializer(recipe, context={"request": rf.get("/")}) assert serializer.data == { "name": recipe.name, "id": recipe.id, "last_updated": Whatever(), "enabled": recipe.enabled, "extra_filter_expression": recipe.extra_filter_expression, "filter_expression": recipe.filter_expression, "filter_object": [], "action": { "arguments_schema": {}, "id": action.id, "implementation_url": Whatever(), "name": action.name, }, "arguments": { "foo": "bar" }, "is_approved": False, "latest_revision": RecipeRevisionSerializer(recipe.latest_revision).data, "approved_revision": None, "approval_request": { "id": approval.id, "created": Whatever(), "creator": Whatever(), "approved": None, "approver": None, "comment": None, }, "identicon_seed": Whatever.startswith("v1:"), }
def test_verify_approver_unenforced(self, settings, mocker): logger = mocker.patch('normandy.recipes.models.logger') settings.PEER_APPROVAL_ENFORCED = False creator = UserFactory() user = UserFactory() req = ApprovalRequestFactory(creator=creator) # Do not raise when creator and approver are different req.verify_approver(user) # Do not raise when creator and approver are the same since enforcement # is disabled. req.verify_approver(creator) logger.warning.assert_called_with(Whatever(), extra={ 'code': WARNING_BYPASSING_PEER_APPROVAL, 'approval_id': req.id, 'approver': creator, })
def test_resolve_all_approval_requests(self, gql_client): a = ApprovalRequestFactory() res = gql_client.execute(GQ().query.allApprovalRequests.fields("id")) assert res == {"data": {"allApprovalRequests": [{"id": str(a.id)}]}}
def test_can_save_open_request(self): request = ApprovalRequestFactory(active=True) # Should be able to call save without an integrity error request.save()