def test_search_back_compat_by_context_id_ok(wa_list): for wa in wa_list: x = CRUD.create_anno(wa) wa = deepcopy(wa_list[0]) search_context_id = 'not_the_normal_context_id' wa['id'] = '12345678901234567890' wa['platform']['context_id'] = search_context_id x = CRUD.create_anno(wa) tudo = Anno._default_manager.all() for z in tudo: print('id({}), platform({}), context_id({}), collection_id({})'.format( z.anno_id, z.raw['platform'].get('platform_name', 'na'), z.raw['platform'].get('context_id', 'na'), z.raw['platform'].get('collection_id', 'na'))) c = Consumer._default_manager.create() payload = make_jwt_payload(apikey=c.consumer) token = make_encoded_token(c.secret_key, payload) client = Client() # check if middleware works query_string = 'contextId={}&context_id={}&platform={}'.format( search_context_id, 'contextId_that_should_be_ignored', wa['platform']['platform_name']) url = '{}?{}'.format(reverse('compat_search'), query_string) response = client.get(url, HTTP_X_ANNOTATOR_AUTH_TOKEN=token) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 1 assert resp['rows'][0]['media'] == 'text' assert str(resp['rows'][0]['id']) == wa['id']
def test_search_by_context_id_ok(wa_text, wa_video, wa_image, wa_audio): for wa in [wa_text, wa_video, wa_image, wa_audio]: x = CRUD.create_anno(wa) wa = deepcopy(wa_audio) search_context_id = 'not_the_normal_context_id' wa['id'] = '12345678' wa['platform']['context_id'] = search_context_id x = CRUD.create_anno(wa) payload = make_jwt_payload() request = make_json_request( method='get', query_string='context_id={}&platform={}'.format( search_context_id, wa['platform']['platform_name'])) request.catchjwt = payload tudo = Anno._default_manager.all() for z in tudo: print('id({}), platform({}), context_id({}), collection_id({})'.format( z.anno_id, z.raw['platform'].get('platform_name', 'na'), z.raw['platform'].get('context_id', 'na'), z.raw['platform'].get('collection_id', 'na'))) response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 1 assert resp['rows'][0]['target']['items'][0]['type'] == 'Audio' assert resp['rows'][0]['id'] == wa['id']
def test_anno_replies_chrono_sorted(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) # create replies x_replies = [] x_r2r_replies = [] for i in range(0, 4): r = make_wa_object(age_in_hours=i+2, media=ANNO, reply_to=x.anno_id) xr = CRUD.create_anno(r) x_replies.append(xr) assert len(x.replies) == 4 for i in range(0, 3): assert x.replies[i].created < x.replies[i+1].created # adding reply2reply because it's supported, so just in case xr = x.replies[0] for i in range(0, 4): r2r = make_wa_object(age_in_hours=i+3, media=ANNO, reply_to=xr.anno_id) x_r2r = CRUD.create_anno(r2r) assert len(xr.replies) == 4 for i in range(0, 3): assert xr.replies[i].created < xr.replies[i+1].created
def test_count_deleted_anno_replies_ok(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) # create replies x_replies = [] x_r2r_replies = [] for i in range(0, 4): r = make_wa_object(age_in_hours=i+2, media=ANNO, reply_to=x.anno_id) xr = CRUD.create_anno(r) # adding reply2reply because it's supported, so just in case r2r = make_wa_object(age_in_hours=i+1, media=ANNO, reply_to=xr.anno_id) x_r2r = CRUD.create_anno(r2r) x_replies.append(xr) x_r2r_replies.append(x_r2r) assert x.total_replies == 4 # delete _ONE_ reply x_deleted = CRUD.delete_anno(x_replies[0]) assert x_deleted is not None assert x_deleted == x_replies[0] x_deleted_fresh = CRUD.get_anno(x_replies[0].anno_id) assert x_deleted_fresh is None assert x.total_replies == 3
def test_search_by_exclude_username_ok(wa_audio): catcha = wa_audio c = deepcopy(catcha) for i in [1, 2, 3, 4, 5]: c['id'] = '{}{}'.format(catcha['id'], i) c['creator']['id'] = '{}-{}'.format(catcha['creator']['id'], i) c['creator']['name'] = '{}-{}'.format(catcha['creator']['name'], i) x = CRUD.create_anno(c) c = deepcopy(catcha) for i in [6, 7, 8, 9]: c['id'] = '{}{}'.format(catcha['id'], i) x = CRUD.create_anno(c) payload = make_jwt_payload(user=catcha['creator']['id']) other_username = '******'.format(catcha['creator']['name']) request = make_json_request( method='get', query_string='exclude_username={}&exclude_username={}'.format( other_username, catcha['creator']['name'])) request.catchjwt = payload response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 4 assert len(resp['rows']) == 4 for a in resp['rows']: print('----- {} : {}'.format(a['id'], a['creator']['name'])) assert (a['creator']['name'] != catcha['creator']['name']) and ( a['creator']['name'] != other_username)
def test_search_by_userid_ok(wa_text): catcha = wa_text c = deepcopy(catcha) for i in [1, 2, 3, 4, 5]: c['id'] = '{}{}'.format(catcha['id'], i) c['creator']['id'] = '{}-{}'.format(catcha['creator']['id'], i) c['creator']['name'] = '{}-{}'.format(catcha['creator']['name'], i) x = CRUD.create_anno(c) c = deepcopy(catcha) for i in [6, 7, 8, 9]: c['id'] = '{}{}'.format(catcha['id'], i) x = CRUD.create_anno(c) payload = make_jwt_payload(user=catcha['creator']['id']) request = make_json_request( method='get', # to send a list of userids query_string='userid={}&userid=1234567890'.format( catcha['creator']['id'])) request.catchjwt = payload response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 4 assert len(resp['rows']) == 4 for a in resp['rows']: assert a['creator']['id'] == catcha['creator']['id']
def test_create_duplicate_anno(wa_image): catcha = wa_image catcha['id'] = 'naomi-x' x1 = CRUD.create_anno(catcha) assert(x1 is not None) assert(Anno._default_manager.count() == 1) x2 = None with pytest.raises(AnnoError) as e: x2 = CRUD.create_anno(catcha) assert x2 is None assert(Anno._default_manager.count() == 1) assert(Target._default_manager.count() == len(catcha['target']['items']))
def test_format_response_multitarget(wa_text): wa = wa_text target_item = { 'source': 'target_source_blah', 'type': 'Text', 'format': 'text/plain', 'selector': { 'type': 'List', 'items': [{ 'type': 'TextQuoteSelector', 'exact': 'Quote selector exact blah', }] } } wa['id'] = '666' wa['target']['items'].append(target_item) x = CRUD.create_anno(wa) assert x.total_targets == 2 resp = _format_response(x, 'ANNOTATORJS_FORMAT') assert str(resp['id']) == x.anno_id assert resp['uri'] in [ wa['target']['items'][0]['source'], wa['target']['items'][1]['source'] ]
def test_update_ok(wa_text): catch = wa_text payload = make_jwt_payload() # requesting user is allowed to update but not admin catch['permissions']['can_update'].append(payload['userId']) x = CRUD.create_anno(catch) original_tags = x.anno_tags.count() original_targets = x.total_targets data = catch.copy() data['body']['items'].append({ 'type': 'TextualBody', 'purpose': 'tagging', 'value': 'winsome' }) assert data['id'] is not None assert data['creator']['id'] is not None assert 'context_id' in data['platform'] request = make_json_request(method='put', anno_id=x.anno_id, data=json.dumps(data)) request.catchjwt = payload response = crud_api(request, x.anno_id) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert 'Location' in response assert response['Location'] is not None assert x.anno_id in response['Location'] assert len(resp['body']['items']) == original_tags + 2 assert len(resp['target']['items']) == original_targets
def test_search_by_target_source_ok(wa_audio, wa_image): catcha1 = wa_audio catcha2 = wa_image tsource = catcha1['target']['items'][0]['source'] ttype = catcha1['target']['items'][0]['type'] for i in [1, 2, 3, 4]: for catcha in [catcha1, catcha2]: c = deepcopy(catcha) c['id'] = '{}{}'.format(catcha['id'], i) x = CRUD.create_anno(c) payload = make_jwt_payload() request = make_json_request( method='get', query_string='target_source={}'.format(tsource)) request.catchjwt = payload response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 4 for a in resp['rows']: assert Catcha.has_target_source(a, tsource, ttype) assert Catcha.has_target_source(a, tsource)
def test_search_by_tag_or_tag(wa_video): catcha = wa_video common_tag_value = 'testing_tag_even' common_tag = make_wa_tag(common_tag_value) for i in [1, 2, 3, 4, 5]: c = deepcopy(catcha) c['id'] = '{}{}'.format(catcha['id'], i) tag = make_wa_tag(tagname='testing_tag{}'.format(i)) c['body']['items'].append(tag) if i % 2 == 0: c['body']['items'].append(common_tag) x = CRUD.create_anno(c) payload = make_jwt_payload(user=catcha['creator']['id']) request = make_json_request( method='get', # to send a list of userids query_string='tag={}&tag={}'.format(common_tag_value, 'testing_tag3')) request.catchjwt = payload response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 3 assert len(resp['rows']) == 3 for a in resp['rows']: if not Catcha.has_tag(a, 'testing_tag3'): assert Catcha.has_tag(a, common_tag_value) is True
def test_update_anno_ok(wa_text): catcha = wa_text x = CRUD.create_anno(catcha, catcha['creator']['id']) # save values before update original_tags = x.anno_tags.count() original_targets = x.target_set.count() original_body_text = x.body_text original_created = x.created # add tag and target wa = dict(wa_text) wa['body']['items'].append({ 'type': 'TextualBody', 'purpose': 'tagging', 'value': 'tag2017', }) wa['target']['type'] = 'List' wa['target']['items'].append({ 'type': 'Video', 'format': 'video/youtube', 'source': 'https://youtu.be/92vuuZt7wak', }) CRUD.update_anno(x, catcha) assert x.modified.utcoffset() is not None assert original_created.utcoffset() is not None assert(x.anno_tags.count() == original_tags+1) assert(x.target_set.count() == original_targets+1) assert(x.body_text == original_body_text) assert(x.created == original_created) assert(x.modified > original_created)
def test_search_by_username_via_client(wa_text, wa_video, wa_image, wa_audio): for wa in [wa_text, wa_video, wa_image, wa_audio]: x = CRUD.create_anno(wa) c = Consumer._default_manager.create() payload = make_jwt_payload(apikey=c.consumer) token = make_encoded_token(c.secret_key, payload) client = Client() # check if middleware works url = '{}?media=Video'.format(reverse('create_or_search')) print('-------{}'.format(url)) response = client.get(url, HTTP_X_ANNOTATOR_AUTH_TOKEN=token) print('-------{}'.format(response.status_code)) print('-------{}'.format(response.content)) print('-------type: {}'.format(type(response.content))) print('-------type decoded: {}'.format( type(response.content.decode('utf-8')))) print('-------content decoded: {}'.format( response.content.decode('utf-8'))) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 1 assert resp['rows'][0]['target']['items'][0]['type'] == 'Video' assert resp['rows'][0]['id'] == wa_video['id']
def test_import_anno_ok_2(wa_image): catcha = wa_image now = datetime.now(tz.tzutc()) # import first because CRUD.create changes created time in input catcha['id'] = 'naomi-xx-imported' resp = CRUD.import_annos([catcha]) x2 = Anno._default_manager.get(pk=catcha['id']) assert x2 is not None assert Anno._default_manager.count() == 1 # x2 was created more in the past? import preserves created date? delta = timedelta(hours=25) assert x2.created < (now - delta) # about to create catcha['id'] = 'naomi-xx' x1 = CRUD.create_anno(catcha) assert x1 is not None assert Anno._default_manager.count() == 2 # x1 was created less than 1m ago? delta = timedelta(minutes=1) assert (now - delta) < x1.created
def test_update_anno_duplicate_tags(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) # save values before update original_tags = x.anno_tags.count() original_targets = x.target_set.count() original_body_text = x.body_text original_created = x.created assert original_tags > 0 # remove tags and add duplicates wa = dict(wa_text) no_tags = [y for y in wa['body']['items'] if y['purpose'] != PURPOSE_TAGGING] wa['body']['items'] = no_tags for i in range(0, 4): wa['body']['items'].append(make_wa_tag('tag_repeat')) CRUD.update_anno(x, wa) assert(x.anno_tags.count() == 1) # get serialized because it's what's returned in a search wa_updated = x.serialized cleaned_tags = [y for y in wa_updated['body']['items'] if y['purpose'] == PURPOSE_TAGGING] assert len(cleaned_tags) == 1 assert cleaned_tags[0]['value'] == 'tag_repeat'
def test_create_anno_ok(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) assert(x is not None) assert(Anno._default_manager.count() == 1) assert(x.target_set.count() == len(catcha['target']['items'])) assert(x.raw['totalReplies']) == 0
def test_read_ok(wa_audio): catcha = wa_audio x = CRUD.create_anno(catcha) request = make_request(method='get', anno_id=x.anno_id) response = crud_api(request, x.anno_id) assert response.status_code == 200 assert response.content is not None
def test_search_private_catcha_ok(wa_list): anno_list = [] for wa in wa_list: wa['creator']['id'] = 'bilbo_baggings' wa['permissions'] = { 'can_read': [wa['creator']['id']], # private annotation 'can_update': [wa['creator']['id']], 'can_delete': [wa['creator']['id']], 'can_admin': [wa['creator']['id']], } x = CRUD.create_anno(wa) anno_list.append(x) total_annotations = len(anno_list) c = Consumer._default_manager.create() payload_creator = make_jwt_payload(apikey=c.consumer, user='******') token_creator = make_encoded_token(c.secret_key, payload_creator) payload_admin = make_jwt_payload(apikey=c.consumer, user='******') token_admin = make_encoded_token(c.secret_key, payload_admin) payload_peasant = make_jwt_payload(apikey=c.consumer, user='******') token_peasant = make_encoded_token(c.secret_key, payload_peasant) client = Client() # search for annotations creator search_url = ('{}?context_id={}&collection_id={}&limit=-1').format( reverse('create_or_search'), anno_list[0].raw['platform']['context_id'], anno_list[0].raw['platform']['collection_id']) response = client.get(search_url, HTTP_AUTHORIZATION='token {}'.format(token_creator)) assert response.status_code == 200 resp = response.json() assert resp['total'] == total_annotations # search for annotations peasant response = client.get(search_url, HTTP_AUTHORIZATION='token {}'.format(token_peasant)) assert response.status_code == 200 resp = response.json() assert resp['total'] == 0 # search for annotations admin response = client.get(search_url, HTTP_AUTHORIZATION='token {}'.format(token_admin)) assert response.status_code == 200 resp = response.json() assert resp['total'] == total_annotations
def test_format_response_id_nan(wa_text): wa = wa_text x = CRUD.create_anno(wa) assert x is not None query_set = Anno._default_manager.all() resp = _format_response(query_set, 'ANNOTATORJS_FORMAT') assert 'failed' in resp assert resp['failed'][0]['id'] == x.anno_id assert 'id is not a number' in resp['failed'][0]['msg'] assert resp['size_failed'] == 1
def test_format_response_reply_to_reply(wa_text): wa = wa_text wa['id'] = '1' x = CRUD.create_anno(wa) wa1 = make_wa_object(1, media='Annotation', reply_to=x.anno_id) wa1['id'] = '2' x1 = CRUD.create_anno(wa1) wa2 = make_wa_object(1, media='Annotation', reply_to=x1.anno_id) wa2['id'] = '3' x2 = CRUD.create_anno(wa2) query_set = Anno._default_manager.all() resp = _format_response(query_set, 'ANNOTATORJS_FORMAT') assert 'failed' in resp assert resp['size_failed'] == 1 assert resp['failed'][0]['id'] == x2.anno_id assert 'reply to a reply' in resp['failed'][0]['msg'] assert 'rows' in resp assert len(resp['rows']) == 2
def test_head_ok(wa_audio): catcha = wa_audio x = CRUD.create_anno(catcha) c = Consumer._default_manager.create() payload = make_jwt_payload(apikey=c.consumer) token = make_encoded_token(c.secret_key, payload) client = Client() # check if middleware works response = client.head(reverse('crud_api', kwargs={'anno_id': x.anno_id}), HTTP_AUTHORIZATION='token ' + token) assert response.status_code == 200 assert len(response.content) == 0
def test_update_no_body_in_request(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) payload = make_jwt_payload(user=catcha['creator']['id']) request = make_request(method='put', anno_id=x.anno_id) request.catchjwt = payload response = crud_api(request, x.anno_id) assert response.status_code == 400 resp = json.loads(response.content.decode('utf-8')) assert len(resp['payload']) > 0 assert 'missing json' in ','.join(resp['payload'])
def test_search_by_media_ok(wa_text, wa_video, wa_image, wa_audio): for wa in [wa_text, wa_video, wa_image, wa_audio]: x = CRUD.create_anno(wa) payload = make_jwt_payload() request = make_json_request(method='get', query_string='media=video') request.catchjwt = payload response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 1 assert resp['rows'][0]['target']['items'][0]['type'] == 'Video' assert resp['rows'][0]['id'] == wa_video['id']
def test_create_duplicate(wa_audio): catch = wa_audio x = CRUD.create_anno(catch) payload = make_jwt_payload(user=catch['creator']['id']) request = make_json_request(method='post', anno_id=x.anno_id, data=json.dumps(catch)) request.catchjwt = payload response = crud_api(request, x.anno_id) assert response.status_code == 409 resp = json.loads(response.content.decode('utf-8')) assert 'failed to create' in resp['payload'][0]
def test_delete_anno_replies_ok(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) # create replies x_replies = [] x_r2r_replies = [] for i in range(0, 4): r = make_wa_object(age_in_hours=i+2, media=ANNO, reply_to=x.anno_id) xr = CRUD.create_anno(r) # adding reply2reply because it's supported, so just in case r2r = make_wa_object(age_in_hours=i+1, media=ANNO, reply_to=xr.anno_id) x_r2r = CRUD.create_anno(r2r) x_replies.append(xr) x_r2r_replies.append(x_r2r) assert len(x.replies) == 4 x_deleted = CRUD.delete_anno(x) assert x_deleted is not None assert x_deleted == x x_deleted_fresh = CRUD.get_anno(x.anno_id) assert x_deleted_fresh is None for i in range(0, 4): xr = CRUD.get_anno(x_replies[i].anno_id) assert xr is None xr = Anno._default_manager.get(pk=x_replies[i].anno_id) assert xr is not None assert xr.anno_deleted x_r2r = Anno._default_manager.get(pk=x_r2r_replies[i].anno_id) assert x_r2r is not None assert x_r2r.anno_deleted
def test_search_by_body_text_ok(wa_list): for wa in wa_list: x = CRUD.create_anno(wa) wa = make_wa_object(age_in_hours=40) # counting that first item in body is the actual annotation wa['body']['items'][0]['value'] += ''' nao mais, musa, nao mais, que a lira tenho destemperada e a voz enrouquecida, e nao to canto, mas de ver que venho cantar a gente surda e endurecida.''' anno = CRUD.create_anno(wa) search_text = 'enrouquecida endurecida' payload = make_jwt_payload() request = make_json_request(method='get', query_string='text={}'.format(search_text)) request.catchjwt = payload response = search_api(request) resp = json.loads(response.content.decode('utf-8')) assert response.status_code == 200 assert resp['total'] == 1 assert resp['rows'][0]['id'] == anno.anno_id
def test_create_anno_invalid_target(wa_video): catcha = wa_video x = CRUD.get_anno(catcha['id']) assert x is None # mess up target type catcha['target']['type'] = 'FLUFFY' x = None with pytest.raises(InvalidAnnotationTargetTypeError): x = CRUD.create_anno(catcha) assert x is None y = CRUD.get_anno(catcha['id']) assert y is None
def test_update_invalid_input(wa_video): catcha = wa_video x = CRUD.create_anno(catcha) payload = make_jwt_payload(user=x.creator_id) data = dict(catcha) data['body'] = {} request = make_json_request(method='put', anno_id=x.anno_id, data=json.dumps(data)) request.catchjwt = payload response = crud_api(request, x.anno_id) assert response.status_code == 400 resp = json.loads(response.content.decode('utf-8')) assert len(resp['payload']) > 0
def test_search_replies_catcha_ok(wa_list): anno_list = [] for wa in wa_list: x = CRUD.create_anno(wa) anno_list.append(x) c = Consumer._default_manager.create() payload = make_jwt_payload(apikey=c.consumer) token = make_encoded_token(c.secret_key, payload) client = Client() # create some replies reply_to = anno_list[0] wa_replies = [] for i in range(1, 5): wa = make_wa_object(age_in_hours=1, media=ANNO, reply_to=reply_to.anno_id, user=payload['userId']) wa_replies.append(wa) response = client.post('{}'.format( reverse('crud_api', kwargs={'anno_id': wa['id']})), data=json.dumps(wa), HTTP_AUTHORIZATION='Token {}'.format(token), content_type='application/json') assert response.status_code == 200 # search for the replies search_url = ('{}?context_id={}&collection_id={}&media=Annotation&' 'limit=-1&target_source_id={}').format( reverse('create_or_search'), reply_to.raw['platform']['context_id'], reply_to.raw['platform']['collection_id'], reply_to.anno_id) response = client.get(search_url, HTTP_AUTHORIZATION='token {}'.format(token)) assert response.status_code == 200 resp = response.json() assert resp['total'] == 4 for catcha in resp['rows']: assert catcha['creator']['id'] == payload['userId'] # find target with media type Annotation target_item = Catcha.fetch_target_item_by_media(catcha, ANNO) assert target_item is not None assert target_item['source'] == reply_to.anno_id
def test_update_anno_tag_too_long(wa_text): catcha = wa_text x = CRUD.create_anno(catcha) # save values before update original_tags = x.anno_tags.count() original_targets = x.target_set.count() original_body_text = x.body_text original_created = x.created # add tag and target wa = dict(wa_text) wa['body']['items'].append({ 'type': 'TextualBody', 'purpose': 'tagging', 'value': ''' Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam tellus metus, efficitur vel diam id, tincidunt faucibus ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin at tincidunt leo. Donec dictum nulla in bibendum sodales. Pellentesque molestie ligula et mi luctus, sed elementum orci consequat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer aliquet tincidunt fringilla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Suspendisse quis magna erat. Sed pellentesque finibus euismod. Curabitur tincidunt molestie purus nec vehicula. Vivamus pretium egestas maximus. Phasellus molestie elementum nunc a imperdiet. Curabitur elementum turpis at mattis molestie. Phasellus volutpat magna ut arcu consectetur, et condimentum dui semper. Morbi quis lorem sed enim molestie vehicula vel eu sapien. Sed pulvinar orci non vulputate tempus. Fusce justo turpis, porttitor in fringilla non, ullamcorper in est. Nulla semper tellus id nunc ultrices, nec finibus elit accumsan. Mauris urna metus, consectetur ac hendrerit volutpat, malesuada eu felis. Mauris varius ante ut placerat dapibus. Cras ac tincidunt eros, ac imperdiet ligula. Nullam eget libero sodales, dapibus orci id, aliquet nulla. Morbi et leo nec felis lacinia dictum. Duis ut mauris dignissim, efficitur justo eu, sollicitudin nisl.''', }) with pytest.raises(InvalidInputWebAnnotationError): CRUD.update_anno(x, catcha) assert(x.anno_tags.count() == original_tags) assert(x.target_set.count() == original_targets) assert(x.body_text == original_body_text) assert(x.created == original_created) assert(x.modified > original_created)