def test_link(self, group_service, links_service): ann = mock.Mock() res = AnnotationContext(ann, group_service, links_service) result = res.link('json') links_service.get.assert_called_once_with(ann, 'json') assert result == links_service.get.return_value
def test_acl_private(self, factories, group_service, links_service): ann = factories.Annotation(shared=False, userid='saoirse') res = AnnotationContext(ann, group_service, links_service) actual = res.__acl__() expect = [(security.Allow, 'saoirse', 'read'), (security.Allow, 'saoirse', 'admin'), (security.Allow, 'saoirse', 'update'), (security.Allow, 'saoirse', 'delete'), security.DENY_ALL] assert actual == expect
def test_rewrites_rangeselectors_same_element(self, group_service, fake_links_service): """ A RangeSelector that starts and ends in the same element should be rewritten to an XPathSelector refinedBy a TextPositionSelector, for the sake of simplicity. """ annotation = mock.Mock(target_uri='http://example.com') annotation.target_selectors = [{ 'type': 'RangeSelector', 'startContainer': '/div[1]/main[1]/article[1]/div[2]/p[339]', 'startOffset': 12, 'endContainer': '/div[1]/main[1]/article[1]/div[2]/p[339]', 'endOffset': 43, }] resource = AnnotationContext(annotation, group_service, fake_links_service) selectors = AnnotationJSONLDPresenter(resource).target[0]['selector'] assert selectors == [{ 'type': 'XPathSelector', 'value': '/div[1]/main[1]/article[1]/div[2]/p[339]', 'refinedBy': { 'type': 'TextPositionSelector', 'start': 12, 'end': 43, } }]
def test_id_returns_jsonld_id_link(self, group_service, fake_links_service): annotation = mock.Mock(id='foobar') resource = AnnotationContext(annotation, group_service, fake_links_service) presenter = AnnotationJSONLDPresenter(resource) assert presenter.id == 'http://fake-link/jsonld_id'
def test_asdict_extra_cannot_override_other_data(self, document_asdict, group_service, fake_links_service): ann = mock.Mock(id='the-real-id', extra={'id': 'the-extra-id'}) resource = AnnotationContext(ann, group_service, fake_links_service) document_asdict.return_value = {} presented = AnnotationJSONPresenter(resource).asdict() assert presented['id'] == 'the-real-id'
def test_id_passes_annotation_to_link_service(self, group_service, fake_links_service): annotation = mock.Mock(id='foobar') resource = AnnotationContext(annotation, group_service, fake_links_service) presenter = AnnotationJSONLDPresenter(resource) presenter.id assert fake_links_service.last_annotation == annotation
def test_formatter_uses_annotation_resource(self, group_service, fake_links_service): annotation = mock.Mock(id='the-id', extra={}) resource = AnnotationContext(annotation, group_service, fake_links_service) formatters = [IDDuplicatingFormatter()] presenter = AnnotationJSONPresenter(resource, formatters) output = presenter.asdict() assert output['duplicated-id'] == 'the-id'
def test_acl_deleted(self, factories, group_service, links_service): """ Nobody -- not even the owner -- should have any permissions on a deleted annotation. """ policy = ACLAuthorizationPolicy() ann = factories.Annotation(userid='saoirse', deleted=True) res = AnnotationContext(ann, group_service, links_service) for perm in ['read', 'admin', 'update', 'delete']: assert not policy.permits(res, ['saiorse'], perm)
def test_asdict_extra_uses_copy_of_extra(self, document_asdict, group_service, fake_links_service): extra = {'foo': 'bar'} ann = mock.Mock(id='my-id', extra=extra) resource = AnnotationContext(ann, group_service, fake_links_service) document_asdict.return_value = {} AnnotationJSONPresenter(resource).asdict() # Presenting the annotation shouldn't change the "extra" dict. assert extra == {'foo': 'bar'}
def test_asdict_merges_formatters(self, group_service, fake_links_service): ann = mock.Mock(id='the-real-id', extra={}) resource = AnnotationContext(ann, group_service, fake_links_service) formatters = [ FakeFormatter({'flagged': 'nope'}), FakeFormatter({'nipsa': 'maybe'}) ] presenter = AnnotationJSONPresenter(resource, formatters) presented = presenter.asdict() assert presented['flagged'] == 'nope' assert presented['nipsa'] == 'maybe'
def test_og_document(factories, pyramid_request, group_service, links_service, sidebar_app): annotation = factories.Annotation(userid='acct:[email protected]') context = AnnotationContext(annotation, group_service, links_service) sidebar_app.side_effect = _fake_sidebar_app ctx = main.annotation_page(context, pyramid_request) def test(d): return '*****@*****.**' in d[ 'content'] and annotation.document.title in d['content'] assert any(test(d) for d in ctx['meta_attrs'])
def test_bodies_returns_textual_body(self, group_service, fake_links_service): annotation = mock.Mock(text='Flib flob flab', tags=None) resource = AnnotationContext(annotation, group_service, fake_links_service) bodies = AnnotationJSONLDPresenter(resource).bodies assert bodies == [{ 'type': 'TextualBody', 'value': 'Flib flob flab', 'format': 'text/markdown', }]
def test_acl_shared_admin_perms(self, factories, group_service, links_service): """ Shared annotation resources should still only give admin/update/delete permissions to the owner. """ policy = ACLAuthorizationPolicy() ann = factories.Annotation(shared=False, userid='saoirse') res = AnnotationContext(ann, group_service, links_service) for perm in ['admin', 'update', 'delete']: assert policy.permits(res, ['saoirse'], perm) assert not policy.permits(res, ['someoneelse'], perm)
def test_og_no_document(pyramid_request, group_service, links_service, sidebar_app): annotation = Annotation(id='123', userid='foo', target_uri='http://example.com') context = AnnotationContext(annotation, group_service, links_service) sidebar_app.side_effect = _fake_sidebar_app ctx = main.annotation_page(context, pyramid_request) def test(d): return 'foo' in d['content'] assert any(test(d) for d in ctx['meta_attrs'])
def test_asdict(self, group_service, fake_links_service): annotation = mock.Mock( id='foobar', created=datetime.datetime(2016, 2, 24, 18, 3, 25, 768), updated=datetime.datetime(2016, 2, 29, 10, 24, 5, 564), userid='acct:luke', target_uri='http://example.com', text='It is magical!', tags=['magic'], target_selectors=[{ 'type': 'TestSelector', 'test': 'foobar' }]) expected = { '@context': 'http://www.w3.org/ns/anno.jsonld', 'type': 'Annotation', 'id': 'http://fake-link/jsonld_id', 'created': '2016-02-24T18:03:25.000768+00:00', 'modified': '2016-02-29T10:24:05.000564+00:00', 'creator': 'acct:luke', 'body': [{ 'type': 'TextualBody', 'format': 'text/markdown', 'value': 'It is magical!' }, { 'type': 'TextualBody', 'purpose': 'tagging', 'value': 'magic' }], 'target': [{ 'source': 'http://example.com', 'selector': [{ 'type': 'TestSelector', 'test': 'foobar' }] }] } resource = AnnotationContext(annotation, group_service, fake_links_service) result = AnnotationJSONLDPresenter(resource).asdict() assert result == expected
def _generate_annotation_event(message, socket, annotation, user_nipsad, group_service): """ Get message about annotation event `message` to be sent to `socket`. Inspects the embedded annotation event and decides whether or not the passed socket should receive notification of the event. Returns None if the socket should not receive any message about this annotation event, otherwise a dict containing information about the event. """ action = message['action'] if action == 'read': return None if message['src_client_id'] == socket.client_id: return None # We don't send anything until we have received a filter from the client if socket.filter is None: return None # Don't sent annotations from NIPSA'd users to anyone other than that # user. if user_nipsad and socket.authenticated_userid != annotation.userid: return None notification = { 'type': 'annotation-notification', 'options': {'action': action}, } base_url = socket.registry.settings.get('h.app_url', 'http://localhost:5000') links_service = LinksService(base_url, socket.registry) resource = AnnotationContext(annotation, group_service, links_service) serialized = presenters.AnnotationJSONPresenter(resource).asdict() permissions = serialized.get('permissions') if not _authorized_to_read(socket.effective_principals, permissions): return None if not socket.filter.match(serialized, action): return None notification['payload'] = [serialized] if action == 'delete': notification['payload'] = [{'id': annotation.id}] return notification
def test_immutable_formatters(self, group_service, fake_links_service): """Double-check we can't mutate the formatters list after the fact. This is an extra check just to make sure we can't accidentally change the constructor so that it simply aliases the list that's passed in, leaving us open to all kinds of mutability horrors. """ ann = mock.Mock(id='the-real-id', extra={}) resource = AnnotationContext(ann, group_service, fake_links_service) formatters = [FakeFormatter({'flagged': 'nope'})] presenter = AnnotationJSONPresenter(resource, formatters) formatters.append(FakeFormatter({'enterprise': 'synergy'})) presented = presenter.asdict() assert 'enterprise' not in presented
def test_bodies_appends_tag_bodies(self, group_service, fake_links_service): annotation = mock.Mock(text='Flib flob flab', tags=['giraffe', 'lion']) resource = AnnotationContext(annotation, group_service, fake_links_service) bodies = AnnotationJSONLDPresenter(resource).bodies assert { 'type': 'TextualBody', 'value': 'giraffe', 'purpose': 'tagging', } in bodies assert { 'type': 'TextualBody', 'value': 'lion', 'purpose': 'tagging', } in bodies
def test_permissions(self, annotation, group_readable, action, expected, group_service, fake_links_service): annotation.deleted = False group_principals = { 'members': (security.Allow, 'group:{}'.format(annotation.groupid), 'read'), 'world': (security.Allow, security.Everyone, 'read'), None: security.DENY_ALL, } group = mock.Mock(spec_set=['__acl__']) group.__acl__.return_value = [group_principals[group_readable]] group_service.find.return_value = group resource = AnnotationContext(annotation, group_service, fake_links_service) presenter = AnnotationJSONPresenter(resource) assert expected == presenter.permissions[action]
def test_ignores_malformed_rangeselectors(self, group_service, fake_links_service): annotation = mock.Mock(target_uri='http://example.com') annotation.target_selectors = [{ 'type': 'RangeSelector', 'startContainer': '/div[1]/main[1]/article[1]/div[2]/h1[1]', 'startOffset': 4, 'endContainer': '/div[1]/main[1]/article[1]/div[2]/p[339]', }] resource = AnnotationContext(annotation, group_service, fake_links_service) target = AnnotationJSONLDPresenter(resource).target[0] assert 'selector' not in target
def test_rewrites_rangeselectors_different_element(self, group_service, fake_links_service): """ A RangeSelector that starts and ends in the different elements should be rewritten to a RangeSelector bounded by two XPathSelectors, each of which is refinedBy a "point"-like TextPositionSelector. """ annotation = mock.Mock(target_uri='http://example.com') annotation.target_selectors = [{ 'type': 'RangeSelector', 'startContainer': '/div[1]/main[1]/article[1]/div[2]/h1[1]', 'startOffset': 4, 'endContainer': '/div[1]/main[1]/article[1]/div[2]/p[339]', 'endOffset': 72, }] resource = AnnotationContext(annotation, group_service, fake_links_service) selectors = AnnotationJSONLDPresenter(resource).target[0]['selector'] assert selectors == [{ 'type': 'RangeSelector', 'startSelector': { 'type': 'XPathSelector', 'value': '/div[1]/main[1]/article[1]/div[2]/h1[1]', 'refinedBy': { 'type': 'TextPositionSelector', 'start': 4, 'end': 4, } }, 'endSelector': { 'type': 'XPathSelector', 'value': '/div[1]/main[1]/article[1]/div[2]/p[339]', 'refinedBy': { 'type': 'TextPositionSelector', 'start': 72, 'end': 72, } }, }]
def test_ignores_selectors_lacking_types(self, group_service, fake_links_service): annotation = mock.Mock(target_uri='http://example.com') annotation.target_selectors = [ { 'type': 'TestSelector', 'test': 'foobar' }, { 'something': 'else' }, ] resource = AnnotationContext(annotation, group_service, fake_links_service) selectors = AnnotationJSONLDPresenter(resource).target[0]['selector'] assert selectors == [{ 'type': 'TestSelector', 'test': 'foobar', }]
def test_acl_shared(self, factories, pyramid_config, pyramid_request, groupid, userid, permitted, group_service, links_service): """ Shared annotation resources should delegate their 'read' permission to their containing group. """ # Set up the test with a dummy authn policy and a real ACL authz # policy: policy = ACLAuthorizationPolicy() pyramid_config.testing_securitypolicy(userid) pyramid_config.set_authorization_policy(policy) ann = factories.Annotation(shared=True, userid='mioara', groupid=groupid) res = AnnotationContext(ann, group_service, links_service) if permitted: assert pyramid_request.has_permission('read', res) else: assert not pyramid_request.has_permission('read', res)
def test_asdict(self, document_asdict, group_service, fake_links_service): ann = mock.Mock(id='the-id', created=datetime.datetime(2016, 2, 24, 18, 3, 25, 768), updated=datetime.datetime(2016, 2, 29, 10, 24, 5, 564), userid='acct:luke', target_uri='http://example.com', text='It is magical!', tags=['magic'], groupid='__world__', shared=True, target_selectors=[{ 'TestSelector': 'foobar' }], references=['referenced-id-1', 'referenced-id-2'], extra={ 'extra-1': 'foo', 'extra-2': 'bar' }) resource = AnnotationContext(ann, group_service, fake_links_service) document_asdict.return_value = {'foo': 'bar'} expected = { 'id': 'the-id', 'created': '2016-02-24T18:03:25.000768+00:00', 'updated': '2016-02-29T10:24:05.000564+00:00', 'user': '******', 'uri': 'http://example.com', 'text': 'It is magical!', 'tags': ['magic'], 'group': '__world__', 'permissions': { 'read': ['group:__world__'], 'admin': ['acct:luke'], 'update': ['acct:luke'], 'delete': ['acct:luke'] }, 'target': [{ 'source': 'http://example.com', 'selector': [{ 'TestSelector': 'foobar' }] }], 'document': { 'foo': 'bar' }, 'links': { 'giraffe': 'http://giraffe.com', 'toad': 'http://toad.net' }, 'references': ['referenced-id-1', 'referenced-id-2'], 'extra-1': 'foo', 'extra-2': 'bar' } result = AnnotationJSONPresenter(resource).asdict() assert result == expected
def _annotation_resource(request, annotation): group_service = request.find_service(IGroupService) links_service = request.find_service(name='links') return AnnotationContext(annotation, group_service, links_service)