diff --git a/rdr_service/api/base_api.py b/rdr_service/api/base_api.py index bbbbd3215..b4e53306d 100644 --- a/rdr_service/api/base_api.py +++ b/rdr_service/api/base_api.py @@ -22,7 +22,6 @@ DEFAULT_MAX_RESULTS = 100 -MAX_MAX_RESULTS = 10000 def log_api_request(log: RequestsLog = None, model_obj=None): @@ -130,6 +129,7 @@ class BaseApi(Resource, ApiUtilMixin): def __init__(self, dao, get_returns_children=False): self.dao = dao self._get_returns_children = get_returns_children + self.max_max_results = 10000 def _get_request_arg_bool(self, key, default=False): """ @@ -280,8 +280,8 @@ def _make_query(self, check_invalid=False): max_results = int(request.args["_count"]) if max_results < 1: raise BadRequest("_count < 1") - if max_results > MAX_MAX_RESULTS: - raise BadRequest("_count exceeds {}".format(MAX_MAX_RESULTS)) + if max_results > self.max_max_results: + raise BadRequest("_count exceeds {}".format(self.max_max_results)) elif key == "_token": pagination_token = value elif key == "_sort" or key == "_sort:asc": diff --git a/rdr_service/api/redcap_workbench_audit_api.py b/rdr_service/api/redcap_workbench_audit_api.py index 94a10660d..fb2342508 100644 --- a/rdr_service/api/redcap_workbench_audit_api.py +++ b/rdr_service/api/redcap_workbench_audit_api.py @@ -13,6 +13,7 @@ class BaseRedcapApi(BaseApi): def __init__(self): super().__init__(WorkbenchWorkspaceAuditDao()) self.get_filters = None + self.max_max_results = 6000 @auth_required(REDCAP_AND_RDR) def get(self): @@ -58,7 +59,16 @@ def __init__(self): def get(self): super(RedcapWorkbenchAuditApi, self).get() - return self.dao.workspace_dao.get_redcap_audit_workspaces(**self.get_filters) + + # No pagination required, response always contains a single record + if self.get_filters.get('snapshot_id') is not None or self.get_filters.get('workspace_id') is not None: + return self.dao.workspace_dao.get_redcap_audit_workspaces(self.get_filters.get('snapshot_id'), + self.get_filters.get('workspace_id')) + # Pagination is required for last_snapshot_id and returning all snapshots + if self.get_filters.get('last_snapshot_id') is not None: + self.dao.workspace_dao.last_snapshot_id = self.get_filters.get('last_snapshot_id') + response = self._query("snapshotId") + return response def _do_insert(self, m): audit_records = super()._do_insert(m) @@ -77,6 +87,12 @@ def _do_insert(self, m): in_seconds=30, queue='resource-rebuild') return audit_records + @classmethod + def _make_resource_url(cls, response_json, id_field, participant_id): + from rdr_service import main + url = main.api.url_for(cls, snapshot_id=response_json[0][id_field], _external=True) + return url + class RedcapResearcherAuditApi(BaseRedcapApi): def __init__(self): super().__init__() diff --git a/rdr_service/dao/workbench_dao.py b/rdr_service/dao/workbench_dao.py index a3aa155b3..24c6105bb 100644 --- a/rdr_service/dao/workbench_dao.py +++ b/rdr_service/dao/workbench_dao.py @@ -36,6 +36,7 @@ WorkbenchResearcherAccessTierShortName, WorkbenchResearcherEthnicCategory, WorkbenchResearcherSexualOrientationV2, \ WorkbenchResearcherGenderIdentity, WorkbenchResearcherYesNoPreferNot, WorkbenchResearcherSexAtBirthV2,\ WorkbenchResearcherEducationV2 +from rdr_service.query import Results from rdr_service.services.system_utils import list_chunks @@ -43,6 +44,7 @@ class WorkbenchWorkspaceDao(UpdatableDao): def __init__(self): super().__init__(WorkbenchWorkspaceApproved, order_by_ending=["id"]) self.is_backfill = False + self.last_snapshot_id = None self.workspace_snapshot_dao = WorkbenchWorkspaceHistoryDao() def get_id(self, obj): @@ -319,12 +321,10 @@ def remove_workspace_by_workspace_id_with_session(session, workspace_id): def get_redcap_audit_workspaces( self, - last_snapshot_id=None, snapshot_id=None, workspace_id=None ): - results = [] with self.session() as session: workbench_workspace_snapshot_alias = aliased(WorkbenchWorkspaceSnapshot) active_id = session.query(func.max(WorkbenchWorkspaceSnapshot.id))\ @@ -355,119 +355,125 @@ def get_redcap_audit_workspaces( if workspace_id: query = query.filter(WorkbenchWorkspaceSnapshot.workspaceSourceId == workspace_id)\ .order_by(desc(subquery.c.id)).limit(1) + return self.get_redcap_audit_workspaces_in_list(query.all()) elif snapshot_id: query = query.filter(subquery.c.id == snapshot_id) - elif last_snapshot_id: - query = query.filter(subquery.c.id > last_snapshot_id)\ + return self.get_redcap_audit_workspaces_in_list(query.all()) + elif self.last_snapshot_id: + query = query.filter(subquery.c.id > self.last_snapshot_id)\ .order_by(subquery.c.id) - items = query.all() - for workspace, researcher, current_id, current_status in items: - verified_institutional_affiliation = {} - affiliations = [] - if researcher.workbenchInstitutionalAffiliations: - for affiliation in researcher.workbenchInstitutionalAffiliations: - affiliations.append( - { - "institution": affiliation.institution, - "role": affiliation.role, - "isVerified": affiliation.isVerified, - "nonAcademicAffiliation": - str(WorkbenchInstitutionNonAcademic(affiliation.nonAcademicAffiliation)) - if affiliation.nonAcademicAffiliation else 'UNSET' - } - ) - if affiliation.isVerified: - verified_institutional_affiliation = { - "institution": affiliation.institution, - "role": affiliation.role, - "nonAcademicAffiliation": - str(WorkbenchInstitutionNonAcademic(affiliation.nonAcademicAffiliation)) - if affiliation.nonAcademicAffiliation else 'UNSET' - } - workspace_researcher = { - "userId": researcher.userSourceId, - "creationTime": researcher.creationTime, - "modifiedTime": researcher.modifiedTime, - "givenName": researcher.givenName, - "familyName": researcher.familyName, - "email": researcher.email, - "accessTier": researcher.get_access_tier(), - "verifiedInstitutionalAffiliation": verified_institutional_affiliation, - "affiliations": affiliations - } + return query - exist = False - for result in results: - if result['snapshotId'] == current_id: - result['workspaceResearchers'].append(workspace_researcher) - exist = True - break - if exist: - continue - record = { - 'snapshotId': current_id, - 'workspaceId': workspace.workspaceSourceId, - 'name': workspace.name, - 'creationTime': workspace.creationTime, - 'modifiedTime': workspace.modifiedTime, - 'status': str(WorkbenchWorkspaceStatus(current_status)), - 'workspaceUsers': [ + def get_redcap_audit_workspaces_in_list(self, items): + results = [] + + for workspace, researcher, current_id, current_status in items: + verified_institutional_affiliation = {} + affiliations = [] + if researcher.workbenchInstitutionalAffiliations: + for affiliation in researcher.workbenchInstitutionalAffiliations: + affiliations.append( { - "userId": user.userId, - "role": str(WorkbenchWorkspaceUserRole(user.role)) if user.role else 'UNSET', - "status": str(WorkbenchWorkspaceStatus(user.status)) if user.status else 'UNSET', - "isCreator": user.isCreator - } for user in workspace.workbenchWorkspaceUser - ] if workspace.workbenchWorkspaceUser else [], - 'workspaceResearchers': [workspace_researcher], - "excludeFromPublicDirectory": workspace.excludeFromPublicDirectory, - "ethicalLegalSocialImplications": workspace.ethicalLegalSocialImplications, - "reviewRequested": workspace.reviewRequested if workspace.reviewRequested else False, - "diseaseFocusedResearch": workspace.diseaseFocusedResearch, - "diseaseFocusedResearchName": workspace.diseaseFocusedResearchName, - "otherPurposeDetails": workspace.otherPurposeDetails, - "methodsDevelopment": workspace.methodsDevelopment, - "controlSet": workspace.controlSet, - "ancestry": workspace.ancestry, - "socialBehavioral": workspace.socialBehavioral, - "populationHealth": workspace.populationHealth, - "drugDevelopment": workspace.drugDevelopment, - "commercialPurpose": workspace.commercialPurpose, - "educational": workspace.educational, - "otherPurpose": workspace.otherPurpose, - "accessTier": str(WorkbenchWorkspaceAccessTier(workspace.accessTier - if workspace.accessTier else 0)), - "scientificApproaches": workspace.scientificApproaches, - "intendToStudy": workspace.intendToStudy, - "findingsFromStudy": workspace.findingsFromStudy, - "focusOnUnderrepresentedPopulations": workspace.focusOnUnderrepresentedPopulations, - "workspaceDemographic": { - "raceEthnicity": [str(WorkbenchWorkspaceRaceEthnicity(value)) - for value in workspace.raceEthnicity] if workspace.raceEthnicity else None, - "age": [str(WorkbenchWorkspaceAge(value)) for value in workspace.age] - if workspace.age else None, - "sexAtBirth": str(WorkbenchWorkspaceSexAtBirth(workspace.sexAtBirth)) - if workspace.sexAtBirth else None, - "genderIdentity": str(WorkbenchWorkspaceGenderIdentity(workspace.genderIdentity)) - if workspace.genderIdentity else None, - "sexualOrientation": str(WorkbenchWorkspaceSexualOrientation(workspace.sexualOrientation)) - if workspace.sexualOrientation else None, - "geography": str(WorkbenchWorkspaceGeography(workspace.geography)) - if workspace.geography else None, - "disabilityStatus": str(WorkbenchWorkspaceDisabilityStatus(workspace.disabilityStatus)) - if workspace.disabilityStatus else None, - "accessToCare": str(WorkbenchWorkspaceAccessToCare(workspace.accessToCare)) - if workspace.accessToCare else None, - "educationLevel": str(WorkbenchWorkspaceEducationLevel(workspace.educationLevel)) - if workspace.educationLevel else None, - "incomeLevel": str(WorkbenchWorkspaceIncomeLevel(workspace.incomeLevel)) - if workspace.incomeLevel else None, - "others": workspace.others - }, - "cdrVersion": workspace.cdrVersion - } - results.append(record) + "institution": affiliation.institution, + "role": affiliation.role, + "isVerified": affiliation.isVerified, + "nonAcademicAffiliation": + str(WorkbenchInstitutionNonAcademic(affiliation.nonAcademicAffiliation)) + if affiliation.nonAcademicAffiliation else 'UNSET' + } + ) + if affiliation.isVerified: + verified_institutional_affiliation = { + "institution": affiliation.institution, + "role": affiliation.role, + "nonAcademicAffiliation": + str(WorkbenchInstitutionNonAcademic(affiliation.nonAcademicAffiliation)) + if affiliation.nonAcademicAffiliation else 'UNSET' + } + workspace_researcher = { + "userId": researcher.userSourceId, + "creationTime": researcher.creationTime, + "modifiedTime": researcher.modifiedTime, + "givenName": researcher.givenName, + "familyName": researcher.familyName, + "email": researcher.email, + "accessTier": researcher.get_access_tier(), + "verifiedInstitutionalAffiliation": verified_institutional_affiliation, + "affiliations": affiliations + } + + exist = False + for result in results: + if result['snapshotId'] == current_id: + result['workspaceResearchers'].append(workspace_researcher) + exist = True + break + if exist: + continue + record = { + 'snapshotId': current_id, + 'workspaceId': workspace.workspaceSourceId, + 'name': workspace.name, + 'creationTime': workspace.creationTime, + 'modifiedTime': workspace.modifiedTime, + 'status': str(WorkbenchWorkspaceStatus(current_status)), + 'workspaceUsers': [ + { + "userId": user.userId, + "role": str(WorkbenchWorkspaceUserRole(user.role)) if user.role else 'UNSET', + "status": str(WorkbenchWorkspaceStatus(user.status)) if user.status else 'UNSET', + "isCreator": user.isCreator + } for user in workspace.workbenchWorkspaceUser + ] if workspace.workbenchWorkspaceUser else [], + 'workspaceResearchers': [workspace_researcher], + "excludeFromPublicDirectory": workspace.excludeFromPublicDirectory, + "ethicalLegalSocialImplications": workspace.ethicalLegalSocialImplications, + "reviewRequested": workspace.reviewRequested if workspace.reviewRequested else False, + "diseaseFocusedResearch": workspace.diseaseFocusedResearch, + "diseaseFocusedResearchName": workspace.diseaseFocusedResearchName, + "otherPurposeDetails": workspace.otherPurposeDetails, + "methodsDevelopment": workspace.methodsDevelopment, + "controlSet": workspace.controlSet, + "ancestry": workspace.ancestry, + "socialBehavioral": workspace.socialBehavioral, + "populationHealth": workspace.populationHealth, + "drugDevelopment": workspace.drugDevelopment, + "commercialPurpose": workspace.commercialPurpose, + "educational": workspace.educational, + "otherPurpose": workspace.otherPurpose, + "accessTier": str(WorkbenchWorkspaceAccessTier(workspace.accessTier + if workspace.accessTier else 0)), + "scientificApproaches": workspace.scientificApproaches, + "intendToStudy": workspace.intendToStudy, + "findingsFromStudy": workspace.findingsFromStudy, + "focusOnUnderrepresentedPopulations": workspace.focusOnUnderrepresentedPopulations, + "workspaceDemographic": { + "raceEthnicity": [str(WorkbenchWorkspaceRaceEthnicity(value)) + for value in workspace.raceEthnicity] if workspace.raceEthnicity else None, + "age": [str(WorkbenchWorkspaceAge(value)) for value in workspace.age] + if workspace.age else None, + "sexAtBirth": str(WorkbenchWorkspaceSexAtBirth(workspace.sexAtBirth)) + if workspace.sexAtBirth else None, + "genderIdentity": str(WorkbenchWorkspaceGenderIdentity(workspace.genderIdentity)) + if workspace.genderIdentity else None, + "sexualOrientation": str(WorkbenchWorkspaceSexualOrientation(workspace.sexualOrientation)) + if workspace.sexualOrientation else None, + "geography": str(WorkbenchWorkspaceGeography(workspace.geography)) + if workspace.geography else None, + "disabilityStatus": str(WorkbenchWorkspaceDisabilityStatus(workspace.disabilityStatus)) + if workspace.disabilityStatus else None, + "accessToCare": str(WorkbenchWorkspaceAccessToCare(workspace.accessToCare)) + if workspace.accessToCare else None, + "educationLevel": str(WorkbenchWorkspaceEducationLevel(workspace.educationLevel)) + if workspace.educationLevel else None, + "incomeLevel": str(WorkbenchWorkspaceIncomeLevel(workspace.incomeLevel)) + if workspace.incomeLevel else None, + "others": workspace.others + }, + "cdrVersion": workspace.cdrVersion + } + results.append(record) return results @@ -1304,7 +1310,7 @@ def get_all_with_children(self): class WorkbenchWorkspaceAuditDao(UpdatableDao): def __init__(self): - super().__init__(WorkbenchWorkspaceAuditDao, order_by_ending=["id"]) + super().__init__(WorkbenchWorkspaceSnapshot, order_by_ending=["id"]) self.workspace_dao = WorkbenchWorkspaceDao() self.workspace_snapshot_dao = WorkbenchWorkspaceHistoryDao() @@ -1376,6 +1382,46 @@ def to_client_json(self, obj): for record in obj: result.append(json.loads(record.resource)) return result + elif isinstance(obj, dict): + return [obj] + + def _initialize_query(self, session, query_def): + return self.workspace_dao.get_redcap_audit_workspaces() + + def query(self, query_definition): + if query_definition.invalid_filters and not query_definition.field_filters: + raise BadRequest("No valid fields were provided") + if not self.order_by_ending: + raise BadRequest(f"Can't query on type {self.model_type} -- no order by ending specified") + with self.session() as session: + total = None + query, field_names = self._make_query(session, query_definition) + + # For WorkbenchWorkspaceAudit, we want to set the results limit after the results have been placed in a list + workbench_max_results = query_definition.max_results + 1 + query_results = query.with_session(session).limit(None).all() + items = self.workspace_dao.get_redcap_audit_workspaces_in_list(query_results)[:workbench_max_results] + + if query_definition.include_total: + total = self._count_query(session, query_definition) + if not items: + return Results([], total=total) + + if len(items) > query_definition.max_results: + # Items, pagination token, and more are available + page = items[0: query_definition.max_results] + token = self._make_pagination_token( + items[query_definition.max_results - 1], + field_names=['snapshotId'] + ) + return Results(page, token, more_available=True, total=total) + else: + token = ( + self._make_pagination_token( + items[-1].asdict(), + field_names) if query_definition.always_return_token else None + ) + return Results(items, token, more_available=False, total=total) def remove_approved_workspace_with_session(self, session, workspace_snapshot_id): workspace_snapshot = self.workspace_snapshot_dao.get_snapshot_by_id_with_session(session, workspace_snapshot_id) diff --git a/tests/api_tests/test_workspace_audit_and_research_directory_api.py b/tests/api_tests/test_workspace_audit_and_research_directory_api.py index 757a11c84..6d95a834a 100644 --- a/tests/api_tests/test_workspace_audit_and_research_directory_api.py +++ b/tests/api_tests/test_workspace_audit_and_research_directory_api.py @@ -5,6 +5,18 @@ class ResearchProjectsDirectoryApiTest(BaseTestCase): def setUp(self): super().setUp(with_data=False) + self.expected_workbench_snapshot_keys = ['snapshotId', 'workspaceId', 'name', 'creationTime', 'modifiedTime', + 'status', 'workspaceUsers', 'workspaceResearchers', + 'excludeFromPublicDirectory', 'ethicalLegalSocialImplications', + 'reviewRequested', 'diseaseFocusedResearch', + 'diseaseFocusedResearchName', + 'otherPurposeDetails', 'methodsDevelopment', 'controlSet', 'ancestry', + 'socialBehavioral', 'populationHealth', 'drugDevelopment', + 'commercialPurpose', 'educational', 'otherPurpose', 'accessTier', + 'scientificApproaches', 'intendToStudy', 'findingsFromStudy', + 'focusOnUnderrepresentedPopulations', 'workspaceDemographic', + 'cdrVersion'] + def test_get_research_projects_directory_end_to_end(self): # create researchers researchers_json = [ @@ -939,138 +951,29 @@ def test_workspace_audit_sync_api(self): self.send_post('workbench/directory/workspaces', request_data=request_json) # test workbench audit result = self.send_get('workbench/audit/workspace/snapshots') - self.assertEqual(len(result), 2) - self.assertIn({'snapshotId': 1, 'workspaceId': 0, 'name': 'workspace name str', - 'creationTime': '2019-11-25T17:43:41.085000', 'modifiedTime': '2019-11-25T17:43:41.085000', - 'status': 'ACTIVE', - 'workspaceUsers': [{'userId': 0, 'role': 'READER', 'status': 'ACTIVE', 'isCreator': True}, - {'userId': 1, 'role': 'OWNER', 'status': 'ACTIVE', 'isCreator': False}], - 'workspaceResearchers': [ - {'userId': 0, 'creationTime': '2019-11-26T21:21:13.056000', - 'modifiedTime': '2019-11-26T21:21:13.056000', 'givenName': 'given name 1', - 'familyName': 'family name 1', 'email': None, 'accessTier': 'REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution1', 'role': 'institution role 1', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}, - {'userId': 1, 'creationTime': '2019-11-27T21:21:13.056000', - 'modifiedTime': '2019-11-27T21:21:13.056000', 'givenName': 'given name 2', - 'familyName': 'family name 2', 'email': None, 'accessTier': 'REGISTERED_AND_CONTROLLED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution2', 'role': 'institution role 2', - 'isVerified': None, 'nonAcademicAffiliation': 'UNSET'}, - {'institution': 'institution22', 'role': 'institution role 22', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}], - 'excludeFromPublicDirectory': False, 'ethicalLegalSocialImplications': True, - 'reviewRequested': False, 'diseaseFocusedResearch': True, - 'diseaseFocusedResearchName': 'disease focused research name str', - 'otherPurposeDetails': 'other purpose details str', 'methodsDevelopment': True, - 'controlSet': True, 'ancestry': True, 'socialBehavioral': True, 'populationHealth': True, - 'drugDevelopment': True, 'commercialPurpose': True, 'educational': True, 'otherPurpose': True, - 'scientificApproaches': 'reasonForInvestigation string', 'intendToStudy': 'intendToStudy string', - 'findingsFromStudy': 'findingsFromStudy string', 'focusOnUnderrepresentedPopulations': True, - 'accessTier': 'UNSET', - 'workspaceDemographic': { - 'raceEthnicity': ['AIAN', 'MENA'], 'age': ['AGE_0_11', 'AGE_65_74'], - 'sexAtBirth': None, 'genderIdentity': 'OTHER_THAN_MAN_WOMAN', - 'sexualOrientation': 'OTHER_THAN_STRAIGHT', 'geography': 'RURAL', - 'disabilityStatus': 'DISABILITY', 'accessToCare': 'NOT_EASILY_ACCESS_CARE', - 'educationLevel': 'LESS_THAN_HIGH_SCHOOL', - 'incomeLevel': 'BELOW_FEDERAL_POVERTY_LEVEL_200_PERCENT', - 'others': 'string'}, - 'cdrVersion': cdr_version - }, result) - self.assertIn({'snapshotId': 2, 'workspaceId': 1, 'name': 'workspace name str 2', - 'creationTime': '2019-11-25T17:43:41.085000', 'modifiedTime': '2019-11-25T17:43:41.085000', - 'status': 'INACTIVE', - 'workspaceUsers': [ - {'userId': 0, 'role': 'OWNER', 'status': 'ACTIVE', 'isCreator': False}, - {'userId': 1, 'role': 'READER', 'status': 'ACTIVE', 'isCreator': False} - ], - 'workspaceResearchers': [ - {'userId': 0, 'creationTime': '2019-11-26T21:21:13.056000', - 'modifiedTime': '2019-11-26T21:21:13.056000', 'givenName': 'given name 1', - 'familyName': 'family name 1', 'email': None, 'accessTier': 'REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution1', 'role': 'institution role 1', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}, - {'userId': 1, 'creationTime': '2019-11-27T21:21:13.056000', - 'modifiedTime': '2019-11-27T21:21:13.056000', 'givenName': 'given name 2', - 'familyName': 'family name 2', 'email': None, 'accessTier': 'REGISTERED_AND_CONTROLLED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution2', 'role': 'institution role 2', 'isVerified': None, - 'nonAcademicAffiliation': 'UNSET'}, - {'institution': 'institution22', 'role': 'institution role 22', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}], - 'excludeFromPublicDirectory': False, 'ethicalLegalSocialImplications': False, - 'reviewRequested': False, 'diseaseFocusedResearch': True, - 'diseaseFocusedResearchName': 'disease focused research name str 2', - 'otherPurposeDetails': 'other purpose details str 2', 'methodsDevelopment': False, - 'controlSet': False, 'ancestry': False, 'socialBehavioral': False, 'populationHealth': False, - 'drugDevelopment': False, 'commercialPurpose': False, 'educational': False, - 'otherPurpose': False, 'scientificApproaches': 'reasonForInvestigation string2', - 'intendToStudy': 'intendToStudy string2', 'findingsFromStudy': 'findingsFromStudy string2', - 'focusOnUnderrepresentedPopulations': None, 'accessTier': 'UNSET', - 'workspaceDemographic': { - 'raceEthnicity': None, 'age': None, 'sexAtBirth': None, 'genderIdentity': None, - 'sexualOrientation': None, 'geography': None, 'disabilityStatus': None, - 'accessToCare': None, 'educationLevel': None, 'incomeLevel': None, 'others': None}, - 'cdrVersion': cdr_version - }, result) + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('entry')) + self.assertEqual(len(result.get('entry')), 2) + + result_data_1 = result.get('entry')[0]['resource'][0] + result_data_2 = result.get('entry')[1]['resource'][0] + self.assertEqual(result_data_1.get('snapshotId'), 1) + self.assertEqual(result_data_2.get('snapshotId'), 2) + self.assertEqual(list(result_data_1.keys()), self.expected_workbench_snapshot_keys) + self.assertEqual(list(result_data_2.keys()), self.expected_workbench_snapshot_keys) result = self.send_get('workbench/audit/workspace/snapshots?last_snapshot_id=1') - self.assertEqual(len(result), 1) - self.assertIn({'snapshotId': 2, 'workspaceId': 1, 'name': 'workspace name str 2', - 'creationTime': '2019-11-25T17:43:41.085000', 'modifiedTime': '2019-11-25T17:43:41.085000', - 'status': 'INACTIVE', - 'workspaceUsers': [ - {'userId': 0, 'role': 'OWNER', 'status': 'ACTIVE', 'isCreator': False}, - {'userId': 1, 'role': 'READER', 'status': 'ACTIVE', 'isCreator': False} - ], - 'workspaceResearchers': [ - {'userId': 0, 'creationTime': '2019-11-26T21:21:13.056000', - 'modifiedTime': '2019-11-26T21:21:13.056000', 'givenName': 'given name 1', - 'familyName': 'family name 1', 'email': None, 'accessTier': 'REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution1', 'role': 'institution role 1', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}, - {'userId': 1, 'creationTime': '2019-11-27T21:21:13.056000', - 'modifiedTime': '2019-11-27T21:21:13.056000', 'givenName': 'given name 2', - 'familyName': 'family name 2', 'email': None, 'accessTier': 'REGISTERED_AND_CONTROLLED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution2', 'role': 'institution role 2', 'isVerified': None, - 'nonAcademicAffiliation': 'UNSET'}, - {'institution': 'institution22', 'role': 'institution role 22', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}], - 'excludeFromPublicDirectory': False, 'ethicalLegalSocialImplications': False, - 'reviewRequested': False, 'diseaseFocusedResearch': True, - 'diseaseFocusedResearchName': 'disease focused research name str 2', - 'otherPurposeDetails': 'other purpose details str 2', 'methodsDevelopment': False, - 'controlSet': False, 'ancestry': False, 'socialBehavioral': False, 'populationHealth': False, - 'drugDevelopment': False, 'commercialPurpose': False, 'educational': False, - 'otherPurpose': False, 'scientificApproaches': 'reasonForInvestigation string2', - 'intendToStudy': 'intendToStudy string2', 'findingsFromStudy': 'findingsFromStudy string2', - 'focusOnUnderrepresentedPopulations': None, 'accessTier': 'UNSET', - 'workspaceDemographic': { - 'raceEthnicity': None, 'age': None, 'sexAtBirth': None, 'genderIdentity': None, - 'sexualOrientation': None, 'geography': None, 'disabilityStatus': None, - 'accessToCare': None, 'educationLevel': None, 'incomeLevel': None, 'others': None}, - 'cdrVersion': cdr_version - }, result) + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('entry')) + self.assertEqual(len(result.get('entry')), 1) + result_data_1 = result.get('entry')[0]['resource'][0] + self.assertEqual(result_data_1.get('snapshotId'), 2) + self.assertEqual(list(result_data_1.keys()), self.expected_workbench_snapshot_keys) result = self.send_get('workbench/audit/workspace/snapshots?last_snapshot_id=2') - self.assertEqual(len(result), 0) + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('entry')) + self.assertEqual(len(result.get('entry')), 0) result = self.send_get('workbench/audit/workspace/snapshots?snapshot_id=1') self.assertEqual(len(result), 1) @@ -1240,7 +1143,9 @@ def test_inactive_workspace_use_most_recent_active_users_info(self): ] self.send_post('workbench/directory/workspaces', request_data=request_json) result = self.send_get('workbench/audit/workspace/snapshots') - self.assertEqual(len(result), 1) + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('entry')) + self.assertEqual(len(result.get('entry')), 1) # workbench will remove users info when a workspace is set to INACTIVE # set this workspace to INACTIVE and remove the users, re-sync to RDR DB @@ -1293,96 +1198,23 @@ def test_inactive_workspace_use_most_recent_active_users_info(self): ] self.send_post('workbench/directory/workspaces', request_data=request_json) result = self.send_get('workbench/audit/workspace/snapshots') - self.assertEqual(len(result), 2) - self.assertIn({'snapshotId': 1, 'workspaceId': 0, 'name': 'workspace name str', - 'creationTime': '2019-11-25T17:43:41.085000', 'modifiedTime': '2019-11-25T17:43:41.085000', - 'status': 'ACTIVE', - 'workspaceUsers': [{'userId': 0, 'role': 'READER', 'status': 'ACTIVE', 'isCreator': True}, - {'userId': 1, 'role': 'OWNER', 'status': 'ACTIVE', 'isCreator': False}], - 'workspaceResearchers': [ - {'userId': 0, 'creationTime': '2019-11-26T21:21:13.056000', - 'modifiedTime': '2019-11-26T21:21:13.056000', 'givenName': 'given name 1', - 'familyName': 'family name 1', 'email': None, 'accessTier': 'NOT_REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution1', 'role': 'institution role 1', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}, - {'userId': 1, 'creationTime': '2019-11-27T21:21:13.056000', - 'modifiedTime': '2019-11-27T21:21:13.056000', 'givenName': 'given name 2', - 'familyName': 'family name 2', 'email': None, 'accessTier': 'NOT_REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution2', 'role': 'institution role 2', - 'isVerified': None, 'nonAcademicAffiliation': 'UNSET'}, - {'institution': 'institution22', 'role': 'institution role 22', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}], - 'excludeFromPublicDirectory': False, 'ethicalLegalSocialImplications': True, - 'reviewRequested': False, 'diseaseFocusedResearch': True, - 'diseaseFocusedResearchName': 'disease focused research name str', - 'otherPurposeDetails': 'other purpose details str', 'methodsDevelopment': True, - 'controlSet': True, 'ancestry': True, 'socialBehavioral': True, 'populationHealth': True, - 'drugDevelopment': True, 'commercialPurpose': True, 'educational': True, 'otherPurpose': True, - 'scientificApproaches': 'reasonForInvestigation string', 'intendToStudy': 'intendToStudy string', - 'findingsFromStudy': 'findingsFromStudy string', 'focusOnUnderrepresentedPopulations': True, - 'accessTier': 'UNSET', - 'workspaceDemographic': { - 'raceEthnicity': ['AIAN', 'MENA'], 'age': ['AGE_0_11', 'AGE_65_74'], - 'sexAtBirth': None, 'genderIdentity': 'OTHER_THAN_MAN_WOMAN', - 'sexualOrientation': 'OTHER_THAN_STRAIGHT', 'geography': 'RURAL', - 'disabilityStatus': 'DISABILITY', 'accessToCare': 'NOT_EASILY_ACCESS_CARE', - 'educationLevel': 'LESS_THAN_HIGH_SCHOOL', - 'incomeLevel': 'BELOW_FEDERAL_POVERTY_LEVEL_200_PERCENT', - 'others': 'string'}, - 'cdrVersion': 'irving' - }, result) - self.assertIn({'snapshotId': 2, 'workspaceId': 0, 'name': 'workspace name str', - 'creationTime': '2019-11-25T17:43:41.085000', 'modifiedTime': '2019-11-25T17:43:41.085000', - 'status': 'INACTIVE', - 'workspaceUsers': [{'userId': 0, 'role': 'READER', 'status': 'ACTIVE', 'isCreator': True}, - {'userId': 1, 'role': 'OWNER', 'status': 'ACTIVE', 'isCreator': False}], - 'workspaceResearchers': [ - {'userId': 0, 'creationTime': '2019-11-26T21:21:13.056000', - 'modifiedTime': '2019-11-26T21:21:13.056000', 'givenName': 'given name 1', - 'familyName': 'family name 1', 'email': None, 'accessTier': 'NOT_REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution1', 'role': 'institution role 1', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}, - {'userId': 1, 'creationTime': '2019-11-27T21:21:13.056000', - 'modifiedTime': '2019-11-27T21:21:13.056000', 'givenName': 'given name 2', - 'familyName': 'family name 2', 'email': None, 'accessTier': 'NOT_REGISTERED', - 'verifiedInstitutionalAffiliation': {}, - 'affiliations': [ - {'institution': 'institution2', 'role': 'institution role 2', - 'isVerified': None, 'nonAcademicAffiliation': 'UNSET'}, - {'institution': 'institution22', 'role': 'institution role 22', 'isVerified': None, - 'nonAcademicAffiliation': 'INDUSTRY'} - ]}], - 'excludeFromPublicDirectory': False, 'ethicalLegalSocialImplications': True, - 'reviewRequested': False, 'diseaseFocusedResearch': True, - 'diseaseFocusedResearchName': 'disease focused research name str', - 'otherPurposeDetails': 'other purpose details str', 'methodsDevelopment': True, - 'controlSet': True, 'ancestry': True, 'socialBehavioral': True, 'populationHealth': True, - 'drugDevelopment': True, 'commercialPurpose': True, 'educational': True, 'otherPurpose': True, - 'scientificApproaches': 'reasonForInvestigation string', 'intendToStudy': 'intendToStudy string', - 'findingsFromStudy': 'findingsFromStudy string', 'focusOnUnderrepresentedPopulations': True, - 'accessTier': 'UNSET', - 'workspaceDemographic': { - 'raceEthnicity': ['AIAN', 'MENA'], 'age': ['AGE_0_11', 'AGE_65_74'], - 'sexAtBirth': None, 'genderIdentity': 'OTHER_THAN_MAN_WOMAN', - 'sexualOrientation': 'OTHER_THAN_STRAIGHT', 'geography': 'RURAL', - 'disabilityStatus': 'DISABILITY', 'accessToCare': 'NOT_EASILY_ACCESS_CARE', - 'educationLevel': 'LESS_THAN_HIGH_SCHOOL', - 'incomeLevel': 'BELOW_FEDERAL_POVERTY_LEVEL_200_PERCENT', - 'others': 'string'}, - 'cdrVersion': 'irving' - }, result) + + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('entry')) + self.assertEqual(len(result.get('entry')), 2) + + result_snapshot_1 = result.get('entry')[0]['resource'][0] + result_snapshot_2 = result.get('entry')[1]['resource'][0] + + self.assertEqual(list(result_snapshot_1.keys()), self.expected_workbench_snapshot_keys) + self.assertEqual(list(result_snapshot_2.keys()), self.expected_workbench_snapshot_keys) + self.assertEqual(result_snapshot_1.get('snapshotId'), 1) + self.assertEqual(result_snapshot_2.get('snapshotId'), 2) result = self.send_get('workbench/audit/workspace/snapshots?last_snapshot_id=1') - self.assertEqual(len(result), 1) + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('entry')) + self.assertEqual(len(result.get('entry')), 1) result = self.send_get('workbench/audit/workspace/snapshots?snapshot_id=2') self.assertEqual(len(result), 1) @@ -1883,3 +1715,433 @@ def test_get_audit_researchers_with_params(self): self.assertIsNone(result[0]['email']) self.assertEqual(result[0]['accessTier'], 'REGISTERED') + def test_redcap_workbench_audit_api_calls_without_pagination(self): + # create researchers + researchers_json = [ + { + "userId": 0, + "creationTime": "2019-11-26T21:21:13.056Z", + "modifiedTime": "2019-11-26T21:21:13.056Z", + "givenName": "given name 1", + "familyName": "family name 1", + "streetAddress1": "string", + "streetAddress2": "string", + "city": "string", + "state": "string", + "zipCode": "string", + "country": "string", + "ethnicity": "HISPANIC", + "gender": ["MAN"], + "race": ["AIAN"], + "sexAtBirth": ["FEMALE"], + "accessTierShortNames": ["REGISTERED"], + "sexualOrientation": "BISEXUAL", + "affiliations": [ + { + "institution": "institution1", + "role": "institution role 1", + "nonAcademicAffiliation": "INDUSTRY" + } + ] + }, + { + "userId": 1, + "creationTime": "2019-11-27T21:21:13.056Z", + "modifiedTime": "2019-11-27T21:21:13.056Z", + "givenName": "given name 2", + "familyName": "family name 2", + "streetAddress1": "string2", + "streetAddress2": "string2", + "city": "string2", + "state": "string2", + "zipCode": "string2", + "country": "string2", + "ethnicity": "HISPANIC", + "sexualOrientation": "BISEXUAL", + "gender": ["MAN", "WOMAN"], + "race": ["AIAN", "WHITE"], + "accessTierShortNames": ["REGISTERED", "CONTROLLED"], + "affiliations": [ + { + "institution": "institution2", + "role": "institution role 2" + }, + { + "institution": "institution22", + "role": "institution role 22", + "nonAcademicAffiliation": "INDUSTRY" + } + ] + } + ] + self.send_post('workbench/directory/researchers', request_data=researchers_json) + + # create workspace + cdr_version = 'irving' + request_json = [ + { + "workspaceId": 0, + "name": "workspace name str", + "creationTime": "2019-11-25T17:43:41.085Z", + "modifiedTime": "2019-11-25T17:43:41.085Z", + "status": "ACTIVE", + "workspaceUsers": [ + { + "userId": 0, + "role": "READER", + "status": "ACTIVE" + }, + { + "userId": 1, + "role": "OWNER", + "status": "ACTIVE" + } + ], + "creator": { + "userId": 0, + "givenName": "aaa", + "familyName": "bbb" + }, + "excludeFromPublicDirectory": False, + "ethicalLegalSocialImplications": True, + "diseaseFocusedResearch": True, + "diseaseFocusedResearchName": "disease focused research name str", + "otherPurposeDetails": "other purpose details str", + "methodsDevelopment": True, + "controlSet": True, + "ancestry": True, + "socialBehavioral": True, + "populationHealth": True, + "drugDevelopment": True, + "commercialPurpose": True, + "educational": True, + "otherPurpose": True, + "scientificApproaches": 'reasonForInvestigation string', + "intendToStudy": 'intendToStudy string', + "findingsFromStudy": 'findingsFromStudy string', + "focusOnUnderrepresentedPopulations": True, + "workspaceDemographic": { + "raceEthnicity": ['AIAN', 'MENA'], + "age": ['AGE_0_11', 'AGE_65_74'], + "sexAtBirth": "UNSET", + "genderIdentity": "OTHER_THAN_MAN_WOMAN", + "sexualOrientation": "OTHER_THAN_STRAIGHT", + "geography": "RURAL", + "disabilityStatus": "DISABILITY", + "accessToCare": "NOT_EASILY_ACCESS_CARE", + "educationLevel": "LESS_THAN_HIGH_SCHOOL", + "incomeLevel": "BELOW_FEDERAL_POVERTY_LEVEL_200_PERCENT", + "others": "string" + }, + "cdrVersionName": cdr_version + }, + { + "workspaceId": 1, + "name": "workspace name str 2", + "creationTime": "2019-11-25T17:43:41.085Z", + "modifiedTime": "2019-11-25T17:43:41.085Z", + "status": "INACTIVE", + "workspaceUsers": [ + { + "userId": 0, + "role": "OWNER", + "status": "ACTIVE" + }, + { + "userId": 1, + "role": "READER", + "status": "ACTIVE" + } + ], + "excludeFromPublicDirectory": False, + "ethicalLegalSocialImplications": False, + "diseaseFocusedResearch": True, + "diseaseFocusedResearchName": "disease focused research name str 2", + "otherPurposeDetails": "other purpose details str 2", + "methodsDevelopment": False, + "controlSet": False, + "ancestry": False, + "socialBehavioral": False, + "populationHealth": False, + "drugDevelopment": False, + "commercialPurpose": False, + "educational": False, + "otherPurpose": False, + "scientificApproaches": 'reasonForInvestigation string2', + "intendToStudy": 'intendToStudy string2', + "findingsFromStudy": 'findingsFromStudy string2', + "cdrVersionName": cdr_version + } + ] + self.send_post('workbench/directory/workspaces', request_data=request_json) + + snapshot_id = 1 + workspace_id = 1 + last_snapshot_id = 0 + + # Call API get for snapshot_id + response = self.send_get(f'workbench/audit/workspace/snapshots?snapshot_id={snapshot_id}') + self.assertEqual(len(response), 1) + + # Call API get for workspace_id + response = self.send_get(f'workbench/audit/workspace/snapshots?workspace_id={workspace_id}') + self.assertEqual(len(response), 1) + + # Get with last_snapshot_id - no pagination & no count + response = self.send_get('workbench/audit/workspace/snapshots?last_snapshot_id=1') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 1) + + response = self.send_get(f'workbench/audit/workspace/snapshots?last_snapshot_id={last_snapshot_id}') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 2) + + # Get with last_snapshot_id - no pagination + response = self.send_get(f'workbench/audit/workspace/snapshots?last_snapshot_id={last_snapshot_id}&_count=2') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 2) + + # Get with all snapshots - no pagination & no count + response = self.send_get(f'workbench/audit/workspace/snapshots') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 2) + + # Get with all snapshots - no pagination + response = self.send_get(f'workbench/audit/workspace/snapshots?_count=2') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 2) + + # Test override MAX_MAX_RESULTS limit + with self.assertRaises(Exception): + self.send_get(f'workbench/audit/workspace/snapshots?_count=7000') + + def test_redcap_workbench_audit_api_calls_with_pagination(self): + # create researchers + researchers_json = [ + { + "userId": 0, + "creationTime": "2019-11-26T21:21:13.056Z", + "modifiedTime": "2019-11-26T21:21:13.056Z", + "givenName": "given name 1", + "familyName": "family name 1", + "streetAddress1": "string", + "streetAddress2": "string", + "city": "string", + "state": "string", + "zipCode": "string", + "country": "string", + "ethnicity": "HISPANIC", + "gender": ["MAN"], + "race": ["AIAN"], + "sexAtBirth": ["FEMALE"], + "accessTierShortNames": ["REGISTERED"], + "sexualOrientation": "BISEXUAL", + "affiliations": [ + { + "institution": "institution1", + "role": "institution role 1", + "nonAcademicAffiliation": "INDUSTRY" + } + ] + }, + { + "userId": 1, + "creationTime": "2019-11-27T21:21:13.056Z", + "modifiedTime": "2019-11-27T21:21:13.056Z", + "givenName": "given name 2", + "familyName": "family name 2", + "streetAddress1": "string2", + "streetAddress2": "string2", + "city": "string2", + "state": "string2", + "zipCode": "string2", + "country": "string2", + "ethnicity": "HISPANIC", + "sexualOrientation": "BISEXUAL", + "gender": ["MAN", "WOMAN"], + "race": ["AIAN", "WHITE"], + "accessTierShortNames": ["REGISTERED", "CONTROLLED"], + "affiliations": [ + { + "institution": "institution2", + "role": "institution role 2" + }, + { + "institution": "institution22", + "role": "institution role 22", + "nonAcademicAffiliation": "INDUSTRY" + } + ] + } + ] + self.send_post('workbench/directory/researchers', request_data=researchers_json) + + # create workspace + cdr_version = 'irving' + request_json = [ + { + "workspaceId": 0, + "name": "workspace name str", + "creationTime": "2019-11-25T17:43:41.085Z", + "modifiedTime": "2019-11-25T17:43:41.085Z", + "status": "ACTIVE", + "workspaceUsers": [ + { + "userId": 0, + "role": "READER", + "status": "ACTIVE" + }, + { + "userId": 1, + "role": "OWNER", + "status": "ACTIVE" + } + ], + "creator": { + "userId": 0, + "givenName": "aaa", + "familyName": "bbb" + }, + "excludeFromPublicDirectory": False, + "ethicalLegalSocialImplications": True, + "diseaseFocusedResearch": True, + "diseaseFocusedResearchName": "disease focused research name str", + "otherPurposeDetails": "other purpose details str", + "methodsDevelopment": True, + "controlSet": True, + "ancestry": True, + "socialBehavioral": True, + "populationHealth": True, + "drugDevelopment": True, + "commercialPurpose": True, + "educational": True, + "otherPurpose": True, + "scientificApproaches": 'reasonForInvestigation string', + "intendToStudy": 'intendToStudy string', + "findingsFromStudy": 'findingsFromStudy string', + "focusOnUnderrepresentedPopulations": True, + "workspaceDemographic": { + "raceEthnicity": ['AIAN', 'MENA'], + "age": ['AGE_0_11', 'AGE_65_74'], + "sexAtBirth": "UNSET", + "genderIdentity": "OTHER_THAN_MAN_WOMAN", + "sexualOrientation": "OTHER_THAN_STRAIGHT", + "geography": "RURAL", + "disabilityStatus": "DISABILITY", + "accessToCare": "NOT_EASILY_ACCESS_CARE", + "educationLevel": "LESS_THAN_HIGH_SCHOOL", + "incomeLevel": "BELOW_FEDERAL_POVERTY_LEVEL_200_PERCENT", + "others": "string" + }, + "cdrVersionName": cdr_version + }, + { + "workspaceId": 1, + "name": "workspace name str 2", + "creationTime": "2019-11-25T17:43:41.085Z", + "modifiedTime": "2019-11-25T17:43:41.085Z", + "status": "INACTIVE", + "workspaceUsers": [ + { + "userId": 0, + "role": "OWNER", + "status": "ACTIVE" + }, + { + "userId": 1, + "role": "READER", + "status": "ACTIVE" + } + ], + "excludeFromPublicDirectory": False, + "ethicalLegalSocialImplications": False, + "diseaseFocusedResearch": True, + "diseaseFocusedResearchName": "disease focused research name str 2", + "otherPurposeDetails": "other purpose details str 2", + "methodsDevelopment": False, + "controlSet": False, + "ancestry": False, + "socialBehavioral": False, + "populationHealth": False, + "drugDevelopment": False, + "commercialPurpose": False, + "educational": False, + "otherPurpose": False, + "scientificApproaches": 'reasonForInvestigation string2', + "intendToStudy": 'intendToStudy string2', + "findingsFromStudy": 'findingsFromStudy string2', + "cdrVersionName": cdr_version + } + ] + self.send_post('workbench/directory/workspaces', request_data=request_json) + + last_snapshot_id = 0 + + # Get with last_snapshot_id - pagination + response = self.send_get(f'workbench/audit/workspace/snapshots?last_snapshot_id={last_snapshot_id}&_count=1') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 1) + + response_data = response.get('entry')[0]['resource'][0] + self.assertEqual(response_data.get('snapshotId'), 1) + + # should have next link + self.assertIsNotNone(response.get('link')) + self.assertEqual(response['link'][0]['relation'], 'next') + + self.assertEqual(len(response['entry']), 1) + self.assertIsNotNone(response['entry'][0]['fullUrl']) + + next_pagination_link = response['link'][0]['url'].split('v1/')[-1] + + next_response = self.send_get(next_pagination_link) + self.assertIsNotNone(response) + self.assertIsNotNone(next_response.get('entry')) + self.assertEqual(len(next_response.get('entry')), 1) + + response_data = next_response.get('entry')[0]['resource'][0] + self.assertEqual(response_data.get('snapshotId'), 2) + + # should not have next link + self.assertIsNone(next_response.get('link')) + self.assertEqual(len(next_response['entry']), 1) + self.assertIsNotNone(next_response['entry'][0]['fullUrl']) + + + # Get all snapshots - pagination + response = self.send_get(f'workbench/audit/workspace/snapshots?_count=1') + self.assertIsNotNone(response) + self.assertIsNotNone(response.get('entry')) + self.assertEqual(len(response.get('entry')), 1) + + response_data = response.get('entry')[0]['resource'][0] + self.assertEqual(response_data.get('snapshotId'), 1) + + # should have next link + self.assertIsNotNone(response.get('link')) + self.assertEqual(response['link'][0]['relation'], 'next') + + self.assertEqual(len(response['entry']), 1) + self.assertIsNotNone(response['entry'][0]['fullUrl']) + + next_pagination_link = response['link'][0]['url'].split('v1/')[-1] + + next_response = self.send_get(next_pagination_link) + self.assertIsNotNone(response) + self.assertIsNotNone(next_response.get('entry')) + self.assertEqual(len(next_response.get('entry')), 1) + + response_data = next_response.get('entry')[0]['resource'][0] + self.assertEqual(response_data.get('snapshotId'), 2) + + # should not have next link + self.assertIsNone(next_response.get('link')) + + self.assertEqual(len(next_response['entry']), 1) + self.assertIsNotNone(next_response['entry'][0]['fullUrl'])