def test_rest_delete_record(app, db, es, es_acl_prepare, test_users): with app.test_client() as client: with db.session.begin_nested(): acl = DefaultACL(name='default', schemas=[RECORD_SCHEMA], priority=0, originator=test_users.u1, operation='update') actor = UserActor(name='u1', users=[test_users.u1], acl=acl, originator=test_users.u1) db.session.add(acl) db.session.add(actor) pid, record = create_record({'keywords': ['blah']}, clz=SchemaEnforcingRecord) RecordIndexer().index(record) current_search_client.indices.refresh() current_search_client.indices.flush() login(client, test_users.u2) response = client.delete(record_url(pid)) assert response.status_code == 403 login(client, test_users.u1) response = client.delete(record_url(pid)) assert response.status_code == 204 with pytest.raises(NoResultFound): Record.get_record(pid.object_uuid)
def test_valid_delete(app): """Test VALID record delete request (DELETE .../records/<record_id>).""" with app.app_context(): # create the record using the internal API pid, record = create_record(test_data) with app.test_client() as client: headers = [('Accept', 'application/json')] res = client.delete(url_for('invenio_records_rest.recid_item', pid_value=pid.pid_value), headers=headers) assert res.status_code == 204 # check database state with pytest.raises(NoResultFound): Record.get_record(record.id) assert pid.is_deleted() # check that DELETE with non JSON accepted format will work # as it returns nothing pid, record = create_record(test_data) headers = [('Accept', 'video/mp4')] res = client.delete(url_for('invenio_records_rest.recid_item', pid_value=pid.pid_value), headers=headers) assert res.status_code == 204
def test_record_update_mutable(app, db): """Test updating mutables in a record.""" recid = uuid.UUID("262d2748-ba41-456f-a844-4d043a419a6f") # Create a new record with two mutables, a list and a dict rec = Record.create({"title": "Title", "list": ["foo"], "dict": {"moo": "boo"}}, id_=recid) # Make sure mutables are there before and after commit assert rec == {"title": "Title", "list": ["foo"], "dict": {"moo": "boo"}} db.session.commit() db.session.expunge_all() rec = Record.get_record(recid) assert rec == {"title": "Title", "list": ["foo"], "dict": {"moo": "boo"}} # Set the mutables under key rec["list"] = ["bar"] rec["dict"] = {"eggs": "bacon"} rec.commit() # Make sure it commits to DB assert rec == {"title": "Title", "list": ["bar"], "dict": {"eggs": "bacon"}} db.session.commit() db.session.expunge_all() rec = Record.get_record(recid) assert rec == {"title": "Title", "list": ["bar"], "dict": {"eggs": "bacon"}} # Update the mutables under key rec["list"].append("spam") rec["dict"]["ham"] = "chicken" rec.commit() # Make sure it commits to DB assert rec == {"title": "Title", "list": ["bar", "spam"], "dict": {"eggs": "bacon", "ham": "chicken"}} db.session.commit() db.session.expunge_all() rec = Record.get_record(recid) assert rec == {"title": "Title", "list": ["bar", "spam"], "dict": {"eggs": "bacon", "ham": "chicken"}}
def test_metadata_extraction_video(app, db, cds_depid, bucket, video): """Test metadata extraction video mp4.""" recid = PersistentIdentifier.get('depid', cds_depid).object_uuid # simulate a no fully filled record record = Record.get_record(recid) if 'title' in record: del record['title'] validator = 'cds.modules.records.validators.PartialDraft4Validator' with pytest.raises(ValidationError): record.commit() record.commit(validator=import_string(validator)) # Extract metadata obj = ObjectVersion.create(bucket=bucket, key='video.mp4') obj_id = str(obj.version_id) dep_id = str(cds_depid) task_s = ExtractMetadataTask().s(uri=video, version_id=obj_id, deposit_id=dep_id) task_s.delay() # Check that deposit's metadata got updated record = Record.get_record(recid) assert 'extracted_metadata' in record['_cds'] assert record['_cds']['extracted_metadata'] # Check that ObjectVersionTags were added tags = ObjectVersion.query.first().get_tags() assert tags['duration'] == '60.095000' assert tags['bit_rate'] == '612177' assert tags['avg_frame_rate'] == '288000/12019' assert tags['codec_name'] == 'h264' assert tags['codec_long_name'] == 'H.264 / AVC / MPEG-4 AVC / ' \ 'MPEG-4 part 10' assert tags['width'] == '640' assert tags['height'] == '360' assert tags['nb_frames'] == '1440' assert tags['display_aspect_ratio'] == '16:9' assert tags['color_range'] == 'tv' # Undo ExtractMetadataTask().clean(deposit_id=dep_id, version_id=obj_id) # Check that deposit's metadata got reverted record = Record.get_record(recid) assert 'extracted_metadata' not in record['_cds'] # Check that ObjectVersionTags are still there (attached to the old obj) tags = ObjectVersion.query.first().get_tags() assert len(tags) == 11 # Simulate failed task, no extracted_metadata ExtractMetadataTask().clean(deposit_id=dep_id, version_id=obj_id) record = Record.get_record(recid) assert 'extracted_metadata' not in record['_cds'] # check again tags tags = ObjectVersion.query.first().get_tags() assert len(tags) == 11
def test_db(): """Test database backend.""" app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get( 'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db' ) FlaskCLI(app) InvenioDB(app) InvenioRecords(app) with app.app_context(): create_database(db.engine.url) db.create_all() assert len(db.metadata.tables) == 3 data = {'title': 'Test'} from invenio_records.models import RecordMetadata as RM # Create a record with app.app_context(): assert RM.query.count() == 0 record_uuid = Record.create(data).id db.session.commit() assert RM.query.count() == 1 db.session.commit() # Retrieve created record with app.app_context(): record = Record.get_record(record_uuid) assert record.dumps() == data with pytest.raises(NoResultFound): Record.get_record(uuid.uuid4()) record['field'] = True record = record.patch([ {'op': 'add', 'path': '/hello', 'value': ['world']} ]) assert record['hello'] == ['world'] record.commit() db.session.commit() with app.app_context(): record2 = Record.get_record(record_uuid) assert record2.model.version_id == 2 assert record2['field'] assert record2['hello'] == ['world'] db.session.commit() # Cannot commit record without model (i.e. Record.create_record) with app.app_context(): record3 = Record({'title': 'Not possible'}) with pytest.raises(RecordNotCommitableError): record3.commit() with app.app_context(): db.drop_all() drop_database(db.engine.url)
def acl_changed_reindex(acl_id): """ ACL has been changed so reindex all the documents in the given index. :param acl_id: id of ACL instance """ logger.info('Reindexing started for ACL=%s', acl_id) timestamp = datetime.datetime.now().astimezone().isoformat() acl = ACL.query.filter_by(id=acl_id).one_or_none() if not acl: # deleted in the meanwhile, so just return return # pragma no cover # make sure all indices are flushed so that no resource is obsolete in index for schema in acl.schemas: current_search_client.indices.flush(index=schema_to_index(schema)[0]) indexer = RecordIndexer() updated_count = 0 removed_count = 0 for id in acl.get_matching_resources(): try: rec = Record.get_record(id) except: # pragma no cover # record removed in the meanwhile by another thread/process, # indexer should have been called to remove it from ES # won't test this so pragma no cover continue try: indexer.index(rec) updated_count += 1 except Exception as e: # pragma no cover logger.exception('Error indexing ACL for resource %s: %s', id, e) # reindex the resources those were indexed by this acl but no longer should be for id in acl.used_in_records(older_than_timestamp=timestamp): try: rec = Record.get_record(id) except NoResultFound: # pragma no cover continue except: # pragma no cover logger.exception('Unexpected exception in record reindexing') continue try: removed_count += 1 indexer.index(rec) except: # pragma no cover logger.exception('Error indexing ACL for obsolete resource %s', id) logger.info( 'Reindexing finished for ACL=%s, acl applied to %s records, acl removed from %s records', acl_id, updated_count, removed_count)
def test_record_deletion(test_record): recid = test_record.get('control_number') assert recid test_record.delete() db.session.commit() pid = PersistentIdentifier.get('recid', recid) assert pid.status == PIDStatus.DELETED with raises(NoResultFound): Record.get_record(pid.object_uuid)
def test_change_record_community(app, test_records): """Test updating the community field fails.""" with app.app_context(): record = Record.get_record(test_records[0].record_id) del record['community'] with pytest.raises(AlteredRecordError): record.commit() with app.app_context(): record = Record.get_record(test_records[0].record_id) record['community'] = str(uuid.uuid4()) with pytest.raises(AlteredRecordError): record.commit()
def revert_delete_record(pid): """Reverts deletion action of a record.""" pid_obj = PersistentIdentifier.query.filter_by(pid_value=pid).one() record_uuid = pid_obj.object_uuid deleted = Record.get_record(record_uuid, with_deleted=True) if not deleted.is_deleted: click.secho(f"Record {pid} was not deleted. Aborting.", fg="red") return # revert to previous revision record = deleted.revert(deleted.revision_id - 1) all_pid_objects = PersistentIdentifier.query.filter_by( object_uuid=record_uuid) # trying to get all the pid types (including legacy pids) for pid_object in all_pid_objects: pid_object.status = PIDStatus.REGISTERED db.session.commit() indexer_class = current_app_ils.indexer_by_pid_type(pid_obj.pid_type) indexer_class.index(record) click.secho(f"Record {pid} is reverted from deletion.", fg="green")
def test_record_patch_immutable_fields(app, test_records, test_users, login_user): """Test invalid modification of record draft with HTTP PATCH.""" with app.app_context(): record = Record.get_record(test_records[0].record_id) with app.test_client() as client: user = test_users['admin'] login_user(user, client) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] for path in IMMUTABLE_PATHS: for command in [ {"op": "replace", "path": path, "value": ""}, {"op": "remove", "path": path}, {"op": "add", "path": path, "value": ""}, {"op": "copy", "from": "/title", "path": path, "value": ""}, {"op": "move", "from": "/title", "path": path, "value": ""}, ]: draft_patch_res = client.patch( url_for('b2share_records_rest.b2rec_item', pid_value=test_records[0].pid), data=json.dumps([command]), headers=headers) assert draft_patch_res.status_code == 400
def test_record_patch_immutable_fields(app, test_records, test_users, login_user): """Test invalid modification of record draft with HTTP PATCH.""" with app.app_context(): record = Record.get_record(test_records[0].record_id) with app.test_client() as client: user = test_users['admin'] login_user(user, client) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] for path in IMMUTABLE_PATHS: for command in [ {"op": "replace", "path": path, "value": ""}, {"op": "remove", "path": path}, {"op": "add", "path": path, "value": ""}, {"op": "copy", "from": "/title", "path": path, "value": ""}, {"op": "move", "from": "/title", "path": path, "value": ""}, ]: draft_patch_res = client.patch( url_for('b2share_records_rest.b2rec_item', pid_value=test_records[0].pid), data=json.dumps([command]), headers=headers) assert draft_patch_res.status_code == 400 data = json.loads(draft_patch_res.get_data(as_text=True)) assert data['errors'][0]['message'] == \ 'The field "{}" is immutable.'.format(path)
def test_metadata_extraction_video_mp4(app, db, depid, bucket, video_mp4): """Test metadata extraction video mp4.""" # Extract metadata obj = ObjectVersion.create(bucket=bucket, key='video.mp4') video_metadata_extraction.s(uri=video_mp4, object_version=str(obj.version_id), deposit_id=str(depid)).delay() # Check that deposit's metadata got updated recid = PersistentIdentifier.get('depid', depid).object_uuid record = Record.get_record(recid) assert 'extracted_metadata' in record['_deposit'] assert record['_deposit']['extracted_metadata'] # Check that ObjectVersionTags were added obj = ObjectVersion.query.first() tags = obj.get_tags() assert tags['duration'] == '60.095000' assert tags['bit_rate'] == '612177' assert tags['size'] == '5510872' assert tags['avg_frame_rate'] == '288000/12019' assert tags['codec_name'] == 'h264' assert tags['width'] == '640' assert tags['height'] == '360' assert tags['nb_frames'] == '1440' assert tags['display_aspect_ratio'] == '0:1' assert tags['color_range'] == 'tv'
def _update_event_bucket(event): """Update event's payload with correct bucket of deposit.""" depid = event.payload['deposit_id'] dep_uuid = str(PersistentIdentifier.get('depid', depid).object_uuid) deposit_bucket = Record.get_record(dep_uuid)['_buckets']['deposit'] event.payload['bucket_id'] = deposit_bucket flag_modified(event, 'payload')
def record_upsert(json): """Insert or update a record.""" control_number = json.get('control_number', json.get('recid')) if control_number: control_number = int(control_number) pid_type = InspireRecordIdProvider.schema_to_pid_type(json['$schema']) try: pid = PersistentIdentifier.get(pid_type, control_number) record = Record.get_record(pid.object_uuid) record.update(json) record.commit() except PIDDoesNotExistError: record = Record.create(json, id_=None) # Create persistent identifier. inspire_recid_minter(str(record.id), json) if json.get('deleted'): new_recid = get_recid_from_ref(json.get('new_record')) if new_recid: merged_record = get_db_record(pid_type, new_recid) merge_pidstores_of_two_merged_records(merged_record.id, record.id) else: soft_delete_pidstore_for_record(record.id) return record
def test_valid_put(app): """Test VALID record patch request (PATCH .../records/<record_id>).""" with app.app_context(): InvenioRecordsREST(app) # create the record using the internal API internal_record = Record.create(test_data) with app.test_client() as client: headers = [('Content-Type', 'application/json'), ('Accept', 'application/json')] res = client.put(url_for(blueprint.name + '.' + RecordResource.view_name, record_id=internal_record.model.id), data=json.dumps(test_data_patched), headers=headers) assert res.status_code == 200 # check that the returned record matches the given data response_data = json.loads(res.get_data(as_text=True)) assert response_data['data'] == test_data_patched # check that an internal record returned id and that it contains # the same data assert 'id' in response_data.keys() internal_record = Record.get_record(response_data['id']) assert internal_record == response_data['data'] # check that the returned self link returns the same data subtest_self_link(response_data, res.headers, client)
def test_change_record_schema_fails(app, test_records): """Test updating the $schema field fails.""" with app.app_context(): record = Record.get_record(test_records[0].record_id) del record['$schema'] with pytest.raises(AlteredRecordError): record.commit()
def files_permission_factory(obj=None, action=None): """Permission for files are always based on the type of bucket. 1. Record bucket: Read access only with open and restricted access. 2. Any other bucket is restricted to admins only. """ # Extract bucket id bucket_id = None if isinstance(obj, Bucket): bucket_id = str(obj.id) elif isinstance(obj, ObjectVersion): bucket_id = str(obj.bucket_id) elif isinstance(obj, MultipartObject): bucket_id = str(obj.bucket_id) elif isinstance(obj, FileObject): bucket_id = str(obj.bucket_id) # Retrieve record if bucket_id is not None: record_bucket = RecordsBuckets.query.filter_by(bucket_id=bucket_id).one_or_none() if record_bucket is not None: record = Record.get_record(record_bucket.record_id) return FilePermission.create(record, action) return AdminPermission.create(obj, action)
def run_task_on_referrers(reference, task, success_task=None, error_task=None): """ Queues a task for all referrers referring the given reference. :param reference: reference for which to run the tasks on referrers :param task: a celery signature :param success_task: a celery signature to handle success of task chain :param error_task: a celery signature to handle error of a certain task """ refs = current_references.get_records(reference) task_list = [] rec_list = [] for ref in refs: rec = Record.get_record(id_=ref.record.record_uuid) # Add the referencing record to the task signature record_task = task.clone(kwargs={'record': rec}) if error_task: record_error = error_task.clone(kwargs={'record': rec}) record_task = record_task.on_error(record_error) task_list.append(record_task) rec_list.append(rec) job = chain(*task_list) if success_task: job_result = job.apply_async(link=success_task.clone( kwargs={'records': rec_list})) else: job_result = job.apply_async() return job_result
def test_record_crud_2(self, load_entry_points, app, db, record_xml): synchronizer = current_oai_client.providers["uk"].synchronizers["xoai"] oai_sync = OAISync(provider_code="uk") synchronizer.oai_sync = oai_sync oai_identifier = "oai:dspace.cuni.cz:20.500.11956/2623" # vytvořenà záznamu synchronizer.record_crud(oai_identifier=oai_identifier, xml=record_xml) db.session.commit() oai_rec = OAIRecord.get_record(oai_identifier) assert oai_rec is not None # smazánà záznamu synchronizer.record_crud(oai_rec=oai_rec, deleted=True) db.session.commit() oai_rec2 = OAIRecord.get_record(oai_identifier) assert oai_rec == oai_rec2 # obnovenà záznamu synchronizer.record_crud(oai_rec=oai_rec, timestamp='2050-10-22T08:18:08.567698+00:00', xml=record_xml) db.session.commit() oai_rec3 = OAIRecord.get_record(oai_identifier) assert oai_rec3 is not None record = Record.get_record(oai_rec.id) assert record is not None
def _get_record_from_workflow(workflow): assert len(workflow.objects) == 1 workflow_object = workflow.objects[0] recid = workflow_object.data['control_number'] pid = PersistentIdentifier.get('recid', recid) return Record.get_record(pid.object_uuid)
def get_record_from_workflow(workflow): assert len(workflow.objects) == 1 workflow_object = workflow.objects[0] recid = workflow_object.data['control_number'] pid = PersistentIdentifier.get('recid', recid) return Record.get_record(pid.object_uuid)
def test_delete_record(self, load_entry_points, app, db, metadata): synchronizer = current_oai_client.providers["uk"].synchronizers["xoai"] record, id_ = synchronizer.create_record(metadata) oai_sync = OAISync(provider_code="uk") db.session.add(oai_sync) db.session.commit() oai_rec = OAIRecord(id=record.id, oai_identifier="oai:example.cz:1", creation_sync_id=oai_sync.id, pid="1", timestamp=datetime.now()) db.session.add(oai_rec) db.session.commit() synchronizer.delete_record(oai_rec) with pytest.raises(NoResultFound): Record.get_record(oai_rec.id) deleted_record = Record.get_record(oai_rec.id, with_deleted=True)
def acl_deleted_reindex(schemas, acl_id): """ ACL has been deleted so reindex all the documents that contain reference to it. :param index: the index of documents :param record_acl_id: if of the ACL instance that has been deleted """ logger.info('Reindexing started for deleted ACL=%s', acl_id) acl = ACL.query.filter_by(id=acl_id).one_or_none() if acl: # pragma no cover raise AttributeError( 'ACL with id %s is still in the database, ' 'please remove it before calling current_explicit_acls.reindex_acl_removed' % acl_id) indexer = RecordIndexer() query = { "nested": { "path": "_invenio_explicit_acls", "query": { "term": { "_invenio_explicit_acls.id": acl_id } } } } removed_count = 0 for schema in schemas: current_search_client.indices.refresh(index=schema_to_index(schema)[0]) current_search_client.indices.flush(index=schema_to_index(schema)[0]) try: index, doc_type = schema_to_index(schema) for doc in elasticsearch.helpers.scan(current_search_client, query={ "query": query, "_source": False, }, index=index, **add_doc_type(doc_type)): try: indexer.index(Record.get_record(doc['_id'])) removed_count += 1 except NoResultFound: # pragma no cover continue except: # pragma no cover logger.exception( 'Unexpected exception in record reindexing') continue except: # pragma no cover logger.exception('Error removing ACL from schema %s', schema) logger.info( 'Reindexing finished for deleted ACL=%s, acl removed from %s records', acl_id, removed_count)
def patch_record(recid, patch, validator=None): """Patch a record.""" with db.session.begin_nested(): record = Record.get_record(recid) record = record.patch(patch) if validator: validator = import_string(validator) record.commit(validator=validator) return record
def check_compliance(obj, *args): if 'control_number' not in obj.data: raise ValueError( "Object should have a 'control_number' key in 'data' dict to be consistent with article upload." ) recid = obj.data['control_number'] pid = PersistentIdentifier.get('recid', recid) record = Record.get_record(pid.object_uuid) checks = {} # Add temporary data to evaluation extra_data = {'extracted_text': __extract_article_text(record)} all_checks_accepted = True for name, func in COMPLIANCE_TASKS: check_accepted, details, debug = func(record, extra_data) all_checks_accepted = all_checks_accepted and check_accepted checks[name] = { 'check': check_accepted, 'details': details, 'debug': debug } c = Compliance.get_or_create(pid.object_uuid) results = { 'checks': checks, 'accepted': all_checks_accepted, 'data': { 'doi': get_first_doi(record), 'publisher': get_abbreviated_publisher(record), 'journal': get_abbreviated_journal(record), 'arxiv': get_first_arxiv(record) } } c.add_results(results) c.id_record = pid.object_uuid db.session.add(c) db.session.commit() # send notification about failed checks need_email = current_app.config.get('COMPLIANCE_SEND_FAILED_EMAILS', True) if need_email and not all_checks_accepted and c.has_final_result_changed(): msg = TemplatedMessage( template_html='scoap3_compliance/admin/failed_email.html', subject='SCOAP3 - Compliance check', sender=current_app.config.get('MAIL_DEFAULT_SENDER'), recipients=current_app.config.get('OPERATIONS_EMAILS'), ctx={ 'results': results, 'id': '%s,%s' % (c.id, record.id), }) current_app.extensions['mail'].send(msg)
def update_authors_recid(record_id, uuid, profile_recid): """Update author profile for a given signature. The method receives UUIDs representing record and signature respectively together with an author profile recid. The new recid will be placed in the signature with the given UUID. :param record_id: A string representing UUID of a given record. Example: record_id = "a5afb151-8f75-4e91-8dc1-05e7e8e8c0b8" :param uuid: A string representing UUID of a given signature. Example: uuid = "c2f432bd-2f52-4c16-ac66-096f168c762f" :param profile_recid: A string representing author profile recid, that updated signature should point to. Example: profile_recid = "1" """ try: record = Record.get_record(record_id) update_flag = False for author in record['authors']: if author['uuid'] == uuid: author['recid'] = str(profile_recid) update_flag = True if update_flag: # Disconnect the signal on insert of a new record. before_record_index.disconnect(append_updated_record_to_queue) # Update the record in the database. record.commit() db.session.commit() # Update the record in Elasticsearch. indexer = RecordIndexer() indexer.index_by_id(record.id) except StaleDataError as exc: raise update_authors_recid.retry(exc=exc) finally: # Reconnect the disconnected signal. before_record_index.connect(append_updated_record_to_queue) # Report. logger.info("Updated signature %s with profile %s", uuid, profile_recid)
def _get_csv(self, date_from=None, date_to=None): chunk_size = 50 countries = Gdp.query.order_by(Gdp.name.asc()).all() record_ids = ArticlesImpact.query.with_entities( ArticlesImpact.control_number).all() record_ids = [r[0] for r in record_ids] header = [ 'doi', 'recid', 'journal', 'creation_date', 'primary_category', 'total_authors' ] header.extend([c.name.strip().replace(',', '') for c in countries]) si = StringIO.StringIO() cw = csv.writer(si, delimiter=";") cw.writerow(header) for i in range((len(record_ids) // chunk_size) + 1): # calculate chunk start and end position ixn = i * chunk_size current_ids = record_ids[ixn:ixn + chunk_size] for record in ArticlesImpact.query.filter( ArticlesImpact.control_number.in_(current_ids)): # FIXME during country share refactor, this logic should be moved if (date_from is not None and date_from > record.creation_date ) or (date_to is not None and date_to < record.creation_date): continue try: r = Record.get_record( PersistentIdentifier.get( 'recid', record.control_number).object_uuid) title = get_title(r).lower() if 'addendum' in title or 'corrigendum' in title or 'erratum' in title: continue except PIDDoesNotExistError: pass total_authors = reduce(lambda x, y: x + y, record.results.values(), 0) country_share = [ float(record.results[c.name.strip()]) / total_authors if c.name.strip() in record.results else 0 for c in countries ] primary_category = record.details.get('arxiv_primary_category') csv_line = [ record.doi, record.control_number, record.journal, record.creation_date, primary_category, total_authors ] csv_line.extend(country_share) cw.writerow(csv_line) return si.getvalue()
def get_primary_arxiv_category(self): try: pid = PersistentIdentifier.get('recid', self.control_number) record = Record.get_record(pid.object_uuid) return get_arxiv_primary_category(record) except PIDDoesNotExistError: # records imported from Inspire won't be found pass return None
def test_delete(testapp, database): """Test delete a record.""" db = database # Create a record, revise it and delete it. record = Record.create({'title': 'test 1'}) db.session.commit() record['title'] = 'test 2' record.commit() db.session.commit() record.delete() db.session.commit() # Deleted records a not retrievable by default pytest.raises(NoResultFound, Record.get_record, record.id) # Deleted records can be retrieved if you explicit request it record = Record.get_record(record.id, with_deleted=True) # Deleted records are empty assert record == {} assert record.model.json is None # Deleted records *cannot* be modified record['title'] = 'deleted' assert pytest.raises(MissingModelError, record.commit) # Deleted records *can* be reverted record = record.revert(-2) assert record['title'] == 'test 2' db.session.commit() # The "undeleted" record can now be retrieve again record = Record.get_record(record.id) assert record['title'] == 'test 2' # Force deleted record cannot be retrieved again record.delete(force=True) db.session.commit() pytest.raises(NoResultFound, Record.get_record, record.id, with_deleted=True)
def get_document_previewer(record_id, filename): """Look for a record and return the file.""" record = Record.get_record(record_id) len_documents = len(Document(record, '/files/').record['files']) for i_document in range(len_documents): document = Document(record, '/files/{0}/uri'.format(i_document)) document_previewer = DocumentPreviewer(record_id, document) if not filename or document_previewer.get_filename() == filename: return document_previewer
def test_undelete_with_get(testapp, db): """Test undelete a record.""" record = Record.create({'title': 'test'}) db.session.commit() record.delete() db.session.commit() record = Record.get_record(record.id, with_deleted=True) record.undelete() record.commit() db.session.commit() assert record == {}
def test_delete(app): """Test delete a record.""" with app.app_context(): # Create a record, revise it and delete it. record = Record.create({'title': 'test 1'}) db.session.commit() record['title'] = 'test 2' record.commit() db.session.commit() record.delete() db.session.commit() # Deleted records a not retrievable by default pytest.raises(NoResultFound, Record.get_record, record.id) # Deleted records can be retrieved if you explicit request it record = Record.get_record(record.id, with_deleted=True) # Deleted records are empty assert record == {} assert record.model.json is None # Deleted records *cannot* be modified record['title'] = 'deleted' assert pytest.raises(MissingModelError, record.commit) # Deleted records *can* be reverted record = record.revert(-2) assert record['title'] == 'test 2' db.session.commit() # The "undeleted" record can now be retrieve again record = Record.get_record(record.id) assert record['title'] == 'test 2' # Force deleted record cannot be retrieved again record.delete(force=True) db.session.commit() pytest.raises( NoResultFound, Record.get_record, record.id, with_deleted=True)
def test_taxonomy_term_moved(app, db, taxonomy_tree, test_record): taxonomy = current_flask_taxonomies.get_taxonomy("test_taxonomy") terms = current_flask_taxonomies.list_taxonomy(taxonomy).all() old_record = Record.get_record(id_=test_record.id) old_taxonomy = old_record["taxonomy"] assert old_taxonomy == [{ 'is_ancestor': True, 'level': 1, 'links': { 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a' }, 'test': 'extra_data' }, { 'is_ancestor': True, 'level': 2, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b' }, 'test': 'extra_data' }, { 'is_ancestor': False, 'level': 3, 'links': { 'parent': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b', 'self': 'http://127.0.0.1:5000/2.0/taxonomies/test_taxonomy/a/b/c' }, 'test': 'extra_data' }] ti = TermIdentification(term=terms[2]) current_flask_taxonomies.move_term(ti, new_parent=terms[0], remove_after_delete=False) db.session.commit() new_record = Record.get_record(id_=test_record.id) new_taxonomy = new_record["taxonomy"] new_terms = current_flask_taxonomies.list_taxonomy(taxonomy).all() assert new_terms[-1].parent_id == 1
def test_record_handling(self, load_entry_points, app, db, record_xml): synchronizer = current_oai_client.providers["uk"].synchronizers["xoai"] oai_sync = OAISync(provider_code="uk") db.session.add(oai_sync) db.session.commit() synchronizer.oai_sync = oai_sync synchronizer.record_handling(1, xml=record_xml) oai_rec = OAIRecord.get_record( oai_identifier="oai:dspace.cuni.cz:20.500.11956/2623") record = Record.get_record(id_=oai_rec.id) assert record == {'pid': '1', 'title': 'Testovacà záznam'}
def test_run_2(self, load_entry_points, app, db): patch = mock.patch('sickle.app.Sickle.harvest', mock_harvest) patch.start() current_oai_client.run(providers_codes=["uk"]) patch.stop() oai_sync = OAISync.query.get(1) assert oai_sync.status == "ok" assert oai_sync.records_created == 1 oai_rec = OAIRecord.query.all()[-1] assert oai_rec.pid == "1" record = Record.get_record(id_=oai_rec.id) assert record["title"] == "Testovacà záznam"
def test_delete_with_sqldatabase_error(app): """Test VALID record delete request (GET .../records/<record_id>).""" with app.app_context(): # create the record using the internal API pid, record = create_record(test_data) db.session.expire(record.model) pid_value = pid.pid_value pid_type = pid.pid_type record_id = record.id db.session.commit() Record.get_record(record_id) def raise_exception(): raise SQLAlchemyError() with app.test_client() as client: # start a new SQLAlchemy session so that it will rollback # everything nested_transaction = db.session().transaction orig_rollback = nested_transaction.rollback flags = {'rollbacked': False} def custom_rollback(*args, **kwargs): flags['rollbacked'] = True orig_rollback(*args, **kwargs) nested_transaction.rollback = custom_rollback with patch.object(PersistentIdentifier, 'delete', side_effect=raise_exception): headers = [('Accept', 'application/json')] res = client.delete(url_for('invenio_records_rest.recid_item', pid_value=pid_value), headers=headers) assert res.status_code == 500 # check that the transaction is finished assert db.session().transaction is not nested_transaction # check that the session has rollbacked assert flags['rollbacked'] with app.app_context(): with app.test_client() as client: # check that the record and PID have not been deleted Record.get_record(record_id) assert not PersistentIdentifier.get(pid_type, pid_value).is_deleted() # try to delete without exception, the transaction should have been # rollbacked headers = [('Accept', 'application/json')] res = client.delete(url_for('invenio_records_rest.recid_item', pid_value=pid_value), headers=headers) assert res.status_code == 204 # check database state with pytest.raises(NoResultFound): Record.get_record(record_id) assert PersistentIdentifier.get(pid_type, pid_value).is_deleted()
def test_cli(app): """Test CLI.""" runner = CliRunner() script_info = ScriptInfo(create_app=lambda info: app) assert 'records_metadata' in db.metadata.tables assert 'records_metadata_version' in db.metadata.tables assert 'transaction' in db.metadata.tables from invenio_records.models import RecordMetadata as RM # Test merging a base another file. with runner.isolated_filesystem(): with open('record.json', 'wb') as f: f.write( json.dumps({ "title": "Test" }, ensure_ascii=False).encode('utf-8')) with open('record.patch', 'wb') as f: f.write( json.dumps([{ "op": "replace", "path": "/title", "value": "Patched Test" }], ensure_ascii=False).encode('utf-8')) result = runner.invoke(cli.records, [], obj=script_info) assert result.exit_code == 0 with app.app_context(): assert RM.query.count() == 0 result = runner.invoke(cli.records, ['create', 'record.json'], obj=script_info) assert result.exit_code == 0 with app.app_context(): assert RM.query.count() == 1 recid = RM.query.first().id result = runner.invoke(cli.records, ['patch', 'record.patch', '-r', recid], obj=script_info) assert result.exit_code == 0 with app.app_context(): record = Record.get_record(recid) assert record['title'] == 'Patched Test' assert record.model.version_id == 2
def check_compliance(obj, *args): if 'control_number' not in obj.data: raise ValueError("Object should have a 'control_number' key in 'data' dict to be consistent with article upload.") recid = obj.data['control_number'] pid = PersistentIdentifier.get('recid', recid) record = Record.get_record(pid.object_uuid) checks = {} # Add temporary data to evalutaion extra_data = {'extracted_text': __extract_article_text(record)} all_checks_accepted = True for name, func in COMPLIANCE_TASKS: check_accepted, details, debug = func(record, extra_data) all_checks_accepted = all_checks_accepted and check_accepted checks[name] = { 'check': check_accepted, 'details': details, 'debug': debug } c = Compliance.get_or_create(pid.object_uuid) results = { 'checks': checks, 'accepted': all_checks_accepted, 'data': { 'doi': get_first_doi(record), 'publisher': get_abbreviated_publisher(record), 'journal': get_abbreviated_journal(record), 'arxiv': get_first_arxiv(record) } } c.add_results(results) c.id_record = pid.object_uuid db.session.add(c) db.session.commit() # send notification about failed checks if not all_checks_accepted: msg = TemplatedMessage( template_html='scoap3_compliance/admin/failed_email.html', subject='SCOAP3 - Compliance check', sender=current_app.config.get('MAIL_DEFAULT_SENDER'), recipients=current_app.config.get('COMPLIANCE_EMAILS'), ctx={'results': results} ) current_app.extensions['mail'].send(msg)
def test_delete(app, db): """Test delete a record.""" # Create a record, revise it and delete it. record = Record.create({"title": "test 1"}) db.session.commit() record["title"] = "test 2" record.commit() db.session.commit() record.delete() db.session.commit() # Deleted records a not retrievable by default pytest.raises(NoResultFound, Record.get_record, record.id) # Deleted records can be retrieved if you explicit request it record = Record.get_record(record.id, with_deleted=True) # Deleted records are empty assert record == {} assert record.model.json is None # Deleted records *cannot* be modified record["title"] = "deleted" assert pytest.raises(MissingModelError, record.commit) # Deleted records *can* be reverted record = record.revert(-2) assert record["title"] == "test 2" db.session.commit() # The "undeleted" record can now be retrieve again record = Record.get_record(record.id) assert record["title"] == "test 2" # Force deleted record cannot be retrieved again record.delete(force=True) db.session.commit() pytest.raises(NoResultFound, Record.get_record, record.id, with_deleted=True)
def record_str(self): """Returns a string representation of referenced Record.""" try: rec = Record.get_record(self.record_id) if 'title' in rec: if '_' in rec['title']: return '%s: %s' % (self.record_id, rec['title']['_']) else: return '%s: %s' % (self.record_id, rec['title']) else: return '%s: %s' % (self.record_id, repr(rec)) except: pass return 'No record for ' + str(self)
def test_record_abuse_report(app, test_records, test_users, login_user): """Test abuse reports send email.""" data = dict( message='my message', name='my name', affiliation='my affiliation', email='*****@*****.**', address='my address', city='my city', country='my country', zipcode='my zipcode', phone='my phone', noresearch=True, abusecontent=False, copyright=False, illegalcontent=False, ) with app.app_context(): record = Record.get_record(test_records[0].record_id) with app.test_client() as client: user = test_users['normal'] login_user(user, client) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] with app.extensions['mail'].record_messages() as outbox: request_res = client.post( url_for('b2share_records_rest.b2rec_abuse', pid_value=test_records[0].pid), data=json.dumps(data), headers=headers) assert request_res.status_code == 200 assert len(outbox) == 1 email = outbox[0] assert email.recipients == [app.config.get('SUPPORT_EMAIL')] assert "Message: {}".format(data['message']) in email.body assert "Reason: No research data" in email.body assert "Link: {}".format( url_for('b2share_records_rest.b2rec_item', pid_value=test_records[0].pid), ) in email.body request_data = json.loads(request_res.get_data(as_text=True)) assert request_data == { 'message':'The record is reported.' }
def proc(article_impact): for country, val in article_impact.results.items(): if country not in data['countries']: data['countries'][country] = 0 data['countries'][country] += val try: record = Record.get_record(PersistentIdentifier.get('recid', article_impact.control_number).object_uuid) author_count = len(record['authors']) except PIDDoesNotExistError: author_count = len(article_impact.details['authors']) sum_values = sum(article_impact.results.values()) if sum_values != author_count: data['not_one'].add((article_impact.control_number, sum_values, author_count))
def test_cli(app): """Test CLI.""" runner = CliRunner() script_info = ScriptInfo(create_app=lambda info: app) assert 'records_metadata' in db.metadata.tables assert 'records_metadata_version' in db.metadata.tables assert 'transaction' in db.metadata.tables from invenio_records.models import RecordMetadata as RM # Test merging a base another file. with runner.isolated_filesystem(): with open('record.json', 'wb') as f: f.write(json.dumps( {"title": "Test"}, ensure_ascii=False ).encode('utf-8')) with open('record.patch', 'wb') as f: f.write(json.dumps([{ "op": "replace", "path": "/title", "value": "Patched Test" }], ensure_ascii=False).encode('utf-8')) result = runner.invoke(cli.records, [], obj=script_info) assert result.exit_code == 0 with app.app_context(): assert RM.query.count() == 0 result = runner.invoke(cli.records, ['create', 'record.json'], obj=script_info) assert result.exit_code == 0 with app.app_context(): assert RM.query.count() == 1 recid = RM.query.first().id result = runner.invoke(cli.records, ['patch', 'record.patch', '-r', recid], obj=script_info) assert result.exit_code == 0 with app.app_context(): record = Record.get_record(recid) assert record['title'] == 'Patched Test' assert record.model.version_id == 2
def record_upsert(json): """Insert or update a record.""" control_number = json.get('control_number', json.get('recid')) if control_number: control_number = int(control_number) pid_type = InspireRecordIdProvider.schema_to_pid_type(json['$schema']) try: pid = PersistentIdentifier.get(pid_type, control_number) record = Record.get_record(pid.object_uuid) record.update(json) record.commit() except PIDDoesNotExistError: record = Record.create(json, id_=None) # Create persistent identifier. inspire_recid_minter(str(record.id), json) return record
def test_record_put_is_disabled(app, test_records, test_users, login_user): """Test invalid modification of record draft with HTTP PUT.""" with app.app_context(): record = Record.get_record(test_records[0].record_id) with app.test_client() as client: user = test_users['admin'] login_user(user, client) headers = [('Content-Type', 'application/json'), ('Accept', 'application/json')] draft_put_res = client.put( url_for('b2share_records_rest.b2rec_item', pid_value=test_records[0].pid), data='{}', headers=headers) assert draft_put_res.status_code == 405
def _get_csv(self, date_from=None, date_to=None): chunk_size = 50 countries = Gdp.query.order_by(Gdp.name.asc()).all() record_ids = ArticlesImpact.query.with_entities(ArticlesImpact.control_number).all() record_ids = [r[0] for r in record_ids] header = ['doi', 'recid', 'journal', 'creation_date', 'primary_category', 'total_authors'] header.extend([c.name.strip().replace(',', '') for c in countries]) si = StringIO.StringIO() cw = csv.writer(si, delimiter=";") cw.writerow(header) for i in range((len(record_ids) // chunk_size) + 1): # calculate chunk start and end position ixn = i * chunk_size current_ids = record_ids[ixn:ixn + chunk_size] for record in ArticlesImpact.query.filter(ArticlesImpact.control_number.in_(current_ids)): # FIXME during country share refactor, this logic should be moved if (date_from is not None and date_from > record.creation_date) or ( date_to is not None and date_to < record.creation_date): continue try: r = Record.get_record(PersistentIdentifier.get('recid', record.control_number).object_uuid) title = get_title(r).lower() if 'addendum' in title or 'corrigendum' in title or 'erratum' in title: continue except PIDDoesNotExistError: pass total_authors = reduce(lambda x, y: x + y, record.results.values(), 0) country_share = [float(record.results[c.name.strip()]) / total_authors if c.name.strip() in record.results else 0 for c in countries] primary_category = record.details.get('arxiv_primary_category') csv_line = [record.doi, record.control_number, record.journal, record.creation_date, primary_category, total_authors] csv_line.extend(country_share) cw.writerow(csv_line) return si.getvalue()
def update_datacite_metadata(doi, object_uuid, job_id): """Update DataCite metadata of a single PersistentIdentifier. :param doi: Value of doi PID, with pid_type='doi'. It could be a normal DOI or a concept DOI. :type doi: str :param object_uuid: Record Metadata UUID. :type object_uuid: str :param job_id: id of the job to which this task belongs. :type job_id: str """ task_details = current_cache.get('update_datacite:task_details') if task_details is None or job_id != task_details['job_id']: return record = Record.get_record(object_uuid) dcp = DataCiteProvider.get(doi) if dcp.pid.status != PIDStatus.REGISTERED: return doc = datacite_v41.serialize(dcp.pid, record) for validator in xsd41(): validator.assertValid(etree.XML(doc.encode('utf8'))) url = None if doi == record.get('doi'): url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=str(record['recid'])) elif doi == record.get('conceptdoi'): url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=str(record['conceptrecid'])) result = dcp.update(url, doc) if result is True: dcp.pid.updated = datetime.utcnow() db.session.commit()
def test_record_add_unknown_fields(app, test_records): """Test adding unknown fields in a record. It should fail.""" for path in [ # all the following paths point to "object" fields in # in the root JSON Schema '/new_field', '/community_specific/new_field', '/creators/0/new_field', '/titles/0/new_field', '/contributors/0/new_field', '/resource_types/0/new_field', '/alternate_identifiers/0/new_field', '/descriptions/0/new_field', '/license/new_field', ]: with app.app_context(): record = Record.get_record(test_records[0].record_id) record = record.patch([ {'op': 'add', 'path': path, 'value': 'any value'} ]) with pytest.raises(ValidationError): record.commit()
def proc(article_impact): try: if 'arxiv_primary_category' in article_impact.details: return pid = PersistentIdentifier.get('recid', article_impact.control_number) record = Record.get_record(pid.object_uuid) if not record: return if 'arxiv_eprints' in record: info('%d: eprints found' % article_impact.control_number) arxiv = (record['arxiv_eprints'][0]['value'].split(':')[1]).split('v')[0] cat = get_arxiv_categories(arxiv)[0] info('category: %s' % cat) if cat: article_impact.details['arxiv_primary_category'] = cat flag_modified(article_impact, 'details') elif 'report_numbers' in record: info('%d: report_numbers found' % article_impact.control_number) cat = get_arxiv_primary_category(record) info('category: %s' % cat) if cat: article_impact.details['arxiv_primary_category'] = cat flag_modified(article_impact, 'details') else: error('%d: no arxiv' % article_impact.control_number) except PIDDoesNotExistError: # records imported from Inspire won't be found pass except AttributeError as e: error('%d: %s' % (article_impact.control_number, e))
def test_record_access_request(app, test_records, test_users, login_user): """Test access requests send email.""" data = dict( message='my message', name='my name', affiliation='my affiliation', email='*****@*****.**', address='my address', city='my city', country='my country', zipcode='my zipcode', phone='my phone', ) with app.app_context(): record = Record.get_record(test_records[0].record_id) with app.test_client() as client: user = test_users['normal'] login_user(user, client) headers = [('Content-Type', 'application/json-patch+json'), ('Accept', 'application/json')] # test with contact_email with app.extensions['mail'].record_messages() as outbox: request_res = client.post( url_for('b2share_records_rest.b2rec_accessrequests', pid_value=test_records[0].pid), data=json.dumps(data), headers=headers) assert request_res.status_code == 200 assert len(outbox) == 1 email = outbox[0] assert email.recipients == [record['contact_email']] assert "Message: {}".format(data['message']) in email.body assert "Link: {}".format( url_for('b2share_records_rest.b2rec_item', pid_value=test_records[0].pid), ) in email.body request_data = json.loads(request_res.get_data(as_text=True)) assert request_data == { 'message': 'An email was sent to the record owner.' } # test with owners del record['contact_email'] record.commit() with app.extensions['mail'].record_messages() as outbox: request_res = client.post( url_for('b2share_records_rest.b2rec_accessrequests', pid_value=test_records[0].pid), data=json.dumps(data), headers=headers) assert request_res.status_code == 200 assert len(outbox) == 1 email = outbox[0] assert email.recipients == [ test_users['deposits_creator'].email ] assert "Message: {}".format(data['message']) in email.body assert "Link: {}".format( url_for('b2share_records_rest.b2rec_item', pid_value=test_records[0].pid), ) in email.body request_data = json.loads(request_res.get_data(as_text=True)) assert request_data == { 'message': 'An email was sent to the record owner.' }
def test_db(): """Test database backend.""" app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get( 'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db' ) FlaskCLI(app) InvenioDB(app) InvenioRecords(app) with app.app_context(): db.drop_all() db.create_all() assert 'records_metadata' in db.metadata.tables assert 'records_metadata_version' in db.metadata.tables assert 'transaction' in db.metadata.tables schema = { 'type': 'object', 'properties': { 'title': {'type': 'string'}, 'field': {'type': 'boolean'}, 'hello': {'type': 'array'}, }, 'required': ['title'], } data = {'title': 'Test', '$schema': schema} from invenio_records.models import RecordMetadata as RM # Create a record with app.app_context(): assert RM.query.count() == 0 record_uuid = Record.create(data).id db.session.commit() assert RM.query.count() == 1 db.session.commit() # Retrieve created record with app.app_context(): record = Record.get_record(record_uuid) assert record.dumps() == data with pytest.raises(NoResultFound): Record.get_record(uuid.uuid4()) record['field'] = True record = record.patch([ {'op': 'add', 'path': '/hello', 'value': ['world']} ]) assert record['hello'] == ['world'] record.commit() db.session.commit() with app.app_context(): record2 = Record.get_record(record_uuid) assert record2.model.version_id == 2 assert record2['field'] assert record2['hello'] == ['world'] db.session.commit() # Cannot commit record without model (i.e. Record.create_record) with app.app_context(): record3 = Record({'title': 'Not possible'}) with pytest.raises(MissingModelError): record3.commit() # Check invalid schema values with app.app_context(): data = { '$schema': 'http://json-schema.org/geo#', 'latitude': 42, 'longitude': 42, } record_with_schema = Record.create(data).commit() db.session.commit() record_with_schema['latitude'] = 'invalid' with pytest.raises(ValidationError): record_with_schema.commit() # Allow types overriding on schema validation with app.app_context(): data = { 'title': 'Test', 'hello': tuple(['foo', 'bar']), '$schema': schema } app.config['RECORDS_VALIDATION_TYPES'] = {} with pytest.raises(ValidationError): Record.create(data).commit() app.config['RECORDS_VALIDATION_TYPES'] = {'array': (list, tuple)} record_uuid = Record.create(data).commit() db.session.commit() with app.app_context(): db.drop_all()
def test_cli(app): """Test CLI.""" runner = CliRunner() script_info = ScriptInfo(create_app=lambda info: app) assert 'records_metadata' in db.metadata.tables assert 'records_metadata_version' in db.metadata.tables assert 'transaction' in db.metadata.tables from invenio_records.models import RecordMetadata as RM # Test merging a base another file. with runner.isolated_filesystem(): with open('record.json', 'wb') as f: f.write(json.dumps( {'title': 'Test'}, ensure_ascii=False ).encode('utf-8')) with open('records.json', 'wb') as f: f.write(json.dumps( [{'title': 'Test1'}, {'title': 'Test2'}], ensure_ascii=False ).encode('utf-8')) with open('record.patch', 'wb') as f: f.write(json.dumps([{ 'op': 'replace', 'path': '/title', 'value': 'Patched Test' }], ensure_ascii=False).encode('utf-8')) result = runner.invoke(cli.records, [], obj=script_info) assert result.exit_code == 0 with app.app_context(): assert RM.query.count() == 0 result = runner.invoke(cli.records, ['create', 'record.json'], obj=script_info) assert result.exit_code == 0 recid = result.output.split('\n')[0] with app.app_context(): assert RM.query.count() == 1 assert recid == str(RM.query.first().id) result = runner.invoke(cli.records, ['patch', 'record.patch', '-i', recid], obj=script_info) assert result.exit_code == 0 with app.app_context(): record = Record.get_record(recid) assert record['title'] == 'Patched Test' assert record.model.version_id == 2 # Test generated UUIDs recid1 = uuid.uuid4() recid2 = uuid.uuid4() assert recid1 != recid2 # More ids than records. result = runner.invoke( cli.records, ['create', 'record.json', '-i', recid1, '--id', recid2], obj=script_info ) assert result.exit_code == -1 result = runner.invoke( cli.records, ['create', 'record.json', '-i', recid], obj=script_info ) assert result.exit_code == 2 result = runner.invoke( cli.records, ['create', 'record.json', '-i', recid, '--force'], obj=script_info ) assert result.exit_code == 0 with app.app_context(): record = Record.get_record(recid) assert record.model.version_id == 3 result = runner.invoke( cli.records, ['create', 'records.json', '-i', recid1], obj=script_info ) assert result.exit_code == -1 result = runner.invoke( cli.records, ['create', 'records.json', '-i', recid1, '-i', recid2], obj=script_info ) assert result.exit_code == 0 with app.app_context(): assert 3 == RM.query.count() # Check metadata after force insert. result = runner.invoke( cli.records, ['create', 'record.json', '-i', recid1, '--force'], obj=script_info ) assert result.exit_code == 0 with app.app_context(): record = Record.get_record(recid1) assert 'Test' == record['title'] assert 'Test1' == record.revisions[0]['title'] # More modifications of record 1. result = runner.invoke( cli.records, ['create', 'records.json', '-i', recid1, '--id', recid2, '--force'], obj=script_info ) assert result.exit_code == 0 with app.app_context(): record = Record.get_record(recid1) assert 'Test1' == record['title'] assert 'Test' == record.revisions[1]['title'] assert 'Test1' == record.revisions[0]['title']
def test_db(): """Test database backend.""" app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = os.environ.get( 'SQLALCHEMY_DATABASE_URI', 'sqlite:///test.db' ) FlaskCLI(app) InvenioDB(app) InvenioRecords(app) with app.app_context(): db.create_all() assert 'records_metadata' in db.metadata.tables assert 'records_metadata_version' in db.metadata.tables assert 'transaction' in db.metadata.tables schema = { 'type': 'object', 'properties': { 'title': {'type': 'string'}, 'field': {'type': 'boolean'}, 'hello': {'type': 'array'}, }, 'required': ['title'], } data = {'title': 'Test', '$schema': schema} from invenio_records.models import RecordMetadata as RM # Create a record with app.app_context(): assert RM.query.count() == 0 record_uuid = Record.create(data).id db.session.commit() assert RM.query.count() == 1 db.session.commit() # Retrieve created record with app.app_context(): record = Record.get_record(record_uuid) assert record.dumps() == data with pytest.raises(NoResultFound): Record.get_record(uuid.uuid4()) record['field'] = True record = record.patch([ {'op': 'add', 'path': '/hello', 'value': ['world']} ]) assert record['hello'] == ['world'] record.commit() db.session.commit() with app.app_context(): record2 = Record.get_record(record_uuid) assert record2.model.version_id == 2 assert record2['field'] assert record2['hello'] == ['world'] db.session.commit() # Cannot commit record without model (i.e. Record.create_record) with app.app_context(): record3 = Record({'title': 'Not possible'}) with pytest.raises(MissingModelError): record3.commit() with app.app_context(): db.drop_all()
def test_revisions(app, db): """Test revisions.""" # Create a record and make modifications to it. record = Record.create({"title": "test 1"}) rec_uuid = record.id db.session.commit() record["title"] = "test 2" record.commit() db.session.commit() record["title"] = "test 3" record.commit() db.session.commit() # Get the record record = Record.get_record(rec_uuid) assert record["title"] == "test 3" assert record.revision_id == 2 # Retrieve specific revisions rev1 = record.revisions[0] assert rev1["title"] == "test 1" assert rev1.revision_id == 0 rev2 = record.revisions[1] assert rev2["title"] == "test 2" assert rev2.revision_id == 1 # Latest revision is identical to record. rev_latest = record.revisions[-1] assert dict(rev_latest) == dict(record) # Revert to a specific revision record = record.revert(rev1.revision_id) assert record["title"] == "test 1" assert record.created == rev1.created assert record.updated != rev1.updated assert record.revision_id == 3 db.session.commit() # Get the record again and check it record = Record.get_record(rec_uuid) assert record["title"] == "test 1" assert record.revision_id == 3 # Make a change and ensure revision id is changed as well. record["title"] = "modification" record.commit() db.session.commit() assert record.revision_id == 4 # Iterate over revisions assert len(record.revisions) == 5 revs = list(record.revisions) assert revs[0]["title"] == "test 1" assert revs[1]["title"] == "test 2" assert revs[2]["title"] == "test 3" assert revs[3]["title"] == "test 1" assert revs[4]["title"] == "modification" assert 2 in record.revisions assert 5 not in record.revisions