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_03_change_profile(self, client: FlaskClient, faker: Faker) -> None: # Always enable during core tests if not Env.get_bool("MAIN_LOGIN_ENABLE"): # pragma: no cover log.warning("Profile is disabled, skipping tests") return headers, _ = self.do_login(client, None, None) # update profile, no auth r = client.put(f"{AUTH_URI}/profile") assert r.status_code == 401 # update profile, no auth r = client.patch(f"{AUTH_URI}/profile") assert r.status_code == 401 # update profile, no data r = client.patch(f"{AUTH_URI}/profile", data={}, headers=headers) assert r.status_code == 204 events = self.get_last_events(1) assert events[0].event == Events.modify.value assert events[0].user == BaseAuthentication.default_user assert events[0].target_type == "User" # It is true in the core, but projects may introduce additional values # and expand the input dictionary even if initially empty # e.g. meteohub adds here the requests_expiration_days parameter # assert len(events[0].payload) == 0 newname = faker.name() newuuid = faker.pystr() r = client.get(f"{AUTH_URI}/profile", headers=headers) assert r.status_code == 200 c = self.get_content(r) assert c.get("name") is not None assert c.get("name") != newname assert c.get("uuid") is not None assert c.get("uuid") != newuuid # update profile data = {"name": newname, "uuid": newuuid} r = client.patch(f"{AUTH_URI}/profile", data=data, headers=headers) # uuid cannot be modified and will raise an unknown field assert r.status_code == 400 data = {"name": newname} r = client.patch(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 204 events = self.get_last_events(1) assert events[0].event == Events.modify.value assert events[0].user == BaseAuthentication.default_user assert events[0].target_type == "User" # It is true in the core, but projects may introduce additional values # and expand the input dictionary even if initially empty # e.g. meteohub adds here the requests_expiration_days parameter # assert len(events[0].payload) == 1 assert "name" in events[0].payload r = client.get(f"{AUTH_URI}/profile", headers=headers) assert r.status_code == 200 c = self.get_content(r) assert c.get("name") == newname assert c.get("uuid") != newuuid # change password, no data r = client.put(f"{AUTH_URI}/profile", data={}, headers=headers) assert r.status_code == 400 # Sending a new_password and/or password_confirm without a password newpassword = faker.password() data = {"new_password": newpassword} r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 data = {"password_confirm": newpassword} r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 data = {"new_password": newpassword, "password_confirm": newpassword} r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 data = {} data["password"] = faker.password(length=5) r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 data["new_password"] = faker.password(length=5) r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 data["password_confirm"] = faker.password(length=5) r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 data["password"] = BaseAuthentication.default_password r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 # Passwords are too short data["password_confirm"] = data["new_password"] r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 400 # Trying to set new password == password... it is not permitted! data["password_confirm"] = data["password"] data["new_password"] = data["password"] if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"): data["totp_code"] = BaseTests.generate_totp( BaseAuthentication.default_user) r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 409 # Change the password data["new_password"] = faker.password(strong=True) data["password_confirm"] = data["new_password"] r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 204 # After a change password a spam of delete Token is expected # Reverse the list and skip all delete tokens to find the change password event events = self.get_last_events(100) events.reverse() for event in events: if event.event == Events.delete.value: assert event.target_type == "Token" continue assert event.event == Events.change_password.value assert event.user == BaseAuthentication.default_user break # verify the new password headers, _ = self.do_login(client, BaseAuthentication.default_user, data["new_password"]) # restore the previous password data["password"] = data["new_password"] data["new_password"] = BaseAuthentication.default_password data["password_confirm"] = BaseAuthentication.default_password if Env.get_bool("AUTH_SECOND_FACTOR_AUTHENTICATION"): data["totp_code"] = BaseTests.generate_totp( BaseAuthentication.default_user) r = client.put(f"{AUTH_URI}/profile", data=data, headers=headers) assert r.status_code == 204 # After a change password a spam of delete Token is expected # Reverse the list and skip all delete tokens to find the change password event events = self.get_last_events(100) events.reverse() for event in events: if event.event == Events.delete.value: assert event.target_type == "Token" continue assert event.event == Events.change_password.value assert event.user == BaseAuthentication.default_user break # verify the new password headers, _ = self.do_login(client, BaseAuthentication.default_user, BaseAuthentication.default_password) self.save("auth_header", headers)
def test_users_custom_fields(self, client: FlaskClient) -> None: output_fields = mem.customizer.get_custom_output_fields(None) profile_inputs = mem.customizer.get_custom_input_fields( request=None, scope=mem.customizer.PROFILE) registration_inputs = mem.customizer.get_custom_input_fields( request=None, scope=mem.customizer.REGISTRATION) admin_inputs = mem.customizer.get_custom_input_fields( request=None, scope=mem.customizer.ADMIN) uuid, data = self.create_user(client) headers, _ = self.do_login(client, data["email"], data["password"]) # Verify custom output fields (if defined) included in the profile response r = client.get(f"{AUTH_URI}/profile", headers=headers) assert r.status_code == 200 response = self.get_content(r) assert isinstance(response, dict) for field in output_fields: assert field in response # Verify custom input fields (if defined) included in the profile input schema r = client.patch(f"{AUTH_URI}/profile", json={"get_schema": 1}, headers=headers) response = self.get_content(r) assert isinstance(response, list) for field in profile_inputs.keys(): for expected in response: if expected["key"] == field: break else: # pragma: no cover pytest.fail( f"Input field {field} not found in profile input schema") # Verify custom registration fields (if defined) included in the reg. schema r = client.post(f"{AUTH_URI}/profile", json={"get_schema": 1}) response = self.get_content(r) assert isinstance(response, list) for field in registration_inputs.keys(): for expected in response: if expected["key"] == field: break else: # pragma: no cover pytest.fail( f"Input field {field} not found in registration input schema" ) headers, _ = self.do_login(client, None, None) # Verify custom admin input fields (if defined) included in admin users schema r = client.post(f"{API_URI}/admin/users", json={"get_schema": 1}, headers=headers) response = self.get_content(r) assert isinstance(response, list) for field in admin_inputs.keys(): for expected in response: if expected["key"] == field: break else: # pragma: no cover pytest.fail( f"Input field {field} not found in admin users input schema" ) # Verify custom admin output fields (if defined) included in admin users output r = client.get(f"{API_URI}/admin/users/{uuid}", headers=headers) response = self.get_content(r) assert isinstance(response, dict) for field in output_fields: # This will fail assert field in response
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_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, )