From af68c9c6c81d2a27693429bbed36606bc4f0a979 Mon Sep 17 00:00:00 2001 From: chdorner Date: Tue, 4 Apr 2017 14:36:26 +0200 Subject: [PATCH] Add unhide annotation endpoint This adds the `DELETE /api/annotations//hide` endpoint, which removes the moderation of the given annotation, effectively showing the annotation to all group readers again. --- h/services/annotation_moderation.py | 14 ++++ h/views/api_moderation.py | 18 ++++ .../h/services/annotation_moderation_test.py | 24 ++++++ tests/h/views/api_moderation_test.py | 83 +++++++++++++------ 4 files changed, 115 insertions(+), 24 deletions(-) diff --git a/h/services/annotation_moderation.py b/h/services/annotation_moderation.py index 3abad080163..6cc4ead39d9 100644 --- a/h/services/annotation_moderation.py +++ b/h/services/annotation_moderation.py @@ -43,6 +43,20 @@ def hide(self, annotation): mod = models.AnnotationModeration(annotation=annotation) self.session.add(mod) + def unhide(self, annotation): + """ + Show a hidden annotation again to other users. + + In case the given annotation is not moderated, this method is a no-op. + + :param annotation: The annotation to unhide. + :type annotation: h.models.Annotation + """ + + self.session.query(models.AnnotationModeration) \ + .filter_by(annotation=annotation) \ + .delete() + def annotation_moderation_service_factory(context, request): return AnnotationModerationService(request.db) diff --git a/h/views/api_moderation.py b/h/views/api_moderation.py index 11bae16e5c5..389072b17e7 100644 --- a/h/views/api_moderation.py +++ b/h/views/api_moderation.py @@ -25,3 +25,21 @@ def create(context, request): request.notify_after_commit(event) return HTTPNoContent() + + +@api_config(route_name='api.annotation_hide', + request_method='DELETE', + link_name='annotation.unhide', + description='Unhide an annotation as a group moderator.', + effective_principals=security.Authenticated) +def delete(context, request): + if not request.has_permission('admin', context.group): + raise HTTPNotFound() + + svc = request.find_service(name='annotation_moderation') + svc.unhide(context.annotation) + + event = events.AnnotationEvent(request, context.annotation.id, 'update') + request.notify_after_commit(event) + + return HTTPNoContent() diff --git a/tests/h/services/annotation_moderation_test.py b/tests/h/services/annotation_moderation_test.py index bd4b342ddfb..5af9243ead4 100644 --- a/tests/h/services/annotation_moderation_test.py +++ b/tests/h/services/annotation_moderation_test.py @@ -44,6 +44,30 @@ def test_it_skips_creating_moderation_when_already_exists(self, svc, factories, assert count == 1 +class TestAnnotationModerationServiceUnhide(object): + def test_it_unhides_given_annotation(self, svc, factories, db_session): + mod = factories.AnnotationModeration() + annotation = mod.annotation + + svc.unhide(annotation) + + assert svc.hidden(annotation.id) is False + + def test_it_leaves_othes_annotations_hidden(self, svc, factories, db_session): + mod1, mod2 = factories.AnnotationModeration(), factories.AnnotationModeration() + + svc.unhide(mod1.annotation) + + assert svc.hidden(mod2.annotation.id) is True + + def test_it_skips_hiding_annotation_when_not_hidden(self, svc, factories, db_session): + annotation = factories.Annotation() + + svc.unhide(annotation) + + assert svc.hidden(annotation.id) is False + + class TestAnnotationNipsaServiceFactory(object): def test_it_returns_service(self, pyramid_request): svc = annotation_moderation_service_factory(None, pyramid_request) diff --git a/tests/h/views/api_moderation_test.py b/tests/h/views/api_moderation_test.py index 9b65050860e..9b339087ca8 100644 --- a/tests/h/views/api_moderation_test.py +++ b/tests/h/views/api_moderation_test.py @@ -39,27 +39,62 @@ def test_it_responds_with_not_found_when_no_admin_access_in_group(self, pyramid_ with pytest.raises(HTTPNotFound): views.create(resource, pyramid_request) - @pytest.fixture - def resource(self): - return mock.Mock(spec_set=['annotation', 'group']) - - @pytest.fixture - def moderation_service(self, pyramid_config): - svc = mock.Mock(spec_set=['hide']) - pyramid_config.register_service(svc, name='annotation_moderation') - return svc - - @pytest.fixture - def has_permission(self, pyramid_request): - func = mock.Mock(return_value=True) - pyramid_request.has_permission = func - return func - - @pytest.fixture - def events(self, patch): - return patch('h.views.api_moderation.events') - - @pytest.fixture - def pyramid_request(self, pyramid_request): - pyramid_request.notify_after_commit = mock.Mock() - return pyramid_request + +@pytest.mark.usefixtures('moderation_service', 'has_permission') +class TestDelete(object): + def test_it_unhides_the_annotation(self, pyramid_request, resource, moderation_service): + views.delete(resource, pyramid_request) + + moderation_service.unhide.assert_called_once_with(resource.annotation) + + def test_it_publishes_update_event(self, pyramid_request, resource, events): + views.delete(resource, pyramid_request) + + events.AnnotationEvent.assert_called_once_with( + pyramid_request, resource.annotation.id, 'update') + + pyramid_request.notify_after_commit.assert_called_once_with( + events.AnnotationEvent.return_value) + + def test_it_renders_no_content(self, pyramid_request, resource): + response = views.delete(resource, pyramid_request) + assert isinstance(response, HTTPNoContent) + + def test_it_checks_for_group_admin_permission(self, pyramid_request, resource): + views.delete(resource, pyramid_request) + pyramid_request.has_permission.assert_called_once_with('admin', resource.group) + + def test_it_responds_with_not_found_when_no_admin_access_in_group(self, pyramid_request, resource): + pyramid_request.has_permission.return_value = False + with pytest.raises(HTTPNotFound): + views.delete(resource, pyramid_request) + + +@pytest.fixture +def resource(): + return mock.Mock(spec_set=['annotation', 'group']) + + +@pytest.fixture +def moderation_service(pyramid_config): + svc = mock.Mock(spec_set=['hide', 'unhide']) + pyramid_config.register_service(svc, name='annotation_moderation') + return svc + + +@pytest.fixture +def has_permission(pyramid_request): + func = mock.Mock(return_value=True) + pyramid_request.has_permission = func + return func + + +@pytest.fixture +def events(patch): + return patch('h.views.api_moderation.events') + + +@pytest.fixture +def pyramid_request(pyramid_request): + pyramid_request.notify_after_commit = mock.Mock() + return pyramid_request