def test_caching_general_clearing(self, client: FlaskClient) -> None: if Env.get_bool("AUTH_ENABLE"): headers, _ = self.do_login(client, None, None) else: headers = None # get method is cached for 200 seconds # First response is not cached r = client.get(f"{API_URI}/tests/cache/long") assert r.status_code == 200 counter1 = self.get_content(r) # Second response is cached r = client.get(f"{API_URI}/tests/cache/long") assert r.status_code == 200 assert self.get_content(r) == counter1 # Empty all the cache Cache.clear() # Third response is no longer cached r = client.get(f"{API_URI}/tests/cache/long") assert r.status_code == 200 counter2 = self.get_content(r) assert counter2 != counter1 # Response is still cached r = client.get(f"{API_URI}/tests/cache/long") assert r.status_code == 200 assert self.get_content(r) == counter2 # Empty the endpoint cache client.delete(f"{API_URI}/tests/cache/long") # Second response is no longer cached r = client.get(f"{API_URI}/tests/cache/long") assert r.status_code == 200 counter3 = self.get_content(r) assert counter3 != counter2 # Response is still cached r = client.get(f"{API_URI}/tests/cache/long") assert r.status_code == 200 assert self.get_content(r) == counter3 # Endpoint is unauthenticated, headers are ignored when building the cache key r = client.get(f"{API_URI}/tests/cache/long", headers=headers) assert r.status_code == 200 assert self.get_content(r) == counter3 # Tokens are ignored even if invalid r = client.get(f"{API_URI}/tests/cache/long", headers={"Authorization": "Bearer invalid"}) assert r.status_code == 200 assert self.get_content(r) == counter3
def delete_test_env( client: FlaskClient, user_A1_headers: Tuple[Optional[Dict[str, str]], str], user_B1_headers: Tuple[Optional[Dict[str, str]], str], user_B1_uuid: str, user_B2_uuid: str, user_A1_uuid: str, uuid_group_A: str, uuid_group_B: str, study1_uuid: Optional[str] = None, study2_uuid: Optional[str] = None, ) -> None: admin_headers, _ = BaseTests.do_login(client, None, None) # delete all the elements used by the test if study1_uuid: r = client.delete(f"{API_URI}/study/{study1_uuid}", headers=user_B1_headers) assert r.status_code == 204 if study2_uuid: r = client.delete(f"{API_URI}/study/{study2_uuid}", headers=user_A1_headers) assert r.status_code == 204 # first user r = client.delete(f"{API_URI}/admin/users/{user_B1_uuid}", headers=admin_headers) assert r.status_code == 204 # second user r = client.delete(f"{API_URI}/admin/users/{user_B2_uuid}", headers=admin_headers) assert r.status_code == 204 # other user r = client.delete(f"{API_URI}/admin/users/{user_A1_uuid}", headers=admin_headers) assert r.status_code == 204 # group A directory INPUT_ROOT_path = INPUT_ROOT.joinpath(uuid_group_A) shutil.rmtree(INPUT_ROOT_path) # group A r = client.delete(f"{API_URI}/admin/groups/{uuid_group_A}", headers=admin_headers) assert r.status_code == 204 # group B directory INPUT_ROOT_path = INPUT_ROOT.joinpath(uuid_group_B) shutil.rmtree(INPUT_ROOT_path) # group B r = client.delete(f"{API_URI}/admin/groups/{uuid_group_B}", headers=admin_headers) assert r.status_code == 204
def check_endpoint( self, client: FlaskClient, method: str, endpoint: str, headers: Optional[Dict[str, str]], expected_authorized: bool, paths: List[str], ) -> List[str]: assert method in ( "GET", "POST", "PUT", "PATCH", "DELETE", ) path = self.get_path(method, endpoint) assert path in paths # SERVER_URI because api and auth are already included in endpoint full_endpoint = f"{SERVER_URI}/{endpoint}" if method == "GET": r = client.get(full_endpoint, headers=headers) elif method == "POST": r = client.post(full_endpoint, headers=headers) elif method == "PUT": r = client.put(full_endpoint, headers=headers) elif method == "PATCH": r = client.patch(full_endpoint, headers=headers) elif method == "DELETE": r = client.delete(full_endpoint, headers=headers) else: # pragma: no cover pytest.fail("Unknown method") if expected_authorized: assert r.status_code != 401 else: assert r.status_code != 400 paths.remove(path) return paths
def test_staff_restrictions(self, client: FlaskClient, faker: Faker) -> None: if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool( "AUTH_ENABLE"): log.warning("Skipping admin/users tests") return auth = Connector.get_authentication_instance() staff_role_enabled = Role.STAFF.value in [ r.name for r in auth.get_roles() ] if not staff_role_enabled: # pragma: no cover log.warning( "Skipping tests of admin/users restrictions, role Staff not enabled" ) return staff_uuid, staff_data = self.create_user(client, roles=[Role.STAFF]) staff_email = staff_data.get("email") staff_password = staff_data.get("password") staff_headers, _ = self.do_login(client, staff_email, staff_password) user_uuid, _ = self.create_user(client, roles=[Role.USER]) admin_headers, _ = self.do_login(client, None, None) r = client.get(f"{AUTH_URI}/profile", headers=admin_headers) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, dict) admin_uuid = content.get("uuid") # Staff users are not allowed to retrieve Admins' data r = client.get(f"{API_URI}/admin/users/{user_uuid}", headers=admin_headers) assert r.status_code == 200 r = client.get(f"{API_URI}/admin/users/{staff_uuid}", headers=admin_headers) assert r.status_code == 200 r = client.get(f"{API_URI}/admin/users/{admin_uuid}", headers=admin_headers) assert r.status_code == 200 r = client.get(f"{API_URI}/admin/users/{user_uuid}", headers=staff_headers) assert r.status_code == 200 r = client.get(f"{API_URI}/admin/users/{staff_uuid}", headers=staff_headers) assert r.status_code == 200 r = client.get(f"{API_URI}/admin/users/{admin_uuid}", headers=staff_headers) assert r.status_code == 404 content = self.get_content(r) assert content == "This user cannot be found or you are not authorized" # Staff users are not allowed to edit Admins r = client.put( f"{API_URI}/admin/users/{admin_uuid}", json={ "name": faker.name(), "roles": orjson.dumps([Role.STAFF]).decode("UTF8"), }, headers=staff_headers, ) assert r.status_code == 404 content = self.get_content(r) assert content == "This user cannot be found or you are not authorized" r = client.put( f"{API_URI}/admin/users/{staff_uuid}", json={ "name": faker.name(), "roles": orjson.dumps([Role.STAFF]).decode("UTF8"), }, headers=staff_headers, ) assert r.status_code == 204 r = client.put( f"{API_URI}/admin/users/{user_uuid}", json={ "name": faker.name(), "roles": orjson.dumps([Role.USER]).decode("UTF8"), }, headers=staff_headers, ) assert r.status_code == 204 # Admin role is not allowed for Staff users tmp_schema = self.get_dynamic_input_schema(client, "admin/users", admin_headers) post_schema = {s["key"]: s for s in tmp_schema} assert "roles" in post_schema assert "options" in post_schema["roles"] assert "normal_user" in post_schema["roles"]["options"] assert "admin_root" in post_schema["roles"]["options"] tmp_schema = self.get_dynamic_input_schema(client, f"admin/users/{user_uuid}", admin_headers, method="put") put_schema = {s["key"]: s for s in tmp_schema} assert "roles" in put_schema assert "options" in post_schema["roles"] assert "normal_user" in post_schema["roles"]["options"] assert "admin_root" in post_schema["roles"]["options"] tmp_schema = self.get_dynamic_input_schema(client, "admin/users", staff_headers) post_schema = {s["key"]: s for s in tmp_schema} assert "roles" in post_schema assert "options" in post_schema["roles"] assert "normal_user" in post_schema["roles"]["options"] assert "admin_root" not in post_schema["roles"]["options"] tmp_schema = self.get_dynamic_input_schema(client, f"admin/users/{user_uuid}", staff_headers, method="put") put_schema = {s["key"]: s for s in tmp_schema} assert "roles" in put_schema assert "options" in post_schema["roles"] assert "normal_user" in post_schema["roles"]["options"] assert "admin_root" not in post_schema["roles"]["options"] # Staff can't send role admin on put r = client.put( f"{API_URI}/admin/users/{user_uuid}", json={ "name": faker.name(), "roles": orjson.dumps([Role.ADMIN]).decode("UTF8"), }, headers=staff_headers, ) assert r.status_code == 400 # Staff can't send role admin on post schema = self.get_dynamic_input_schema(client, "admin/users", staff_headers) data = self.buildData(schema) data["email_notification"] = True data["is_active"] = True data["expiration"] = None data["roles"] = orjson.dumps([Role.ADMIN]).decode("UTF8") r = client.post(f"{API_URI}/admin/users", json=data, headers=staff_headers) assert r.status_code == 400 # Admin users are filtered out when asked from a Staff user r = client.get(f"{API_URI}/admin/users", headers=admin_headers) assert r.status_code == 200 users_list = self.get_content(r) assert isinstance(users_list, list) assert len(users_list) > 0 email_list = [u.get("email") for u in users_list] assert staff_email in email_list assert BaseAuthentication.default_user in email_list r = client.get(f"{API_URI}/admin/users", headers=staff_headers) assert r.status_code == 200 users_list = self.get_content(r) assert isinstance(users_list, list) assert len(users_list) > 0 email_list = [u.get("email") for u in users_list] assert staff_email in email_list assert BaseAuthentication.default_user not in email_list # Staff users are not allowed to delete Admins r = client.delete(f"{API_URI}/admin/users/{admin_uuid}", headers=staff_headers) assert r.status_code == 404 content = self.get_content(r) assert content == "This user cannot be found or you are not authorized" r = client.delete(f"{API_URI}/admin/users/{user_uuid}", headers=staff_headers) assert r.status_code == 204
def test_admin_users(self, client: FlaskClient, faker: Faker) -> None: if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool( "AUTH_ENABLE"): log.warning("Skipping admin/users tests") return project_tile = get_project_configuration("project.title", default="YourProject") auth = Connector.get_authentication_instance() staff_role_enabled = Role.STAFF.value in [ r.name for r in auth.get_roles() ] for role in ( Role.ADMIN, Role.STAFF, ): if not staff_role_enabled: # pragma: no cover log.warning( "Skipping tests of admin/users endpoints, role Staff not enabled" ) continue else: log.warning("Testing admin/users endpoints as {}", role) if role == Role.ADMIN: user_email = BaseAuthentication.default_user user_password = BaseAuthentication.default_password elif role == Role.STAFF: _, user_data = self.create_user(client, roles=[Role.STAFF]) user_email = user_data.get("email") user_password = user_data.get("password") headers, _ = self.do_login(client, user_email, user_password) r = client.get(f"{API_URI}/admin/users", headers=headers) assert r.status_code == 200 schema = self.get_dynamic_input_schema(client, "admin/users", headers) data = self.buildData(schema) data["email_notification"] = True data["is_active"] = True data["expiration"] = None # Event 1: create r = client.post(f"{API_URI}/admin/users", json=data, headers=headers) assert r.status_code == 200 uuid = self.get_content(r) assert isinstance(uuid, str) # A new User is created events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.create.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].url == "/api/admin/users" assert "name" in events[0].payload assert "surname" in events[0].payload assert "email" in events[0].payload # Save it for the following tests event_target_id1 = events[0].target_id mail = self.read_mock_email() body = mail.get("body", "") # Subject: is a key in the MIMEText assert body is not None assert mail.get("headers") is not None assert f"Subject: {project_tile}: New credentials" in mail.get( "headers", "") assert data.get("email", "MISSING").lower() in body assert (data.get("password", "MISSING") in body or escape(str(data.get("password"))) in body) # Test the differences between post and put schema post_schema = {s["key"]: s for s in schema} tmp_schema = self.get_dynamic_input_schema(client, f"admin/users/{uuid}", headers, method="put") put_schema = {s["key"]: s for s in tmp_schema} assert "email" in post_schema assert post_schema["email"]["required"] assert "email" not in put_schema assert "name" in post_schema assert post_schema["name"]["required"] assert "name" in put_schema assert not put_schema["name"]["required"] assert "surname" in post_schema assert post_schema["surname"]["required"] assert "surname" in put_schema assert not put_schema["surname"]["required"] assert "password" in post_schema assert post_schema["password"]["required"] assert "password" in put_schema assert not put_schema["password"]["required"] assert "group" in post_schema assert post_schema["group"]["required"] assert "group" in put_schema assert not put_schema["group"]["required"] # Event 2: read r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers) assert r.status_code == 200 users_list = self.get_content(r) assert isinstance(users_list, dict) assert len(users_list) > 0 # email is saved lowercase assert users_list.get("email") == data.get("email", "MISSING").lower() # Access to the user events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.access.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id == event_target_id1 assert events[0].url == f"/api/admin/users/{event_target_id1}" assert len(events[0].payload) == 0 # Check duplicates r = client.post(f"{API_URI}/admin/users", json=data, headers=headers) assert r.status_code == 409 assert (self.get_content(r) == f"A User already exists with email: {data['email']}") data["email"] = BaseAuthentication.default_user r = client.post(f"{API_URI}/admin/users", json=data, headers=headers) assert r.status_code == 409 assert ( self.get_content(r) == f"A User already exists with email: {BaseAuthentication.default_user}" ) # Create another user data2 = self.buildData(schema) data2["email_notification"] = True data2["is_active"] = True data2["expiration"] = None # Event 3: create r = client.post(f"{API_URI}/admin/users", json=data2, headers=headers) assert r.status_code == 200 uuid2 = self.get_content(r) assert isinstance(uuid2, str) # Another User is created events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.create.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id != event_target_id1 assert events[0].url == "/api/admin/users" assert "name" in events[0].payload assert "surname" in events[0].payload assert "email" in events[0].payload # Save it for the following tests event_target_id2 = events[0].target_id mail = self.read_mock_email() body = mail.get("body", "") # Subject: is a key in the MIMEText assert body is not None assert mail.get("headers") is not None assert f"Subject: {project_tile}: New credentials" in mail.get( "headers", "") assert data2.get("email", "MISSING").lower() in body pwd = data2.get("password", "MISSING") assert pwd in body or escape(str(pwd)) in body # send and invalid user_id r = client.put( f"{API_URI}/admin/users/invalid", json={"name": faker.name()}, headers=headers, ) assert r.status_code == 404 # Event 4: modify r = client.put( f"{API_URI}/admin/users/{uuid}", json={"name": faker.name()}, headers=headers, ) assert r.status_code == 204 # User 1 modified (same target_id as above) events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.modify.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id == event_target_id1 assert events[0].url == f"/api/admin/users/{event_target_id1}" assert "name" in events[0].payload assert "surname" not in events[0].payload assert "email" not in events[0].payload assert "password" not in events[0].payload # email cannot be modified new_data = {"email": data.get("email")} r = client.put(f"{API_URI}/admin/users/{uuid2}", json=new_data, headers=headers) # from webargs >= 6 this endpoint no longer return a 204 but a 400 # because email is an unknown field # assert r.status_code == 204 assert r.status_code == 400 # Event 5: read r = client.get(f"{API_URI}/admin/users/{uuid2}", headers=headers) assert r.status_code == 200 users_list = self.get_content(r) assert isinstance(users_list, dict) assert len(users_list) > 0 # email is not modified -> still equal to data2, not data1 assert users_list.get("email") != data.get("email", "MISSING").lower() assert users_list.get("email") == data2.get("email", "MISSING").lower() # Access to user 2 events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.access.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id == event_target_id2 assert events[0].url == f"/api/admin/users/{event_target_id2}" assert len(events[0].payload) == 0 r = client.delete(f"{API_URI}/admin/users/invalid", headers=headers) assert r.status_code == 404 # Event 6: delete r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers) assert r.status_code == 204 # User 1 is deleted (same target_id as above) events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.delete.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id == event_target_id1 assert events[0].url == f"/api/admin/users/{event_target_id1}" assert len(events[0].payload) == 0 r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers) assert r.status_code == 404 # change password of user2 # Event 7: modify newpwd = faker.password(strong=True) data = {"password": newpwd, "email_notification": True} r = client.put(f"{API_URI}/admin/users/{uuid2}", json=data, headers=headers) assert r.status_code == 204 # User 2 modified (same target_id as above) events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.modify.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id == event_target_id2 assert events[0].url == f"/api/admin/users/{event_target_id2}" assert "name" not in events[0].payload assert "surname" not in events[0].payload assert "email" not in events[0].payload assert "password" in events[0].payload assert "email_notification" in events[0].payload # Verify that the password is obfuscated in the log: assert events[0].payload["password"] == OBSCURE_VALUE mail = self.read_mock_email() # Subject: is a key in the MIMEText assert mail.get("body", "") is not None assert mail.get("headers", "") is not None assert f"Subject: {project_tile}: Password changed" in mail.get( "headers", "") assert data2.get("email", "MISSING").lower() in mail.get("body", "") assert newpwd in mail.get( "body", "") or escape(newpwd) in mail.get("body", "") # login with a newly created user headers2, _ = self.do_login(client, data2.get("email"), newpwd) # normal users cannot access to this endpoint r = client.get(f"{API_URI}/admin/users", headers=headers2) assert r.status_code == 401 r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers2) assert r.status_code == 401 r = client.post(f"{API_URI}/admin/users", json=data, headers=headers2) assert r.status_code == 401 r = client.put( f"{API_URI}/admin/users/{uuid}", json={"name": faker.name()}, headers=headers2, ) assert r.status_code == 401 r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers2) assert r.status_code == 401 # Users are not authorized to /admin/tokens # These two tests should be moved in test_endpoints_tokens.py r = client.get(f"{API_URI}/admin/tokens", headers=headers2) assert r.status_code == 401 r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=headers2) assert r.status_code == 401 # let's delete the second user # Event 8: delete r = client.delete(f"{API_URI}/admin/users/{uuid2}", headers=headers) assert r.status_code == 204 # User 2 is deleted (same target_id as above) events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.delete.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id == event_target_id2 assert events[0].url == f"/api/admin/users/{event_target_id2}" assert len(events[0].payload) == 0 # Restore the default password (changed due to FORCE_FIRST_PASSWORD_CHANGE) # or MAX_PASSWORD_VALIDITY errors r = client.get(f"{AUTH_URI}/profile", headers=headers) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, dict) uuid = str(content.get("uuid")) data = { "password": user_password, # very important, otherwise the default user will lose its role "roles": orjson.dumps([role]).decode("UTF8"), } # Event 9: modify r = client.put(f"{API_URI}/admin/users/{uuid}", json=data, headers=headers) assert r.status_code == 204 # Default user is modified events = self.get_last_events(1, filters={"target_type": "User"}) assert events[0].event == Events.modify.value assert events[0].user == user_email assert events[0].target_type == "User" assert events[0].target_id != event_target_id1 assert events[0].target_id != event_target_id2 assert events[0].url != f"/api/admin/users/{event_target_id1}" assert events[0].url != f"/api/admin/users/{event_target_id2}" assert "name" not in events[0].payload assert "surname" not in events[0].payload assert "email" not in events[0].payload assert "password" in events[0].payload assert "roles" in events[0].payload assert "email_notification" not in events[0].payload # Verify that the password is obfuscated in the log: assert events[0].payload["password"] == OBSCURE_VALUE r = client.get(f"{AUTH_URI}/logout", headers=headers) assert r.status_code == 204
def test_sendmail(self, client: FlaskClient, faker: Faker) -> None: headers, _ = self.do_login(client, None, None) r = client.get(f"{API_URI}/admin/mail", headers=headers) assert r.status_code == 405 r = client.put(f"{API_URI}/admin/mail", headers=headers) assert r.status_code == 405 r = client.patch(f"{API_URI}/admin/mail", headers=headers) assert r.status_code == 405 r = client.delete(f"{API_URI}/admin/mail", headers=headers) assert r.status_code == 405 data: Dict[str, Any] = {"dry_run": False} r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["subject"] = faker.pystr() r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["body"] = faker.text() r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["to"] = faker.pystr() r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["to"] = faker.ascii_email() data["body"] = "TEST EMAIL BODY" r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 204 mail = self.read_mock_email() body = mail.get("body", "") assert "TEST EMAIL BODY" in body data["dry_run"] = True r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert "html_body" in response assert "plain_body" in response assert "subject" in response assert "to" in response assert "cc" in response assert "bcc" in response data["dry_run"] = False data["body"] = "TEST EMAIL <b>HTML</b> BODY" r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 204 mail = self.read_mock_email() body = mail.get("body", "") assert "TEST EMAIL <b>HTML</b> BODY" in body data["dry_run"] = True r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert "html_body" in response assert "plain_body" in response assert "subject" in response assert "to" in response assert "cc" in response assert "bcc" in response data["dry_run"] = False data["body"] = faker.text() data["cc"] = faker.pystr() r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["cc"] = faker.ascii_email() r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 204 data["cc"] = f"{faker.ascii_email()},{faker.pystr()}" r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["cc"] = f"{faker.ascii_email()},{faker.ascii_email()}" r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 204 data["bcc"] = faker.pystr() r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["bcc"] = f"{faker.ascii_email()},{faker.pystr()}" r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 400 data["bcc"] = f"{faker.ascii_email()},{faker.ascii_email()}" r = client.post(f"{API_URI}/admin/mail", json=data, headers=headers) assert r.status_code == 204 mail = self.read_mock_email() body = mail.get("body", "") email_headers = mail.get("headers", "") assert body is not None assert email_headers is not None # Subject: is a key in the MIMEText assert f"Subject: {data['subject']}" in email_headers ccs = mail.get("cc", []) assert ccs[0] == data["to"] assert ccs[1] == data["cc"].split(",") assert ccs[2] == data["bcc"].split(",")
def test_api_phenotype(self, client: FlaskClient, faker: Faker) -> None: # setup the test env ( admin_headers, uuid_group_A, user_A1_uuid, user_A1_headers, uuid_group_B, user_B1_uuid, user_B1_headers, user_B2_uuid, user_B2_headers, study1_uuid, study2_uuid, ) = create_test_env(client, faker, study=True) # create a new phenotype with wrong age phenotype1 = { "name": faker.pystr(), "age": -2, "sex": "male", } r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data=phenotype1, ) assert r.status_code == 400 # create a new phenotype phenotype1["age"] = faker.pyint(0, 100) r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data=phenotype1, ) assert r.status_code == 200 phenotype1_uuid = self.get_content(r) assert isinstance(phenotype1_uuid, str) # create a new phenotype in a study of an other group r = client.post( f"{API_URI}/study/{study2_uuid}/phenotypes", headers=user_B1_headers, data=phenotype1, ) assert r.status_code == 404 # create a new phenotype as admin not belonging to study group phenotype2 = { "name": faker.pystr(), "age": faker.pyint(0, 100), "sex": "female", } r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=admin_headers, data=phenotype2, ) assert r.status_code == 404 # create a phenotype with a geodata and a list of hpo graph = neo4j.get_instance() geodata_nodes = graph.GeoData.nodes geodata_uuid = geodata_nodes[0].uuid hpo_nodes = graph.HPO.nodes hpo1_id = hpo_nodes[0].hpo_id hpo2_id = hpo_nodes[1].hpo_id phenotype2["birth_place"] = geodata_uuid phenotype2["hpo"] = [hpo1_id, hpo2_id] phenotype2["hpo"] = json.dumps(phenotype2["hpo"]) r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data=phenotype2, ) assert r.status_code == 200 phenotype2_uuid = self.get_content(r) assert isinstance(phenotype2_uuid, str) # test phenotype access # test phenotype list response r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 2 # test phenotype list response for a study you don't have access r = client.get(f"{API_URI}/study/{study2_uuid}/phenotypes", headers=user_B1_headers) assert r.status_code == 404 # test phenotype list response for admin r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes", headers=admin_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 2 # test empty list of phenotypes in a study r = client.get(f"{API_URI}/study/{study2_uuid}/phenotypes", headers=user_A1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert not response # study owner r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_B1_headers) assert r.status_code == 200 # same group of the study owner r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B2_headers) assert r.status_code == 200 # check hpo and geodata were correctly linked response = self.get_content(r) assert isinstance(response, dict) assert response["birth_place"]["uuid"] == geodata_uuid assert len(response["hpo"]) == 2 hpo_list = [] for el in response["hpo"]: hpo_list.append(el["hpo_id"]) assert hpo1_id in hpo_list assert hpo2_id in hpo_list # phenotype owned by an other group r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_A1_headers) assert r.status_code == 404 not_authorized_message = self.get_content(r) assert isinstance(not_authorized_message, str) # admin access r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=admin_headers) assert r.status_code == 200 # test phenotype modification # modify a non existent phenotype random_phenotype = faker.pystr() r = client.put( f"{API_URI}/phenotype/{random_phenotype}", headers=user_A1_headers, data={ "name": faker.pystr(), "sex": "female" }, ) assert r.status_code == 404 # modify a phenotype you do not own r = client.put( f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_A1_headers, data={ "name": faker.pystr(), "sex": "female" }, ) assert r.status_code == 404 # modify a phenotype using a wrong age phenotype1["age"] = -8 r = client.put( f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_B1_headers, data=phenotype1, ) assert r.status_code == 400 # modify a phenotype you own phenotype1["age"] = faker.pyint(0, 100) r = client.put( f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_B1_headers, data=phenotype1, ) assert r.status_code == 204 # admin modify a phenotype of a group he don't belongs r = client.put( f"{API_URI}/phenotype/{phenotype1_uuid}", headers=admin_headers, data={ "name": faker.pystr(), "sex": "female" }, ) assert r.status_code == 404 # add a new hpo and change the previous geodata hpo3_id = hpo_nodes[2].hpo_id geodata2_uuid = geodata_nodes[1].uuid phenotype2["name"] = faker.pystr() phenotype2["sex"] = "male" phenotype2["birth_place"] = geodata2_uuid phenotype2["hpo"] = [hpo1_id, hpo2_id, hpo3_id] phenotype2["hpo"] = json.dumps(phenotype2["hpo"]) r = client.put( f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B1_headers, data=phenotype2, ) assert r.status_code == 204 r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B2_headers) res = self.get_content(r) assert isinstance(res, dict) assert res["birth_place"]["uuid"] == geodata2_uuid assert len(res["hpo"]) == 3 # delete all hpo and geodata data: Dict[str, Any] = {**phenotype2} data.pop("birth_place", None) data.pop("hpo", None) r = client.put(f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B1_headers, data=data) assert r.status_code == 204 r = client.get(f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B2_headers) response = self.get_content(r) assert isinstance(response, dict) assert "birth_place" not in response assert not response["hpo"] # add a no existing geodata phenotype2["birth_place"] = faker.pystr() r = client.put( f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B1_headers, data=phenotype2, ) assert r.status_code == 400 # delete a phenotype # delete a phenotype that does not exists r = client.delete(f"{API_URI}/phenotype/{random_phenotype}", headers=user_A1_headers) assert r.status_code == 404 # delete a phenotype in a study you do not own r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_A1_headers) assert r.status_code == 404 # admin delete a phenotype of a group he don't belong r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=admin_headers) assert r.status_code == 404 # delete a phenotype in a study you own r = client.delete(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_B1_headers) assert r.status_code == 204 # delete a phenotype in a study own by your group r = client.delete(f"{API_URI}/phenotype/{phenotype2_uuid}", headers=user_B2_headers) assert r.status_code == 204 # check phenotype deletion r = client.get(f"{API_URI}/phenotype/{phenotype1_uuid}", headers=user_B1_headers) assert r.status_code == 404 not_existent_message = self.get_content(r) assert isinstance(not_existent_message, str) assert not_existent_message == not_authorized_message # delete all the elements used by the test delete_test_env( client, user_A1_headers, user_B1_headers, user_B1_uuid, user_B2_uuid, user_A1_uuid, uuid_group_A, uuid_group_B, study1_uuid=study1_uuid, study2_uuid=study2_uuid, )
def test_admin_users(self, client: FlaskClient, faker: Faker) -> None: if not Env.get_bool("MAIN_LOGIN_ENABLE"): # pragma: no cover log.warning("Skipping admin/users tests") return project_tile = get_project_configuration("project.title", default="YourProject") headers, _ = self.do_login(client, None, None) r = client.get(f"{API_URI}/admin/users", headers=headers) assert r.status_code == 200 schema = self.getDynamicInputSchema(client, "admin/users", headers) data = self.buildData(schema) data["email_notification"] = True data["is_active"] = True data["expiration"] = None # Event 1: create r = client.post(f"{API_URI}/admin/users", data=data, headers=headers) assert r.status_code == 200 uuid = self.get_content(r) mail = self.read_mock_email() # Subject: is a key in the MIMEText assert mail.get("body") is not None assert mail.get("headers") is not None assert f"Subject: {project_tile}: new credentials" in mail.get( "headers") assert f"Username: {data.get('email', 'MISSING').lower()}" in mail.get( "body") assert f"Password: {data.get('password')}" in mail.get("body") # Event 2: read r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers) assert r.status_code == 200 users_list = self.get_content(r) assert len(users_list) > 0 # email is saved lowercase assert users_list[0].get("email") == data.get("email", "MISSING").lower() # Check duplicates r = client.post(f"{API_URI}/admin/users", data=data, headers=headers) assert r.status_code == 409 # Create another user data2 = self.buildData(schema) data2["email_notification"] = True data2["is_active"] = True data2["expiration"] = None # Event 3: create r = client.post(f"{API_URI}/admin/users", data=data2, headers=headers) assert r.status_code == 200 uuid2 = self.get_content(r) mail = self.read_mock_email() # Subject: is a key in the MIMEText assert mail.get("body") is not None assert mail.get("headers") is not None assert f"Subject: {project_tile}: new credentials" in mail.get( "headers") assert f"Username: {data2.get('email', 'MISSING').lower()}" in mail.get( "body") assert f"Password: {data2.get('password')}" in mail.get("body") # send and invalid user_id r = client.put( f"{API_URI}/admin/users/invalid", data={"name": faker.name()}, headers=headers, ) assert r.status_code == 404 # Event 4: modify r = client.put( f"{API_URI}/admin/users/{uuid}", data={"name": faker.name()}, headers=headers, ) assert r.status_code == 204 # email cannot be modified new_data = {"email": data.get("email")} r = client.put(f"{API_URI}/admin/users/{uuid2}", data=new_data, headers=headers) # from webargs >= 6 this endpoint no longer return a 204 but a 400 # because email is an unknown field # assert r.status_code == 204 assert r.status_code == 400 # Event 5: read r = client.get(f"{API_URI}/admin/users/{uuid2}", headers=headers) assert r.status_code == 200 users_list = self.get_content(r) assert len(users_list) > 0 # email is not modified -> still equal to data2, not data1 assert users_list[0].get("email") != data.get("email", "MISSING").lower() assert users_list[0].get("email") == data2.get("email", "MISSING").lower() r = client.delete(f"{API_URI}/admin/users/invalid", headers=headers) assert r.status_code == 404 # Event 6: delete r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers) assert r.status_code == 204 r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers) assert r.status_code == 404 # change password of user2 # Event 7: modify newpwd = faker.password(strong=True) data = {"password": newpwd, "email_notification": True} r = client.put(f"{API_URI}/admin/users/{uuid2}", data=data, headers=headers) assert r.status_code == 204 mail = self.read_mock_email() # Subject: is a key in the MIMEText assert mail.get("body") is not None assert mail.get("headers") is not None assert f"Subject: {project_tile}: password changed" in mail.get( "headers") assert f"Username: {data2.get('email', 'MISSING').lower()}" in mail.get( "body") assert f"Password: {newpwd}" in mail.get("body") # login with a newly created user headers2, _ = self.do_login(client, data2.get("email"), newpwd) # normal users cannot access to this endpoint r = client.get(f"{API_URI}/admin/users", headers=headers2) assert r.status_code == 401 r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers2) assert r.status_code == 401 r = client.post(f"{API_URI}/admin/users", data=data, headers=headers2) assert r.status_code == 401 r = client.put( f"{API_URI}/admin/users/{uuid}", data={"name": faker.name()}, headers=headers2, ) assert r.status_code == 401 r = client.delete(f"{API_URI}/admin/users/{uuid}", headers=headers2) assert r.status_code == 401 # Users are not authorized to /admin/tokens # These two tests should be moved in test_endpoints_tokens.py r = client.get(f"{API_URI}/admin/tokens", headers=headers2) assert r.status_code == 401 r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=headers2) assert r.status_code == 401 # let's delete the second user # Event 8: delete r = client.delete(f"{API_URI}/admin/users/{uuid2}", headers=headers) assert r.status_code == 204 # Restore the default password, if it changed due to FORCE_FIRST_PASSWORD_CHANGE # or MAX_PASSWORD_VALIDITY errors r = client.get(f"{AUTH_URI}/profile", headers=headers) assert r.status_code == 200 uuid = self.get_content(r).get("uuid") data = { "password": BaseAuthentication.default_password, # very important, otherwise the default user will lose its admin role "roles": json.dumps(["admin_root"]), } # Event 9: modify r = client.put(f"{API_URI}/admin/users/{uuid}", data=data, headers=headers) assert r.status_code == 204 r = client.get(f"{AUTH_URI}/logout", headers=headers) assert r.status_code == 204
def test_tokens(self, client: FlaskClient, faker: Faker) -> None: if not Env.get_bool("AUTH_ENABLE"): log.warning("Skipping tokens tests") return last_token = None last_tokens_header = None token_id = None for _ in range(3): header, token = self.do_login(client, None, None) last_tokens_header = header last_token = token # TEST GET ALL TOKENS r = client.get(f"{AUTH_URI}/tokens", headers=last_tokens_header) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) # Probably due to password expiration: # change password invalidated tokens created before # => create tokens again if len(content) < 3: # pragma: no cover for _ in range(3): header, token = self.do_login(client, None, None) last_tokens_header = header last_token = token # TEST GET ALL TOKENS r = client.get(f"{AUTH_URI}/tokens", headers=last_tokens_header) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) assert len(content) >= 3 # save a token to be used for further tests for c in content: if c["token"] == last_token: continue token_id = c["id"] # SINGLE TOKEN IS NOT ALLOWED r = client.get(f"{AUTH_URI}/tokens/{token_id}", headers=last_tokens_header) assert r.status_code == 405 # TEST GET ALL TOKENS r = client.get(f"{API_URI}/admin/tokens") assert r.status_code == 401 # TEST GET ALL TOKENS r = client.get( f"{API_URI}/admin/tokens", query_string={"get_total": True}, headers=last_tokens_header, ) assert r.status_code == 206 content = self.get_content(r) assert isinstance(content, dict) assert "total" in content assert content["total"] > 0 r = client.get( f"{API_URI}/admin/tokens", query_string={"get_total": True, "input_filter": "1"}, headers=last_tokens_header, ) assert r.status_code == 206 content = self.get_content(r) assert isinstance(content, dict) assert "total" in content assert content["total"] > 0 r = client.get( f"{API_URI}/admin/tokens", query_string={"get_total": True, "input_filter": faker.pystr()}, headers=last_tokens_header, ) assert r.status_code == 206 content = self.get_content(r) assert isinstance(content, dict) assert "total" in content assert content["total"] == 0 r = client.get( f"{API_URI}/admin/tokens", query_string={"get_total": True, "page": 1, "size": 20}, headers=last_tokens_header, ) assert r.status_code == 206 content = self.get_content(r) assert isinstance(content, dict) assert "total" in content assert content["total"] > 0 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 0, "size": 20}, headers=last_tokens_header, ) assert r.status_code == 400 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 1, "size": 0}, headers=last_tokens_header, ) assert r.status_code == 400 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 1, "size": 101}, headers=last_tokens_header, ) assert r.status_code == 400 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 99999, "size": 20}, headers=last_tokens_header, ) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) assert len(content) == 0 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 1, "size": 2}, headers=last_tokens_header, ) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) assert len(content) <= 2 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 1, "size": 20, "input_filter": "1"}, headers=last_tokens_header, ) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) assert len(content) >= 1 r = client.get( f"{API_URI}/admin/tokens", query_string={"page": 1, "size": 20, "input_filter": faker.pystr()}, headers=last_tokens_header, ) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) assert len(content) == 0 r = client.get( f"{API_URI}/admin/tokens", query_string={ "page": 1, "size": 20, "input_filter": "1", # Sort_by emitted or other date cannot be done, because mysql truncate # the date, so that sort can't be predicted [several dates are reported] # as the same. Let's use a certainly unique field like uuid "sort_by": "uuid", }, headers=last_tokens_header, ) assert r.status_code == 200 default_sort = self.get_content(r) assert isinstance(default_sort, list) assert len(default_sort) >= 2 r = client.get( f"{API_URI}/admin/tokens", query_string={ "page": 1, "size": 20, "input_filter": "1", # Sort_by emitted or other date cannot be done, because mysql truncate # the date, so that sort can't be predicted [several dates are reported] # as the same. Let's use a certainly unique field like uuid "sort_by": "uuid", "sort_order": "asc", }, headers=last_tokens_header, ) assert r.status_code == 200 asc_sort = self.get_content(r) assert isinstance(asc_sort, list) assert len(asc_sort) >= 2 assert default_sort[0]["token"] == asc_sort[0]["token"] assert default_sort[-1]["token"] == asc_sort[-1]["token"] r = client.get( f"{API_URI}/admin/tokens", query_string={ "page": 1, "size": 20, "input_filter": "1", # Sort_by emitted or other date cannot be done, because mysql truncate # the date, so that sort can't be predicted [several dates are reported] # as the same. Let's use a certainly unique field like uuid "sort_by": "uuid", "sort_order": "desc", }, headers=last_tokens_header, ) assert r.status_code == 200 desc_sort = self.get_content(r) assert isinstance(desc_sort, list) # Results of desc_sort can't be compared with previous contents # It may only be done if we were able to retrieve all tokens, in this case the # first desc will be the last asc... But we cannot ensure to be able to always # retrieve all tokens. assert len(desc_sort) >= 2 # At least they should be different # assert asc_sort[0] != desc_sort[0] # assert asc_sort[-1] != desc_sort[-1] # TEST GET ALL TOKENS r = client.get(f"{API_URI}/admin/tokens", headers=last_tokens_header) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) assert len(content) >= 3 # DELETE INVALID TOKEN r = client.delete(f"{API_URI}/admin/tokens/xyz", headers=last_tokens_header) assert r.status_code == 404 # TEST DELETE OF A SINGLE TOKEN r = client.delete(f"{AUTH_URI}/tokens/{token_id}", headers=last_tokens_header) assert r.status_code == 204 events = self.get_last_events(1) assert events[0].event == Events.delete.value assert events[0].target_type == "Token" # Tokens does not have a uuid... # assert events[0].target_id == token_id assert events[0].user == "-" assert events[0].url == f"/auth/tokens/{token_id}" # TEST AN ALREADY DELETED TOKEN r = client.delete(f"{AUTH_URI}/tokens/{token_id}", headers=last_tokens_header) assert r.status_code == 403 # TEST INVALID DELETE OF A SINGLE TOKEN r = client.delete(f"{AUTH_URI}/tokens/0", headers=last_tokens_header) assert r.status_code == 403 # TEST TOKEN IS STILL VALID r = client.get(f"{AUTH_URI}/tokens", headers=last_tokens_header) assert r.status_code == 200 # user_header will be used as target for deletion # Always enabled in core tests if not Env.get_bool("MAIN_LOGIN_ENABLE"): # pragma: no cover uuid = None user_header, token = self.do_login(client, None, None) else: uuid, data = self.create_user(client) user_header, token = self.do_login(client, data["email"], data["password"]) r = client.get(f"{AUTH_URI}/status", headers=user_header) assert r.status_code == 200 # TEST GET ALL TOKENS r = client.get(f"{AUTH_URI}/tokens", headers=user_header) assert r.status_code == 200 content = self.get_content(r) assert isinstance(content, list) token_id = None for c in content: if c["token"] == token: token_id = c["id"] break assert token_id is not None last_tokens_header, _ = self.do_login(client, None, None) r = client.delete( f"{API_URI}/admin/tokens/{token_id}", headers=last_tokens_header ) assert r.status_code == 204 events = self.get_last_events(1) assert events[0].event == Events.delete.value assert events[0].target_type == "Token" # Tokens does not have a uuid... # assert events[0].target_id == token_id assert events[0].user == "-" assert events[0].url == f"/api/admin/tokens/{token_id}" r = client.delete( f"{API_URI}/admin/tokens/{token_id}", headers=last_tokens_header ) assert r.status_code == 404 r = client.get(f"{AUTH_URI}/status", headers=user_header) assert r.status_code == 401 # Goodbye temporary user (if previously created) if uuid: self.delete_user(client, uuid)
def test_api_family(self, client: FlaskClient, faker: Faker) -> None: # setup the test env ( admin_headers, uuid_group_A, user_A1_uuid, user_A1_headers, uuid_group_B, user_B1_uuid, user_B1_headers, user_B2_uuid, user_B2_headers, study1_uuid, study2_uuid, ) = create_test_env(client, faker, study=True) # create new phenotypes phenotype_father = { "name": faker.pystr(), "age": faker.pyint(0, 100), "sex": "male", } r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data=phenotype_father, ) assert r.status_code == 200 phenotype_father_uuid = self.get_content(r) assert isinstance(phenotype_father_uuid, str) phenotype_mother = { "name": faker.pystr(), "age": faker.pyint(0, 100), "sex": "female", } r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data=phenotype_mother, ) assert r.status_code == 200 phenotype_mother_uuid = self.get_content(r) assert isinstance(phenotype_mother_uuid, str) phenotype_son_B = { "name": faker.pystr(), "age": faker.pyint(0, 100), "sex": "female", } r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data=phenotype_son_B, ) assert r.status_code == 200 phenotype_son_B_uuid = self.get_content(r) assert isinstance(phenotype_son_B_uuid, str) phenotype_son_A = { "name": faker.pystr(), "age": faker.pyint(0, 100), "sex": "female", } r = client.post( f"{API_URI}/study/{study2_uuid}/phenotypes", headers=user_A1_headers, data=phenotype_son_A, ) assert r.status_code == 200 phenotype_son_A_uuid = self.get_content(r) assert isinstance(phenotype_son_A_uuid, str) # create a relationship # father case r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 200 graph = neo4j.get_instance() phenotype_father_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_father_uuid) phenotype_son_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_son_B_uuid) assert phenotype_father_node.son.is_connected(phenotype_son_node) assert phenotype_son_node.father.is_connected(phenotype_father_node) # test relationships in get phenotype list response r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) for el in response: if el["uuid"] == phenotype_son_B_uuid: assert el["relationships"]["father"][ "uuid"] == phenotype_father_uuid if el["uuid"] == phenotype_father_uuid: assert el["relationships"]["sons"][0][ "uuid"] == phenotype_son_B_uuid # test relationships in get single phenotype response r = client.get(f"{API_URI}/phenotype/{phenotype_son_B_uuid}", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert response["relationships"]["father"][ "uuid"] == phenotype_father_uuid r = client.get(f"{API_URI}/phenotype/{phenotype_father_uuid}", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert response["relationships"]["sons"][0][ "uuid"] == phenotype_son_B_uuid # create a relationship for two phenotypes in an other study r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=user_A1_headers, ) assert r.status_code == 404 # admin creates a relationship r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=admin_headers, ) assert r.status_code == 404 # a user of the same group of the owner create a relationship # mother case r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=user_B2_headers, ) assert r.status_code == 200 graph = neo4j.get_instance() phenotype_mother_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_mother_uuid) phenotype_son_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_son_B_uuid) assert phenotype_mother_node.son.is_connected(phenotype_son_node) assert phenotype_son_node.mother.is_connected(phenotype_mother_node) # test relationships in get phenotype list response r = client.get(f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) for el in response: if el["uuid"] == phenotype_son_B_uuid: assert el["relationships"]["mother"][ "uuid"] == phenotype_mother_uuid if el["uuid"] == phenotype_mother_uuid: assert el["relationships"]["sons"][0][ "uuid"] == phenotype_son_B_uuid # test relationships in get single phenotype response r = client.get(f"{API_URI}/phenotype/{phenotype_son_B_uuid}", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert response["relationships"]["mother"][ "uuid"] == phenotype_mother_uuid # relationship between phenotype from different studies r = client.post( f"{API_URI}/phenotype/{phenotype_son_A_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 404 # relationship with a random phenotype as son random_phenotype_uuid = faker.pystr() r = client.post( f"{API_URI}/phenotype/{random_phenotype_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 404 # relationship with a random phenotype as father r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{random_phenotype_uuid}", headers=user_B1_headers, ) assert r.status_code == 404 # relationship with itself r = client.post( f"{API_URI}/phenotype/{phenotype_father_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 400 # delete a relationship # father case r = client.delete( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 204 graph = neo4j.get_instance() phenotype_father_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_father_uuid) phenotype_son_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_son_B_uuid) assert not phenotype_father_node.son.single() assert not phenotype_son_node.father.single() # delete a relationship for two phenotypes in an other study r = client.delete( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=user_A1_headers, ) assert r.status_code == 404 # admin delete a relationship r = client.delete( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=admin_headers, ) assert r.status_code == 404 # a user of the same group of the owner delete a relationship # mother case r = client.delete( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=user_B2_headers, ) assert r.status_code == 204 graph = neo4j.get_instance() phenotype_mother_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_mother_uuid) phenotype_son_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_son_B_uuid) assert not phenotype_mother_node.son.single() assert not phenotype_son_node.mother.single() # delete relationship between phenotype from different studies r = client.delete( f"{API_URI}/phenotype/{phenotype_son_A_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 404 # delete relationship with a random phenotype as son r = client.delete( f"{API_URI}/phenotype/{random_phenotype_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 404 # delete relationship with a random phenotype as father r = client.delete( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{random_phenotype_uuid}", headers=user_B1_headers, ) assert r.status_code == 404 r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_father_uuid}", headers=user_B1_headers, ) assert r.status_code == 200 r = client.post( f"{API_URI}/phenotype/{phenotype_son_B_uuid}/relationships/{phenotype_mother_uuid}", headers=user_B1_headers, ) assert r.status_code == 200 # delete a son relationship r = client.delete( f"{API_URI}/phenotype/{phenotype_mother_uuid}/relationships/{phenotype_son_B_uuid}", headers=user_B1_headers, ) assert r.status_code == 204 graph = neo4j.get_instance() phenotype_mother_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_mother_uuid) phenotype_son_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_son_B_uuid) assert not phenotype_mother_node.son.single() assert not phenotype_son_node.mother.single() r = client.delete( f"{API_URI}/phenotype/{phenotype_father_uuid}/relationships/{phenotype_son_B_uuid}", headers=user_B1_headers, ) assert r.status_code == 204 graph = neo4j.get_instance() phenotype_father_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_father_uuid) phenotype_son_node = graph.Phenotype.nodes.get_or_none( uuid=phenotype_son_B_uuid) assert not phenotype_father_node.son.single() assert not phenotype_son_node.father.single() # delete all the elements used by the test delete_test_env( client, user_A1_headers, user_B1_headers, user_B1_uuid, user_B2_uuid, user_A1_uuid, uuid_group_A, uuid_group_B, study1_uuid=study1_uuid, study2_uuid=study2_uuid, )
def test_api_study(self, client: FlaskClient, faker: Faker) -> None: # setup the test env ( admin_headers, uuid_group_A, user_A1_uuid, user_A1_headers, uuid_group_B, user_B1_uuid, user_B1_headers, user_B2_uuid, user_B2_headers, study1_uuid, study2_uuid, ) = create_test_env(client, faker, study=False) # create a new study for the group B random_name = faker.pystr() study1 = {"name": random_name, "description": faker.pystr()} r = client.post(f"{API_URI}/study", headers=user_B1_headers, data=study1) assert r.status_code == 200 study1_uuid = self.get_content(r) assert isinstance(study1_uuid, str) # create a new study for the group A random_name2 = faker.pystr() study2 = {"name": random_name2, "description": faker.pystr()} r = client.post(f"{API_URI}/study", headers=user_A1_headers, data=study2) assert r.status_code == 200 study2_uuid = self.get_content(r) assert isinstance(study2_uuid, str) # check the directory was created dir_path = INPUT_ROOT.joinpath(uuid_group_A, study2_uuid) assert dir_path.is_dir() # test study access # test study list response r = client.get(f"{API_URI}/study", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 1 # test admin access r = client.get(f"{API_URI}/study/{study1_uuid}", headers=admin_headers) assert r.status_code == 200 # study owner r = client.get(f"{API_URI}/study/{study1_uuid}", headers=user_B1_headers) assert r.status_code == 200 # other component of the group r = client.get(f"{API_URI}/study/{study1_uuid}", headers=user_B2_headers) assert r.status_code == 200 # study own by an other group r = client.get(f"{API_URI}/study/{study1_uuid}", headers=user_A1_headers) assert r.status_code == 404 not_authorized_message = self.get_content(r) assert isinstance(not_authorized_message, str) # test study modification # modify a study you do not own r = client.put( f"{API_URI}/study/{study1_uuid}", headers=user_A1_headers, data={"description": faker.pystr()}, ) assert r.status_code == 404 # modify a study you own r = client.put( f"{API_URI}/study/{study1_uuid}", headers=user_B1_headers, data={"description": faker.pystr()}, ) assert r.status_code == 204 # delete a study # delete a study you do not own r = client.delete(f"{API_URI}/study/{study1_uuid}", headers=user_A1_headers) assert r.status_code == 404 # delete a study you own # create a new dataset to test if it's deleted with the study dataset = {"name": faker.pystr(), "description": faker.pystr()} r = client.post( f"{API_URI}/study/{study2_uuid}/datasets", headers=user_A1_headers, data=dataset, ) assert r.status_code == 200 dataset_uuid = self.get_content(r) assert isinstance(dataset_uuid, str) dataset_path = dir_path.joinpath(dataset_uuid) assert dataset_path.is_dir() # create a new file to test if it's deleted with the study filename = f"{faker.pystr()}_R1" file_data = { "name": f"{filename}.fastq.gz", "mimeType": "application/gzip", "size": faker.pyint(), "lastModified": faker.pyint(), } r = client.post( f"{API_URI}/dataset/{dataset_uuid}/files/upload", headers=user_A1_headers, data=file_data, ) assert r.status_code == 201 # get the file uuid r = client.get( f"{API_URI}/dataset/{dataset_uuid}/files", headers=user_A1_headers, ) assert r.status_code == 200 file_list = self.get_content(r) assert isinstance(file_list, list) file_uuid = file_list[0]["uuid"] # create a new technical to test if it's deleted with the study techmeta = {"name": faker.pystr()} r = client.post( f"{API_URI}/study/{study2_uuid}/technicals", headers=user_A1_headers, data=techmeta, ) assert r.status_code == 200 techmeta_uuid = self.get_content(r) assert isinstance(techmeta_uuid, str) # create a new phenotype to test if it's deleted with the study phenotype = {"name": faker.pystr(), "sex": "male"} r = client.post( f"{API_URI}/study/{study2_uuid}/phenotypes", headers=user_A1_headers, data=phenotype, ) assert r.status_code == 200 phenotype_uuid = self.get_content(r) assert isinstance(phenotype_uuid, str) # simulate the study has an output directory # create the output directory in the same way is created in launch pipeline task output_path = OUTPUT_ROOT.joinpath( dataset_path.relative_to(INPUT_ROOT)) output_path.mkdir(parents=True) assert output_path.is_dir() # delete the study r = client.delete(f"{API_URI}/study/{study2_uuid}", headers=user_A1_headers) assert r.status_code == 204 assert not dir_path.is_dir() assert not dataset_path.is_dir() # check the dataset was deleted r = client.get(f"{API_URI}/dataset/{dataset_uuid}", headers=user_A1_headers) assert r.status_code == 404 # check the file was deleted r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_A1_headers) assert r.status_code == 404 # check the technical was deleted r = client.get(f"{API_URI}/technical/{techmeta_uuid}", headers=user_A1_headers) assert r.status_code == 404 # check the phenotype was deleted r = client.get(f"{API_URI}/phenotype/{phenotype_uuid}", headers=user_A1_headers) assert r.status_code == 404 # check the output dir was deleted assert not output_path.is_dir() # delete a study own by your group r = client.delete(f"{API_URI}/study/{study1_uuid}", headers=user_B2_headers) assert r.status_code == 204 # check study deletion r = client.get(f"{API_URI}/study/{study1_uuid}", headers=user_B1_headers) assert r.status_code == 404 not_existent_message = self.get_content(r) assert isinstance(not_existent_message, str) assert not_existent_message == not_authorized_message # delete all the elements used by the test delete_test_env( client, user_A1_headers, user_B1_headers, user_B1_uuid, user_B2_uuid, user_A1_uuid, uuid_group_A, uuid_group_B, )
def test_api_techmeta(self, client: FlaskClient, faker: Faker) -> None: # setup the test env ( admin_headers, uuid_group_A, user_A1_uuid, user_A1_headers, uuid_group_B, user_B1_uuid, user_B1_headers, user_B2_uuid, user_B2_headers, study1_uuid, study2_uuid, ) = create_test_env(client, faker, study=True) # create a new techmeta techmeta1 = { "name": faker.pystr(), "sequencing_date": faker.date(), "platform": "Other", } r = client.post( f"{API_URI}/study/{study1_uuid}/technicals", headers=user_B1_headers, data=techmeta1, ) assert r.status_code == 200 techmeta1_uuid = self.get_content(r) assert isinstance(techmeta1_uuid, str) # create a new techmeta in a study of an other group r = client.post( f"{API_URI}/study/{study2_uuid}/technicals", headers=user_B1_headers, data=techmeta1, ) assert r.status_code == 404 # create a new technical as admin not belonging to study group techmeta2 = { "name": faker.pystr(), "sequencing_date": faker.date(), "platform": "Other", } r = client.post( f"{API_URI}/study/{study1_uuid}/technicals", headers=admin_headers, data=techmeta2, ) assert r.status_code == 404 r = client.post( f"{API_URI}/study/{study1_uuid}/technicals", headers=user_B1_headers, data=techmeta2, ) assert r.status_code == 200 techmeta2_uuid = self.get_content(r) assert isinstance(techmeta2_uuid, str) # test technical access # test technical list response r = client.get(f"{API_URI}/study/{study1_uuid}/technicals", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 2 # test technical list response for a study you don't have access r = client.get(f"{API_URI}/study/{study2_uuid}/technicals", headers=user_B1_headers) assert r.status_code == 404 # test technical list response for admin r = client.get(f"{API_URI}/study/{study1_uuid}/technicals", headers=admin_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 2 # test empty list of technicals in a study r = client.get(f"{API_URI}/study/{study2_uuid}/technicals", headers=user_A1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert not response # study owner r = client.get(f"{API_URI}/technical/{techmeta1_uuid}", headers=user_B1_headers) assert r.status_code == 200 # same group of the study owner r = client.get(f"{API_URI}/technical/{techmeta1_uuid}", headers=user_B2_headers) assert r.status_code == 200 # technical owned by an other group r = client.get(f"{API_URI}/technical/{techmeta1_uuid}", headers=user_A1_headers) assert r.status_code == 404 not_authorized_message = self.get_content(r) assert isinstance(not_authorized_message, str) # admin access r = client.get(f"{API_URI}/technical/{techmeta1_uuid}", headers=admin_headers) assert r.status_code == 200 # test technical modification # modify a non existent technical random_technical = faker.pystr() r = client.put( f"{API_URI}/technical/{random_technical}", headers=user_A1_headers, data={"name": faker.pystr()}, ) assert r.status_code == 404 # modify a technical you do not own r = client.put( f"{API_URI}/technical/{techmeta1_uuid}", headers=user_A1_headers, data={"name": faker.pystr()}, ) assert r.status_code == 404 # modify a technical you own r = client.put( f"{API_URI}/technical/{techmeta1_uuid}", headers=user_B1_headers, data={ "name": faker.pystr(), "sequencing_date": faker.date() }, ) assert r.status_code == 204 # admin modify a technical of a group he don't belongs r = client.put( f"{API_URI}/technical/{techmeta1_uuid}", headers=admin_headers, data={"name": faker.pystr()}, ) assert r.status_code == 404 # delete a technical # delete a technical that does not exists r = client.delete(f"{API_URI}/technical/{random_technical}", headers=user_A1_headers) assert r.status_code == 404 # delete a technical in a study you do not own r = client.delete(f"{API_URI}/technical/{techmeta1_uuid}", headers=user_A1_headers) assert r.status_code == 404 # admin delete a technical of a group he don't belong r = client.delete(f"{API_URI}/technical/{techmeta1_uuid}", headers=admin_headers) assert r.status_code == 404 # delete a technical in a study you own r = client.delete(f"{API_URI}/technical/{techmeta1_uuid}", headers=user_B1_headers) assert r.status_code == 204 # delete a technical in a study own by your group r = client.delete(f"{API_URI}/technical/{techmeta2_uuid}", headers=user_B2_headers) assert r.status_code == 204 # check technical deletion r = client.get(f"{API_URI}/technical/{techmeta1_uuid}", headers=user_B1_headers) assert r.status_code == 404 not_existent_message = self.get_content(r) assert isinstance(not_existent_message, str) assert not_existent_message == not_authorized_message # delete all the elements used by the test delete_test_env( client, user_A1_headers, user_B1_headers, user_B1_uuid, user_B2_uuid, user_A1_uuid, uuid_group_A, uuid_group_B, study1_uuid=study1_uuid, study2_uuid=study2_uuid, )
def test_api_file(self, client: FlaskClient, faker: Faker) -> None: # setup the test env ( admin_headers, uuid_group_A, user_A1_uuid, user_A1_headers, uuid_group_B, user_B1_uuid, user_B1_headers, user_B2_uuid, user_B2_headers, study1_uuid, study2_uuid, ) = create_test_env(client, faker, study=True) # create a new dataset dataset_B = {"name": faker.pystr(), "description": faker.pystr()} r = client.post( f"{API_URI}/study/{study1_uuid}/datasets", headers=user_B1_headers, data=dataset_B, ) assert r.status_code == 200 dataset_B_uuid = self.get_content(r) assert isinstance(dataset_B_uuid, str) # check accesses for post request # upload a new file in a dataset of an other group fake_file = { "name": f"{faker.pystr()}_R1.fastq.gz", "mimeType": "application/gzip", "size": faker.pyint(), "lastModified": faker.pyint(), } r = client.post( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload", headers=user_A1_headers, data=fake_file, ) assert r.status_code == 404 # upload a new file as admin not belonging to study group r = client.post( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload", headers=admin_headers, data=fake_file, ) assert r.status_code == 404 # try to upload a file with a non allowed format fake_format = { "name": f"{faker.pystr()}_R1.txt", "mimeType": "text/plain", "size": faker.pyint(), "lastModified": faker.pyint(), } r = client.post( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload", headers=user_B1_headers, data=fake_format, ) assert r.status_code == 400 # try to upload a file with a wrong nomenclature fake_nomencl_file = { "name": f"{faker.pystr()}.{faker.pystr()}_R1.fastq.gz.{faker.pystr()}", "mimeType": "text/plain", "size": faker.pyint(), "lastModified": faker.pyint(), } r = client.post( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload", headers=user_B1_headers, data=fake_nomencl_file, ) assert r.status_code == 400 fake_nomencl_file2 = { "name": f"{faker.pystr()}.{faker.pystr()}_R1.fastq.gz", "mimeType": "text/plain", "size": faker.pyint(), "lastModified": faker.pyint(), } r = client.post( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload", headers=user_B1_headers, data=fake_nomencl_file2, ) assert r.status_code == 400 valid_fcontent = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=12)}" # invalid header, @ is missing invalid_header = f"SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=12)}" # CASE invalid separator invalid_separator = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n{faker.pystr()}\n{faker.pystr(max_chars=12)}" # len of sequence != len of quality invalid_sequence = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=8)}" # invalid second header invalid_header2 = f"@SEQ_ID\n{faker.pystr(max_chars=12)}\n+{faker.pystr()}\n{faker.pystr(max_chars=12)}\n{faker.pystr()}" # create a file to upload fastq = self.create_fastq_gz(faker, valid_fcontent) # upload a file response = self.upload_file(client, user_B1_headers, fastq, dataset_B_uuid, stream=True) assert response.status_code == 200 # check the file exists and have the expected size filename = fastq.name filesize = fastq.stat().st_size filepath = INPUT_ROOT.joinpath(uuid_group_B, study1_uuid, dataset_B_uuid, filename) assert filepath.is_file() assert filepath.stat().st_size == filesize # upload the same file twice response = self.upload_file(client, user_B2_headers, fastq, dataset_B_uuid, stream=True) assert response.status_code == 409 # upload the same file in a different dataset # create a new dataset dataset_B2 = {"name": faker.pystr(), "description": faker.pystr()} r = client.post( f"{API_URI}/study/{study1_uuid}/datasets", headers=user_B1_headers, data=dataset_B2, ) assert r.status_code == 200 dataset_B2_uuid = self.get_content(r) assert isinstance(dataset_B2_uuid, str) response = self.upload_file(client, user_B2_headers, fastq, dataset_B2_uuid, stream=True) assert response.status_code == 200 # check error if final file size is different from the expected # rename the file to upload fastq2 = fastq.parent.joinpath(f"{faker.pystr()}_R1.fastq.gz") fastq.rename(fastq2) # upload without streaming response = self.upload_file( client, user_B1_headers, fastq2, dataset_B_uuid, stream=False, ) assert response.status_code == 500 error_message = self.get_content(response) assert isinstance(error_message, str) assert ( error_message == "File has not been uploaded correctly: final size does not correspond to total size. Please try a new upload" ) # check uncomplete file has been removed check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, fastq2.name, ) assert not check_filepath.is_file() # check file validation # upload an empty file empty_file = self.create_fastq_gz(faker, "") response = self.upload_file( client, user_B1_headers, empty_file, dataset_B_uuid, stream=True, ) assert response.status_code == 400 # check the empty file has been removed check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, empty_file.name, ) assert not check_filepath.is_file() empty_file.unlink() # upload a file with not valid content # CASE wrong gzip file wrong_gzip = Path(tempfile.gettempdir(), f"{faker.pystr()}_R1.fastq.gz") # Directly write the gz => it is an ascii file and not a valid gz with open(wrong_gzip, "w") as f: f.write(valid_fcontent) response = self.upload_file( client, user_B1_headers, wrong_gzip, dataset_B_uuid, stream=True, ) assert response.status_code == 400 error_message = self.get_content(response) assert isinstance(error_message, str) assert "gzipped" in error_message # check the empty file has been removed check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, wrong_gzip.name, ) assert not check_filepath.is_file() wrong_gzip.unlink() # CASE binary file instead of a text file binary_file = self.create_fastq_gz(faker, faker.binary(), mode="wb") response = self.upload_file( client, user_B1_headers, binary_file, dataset_B_uuid, stream=True, ) assert response.status_code == 400 error_message = self.get_content(response) assert isinstance(error_message, str) assert "binary" in error_message check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, binary_file.name, ) assert not check_filepath.is_file() binary_file.unlink() # CASE invalid header invalid_fastq = self.create_fastq_gz(faker, invalid_header) response = self.upload_file( client, user_B1_headers, invalid_fastq, dataset_B_uuid, stream=True, ) assert response.status_code == 400 error_message = self.get_content(response) assert isinstance(error_message, str) assert "header" in error_message check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, invalid_fastq.name, ) assert not check_filepath.is_file() invalid_fastq.unlink() # CASE invalid separator invalid_fastq = self.create_fastq_gz(faker, invalid_separator) response = self.upload_file( client, user_B1_headers, invalid_fastq, dataset_B_uuid, stream=True, ) assert response.status_code == 400 error_message = self.get_content(response) assert isinstance(error_message, str) assert "separator" in error_message check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, invalid_fastq.name, ) assert not check_filepath.is_file() invalid_fastq.unlink() # CASE invalid sequence line invalid_fastq = self.create_fastq_gz(faker, invalid_sequence) response = self.upload_file( client, user_B1_headers, invalid_fastq, dataset_B_uuid, stream=True, ) assert response.status_code == 400 error_message = self.get_content(response) assert isinstance(error_message, str) assert "lines lengths differ" in error_message check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, invalid_fastq.name, ) assert not check_filepath.is_file() invalid_fastq.unlink() # CASE invalid header for the second read invalid_fastq = self.create_fastq_gz(faker, invalid_header2) response = self.upload_file( client, user_B1_headers, invalid_fastq, dataset_B_uuid, stream=True, ) assert response.status_code == 400 error_message = self.get_content(response) assert isinstance(error_message, str) assert "header" in error_message check_filepath = INPUT_ROOT.joinpath( uuid_group_B, study1_uuid, dataset_B_uuid, invalid_fastq.name, ) assert not check_filepath.is_file() invalid_fastq.unlink() # check accesses on put endpoint # put on a file in a dataset of an other group r = client.put( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload/{filename}", headers=user_A1_headers, ) assert r.status_code == 404 # put a file as admin not belonging to study group r = client.put( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload/{filename}", headers=admin_headers, ) assert r.status_code == 404 # put of a ton existent file r = client.put( f"{API_URI}/dataset/{dataset_B_uuid}/files/upload/{fastq2}.txt.gz", headers=user_B1_headers, ) assert r.status_code == 404 # test file access # test file list response r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files", headers=user_B1_headers) assert r.status_code == 200 file_list = self.get_content(r) assert isinstance(file_list, list) assert len(file_list) == 1 file_uuid = file_list[0]["uuid"] # test file list response for a dataset you don't have access r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files", headers=user_A1_headers) assert r.status_code == 404 # test file list response for admin r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files", headers=admin_headers) assert r.status_code == 200 file_list = self.get_content(r) assert isinstance(file_list, list) assert len(file_list) == 1 # check use case of file not in the folder # rename the file in the folder as it will not be found temporary_filepath = filepath.with_suffix(".fastq.tmp") filepath.rename(temporary_filepath) r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files", headers=user_B1_headers) assert r.status_code == 200 file_list = self.get_content(r) assert isinstance(file_list, list) assert file_list[0]["status"] == "unknown" # create an empty file with the original name # test status from unknown to importing filepath.touch() r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files", headers=user_B1_headers) assert r.status_code == 200 file_list = self.get_content(r) assert isinstance(file_list, list) assert file_list[0]["status"] == "importing" # restore the original file filepath.unlink() r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 200 file_response = self.get_content(r) assert isinstance(file_response, dict) assert file_response["status"] == "unknown" temporary_filepath.rename(filepath) r = client.get(f"{API_URI}/dataset/{dataset_B_uuid}/files", headers=user_B1_headers) assert r.status_code == 200 file_list = self.get_content(r) assert isinstance(file_list, list) assert file_list[0]["status"] == "uploaded" # dataset owner r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 200 # same group of the dataset owner r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B2_headers) assert r.status_code == 200 # file owned by an other group r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_A1_headers) assert r.status_code == 404 not_authorized_message = self.get_content(r) assert isinstance(not_authorized_message, str) # admin access r = client.get(f"{API_URI}/file/{file_uuid}", headers=admin_headers) assert r.status_code == 200 # check use case of file not in the folder # rename the file in the folder as it will not be found filepath.rename(temporary_filepath) r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 200 # create an empty file with the original name # test status from unknown to importing filepath.touch() r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 200 file_res = self.get_content(r) assert isinstance(file_res, dict) assert file_res["status"] == "importing" # restore the original file filepath.unlink() r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 200 temporary_filepath.rename(filepath) r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 200 file_res = self.get_content(r) assert isinstance(file_res, dict) assert file_res["status"] == "uploaded" # delete a file # delete a file that does not exists fake_filename = f"{faker.pystr()}_R1" r = client.delete(f"{API_URI}/file/{fake_filename}", headers=user_A1_headers) assert r.status_code == 404 # delete a file in a dataset you do not own r = client.delete(f"{API_URI}/file/{file_uuid}", headers=user_A1_headers) assert r.status_code == 404 # admin delete a file of a dataset he don't belong r = client.delete(f"{API_URI}/file/{file_uuid}", headers=admin_headers) assert r.status_code == 404 # delete a file in a dataset you own r = client.delete(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 204 # delete a file in a dataset own by your group r = client.get(f"{API_URI}/dataset/{dataset_B2_uuid}/files", headers=user_B2_headers) file_list = self.get_content(r) assert isinstance(file_list, list) file2_uuid = file_list[0]["uuid"] r = client.delete(f"{API_URI}/file/{file2_uuid}", headers=user_B2_headers) assert r.status_code == 204 # check file deletion r = client.get(f"{API_URI}/file/{file_uuid}", headers=user_B1_headers) assert r.status_code == 404 not_existent_message = self.get_content(r) assert isinstance(not_existent_message, str) assert not_existent_message == not_authorized_message # check physical deletion from the folder assert not filepath.is_file() if fastq.exists(): fastq.unlink() if fastq2.exists(): fastq2.unlink() # delete all the elements used by the test delete_test_env( client, user_A1_headers, user_B1_headers, user_B1_uuid, user_B2_uuid, user_A1_uuid, uuid_group_A, uuid_group_B, study1_uuid=study1_uuid, study2_uuid=study2_uuid, )
def test_group_users(self, client: FlaskClient, faker: Faker) -> None: if not Env.get_bool("MAIN_LOGIN_ENABLE") or not Env.get_bool( "AUTH_ENABLE"): log.warning("Skipping group/users tests") return # Create group 1 with 1 Coordinator and 1 User group1_uuid, _ = self.create_group(client) _, user1_data = self.create_user(client, roles=[Role.COORDINATOR], data={"group": group1_uuid}) _, user2_data = self.create_user(client, roles=[Role.USER], data={"group": group1_uuid}) # Create group 2 with only 1 Coordinator group2_uuid, _ = self.create_group(client) _, user3_data = self.create_user(client, roles=[Role.COORDINATOR], data={"group": group2_uuid}) # Verify POST / PUT and DELETE are not enabled headers, _ = self.do_login(client, user1_data["email"], user1_data["password"]) r = client.post(f"{API_URI}/group/users", headers=headers) assert r.status_code == 405 r = client.put(f"{API_URI}/group/users", headers=headers) assert r.status_code == 405 r = client.delete(f"{API_URI}/group/users", headers=headers) assert r.status_code == 405 r = client.put(f"{API_URI}/group/users/{group1_uuid}", headers=headers) assert r.status_code == 404 r = client.delete(f"{API_URI}/group/users/{group1_uuid}", headers=headers) assert r.status_code == 404 # Verify GET response r = client.get(f"{API_URI}/group/users", headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert response is not None assert len(response) == 2 assert "email" in response[0] assert "name" in response[0] assert "surname" in response[0] assert "roles" in response[0] assert "password" not in response[0] assert "uuid" not in response[0] assert "group" not in response[0] assert "belongs_to" not in response[0] assert "first_login" not in response[0] assert "last_login" not in response[0] assert "last_password_change" not in response[0] assert "is_active" not in response[0] assert "privacy_accepted" not in response[0] assert "expiration" not in response[0] email1 = response[0]["email"] email2 = response[1]["email"] assert email1 == user1_data["email"] or email2 == user1_data["email"] assert email1 == user2_data["email"] or email2 == user2_data["email"] assert email1 != user3_data["email"] and email2 != user3_data["email"] # Verify GET response with the other group headers, _ = self.do_login(client, user3_data["email"], user3_data["password"]) r = client.get(f"{API_URI}/group/users", headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert response is not None assert len(response) == 1 assert "email" in response[0] assert "name" in response[0] assert "surname" in response[0] assert "roles" in response[0] assert "password" not in response[0] assert "uuid" not in response[0] assert "group" not in response[0] assert "belongs_to" not in response[0] assert "first_login" not in response[0] assert "last_login" not in response[0] assert "last_password_change" not in response[0] assert "is_active" not in response[0] assert "privacy_accepted" not in response[0] assert "expiration" not in response[0] assert response[0]["email"] == user3_data["email"] assert response[0]["email"] != user1_data["email"] assert response[0]["email"] != user2_data["email"] # Add an admin to group1 _, user4_data = self.create_user(client, roles=[Role.ADMIN, Role.COORDINATOR], data={"group": group1_uuid}) # Verify as Admin AND Coordinator (Expected: all members, including admins) headers, _ = self.do_login(client, user4_data["email"], user4_data["password"]) r = client.get(f"{API_URI}/group/users", headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) members = {r["email"] for r in response} assert len(members) == 3 assert user1_data["email"] in members assert user2_data["email"] in members assert user3_data["email"] not in members assert user4_data["email"] in members # Verify as Coordinator only (Expected: admins to be filtered out) headers, _ = self.do_login(client, user1_data["email"], user1_data["password"]) r = client.get(f"{API_URI}/group/users", headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) members = {r["email"] for r in response} assert len(members) == 2 assert user1_data["email"] in members assert user2_data["email"] in members assert user3_data["email"] not in members assert user4_data["email"] not in members
def test_api_dataset(self, client: FlaskClient, faker: Faker) -> None: # setup the test env ( admin_headers, uuid_group_A, user_A1_uuid, user_A1_headers, uuid_group_B, user_B1_uuid, user_B1_headers, user_B2_uuid, user_B2_headers, study1_uuid, study2_uuid, ) = create_test_env(client, faker, study=True) # create a new dataset dataset1 = {"name": faker.pystr(), "description": faker.pystr()} r = client.post( f"{API_URI}/study/{study1_uuid}/datasets", headers=user_B1_headers, data=dataset1, ) assert r.status_code == 200 dataset1_uuid = self.get_content(r) assert isinstance(dataset1_uuid, str) # check the directory exists dir_path = INPUT_ROOT.joinpath(uuid_group_B, study1_uuid, dataset1_uuid) assert dir_path.is_dir() # create a new dataset in a study of an other group r = client.post( f"{API_URI}/study/{study2_uuid}/datasets", headers=user_B1_headers, data=dataset1, ) assert r.status_code == 404 # create a technical r = client.post( f"{API_URI}/study/{study1_uuid}/technicals", headers=user_B1_headers, data={"name": faker.pystr()}, ) assert r.status_code == 200 technical_uuid = self.get_content(r) assert isinstance(technical_uuid, str) # create a phenotype r = client.post( f"{API_URI}/study/{study1_uuid}/phenotypes", headers=user_B1_headers, data={ "name": faker.pystr(), "sex": "male" }, ) assert r.status_code == 200 phenotype_uuid = self.get_content(r) assert isinstance(phenotype_uuid, str) # create a new dataset as admin not belonging to study group dataset2 = { "name": faker.pystr(), "description": faker.pystr(), "phenotype": phenotype_uuid, "technical": technical_uuid, } r = client.post( f"{API_URI}/study/{study1_uuid}/datasets", headers=admin_headers, data=dataset2, ) assert r.status_code == 404 r = client.post( f"{API_URI}/study/{study1_uuid}/datasets", headers=user_B1_headers, data=dataset2, ) assert r.status_code == 200 dataset2_uuid = self.get_content(r) assert isinstance(dataset2_uuid, str) # test dataset access # test dataset list response r = client.get(f"{API_URI}/study/{study1_uuid}/datasets", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 2 # test dataset list response for a study you don't have access r = client.get(f"{API_URI}/study/{study2_uuid}/datasets", headers=user_B1_headers) assert r.status_code == 404 # test dataset list response for admin r = client.get(f"{API_URI}/study/{study1_uuid}/datasets", headers=admin_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert len(response) == 2 # test empty list of datasets in a study r = client.get(f"{API_URI}/study/{study2_uuid}/datasets", headers=user_A1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, list) assert not response # dataset owner r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers) assert r.status_code == 200 # same group of the owner r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B2_headers) assert r.status_code == 200 # dataset owned by an other group r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_A1_headers) assert r.status_code == 404 not_authorized_message = self.get_content(r) assert isinstance(not_authorized_message, str) # admin access r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=admin_headers) assert r.status_code == 200 # test technical and phenoype assignation when a new dataset is created r = client.get(f"{API_URI}/dataset/{dataset2_uuid}", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert "technical" in response assert "phenotype" in response assert response["technical"]["uuid"] == technical_uuid # check phenotype was correctly assigned assert response["phenotype"]["uuid"] == phenotype_uuid # test dataset changing status r = client.patch( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers, data={"status": "UPLOAD COMPLETED"}, ) assert r.status_code == 204 # check new status in get response r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert "status" in response # delete status r = client.patch( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers, data={"status": "-1"}, ) assert r.status_code == 204 # check status has been removed r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert not response["status"] # try to modify a status when the dataset is running graph = neo4j.get_instance() dataset = graph.Dataset.nodes.get_or_none(uuid=dataset1_uuid) dataset.status = "RUNNING" dataset.save() r = client.patch( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers, data={"status": "UPLOAD COMPLETED"}, ) assert r.status_code == 400 # admin tries to modify a status when the dataset is running r = client.patch( f"{API_URI}/dataset/{dataset1_uuid}", headers=admin_headers, data={"status": "UPLOAD COMPLETED"}, ) assert r.status_code == 204 # test dataset modification # modify a dataset you do not own r = client.put( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_A1_headers, data={"description": faker.pystr()}, ) assert r.status_code == 404 # modify a dataset you own r = client.put( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers, data={"description": faker.pystr()}, ) assert r.status_code == 204 # modify a dataset of your group assigning a technical and a phenotype r = client.put( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B2_headers, data={ "name": faker.pystr(), "technical": technical_uuid, "phenotype": phenotype_uuid, }, ) assert r.status_code == 204 # check technical was correctly assigned r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B2_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert "technical" in response assert "phenotype" in response assert response["technical"]["uuid"] == technical_uuid # check phenotype was correctly assigned assert response["phenotype"]["uuid"] == phenotype_uuid # modify a dataset of your group removing a technical and a phenotype r = client.put( f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B2_headers, data={ "technical": "-1", "phenotype": "-1" }, ) assert r.status_code == 204 # check technical was correctly removed r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B2_headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) assert response["technical"] is None # check phenotype was correctly removed assert response["phenotype"] is None # admin modify a dataset of a group he don't belongs r = client.put( f"{API_URI}/dataset/{dataset1_uuid}", headers=admin_headers, data={"description": faker.pystr()}, ) assert r.status_code == 404 # simulate the dataset has an output directory # create the output directory in the same way is created in launch pipeline task output_path = OUTPUT_ROOT.joinpath(dir_path.relative_to(INPUT_ROOT)) output_path.mkdir(parents=True) assert output_path.is_dir() # delete a dataset # delete a dataset you do not own r = client.delete(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_A1_headers) assert r.status_code == 404 # admin delete a dataset of a group he don't belong r = client.delete(f"{API_URI}/dataset/{dataset1_uuid}", headers=admin_headers) assert r.status_code == 404 # delete a dataset you own r = client.delete(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers) assert r.status_code == 204 assert not dir_path.is_dir() # delete a study own by your group r = client.delete(f"{API_URI}/dataset/{dataset2_uuid}", headers=user_B2_headers) assert r.status_code == 204 # check dataset deletion r = client.get(f"{API_URI}/dataset/{dataset1_uuid}", headers=user_B1_headers) assert r.status_code == 404 not_existent_message = self.get_content(r) assert isinstance(not_existent_message, str) assert not_existent_message == not_authorized_message # check directory deletion assert not dir_path.is_dir() assert not output_path.is_dir() # delete all the elements used by the test delete_test_env( client, user_A1_headers, user_B1_headers, user_B1_uuid, user_B2_uuid, user_A1_uuid, uuid_group_A, uuid_group_B, study1_uuid=study1_uuid, study2_uuid=study2_uuid, )
def test_admin_groups(self, client: FlaskClient, faker: Faker) -> None: if not Env.get_bool("MAIN_LOGIN_ENABLE"): # pragma: no cover log.warning("Skipping admin/users tests") return headers, _ = self.do_login(client, None, None) r = client.get(f"{API_URI}/admin/groups", headers=headers) assert r.status_code == 200 schema = self.getDynamicInputSchema(client, "admin/groups", headers) data = self.buildData(schema) # Event 1: create r = client.post(f"{API_URI}/admin/groups", data=data, headers=headers) assert r.status_code == 200 uuid = self.get_content(r) r = client.get(f"{API_URI}/admin/groups", headers=headers) assert r.status_code == 200 groups = self.get_content(r) assert groups assert len(groups) > 0 fullname = None for g in groups: if g.get("uuid") == uuid: fullname = g.get("fullname") break else: # pragma: no cover pytest.fail("Group not found") assert fullname is not None newdata = { "shortname": faker.company(), "fullname": faker.company(), } # Event 2: modify r = client.put(f"{API_URI}/admin/groups/{uuid}", data=newdata, headers=headers) assert r.status_code == 204 r = client.get(f"{API_URI}/admin/groups", headers=headers) assert r.status_code == 200 groups = self.get_content(r) for g in groups: if g.get("uuid") == uuid: assert g.get("fullname") == newdata.get("fullname") assert g.get("fullname") != data.get("fullname") assert g.get("fullname") != fullname r = client.put(f"{API_URI}/admin/groups/xyz", data=data, headers=headers) assert r.status_code == 404 # Event 3: delete r = client.delete(f"{API_URI}/admin/groups/{uuid}", headers=headers) assert r.status_code == 204 r = client.get(f"{API_URI}/admin/groups", headers=headers) assert r.status_code == 200 groups = self.get_content(r) for g in groups: if g.get("uuid") == uuid: # pragma: no cover pytest.fail("Group not deleted!") r = client.delete(f"{API_URI}/admin/groups/xyz", headers=headers) assert r.status_code == 404 # Create a group and assign it to the main user # Profile and AdminUsers will react to this change # Very important: admin_groups must be tested before admin_users and profile r = client.get(f"{AUTH_URI}/profile", headers=headers) assert r.status_code == 200 user_uuid = self.get_content(r).get("uuid") data = { "fullname": "Default group", "shortname": faker.company(), } # Event 4: create uuid, _ = self.create_group(client, data=data) data = { "group": uuid, # very important, otherwise the default user will lose its admin role "roles": json.dumps(["admin_root"]), } headers, _ = self.do_login(client, None, None) # Event 5: modify r = client.put(f"{API_URI}/admin/users/{user_uuid}", data=data, headers=headers) assert r.status_code == 204