def test_files_access(draft_access, submitted_access, published_access, user=None): def get_file(deposit, file_access): with app.test_client() as client: if user is not None: login_user(user, client) subtest_file_bucket_permissions(client, deposit.files.bucket, access_level=file_access, is_authenticated=user is not None) deposit = create_deposit(test_record_data, creator, uploaded_files) get_file(deposit, file_access=draft_access) deposit = create_deposit(test_record_data, creator, uploaded_files) deposit.submit() get_file(deposit, file_access=submitted_access) deposit = create_deposit(test_record_data, creator, uploaded_files) deposit.submit() deposit.publish() get_file(deposit, file_access=published_access)
def test_deposit_create_with_invalid_fields_fails(app, test_records_data): """Test deposit creation without or with an invalid field fails.""" data = test_records_data[0] with app.app_context(): data['publication_state'] = 'published' with pytest.raises(InvalidDepositError): deposit = create_deposit(data=data) with app.app_context(): data['$schema'] = '__garbage__' with pytest.raises(InvalidDepositError): deposit = create_deposit(data=data)
def test_deposit_create_with_invalid_community_fails(app, test_records_data): """Test deposit creation without or with an invalid community fails.""" data = test_records_data[0] with app.app_context(): # test with no community del data['community'] with pytest.raises(ValidationError): deposit = create_deposit(data=data) with app.app_context(): # test with an invalid community data['community'] = str(uuid.uuid4()) with pytest.raises(InvalidDepositError): deposit = create_deposit(data=data)
def test_publish(status, user=None): deposit = create_deposit(record_data, creator) deposit.submit() headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] with app.test_client() as client: if user is not None: login_user(user, client) request_res = client.patch(url_for( 'b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), data=json.dumps([{ "op": "replace", "path": "/publication_state", "value": PublicationStates.published.name }, { "op": "replace", "path": "/titles", "value": [{ 'title': 'newtitle' }] }]), headers=headers) assert request_res.status_code == status
def test_generation_of_external_pids(app, records_data_with_external_pids, deposit_with_external_pids, test_users): """Test the generate_external_pids function.""" expected_output = records_data_with_external_pids[ 'external_pids'][:] with app.app_context(): deposit = Deposit.get_record(deposit_with_external_pids.deposit_id) output = generate_external_pids(deposit) assert output == expected_output all_files = list(deposit.files) for f in all_files: if f.obj.key == \ records_data_with_external_pids['external_pids'][0]['key']: f.delete(f.obj.bucket, f.obj.key) output = generate_external_pids(deposit) expected_output_sorted = \ records_data_with_external_pids['external_pids'][1:] expected_output_sorted.sort(key=lambda f: f['key']) assert output == expected_output_sorted records_data_with_external_pids['external_pids'].reverse() deposit2 = create_deposit(records_data_with_external_pids, test_users['deposits_creator']) output = generate_external_pids(deposit2) assert output == expected_output
def test_generation_of_external_pids(app, records_data_with_external_pids, deposit_with_external_pids, test_users): """Test the generate_external_pids function.""" expected_output = records_data_with_external_pids['external_pids'][:] with app.app_context(): deposit = Deposit.get_record(deposit_with_external_pids.deposit_id) output = generate_external_pids(deposit) assert output == expected_output all_files = list(deposit.files) for f in all_files: if f.obj.key == \ records_data_with_external_pids['external_pids'][0]['key']: f.delete(f.obj.bucket, f.obj.key) output = generate_external_pids(deposit) expected_output_sorted = \ records_data_with_external_pids['external_pids'][1:] expected_output_sorted.sort(key=lambda f: f['key']) assert output == expected_output_sorted records_data_with_external_pids['external_pids'].reverse() deposit2 = create_deposit(records_data_with_external_pids, test_users['deposits_creator']) output = generate_external_pids(deposit2) assert output == expected_output
def test_deposit_delete(app, draft_deposits, test_records, test_users, test_communities): """Test deposit deletion.""" with app.app_context(): # create a deposit with a parent pid which has no other children first_deposit = create_deposit( {'community': str(test_communities['MyTestCommunity1'])}, test_users['deposits_creator']) parent_pid = first_deposit['_pid'][0]['value'] deposit_pid_value = first_deposit['_deposit']['id'] # delete the deposit first_deposit.delete() deposit = PersistentIdentifier.query.filter_by( pid_value=deposit_pid_value).first() parent = PersistentIdentifier.query.filter_by( pid_value=parent_pid).one_or_none() # check that deleting it deletes parent from the db because there are # no remaining published versions and draft. assert not parent assert deposit.status == PIDStatus.DELETED # check that the buckets are removed assert not db.session.query( Bucket.query.join(RecordsBuckets).filter( RecordsBuckets.bucket_id == Bucket.id, RecordsBuckets.record_id == first_deposit.id).exists()).scalar() # create a deposit with a parent which has other children v1 = test_records[0].data v1_rec = B2ShareRecord.get_record(test_records[0].record_id) v1_pid, v1_id = pid_of(v1) data = copy_data_from_previous(v1_rec.model.json) v2 = create_deposit(data, test_users['deposits_creator'], version_of=v1_id) deposit_pid_value = v2['_deposit']['id'] parent_pid = v2['_pid'][0]['value'] v2.delete() deposit = PersistentIdentifier.query.filter_by( pid_value=deposit_pid_value).first() parent = PersistentIdentifier.query.filter_by( pid_value=parent_pid).first() # check that the parent status is not changed after deleting assert parent.status != PIDStatus.DELETED assert parent.get_redirect() == v1_pid assert deposit.status == PIDStatus.DELETED
def test_deposit_delete_permissions(app, test_records_data, login_user, test_users): """Test deposit delete with HTTP DELETE.""" with app.app_context(): admin = test_users['admin'] def test_delete(deposit, status, user=None): with app.test_client() as client: if user is not None: login_user(user, client) headers = [('Accept', 'application/json')] request_res = client.delete(url_for( 'b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), headers=headers) assert request_res.status_code == status creator = create_user('creator') non_creator = create_user('non-creator') # test with anonymous user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 401) deposit.submit() test_delete(deposit, 401) deposit.publish() test_delete(deposit, 401) # test with non creator user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 403, non_creator) deposit.submit() test_delete(deposit, 403, non_creator) deposit.publish() test_delete(deposit, 403, non_creator) # test with creator user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 204, creator) deposit = create_deposit(test_records_data[0], creator) deposit.submit() test_delete(deposit, 204, creator) deposit = create_deposit(test_records_data[0], creator) deposit.submit() deposit.publish() test_delete(deposit, 403, creator) # test with admin user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 204, admin) deposit = create_deposit(test_records_data[0], creator) deposit.submit() test_delete(deposit, 204, admin) deposit = create_deposit(test_records_data[0], creator) deposit.submit() deposit.publish() # FIXME: handle the deletion of published deposits test_delete(deposit, 403, admin)
def test_deposit_delete_permissions(app, test_records_data, login_user, test_users): """Test deposit delete with HTTP DELETE.""" with app.app_context(): admin = test_users['admin'] def test_delete(deposit, status, user=None): with app.test_client() as client: if user is not None: login_user(user, client) headers = [('Accept', 'application/json')] request_res = client.delete( url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), headers=headers) assert request_res.status_code == status creator = create_user('creator') non_creator = create_user('non-creator') # test with anonymous user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 401) deposit.submit() test_delete(deposit, 401) deposit.publish() test_delete(deposit, 401) # test with non creator user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 403, non_creator) deposit.submit() test_delete(deposit, 403, non_creator) deposit.publish() test_delete(deposit, 403, non_creator) # test with creator user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 204, creator) deposit = create_deposit(test_records_data[0], creator) deposit.submit() test_delete(deposit, 204, creator) deposit = create_deposit(test_records_data[0], creator) deposit.submit() deposit.publish() test_delete(deposit, 403, creator) # test with admin user deposit = create_deposit(test_records_data[0], creator) test_delete(deposit, 204, admin) deposit = create_deposit(test_records_data[0], creator) deposit.submit() test_delete(deposit, 204, admin) deposit = create_deposit(test_records_data[0], creator) deposit.submit() deposit.publish() test_delete(deposit, 204, admin)
def test_deposit_create_with_incomplete_metadata(app, test_incomplete_records_data): """Test deposit creation with incomplete metadata succeeds.""" with app.app_context(): for data in test_incomplete_records_data: deposit = create_deposit(data=data.incomplete_data) assert (deposit['publication_state'] == PublicationStates.draft.name) assert (deposit['_deposit']['status'] == 'draft')
def test_deposit_delete(app, draft_deposits, test_records, test_users, test_communities): """Test deposit deletion.""" with app.app_context(): # create a deposit with a parent pid which has no other children first_deposit = create_deposit( {'community': str(test_communities['MyTestCommunity1'])}, test_users['deposits_creator']) parent_pid = first_deposit['_pid'][0]['value'] deposit_pid_value = first_deposit['_deposit']['id'] # delete the deposit first_deposit.delete() deposit = PersistentIdentifier.query.filter_by(pid_value=deposit_pid_value).first() parent = PersistentIdentifier.query.filter_by(pid_value=parent_pid).one_or_none() # check that deleting it deletes parent from the db because there are # no remaining published versions and draft. assert not parent assert deposit.status == PIDStatus.DELETED # check that the buckets are removed assert not db.session.query( Bucket.query.join(RecordsBuckets). filter(RecordsBuckets.bucket_id == Bucket.id, RecordsBuckets.record_id == first_deposit.id).exists() ).scalar() # create a deposit with a parent which has other children v1 = test_records[0].data v1_rec = B2ShareRecord.get_record(test_records[0].record_id) v1_pid, v1_id = pid_of(v1) data = copy_data_from_previous(v1_rec.model.json) v2 = create_deposit(data, test_users['deposits_creator'], version_of=v1_id) deposit_pid_value = v2['_deposit']['id'] parent_pid = v2['_pid'][0]['value'] v2.delete() deposit = PersistentIdentifier.query.filter_by(pid_value=deposit_pid_value).first() parent = PersistentIdentifier.query.filter_by(pid_value=parent_pid).first() # check that the parent status is not changed after deleting assert parent.status != PIDStatus.DELETED assert parent.get_redirect() == v1_pid assert deposit.status == PIDStatus.DELETED
def test_deposit_submit_with_incomplete_metadata(app, test_incomplete_records_data): """Test deposit submission with incomplete metadata fails.""" for data in test_incomplete_records_data: with app.app_context(): deposit = create_deposit(data=data.complete_data) deposit.commit() # make the data incomplete deposit = deposit.patch(data.patch) with pytest.raises(ValidationError): deposit.submit()
def test_records_type_helpers(app, test_records_data): """Test record util functions retrieving the record type.""" with app.app_context(): creator = create_user('creator') deposit = create_deposit(test_records_data[0], creator) deposit.submit() deposit.publish() _, record = deposit.fetch_published() assert is_deposit(deposit.model) assert not is_deposit(record.model) assert is_publication(record.model) assert not is_publication(deposit.model)
def test_records_type_helpers(app, test_records_data): """Test record util functions retrieving the record type.""" with app.app_context(): creator = create_user('creator') deposit = create_deposit(test_records_data[0], creator) deposit.submit() deposit.publish() _, record = deposit.fetch_published() assert is_deposit(deposit.model) assert not is_deposit(record.model) assert is_publication(record.model) assert not is_publication(deposit.model)
def test_deposit_publish_with_incomplete_metadata(app, test_incomplete_records_data): """Test publication of an incomplete deposit fails.""" for data in test_incomplete_records_data: with app.app_context(): deposit = create_deposit(data=data.complete_data) deposit.submit() deposit.commit() # make the data incomplete deposit = deposit.patch(data.patch) with pytest.raises(ValidationError): deposit.publish()
def test_create_deposit_with_external_pids_errors( app, records_data_with_external_pids): """Test errors when a deposit is created with invalid external files.""" data_without_pid = deepcopy(records_data_with_external_pids) del data_without_pid['external_pids'][0]['ePIC_PID'] with app.app_context(): with pytest.raises(ValidationError, match="'ePIC_PID' is a required property.*"): create_deposit(data_without_pid) data_without_key = deepcopy(records_data_with_external_pids) del data_without_key['external_pids'][0]['key'] with app.app_context(): with pytest.raises(ValidationError, match="'key' is a required property.*"): create_deposit(data_without_key) data_with_unknown_key = deepcopy(records_data_with_external_pids) data_with_unknown_key['external_pids'][0]['unknown'] = 'value' with app.app_context(): with pytest.raises(ValidationError, match="Additional properties are not allowed " "\('unknown' was unexpected\).*"): create_deposit(data_with_unknown_key)
def test_files_access(draft_access, submitted_access, published_access, user=None): def get_file(deposit, file_access): with app.test_client() as client: if user is not None: login_user(user, client) subtest_file_bucket_permissions( client, deposit.files.bucket, access_level=file_access, is_authenticated=user is not None ) deposit = create_deposit(test_record_data, creator, uploaded_files) get_file(deposit, file_access=draft_access) deposit = create_deposit(test_record_data, creator, uploaded_files) deposit.submit() get_file(deposit, file_access=submitted_access) deposit = create_deposit(test_record_data, creator, uploaded_files) deposit.submit() deposit.publish() get_file(deposit, file_access=published_access)
def test_new_deposit_versions_preserve_schema(app, test_records, test_users): """Creating new versions of existing records.""" with app.app_context(): # Retrieve a record which will be the first version, and its deposit v1_rec = B2ShareRecord.get_record(test_records[0].record_id) _, v1_id = pid_of(test_records[0].data) # update the community's schema community_id = v1_rec.model.json['community'] old_schema = CommunitySchema.get_community_schema(community_id) json_schema = json.loads(old_schema.model.community_schema) new_schema = CommunitySchema.create_version(community_id, json_schema) assert new_schema.version > old_schema.version # create new, unversioned, draft and records data = copy_data_from_previous(v1_rec.model.json) unver_dep = create_deposit(data, test_users['deposits_creator']) unver_dep.submit() unver_dep.publish() _, unver_rec = unver_dep.fetch_published() # the unversioned draft or record in a version chain have the updated schema assert unver_dep['$schema'] != Deposit._build_deposit_schema( v1_rec.model.json) assert unver_rec.model.json['$schema'] != v1_rec.model.json['$schema'] # create new draft and record in the version chain of v1 data = copy_data_from_previous(v1_rec.model.json) v2_dep = create_deposit(data, test_users['deposits_creator'], version_of=v1_id) v2_dep.submit() v2_dep.publish() _, v2_rec = v2_dep.fetch_published() # the new draft and record in a version chain preserve the version of the schema assert v2_dep['$schema'] == Deposit._build_deposit_schema( v1_rec.model.json) assert v2_rec.model.json['$schema'] == v1_rec.model.json['$schema']
def test_verify_checksum_in_deposit(app, test_communities, login_user, test_users): """Test uploading and reading deposit files.""" with app.app_context(): creator = create_user('creator') uploaded_files = { 'myfile1.dat': b'contents1', 'myfile2.dat': b'contents2', 'replaced.dat': b'old_content', } test_record_data = generate_record_data() deposit = create_deposit(test_record_data, creator, uploaded_files) uploaded_file_name = 'additional.dat' uploaded_file_content = b'additional content' headers = [('Accept', '*/*')] with app.test_client() as client: login_user(creator, client) # try uploading a new file file_url = url_for_file(deposit.files.bucket.id, uploaded_file_name) client.put(file_url, input_stream=BytesIO(uploaded_file_content), headers=headers) uploaded_files[uploaded_file_name] = uploaded_file_content # get file content by its uri file_instance = deposit.files['replaced.dat'].obj.file file_reader = urlopen('file://' + file_instance.uri) content = file_reader.read() assert content == b'old_content' # first make it writeable file_instance.writable = True file_instance.set_contents(BytesIO(b'test')) db.session.add(file_instance) db.session.commit() # changing the contents this way should be okay # as the checksum is updated after writing verify_checksum.apply([str(file_instance.id)]) assert file_instance.last_check # directly changing the contents at the uri though # will cause verify_checksum to change the last_check to # False as the checksum will be different now with open(file_instance.uri, 'w') as file_writer: file_writer.write('modified content') verify_checksum.apply([str(file_instance.id)]) assert not file_instance.last_check
def test_deposit_files(app, test_communities, login_user, test_users): """Test uploading and reading deposit files.""" with app.app_context(): admin = test_users['admin'] creator = create_user('creator') uploaded_files = { 'myfile1.dat': b'contents1', 'myfile2.dat': b'contents2', 'replaced.dat': b'old_content', } test_record_data = generate_record_data() # test with anonymous user deposit = create_deposit(test_record_data, creator, uploaded_files) uploaded_file_name = 'additional.dat' uploaded_file_content = b'additional content' # Test file upload headers = [('Accept', '*/*')] with app.test_client() as client: login_user(creator, client) # try uploading a new file file_url = url_for_file(deposit.files.bucket.id, uploaded_file_name) file_put_res = client.put( file_url, input_stream=BytesIO(uploaded_file_content), headers=headers ) uploaded_files[uploaded_file_name] = uploaded_file_content # try replacing an existing file file_url = url_for_file(deposit.files.bucket.id, 'replaced.dat') file_put_res = client.put( file_url, input_stream=BytesIO(b'new_content'), headers=headers ) uploaded_files['replaced.dat'] = b'new_content' # try removing a file file_url2 = url_for_file(deposit.files.bucket.id, 'myfile2.dat') file_put_res = client.delete( file_url2, input_stream=BytesIO(uploaded_file_content), headers=headers ) del uploaded_files['myfile2.dat'] # check that the files can be retrieved properly subtest_file_bucket_content(client, deposit.files.bucket, uploaded_files)
def test_deposit_files(app, test_communities, login_user, test_users): """Test uploading and reading deposit files.""" with app.app_context(): admin = test_users['admin'] creator = create_user('creator') uploaded_files = { 'myfile1.dat': b'contents1', 'myfile2.dat': b'contents2', 'replaced.dat': b'old_content', } test_record_data = generate_record_data() # test with anonymous user deposit = create_deposit(test_record_data, creator, uploaded_files) uploaded_file_name = 'additional.dat' uploaded_file_content = b'additional content' # Test file upload headers = [('Accept', '*/*')] with app.test_client() as client: login_user(creator, client) # try uploading a new file file_url = url_for_file(deposit.files.bucket.id, uploaded_file_name) file_put_res = client.put( file_url, input_stream=BytesIO(uploaded_file_content), headers=headers ) uploaded_files[uploaded_file_name] = uploaded_file_content # try replacing an existing file file_url = url_for_file(deposit.files.bucket.id, 'replaced.dat') file_put_res = client.put( file_url, input_stream=BytesIO(b'new_content'), headers=headers ) uploaded_files['replaced.dat'] = b'new_content' # try removing a file file_url2 = url_for_file(deposit.files.bucket.id, 'myfile2.dat') file_put_res = client.delete( file_url2, input_stream=BytesIO(uploaded_file_content), headers=headers ) del uploaded_files['myfile2.dat'] # check that the files can be retrieved properly subtest_file_bucket_content(client, deposit.files.bucket, uploaded_files)
def test_deposit_modify_published_permissions(app, login_user, test_communities, test_users): """Test deposit edition after its publication. FIXME: This test should evolve when we allow deposit edition. """ with app.app_context(): community_name = 'MyTestCommunity1' record_data = generate_record_data(community=community_name) admin = test_users['admin'] creator = create_user('creator') non_creator = create_user('non-creator') community = Community.get(name=community_name) com_member = create_user('com_member', roles=[community.member_role]) com_admin = create_user('com_admin', roles=[community.admin_role]) deposit = create_deposit(record_data, creator) deposit.submit() deposit.publish() def test_edit(status, user=None): headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] with app.test_client() as client: if user is not None: login_user(user, client) request_res = client.patch(url_for( 'b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), data=json.dumps([{ "op": "replace", "path": "/publication_state", "value": PublicationStates.draft.name }]), headers=headers) assert request_res.status_code == status # test with anonymous user test_edit(401) test_edit(403, non_creator) test_edit(403, creator) test_edit(403, admin) test_edit(403, com_member) test_edit(403, com_admin)
def test_deposit_modify_published_permissions(app, login_user, test_communities, test_users): """Test deposit edition after its publication. FIXME: This test should evolve when we allow deposit edition. """ with app.app_context(): community_name = 'MyTestCommunity1' record_data = generate_record_data(community=community_name) admin = test_users['admin'] creator = create_user('creator') non_creator = create_user('non-creator') community = Community.get(name=community_name) com_member = create_user('com_member', roles=[community.member_role]) com_admin = create_user('com_admin', roles=[community.admin_role]) deposit = create_deposit(record_data, creator) deposit.submit() deposit.publish() def test_edit(status, user=None): headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] with app.test_client() as client: if user is not None: login_user(user, client) request_res = client.patch( url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), data=json.dumps([{ "op": "replace", "path": "/publication_state", "value": PublicationStates.draft.name }]), headers=headers) assert request_res.status_code == status # test with anonymous user test_edit(401) test_edit(403, non_creator) test_edit(403, creator) test_edit(403, admin) test_edit(403, com_member) test_edit(403, com_admin)
def test_submit(status, user=None): deposit = create_deposit(record_data, creator) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] with app.test_client() as client: if user is not None: login_user(user, client) request_res = client.patch( url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), data=json.dumps([{ "op": "replace", "path": "/publication_state", "value": PublicationStates.submitted.name }, { "op": "replace", "path": "/titles", "value": [{'title':'newtitle'}] }]), headers=headers) assert request_res.status_code == status
def test_deposit_publish(app, test_users, test_communities, login_user): """Test record draft publication with HTTP PATCH.""" with app.app_context(): community_name = 'MyTestCommunity1' creator = test_users['deposits_creator'] record_data = generate_record_data(community=community_name) community = Community.get(name=community_name) com_admin = create_user('com_admin', roles=[community.admin_role]) deposit = create_deposit(record_data, creator) deposit_id = deposit.id deposit.submit() db.session.commit() with app.test_client() as client: login_user(com_admin, client) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] draft_patch_res = client.patch(url_for( 'b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), data=json.dumps([{ "op": "replace", "path": "/publication_state", "value": PublicationStates.published.name }]), headers=headers) assert draft_patch_res.status_code == 200 draft_patch_data = json.loads( draft_patch_res.get_data(as_text=True)) expected_metadata = build_expected_metadata( record_data, PublicationStates.published.name, owners=[creator.id], draft=True, PID=draft_patch_data['metadata'].get('ePIC_PID'), DOI=draft_patch_data['metadata'].get('DOI'), ) with app.app_context(): deposit = Deposit.get_record(deposit_id) with app.test_client() as client: login_user(creator, client) assert expected_metadata == draft_patch_data['metadata'] assert (deposit['publication_state'] == PublicationStates.published.name) subtest_self_link(draft_patch_data, draft_patch_res.headers, client) pid, published = deposit.fetch_published() # check that the published record and the deposit are equal except # for the schema cleaned_deposit = { f: v for f, v in deposit.items() if f != '$schema' } cleaned_published = { f: v for f, v in deposit.items() if f != '$schema' } assert cleaned_published == cleaned_deposit # check "published" link assert draft_patch_data['links']['publication'] == \ url_for('b2share_records_rest.{0}_item'.format( RecordUUIDProvider.pid_type ), pid_value=pid.pid_value, _external=True) # check that the published record content match the deposit headers = [('Accept', 'application/json')] self_response = client.get( draft_patch_data['links']['publication'], headers=headers) assert self_response.status_code == 200 published_data = json.loads(self_response.get_data(as_text=True)) # we don't want to compare the links and dates cleaned_published_data = deepcopy(published_data) # the published record has an extra empty 'files' array assert cleaned_published_data['files'] == [] del cleaned_published_data['files'] cleaned_draft_data = deepcopy(draft_patch_data) for item in [cleaned_published_data, cleaned_draft_data]: del item['links'] del item['created'] del item['updated'] del item['metadata']['$schema'] assert cleaned_draft_data == cleaned_published_data
def test_getting_record_with_external_pids(app, login_user, test_users, deposit_with_external_pids, records_data_with_external_pids): """External pids are serialized in the metadata when it is allowed.""" def test_get_deposit(deposit_pid_value, user): with app.test_client() as client: login_user(user, client) deposit_url = url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit_pid_value) resp = client.get(deposit_url) deposit_data = json.loads(resp.get_data(as_text=True)) return deposit_data def test_get_record(record_pid_value, user): with app.test_client() as client: login_user(user, client) record_url = url_for('b2share_records_rest.b2rec_item', pid_value=record_pid_value) resp = client.get(record_url) record_data = json.loads(resp.get_data(as_text=True)) return record_data def test_search_deposits(user): with app.test_client() as client: login_user(user, client) search_deposits_url = url_for('b2share_records_rest.b2rec_list', drafts=1, size=100) headers = [('Content-Type', 'application/json'), ('Accept', 'application/json')] resp = client.get(search_deposits_url, headers=headers) deposit_search_res = json.loads(resp.get_data(as_text=True)) return deposit_search_res def test_search_records(user): with app.test_client() as client: login_user(user, client) search_records_url = url_for('b2share_records_rest.b2rec_list', size=100) headers = [('Content-Type', 'application/json'), ('Accept', 'application/json')] resp = client.get(search_records_url, headers=headers) record_search_res = json.loads(resp.get_data(as_text=True)) return record_search_res with app.app_context(): deposit = Deposit.get_record(deposit_with_external_pids.deposit_id) with app.app_context(): deposit_data = test_get_deposit(deposit.pid.pid_value, test_users['deposits_creator']) # assert that the external_pids are visible # when getting a specific deposit assert_external_files(deposit, deposit_data['metadata']['external_pids']) current_search_client.indices.refresh('*') deposit_search_data = test_search_deposits(test_users['deposits_creator']) assert deposit_search_data['hits']['total'] == 1 # external_pids are not shown in a deposit search because it would use # too much resources to generate it for each search hit. assert 'external_pids' not in deposit_search_data['hits']['hits'][0][ 'metadata'] with app.app_context(): deposit = Deposit.get_record(deposit_with_external_pids.deposit_id) deposit.submit() deposit.publish() record_resolver = Resolver( pid_type='b2rec', object_type='rec', getter=B2ShareRecord.get_record, ) record_pid, record = record_resolver.resolve(deposit.pid.pid_value) current_search_client.indices.refresh('*') record_data = test_get_record(record_pid.pid_value, test_users['deposits_creator']) # when getting a specific record the owner sees the external_pids assert_external_files(record, record_data['metadata']['external_pids']) with app.app_context(): record_data = test_get_record(record_pid.pid_value, test_users['normal']) # and all other users as well if it is open access assert_external_files(record, record_data['metadata']['external_pids']) deposit_search_data = test_search_deposits(test_users['deposits_creator']) assert deposit_search_data['hits']['total'] == 1 # external_pids are not shown in deposit search even when published assert 'external_pids' not in deposit_search_data['hits']['hits'][0][ 'metadata'] record_search_data = test_search_records(test_users['deposits_creator']) assert record_search_data['hits']['total'] == 1 # external_pids are shown for record search if they are open access # for all users assert 'external_pids' in record_search_data['hits']['hits'][0]['metadata'] record_search_data = test_search_records(test_users['normal']) assert record_search_data['hits']['total'] == 1 assert 'external_pids' in record_search_data['hits']['hits'][0]['metadata'] with app.app_context(): deposit2 = create_deposit(records_data_with_external_pids, test_users['deposits_creator']) deposit2 = deposit2.patch([{ 'op': 'add', 'path': '/embargo_date', 'value': (datetime.utcnow() + timedelta(days=1)).isoformat() }, { 'op': 'replace', 'path': '/open_access', 'value': False }]) deposit2.commit() deposit2.submit() deposit2.publish() record_resolver = Resolver( pid_type='b2rec', object_type='rec', getter=B2ShareRecord.get_record, ) record_pid, record = record_resolver.resolve(deposit2.pid.pid_value) record_data = test_get_record(record_pid.pid_value, test_users['deposits_creator']) # owners of records have access to files and # external_pids of embargoed records assert_external_files(record, record_data['metadata']['external_pids']) with app.app_context(): record_data = test_get_record(record_pid.pid_value, test_users['normal']) # normal users shouldn't have access to the # files and external_pids of an embargoed record assert 'metadata' in record_data assert 'external_pids' not in record_data['metadata']
def test_deposit_publish(app, test_users, test_communities, login_user): """Test record draft publication with HTTP PATCH.""" with app.app_context(): community_name = 'MyTestCommunity1' creator = test_users['deposits_creator'] record_data = generate_record_data(community=community_name) community = Community.get(name=community_name) com_admin = create_user('com_admin', roles=[community.admin_role]) deposit = create_deposit(record_data, creator) deposit_id = deposit.id deposit.submit() db.session.commit() with app.test_client() as client: login_user(com_admin, client) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] draft_patch_res = client.patch( url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), data=json.dumps([{ "op": "replace", "path": "/publication_state", "value": PublicationStates.published.name }]), headers=headers) assert draft_patch_res.status_code == 200 draft_patch_data = json.loads( draft_patch_res.get_data(as_text=True)) expected_metadata = build_expected_metadata( record_data, PublicationStates.published.name, owners=[creator.id], draft=True, PID=draft_patch_data['metadata'].get('ePIC_PID'), DOI=draft_patch_data['metadata'].get('DOI'), ) with app.app_context(): deposit = Deposit.get_record(deposit_id) with app.test_client() as client: login_user(creator, client) assert expected_metadata == draft_patch_data['metadata'] assert (deposit['publication_state'] == PublicationStates.published.name) subtest_self_link(draft_patch_data, draft_patch_res.headers, client) pid, published = deposit.fetch_published() # check that the published record and the deposit are equal except # for the schema cleaned_deposit = {f: v for f, v in deposit.items() if f != '$schema'} cleaned_published = {f: v for f, v in deposit.items() if f != '$schema'} assert cleaned_published == cleaned_deposit # check "published" link assert draft_patch_data['links']['publication'] == \ url_for('b2share_records_rest.{0}_item'.format( RecordUUIDProvider.pid_type ), pid_value=pid.pid_value, _external=True) # check that the published record content match the deposit headers = [('Accept', 'application/json')] self_response = client.get( draft_patch_data['links']['publication'], headers=headers ) assert self_response.status_code == 200 published_data = json.loads(self_response.get_data( as_text=True)) # we don't want to compare the links and dates cleaned_published_data = deepcopy(published_data) # the published record has an extra empty 'files' array assert cleaned_published_data['files'] == [] del cleaned_published_data['files'] cleaned_draft_data = deepcopy(draft_patch_data) for item in [cleaned_published_data, cleaned_draft_data]: del item['links'] del item['created'] del item['updated'] del item['metadata']['$schema'] assert cleaned_draft_data == cleaned_published_data
def test_verify_checksum_in_deposit(app, test_communities, login_user, test_users): """Test checksum for files uploaded in a draft.""" with app.app_context(): creator = create_user('creator') uploaded_files = { 'myfile1.dat': b'contents1', 'myfile2.dat': b'contents2', 'replaced.dat': b'old_content', } test_record_data = generate_record_data() deposit = create_deposit(test_record_data, creator, uploaded_files) uploaded_file_name = 'additional.dat' uploaded_file_content = b'additional content' headers = [('Accept', '*/*')] with app.test_client() as client: login_user(creator, client) # try uploading a new file file_url = url_for_file(deposit.files.bucket.id, uploaded_file_name) client.put(file_url, input_stream=BytesIO(uploaded_file_content), headers=headers) uploaded_files[uploaded_file_name] = uploaded_file_content # get file content by its uri file_instance = deposit.files['replaced.dat'].obj.file file_reader = urlopen('file://' + file_instance.uri) content = file_reader.read() assert content == b'old_content' # first make it writeable file_instance.writable = True file_instance.set_contents(BytesIO(b'test')) db.session.add(file_instance) db.session.commit() # changing the contents this way should be okay # as the checksum is updated after writing verify_checksum.apply([str(file_instance.id)]) assert file_instance.last_check # directly changing the contents at the uri with open(file_instance.uri, 'w') as file_writer: file_writer.write('modified content') with app.extensions['mail'].record_messages() as outbox: verify_checksum.apply([str(file_instance.id)]) # last_check=False as the checksum will be different now assert not file_instance.last_check schedule_failed_checksum_files(max_count=0, max_size=0, batch_interval={'seconds': 1}, frequency={'seconds': 1}) assert len(outbox) == 1 # assert that an email is sent afterwards to the support email # mentioning the uri of the file with the different checksum email = outbox[0] assert file_instance.uri in email.body assert current_app.config['SUPPORT_EMAIL'] in email.recipients
def test_deposit_read_permissions(app, login_user, test_users, test_communities): """Test deposit read with HTTP GET.""" with app.app_context(): community_name = 'MyTestCommunity1' record_data = generate_record_data(community=community_name) community = Community.get(name=community_name) admin = test_users['admin'] creator = create_user('creator') non_creator = create_user('non-creator') com_member = create_user('com_member', roles=[community.member_role]) com_admin = create_user('com_admin', roles=[community.admin_role]) def test_get(deposit, status, user=None): with app.test_client() as client: if user is not None: login_user(user, client) headers = [('Accept', 'application/json')] request_res = client.get(url_for( 'b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), headers=headers) assert request_res.status_code == status # test with anonymous user deposit = create_deposit(record_data, creator) test_get(deposit, 401) deposit.submit() test_get(deposit, 401) deposit.publish() test_get(deposit, 401) deposit = create_deposit(record_data, creator) test_get(deposit, 403, non_creator) deposit.submit() test_get(deposit, 403, non_creator) deposit.publish() test_get(deposit, 403, non_creator) deposit = create_deposit(record_data, creator) test_get(deposit, 200, creator) deposit.submit() test_get(deposit, 200, creator) deposit.publish() test_get(deposit, 200, creator) deposit = create_deposit(record_data, creator) test_get(deposit, 200, admin) deposit.submit() test_get(deposit, 200, admin) deposit.publish() test_get(deposit, 200, admin) deposit = create_deposit(record_data, creator) test_get(deposit, 403, com_member) deposit.submit() test_get(deposit, 403, com_member) deposit.publish() test_get(deposit, 403, com_member) deposit = create_deposit(record_data, creator) test_get(deposit, 403, com_admin) deposit.submit() test_get(deposit, 200, com_admin) deposit.publish() test_get(deposit, 200, com_admin)
def test_getting_record_with_external_pids(app, login_user, test_users, deposit_with_external_pids, records_data_with_external_pids): """External pids are serialized in the metadata when it is allowed.""" def test_get_deposit(deposit_pid_value, user): with app.test_client() as client: login_user(user, client) deposit_url = url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit_pid_value) resp = client.get(deposit_url) deposit_data = json.loads( resp.get_data(as_text=True)) return deposit_data def test_get_record(record_pid_value, user): with app.test_client() as client: login_user(user, client) record_url = url_for('b2share_records_rest.b2rec_item', pid_value=record_pid_value) resp = client.get(record_url) record_data = json.loads( resp.get_data(as_text=True)) return record_data def test_search_deposits(user): with app.test_client() as client: login_user(user, client) search_deposits_url = url_for( 'b2share_records_rest.b2rec_list', drafts=1, size=100) headers = [('Content-Type', 'application/json'), ('Accept', 'application/json')] resp = client.get( search_deposits_url, headers=headers) deposit_search_res = json.loads( resp.get_data(as_text=True)) return deposit_search_res def test_search_records(user): with app.test_client() as client: login_user(user, client) search_records_url = url_for( 'b2share_records_rest.b2rec_list', size=100) headers = [('Content-Type', 'application/json'), ('Accept', 'application/json')] resp = client.get( search_records_url, headers=headers) record_search_res = json.loads( resp.get_data(as_text=True)) return record_search_res with app.app_context(): deposit = Deposit.get_record(deposit_with_external_pids.deposit_id) with app.app_context(): deposit_data = test_get_deposit(deposit.pid.pid_value, test_users['deposits_creator']) # assert that the external_pids are visible # when getting a specific deposit assert_external_files( deposit, deposit_data['metadata']['external_pids']) current_search_client.indices.refresh('*') deposit_search_data = test_search_deposits( test_users['deposits_creator']) assert deposit_search_data['hits']['total'] == 1 # external_pids are not shown in a deposit search because it would use # too much resources to generate it for each search hit. assert 'external_pids' not in deposit_search_data[ 'hits']['hits'][0]['metadata'] with app.app_context(): deposit = Deposit.get_record(deposit_with_external_pids.deposit_id) deposit.submit() deposit.publish() record_resolver = Resolver( pid_type='b2rec', object_type='rec', getter=B2ShareRecord.get_record, ) record_pid, record = record_resolver.resolve(deposit.pid.pid_value) current_search_client.indices.refresh('*') record_data = test_get_record( record_pid.pid_value, test_users['deposits_creator']) # when getting a specific record the owner sees the external_pids assert_external_files(record, record_data['metadata']['external_pids']) with app.app_context(): record_data = test_get_record( record_pid.pid_value, test_users['normal']) # and all other users as well if it is open access assert_external_files(record, record_data['metadata']['external_pids']) deposit_search_data = test_search_deposits( test_users['deposits_creator']) assert deposit_search_data['hits']['total'] == 1 # external_pids are not shown in deposit search even when published assert 'external_pids' not in deposit_search_data[ 'hits']['hits'][0]['metadata'] record_search_data = test_search_records( test_users['deposits_creator']) assert record_search_data['hits']['total'] == 1 # external_pids are shown for record search if they are open access # for all users assert 'external_pids' in record_search_data[ 'hits']['hits'][0]['metadata'] record_search_data = test_search_records(test_users['normal']) assert record_search_data['hits']['total'] == 1 assert 'external_pids' in record_search_data[ 'hits']['hits'][0]['metadata'] with app.app_context(): deposit2 = create_deposit(records_data_with_external_pids, test_users['deposits_creator']) deposit2 = deposit2.patch([ {'op': 'add', 'path': '/embargo_date', 'value': (datetime.utcnow() + timedelta(days=1)).isoformat()}, {'op': 'replace', 'path': '/open_access', 'value': False} ]) deposit2.commit() deposit2.submit() deposit2.publish() record_resolver = Resolver( pid_type='b2rec', object_type='rec', getter=B2ShareRecord.get_record, ) record_pid, record = record_resolver.resolve(deposit2.pid.pid_value) record_data = test_get_record( record_pid.pid_value, test_users['deposits_creator']) # owners of records have access to files and # external_pids of embargoed records assert_external_files(record, record_data['metadata']['external_pids']) with app.app_context(): record_data = test_get_record( record_pid.pid_value, test_users['normal']) # normal users shouldn't have access to the # files and external_pids of an embargoed record assert 'metadata' in record_data assert 'external_pids' not in record_data['metadata']
def test_deposit_read_permissions(app, login_user, test_users, test_communities): """Test deposit read with HTTP GET.""" with app.app_context(): community_name = 'MyTestCommunity1' record_data = generate_record_data(community=community_name) community = Community.get(name=community_name) admin = test_users['admin'] creator = create_user('creator') non_creator = create_user('non-creator') com_member = create_user('com_member', roles=[community.member_role]) com_admin = create_user('com_admin', roles=[community.admin_role]) def test_get(deposit, status, user=None): with app.test_client() as client: if user is not None: login_user(user, client) headers = [('Accept', 'application/json')] request_res = client.get( url_for('b2share_deposit_rest.b2dep_item', pid_value=deposit.pid.pid_value), headers=headers) assert request_res.status_code == status # test with anonymous user deposit = create_deposit(record_data, creator) test_get(deposit, 401) deposit.submit() test_get(deposit, 401) deposit.publish() test_get(deposit, 401) deposit = create_deposit(record_data, creator) test_get(deposit, 403, non_creator) deposit.submit() test_get(deposit, 403, non_creator) deposit.publish() test_get(deposit, 403, non_creator) deposit = create_deposit(record_data, creator) test_get(deposit, 200, creator) deposit.submit() test_get(deposit, 200, creator) deposit.publish() test_get(deposit, 200, creator) deposit = create_deposit(record_data, creator) test_get(deposit, 200, admin) deposit.submit() test_get(deposit, 200, admin) deposit.publish() test_get(deposit, 200, admin) deposit = create_deposit(record_data, creator) test_get(deposit, 403, com_member) deposit.submit() test_get(deposit, 403, com_member) deposit.publish() test_get(deposit, 403, com_member) deposit = create_deposit(record_data, creator) test_get(deposit, 403, com_admin) deposit.submit() test_get(deposit, 200, com_admin) deposit.publish() test_get(deposit, 200, com_admin)
def test_verify_checksum_in_deposit(app, test_communities, login_user, test_users): """Test checksum for files uploaded in a draft.""" with app.app_context(): creator = create_user('creator') uploaded_files = { 'myfile1.dat': b'contents1', 'myfile2.dat': b'contents2', 'replaced.dat': b'old_content', } test_record_data = generate_record_data() deposit = create_deposit(test_record_data, creator, uploaded_files) uploaded_file_name = 'additional.dat' uploaded_file_content = b'additional content' headers = [('Accept', '*/*')] with app.test_client() as client: login_user(creator, client) # try uploading a new file file_url = url_for_file(deposit.files.bucket.id, uploaded_file_name) client.put( file_url, input_stream=BytesIO(uploaded_file_content), headers=headers ) uploaded_files[uploaded_file_name] = uploaded_file_content # get file content by its uri file_instance = deposit.files['replaced.dat'].obj.file file_reader = urlopen('file://' + file_instance.uri) content = file_reader.read() assert content == b'old_content' # first make it writeable file_instance.writable = True file_instance.set_contents(BytesIO(b'test')) db.session.add(file_instance) db.session.commit() # changing the contents this way should be okay # as the checksum is updated after writing verify_checksum.apply([str(file_instance.id)]) assert file_instance.last_check # directly changing the contents at the uri with open(file_instance.uri, 'w') as file_writer: file_writer.write('modified content') with app.extensions['mail'].record_messages() as outbox: verify_checksum.apply([str(file_instance.id)]) # last_check=False as the checksum will be different now assert not file_instance.last_check schedule_failed_checksum_files( max_count=0, max_size=0, batch_interval={'seconds': 1}, frequency={'seconds': 1} ) assert len(outbox) == 1 # assert that an email is sent afterwards to the support email # mentioning the uri of the file with the different checksum email = outbox[0] assert file_instance.uri in email.body assert current_app.config['SUPPORT_EMAIL'] in email.recipients
def test_record_delete_version(app, test_records, test_users): """Test deletion of a record version.""" with app.app_context(): resolver = Resolver( pid_type='b2rec', object_type='rec', getter=B2ShareRecord.get_record, ) v1 = test_records[0].data v1_pid, v1_id = pid_of(v1) _, v1_rec = resolver.resolve(v1_id) data = copy_data_from_previous(v1_rec.model.json) v2 = create_deposit(data, test_users['deposits_creator'], version_of=v1_id) ObjectVersion.create(v2.files.bucket, 'myfile1', stream=BytesIO(b'mycontent')) v2.submit() v2.publish() v2_pid, v2_id = pid_of(v2) data = copy_data_from_previous(v2.model.json) v3 = create_deposit(data, test_users['deposits_creator'], version_of=v2_id) v3.submit() v3.publish() v3_pid, v3_id = pid_of(v3) v3_pid, v3_rec = resolver.resolve(v3_pid.pid_value) # chain is now: [v1] -- [v2] -- [v3] version_child = PIDVersioning(child=v2_pid) version_master = PIDVersioning(parent=version_child.parent) assert len(version_master.children.all()) == 3 v3_rec.delete() assert len(version_master.children.all()) == 2 # chain is now [v1] -- [v2] # assert that we can create again a new version from v2 data = copy_data_from_previous(v2.model.json) v3 = create_deposit(data, test_users['deposits_creator'], version_of=v2_id) v3.submit() v3.publish() v3_pid, v3_id = pid_of(v3) v3_pid, v3_rec = resolver.resolve(v3_pid.pid_value) assert len(version_master.children.all()) == 3 v2_pid, v2_rec = resolver.resolve(v2_pid.pid_value) # Delete an intermediate version v2_rec.delete() assert len(version_master.children.all()) == 2 # chain is now [v1] -- [v3] # Add a new version data = copy_data_from_previous(v3.model.json) v4 = create_deposit(data, test_users['deposits_creator'], version_of=v3_id) v4.submit() v4.publish() assert len(version_master.children.all()) == 3 # final chain [v1] -- [v3] -- [v4] v4_pid, v4_id = pid_of(v4) v4_pid, v4_rec = resolver.resolve(v4_pid.pid_value) data = copy_data_from_previous(v4) draft_child = create_deposit(data, test_users['deposits_creator'], version_of=v4_id) draft_child.submit() # delete all children except the draft child assert len(version_master.children.all()) == 3 v4_rec.delete() assert len(version_master.children.all()) == 2 v3_rec.delete() assert len(version_master.children.all()) == 1 v1_rec.delete() assert len(version_master.children.all()) == 0 assert version_master.parent.status != PIDStatus.DELETED draft_child.publish() draft_child_pid, draft_child_id = pid_of(draft_child) draft_child_pid, draft_child_rec = \ resolver.resolve(draft_child_pid.pid_value) # assert that we can create again a new version assert len(version_master.children.all()) == 1 # no child remains and there is no draft_child draft_child_rec.delete() assert version_master.parent.status == PIDStatus.DELETED
def test_deposit_versions_create(app, test_records, test_users): """Creating new versions of existing records.""" with app.app_context(): # Retrieve a record which will be the first version v1 = test_records[0].data v1_rec = B2ShareRecord.get_record(test_records[0].record_id) v1_pid, v1_id = pid_of(v1) assert list_published_pids(v1_pid) == [v1_pid] # create draft in version chain: # version chain becomes: [v1] -- [v2 draft] # v2 = create_deposit({}, version_of=v1_id) data = copy_data_from_previous(v1_rec.model.json) v2 = create_deposit(data, test_users['deposits_creator'], version_of=v1_id) assert filenames(v2) == [] ObjectVersion.create(v2.files.bucket, 'myfile1', stream=BytesIO(b'mycontent')) assert filenames(v2) == ['myfile1'] assert list_published_pids(v1_pid) == [v1_pid] # cannot create another draft if one exists # not possible: [v1] -- [v2 draft] # `- [new draft] with pytest.raises(DraftExistsVersioningError): data = copy_data_from_previous(v1_rec.model.json) create_deposit(data, test_users['deposits_creator'], version_of=v1_id) # cannot create a version from a draft pid # not possible: [v1] -- [v2 draft] -- [new draft] with pytest.raises(IncorrectRecordVersioningError): # record pid not created yet data = copy_data_from_previous(v1_rec.model.json) create_deposit(data, test_users['deposits_creator'], version_of=v2['_deposit']['id']) # publish previous draft # version chain becomes: [v1] -- [v2] v2.submit() v2.publish() v2_pid, v2_id = pid_of(v2) assert list_published_pids(v1_pid) == [v1_pid, v2_pid] # cannot create draft based on the first version in a chain # not possible: [v1] -- [v2] # `- [new draft] with pytest.raises(IncorrectRecordVersioningError): data = copy_data_from_previous(v1_rec.model.json) create_deposit(data, test_users['deposits_creator'], version_of=v1_id) # create and publish other versions: # version chain becomes: [v1] -- [v2] -- [v3] data = copy_data_from_previous(v1_rec.model.json) v3 = create_deposit(data, test_users['deposits_creator'], version_of=v2_id) # assert files are imported from v2 assert filenames(v3) == ['myfile1'] ObjectVersion.create(v3.files.bucket, 'myfile2', stream=BytesIO(b'mycontent')) assert filenames(v3) == ['myfile1', 'myfile2'] assert list_published_pids(v1_pid) == [v1_pid, v2_pid] v3.submit() v3.publish() v3_pid, v3_id = pid_of(v3) v3_rec = Record.get_record(v3_id) assert filenames(v3_rec) == ['myfile1', 'myfile2'] assert list_published_pids(v1_pid) == [v1_pid, v2_pid, v3_pid] # cannot create draft based on an intermediate version in a chain # not possible: [v1] -- [v2] -- [v3] # `- [new draft] with pytest.raises(IncorrectRecordVersioningError): create_deposit({}, test_users['deposits_creator'], version_of=v2_id) # Create yet another version # Version chain becomes: [v1] -- [v2] -- [v3] -- [v4] data = copy_data_from_previous(v1_rec.model.json) v4 = create_deposit(data, test_users['deposits_creator'], version_of=v3_id) v4.submit() v4.publish() assert filenames(v4) == ['myfile1', 'myfile2'] v4_pid, v4_id = pid_of(v4) assert list_published_pids(v1_pid) == [ v1_pid, v2_pid, v3_pid, v4_pid] # assert that creating a new version from a deleted pid is not allowed resolver = Resolver(pid_type=v4_pid.pid_type, object_type='rec', getter=partial(B2ShareRecord.get_record, with_deleted=True)) v4_pid, v4_rec = LazyPIDValue(resolver, v4_pid.pid_value).data # delete [v4] v4_rec.delete() with pytest.raises(RecordNotFoundVersioningError): v5 = create_deposit(data, test_users['deposits_creator'], version_of=v4_id)
def test_deposit_versions_create(app, test_records, test_users): """Creating new versions of existing records.""" with app.app_context(): # Retrieve a record which will be the first version v1 = test_records[0].data v1_rec = B2ShareRecord.get_record(test_records[0].record_id) v1_pid, v1_id = pid_of(v1) assert list_published_pids(v1_pid) == [v1_pid] # create draft in version chain: # version chain becomes: [v1] -- [v2 draft] # v2 = create_deposit({}, version_of=v1_id) data = copy_data_from_previous(v1_rec.model.json) v2 = create_deposit(data, test_users['deposits_creator'], version_of=v1_id) assert filenames(v2) == [] ObjectVersion.create(v2.files.bucket, 'myfile1', stream=BytesIO(b'mycontent')) assert filenames(v2) == ['myfile1'] assert list_published_pids(v1_pid) == [v1_pid] # cannot create another draft if one exists # not possible: [v1] -- [v2 draft] # `- [new draft] with pytest.raises(DraftExistsVersioningError): data = copy_data_from_previous(v1_rec.model.json) create_deposit(data, test_users['deposits_creator'], version_of=v1_id) # cannot create a version from a draft pid # not possible: [v1] -- [v2 draft] -- [new draft] with pytest.raises( IncorrectRecordVersioningError): # record pid not created yet data = copy_data_from_previous(v1_rec.model.json) create_deposit(data, test_users['deposits_creator'], version_of=v2['_deposit']['id']) # publish previous draft # version chain becomes: [v1] -- [v2] v2.submit() v2.publish() v2_pid, v2_id = pid_of(v2) assert list_published_pids(v1_pid) == [v1_pid, v2_pid] # cannot create draft based on the first version in a chain # not possible: [v1] -- [v2] # `- [new draft] with pytest.raises(IncorrectRecordVersioningError): data = copy_data_from_previous(v1_rec.model.json) create_deposit(data, test_users['deposits_creator'], version_of=v1_id) # create and publish other versions: # version chain becomes: [v1] -- [v2] -- [v3] data = copy_data_from_previous(v1_rec.model.json) v3 = create_deposit(data, test_users['deposits_creator'], version_of=v2_id) # assert files are imported from v2 assert filenames(v3) == ['myfile1'] ObjectVersion.create(v3.files.bucket, 'myfile2', stream=BytesIO(b'mycontent')) assert filenames(v3) == ['myfile1', 'myfile2'] assert list_published_pids(v1_pid) == [v1_pid, v2_pid] v3.submit() v3.publish() v3_pid, v3_id = pid_of(v3) v3_rec = Record.get_record(v3_id) assert filenames(v3_rec) == ['myfile1', 'myfile2'] assert list_published_pids(v1_pid) == [v1_pid, v2_pid, v3_pid] # cannot create draft based on an intermediate version in a chain # not possible: [v1] -- [v2] -- [v3] # `- [new draft] with pytest.raises(IncorrectRecordVersioningError): create_deposit({}, test_users['deposits_creator'], version_of=v2_id) # Create yet another version # Version chain becomes: [v1] -- [v2] -- [v3] -- [v4] data = copy_data_from_previous(v1_rec.model.json) v4 = create_deposit(data, test_users['deposits_creator'], version_of=v3_id) v4.submit() v4.publish() assert filenames(v4) == ['myfile1', 'myfile2'] v4_pid, v4_id = pid_of(v4) assert list_published_pids(v1_pid) == [v1_pid, v2_pid, v3_pid, v4_pid] # assert that creating a new version from a deleted pid is not allowed resolver = Resolver(pid_type=v4_pid.pid_type, object_type='rec', getter=partial(B2ShareRecord.get_record, with_deleted=True)) v4_pid, v4_rec = LazyPIDValue(resolver, v4_pid.pid_value).data # delete [v4] v4_rec.delete() with pytest.raises(RecordNotFoundVersioningError): v5 = create_deposit(data, test_users['deposits_creator'], version_of=v4_id)