From 5f97e93434339a15b15a6ffbb8710aa197398f55 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 14 Sep 2023 15:18:15 +0200 Subject: [PATCH 001/155] Revoke project access for invited email --- dds_web/api/user.py | 61 +++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index aaedce3b9..97127b3a4 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -874,31 +874,41 @@ def post(self): # Check if email is registered to a user try: existing_user = user_schemas.UserSchema().load({"email": user_email}) + unanswered_invite = user_schemas.UnansweredInvite().load({"email": user_email}) except sqlalchemy.exc.OperationalError as err: raise ddserr.DatabaseError(message=str(err), alt_message="Unexpected database error.") if not existing_user: - raise ddserr.NoSuchUserError( - f"The user with email '{user_email}' does not have access to the specified project." - " Cannot remove non-existent project access." - ) + if not unanswered_invite: + # The user doesn't exist and doesnt have a pending invite either + raise ddserr.NoSuchUserError( + f"The user with email '{user_email}' does not have access to the specified project." + " Cannot remove non-existent project access." + ) + else: + invite_id = unanswered_invite.id + project_invite_key = models.ProjectInviteKeys.query.filter_by(invite_id=invite_id,project_id=project.id).one_or_none() + if project_invite_key: + db.session.delete(unanswered_invite) + db.session.delete(project_invite_key) - user_in_project = False - for user_association in project.researchusers: - if user_association.user_id == existing_user.username: - user_in_project = True - db.session.delete(user_association) - project_user_key = models.ProjectUserKeys.query.filter_by( - project_id=project.id, user_id=existing_user.username - ).first() - if project_user_key: - db.session.delete(project_user_key) - - if not user_in_project: - raise ddserr.NoSuchUserError( - f"The user with email '{user_email}' does not have access to the specified project." - " Cannot remove non-existent project access." - ) + else: + user_in_project = False + for user_association in project.researchusers: + if user_association.user_id == existing_user.username: + user_in_project = True + db.session.delete(user_association) + project_user_key = models.ProjectUserKeys.query.filter_by( + project_id=project.id, user_id=existing_user.username + ).first() + if project_user_key: + db.session.delete(project_user_key) + + if not user_in_project: + raise ddserr.NoSuchUserError( + f"The user with email '{user_email}' does not have access to the specified project." + " Cannot remove non-existent project access." + ) try: db.session.commit() @@ -919,12 +929,15 @@ def post(self): ), ) from err - flask.current_app.logger.debug( - f"User {existing_user.username} no longer associated with project {project.public_id}." - ) + if unanswered_invite: + msg = f"Invited user is no longer associated with project {project.public_id}." + else: + msg = f"User {existing_user.username} no longer associated with project {project.public_id}." + + flask.current_app.logger.debug(msg) return { - "message": f"User with email {user_email} no longer associated with {project.public_id}." + "message": msg } From aef7c3d5ca1023618b3900b9901772b4843c0940 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 14 Sep 2023 15:18:31 +0200 Subject: [PATCH 002/155] Revoke project access for invited email --- dds_web/api/user.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 97127b3a4..41f83aad0 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -887,7 +887,9 @@ def post(self): ) else: invite_id = unanswered_invite.id - project_invite_key = models.ProjectInviteKeys.query.filter_by(invite_id=invite_id,project_id=project.id).one_or_none() + project_invite_key = models.ProjectInviteKeys.query.filter_by( + invite_id=invite_id, project_id=project.id + ).one_or_none() if project_invite_key: db.session.delete(unanswered_invite) db.session.delete(project_invite_key) @@ -933,12 +935,10 @@ def post(self): msg = f"Invited user is no longer associated with project {project.public_id}." else: msg = f"User {existing_user.username} no longer associated with project {project.public_id}." - + flask.current_app.logger.debug(msg) - return { - "message": msg - } + return {"message": msg} class EncryptedToken(flask_restful.Resource): From 9039db0c93831864dbde6d6aed9bb363fbe36ea3 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 15 Sep 2023 11:03:21 +0200 Subject: [PATCH 003/155] started testing --- tests/test_project_access.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 6028825a0..0564c48a9 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -405,3 +405,9 @@ def test_fix_access_unitadmin_valid_email_unituser(client): project_id=project.id, user_id="unituser" ).first() assert user_project_key_row + +def revoking_access_to_existing_user(client): + pass + +def revoking_access_to_unacepted_invite(client): + pass \ No newline at end of file From 05ed50b3f2f7154f6a4d230aac8f4eeb89db0aaa Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 18 Sep 2023 13:45:51 +0200 Subject: [PATCH 004/155] tests --- tests/test_project_access.py | 42 +++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 0564c48a9..de9cb8ed9 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -14,6 +14,8 @@ # proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} proj_query = {"project": "public_project_id"} # proj_query_restricted = {"project": "restricted_project_id"} +first_new_email = {"email": "first_test_email@mailtrap.io"} +first_new_user = {**first_new_email, "role": "Researcher"} # TESTS #################################################################################### TESTS # @@ -406,8 +408,42 @@ def test_fix_access_unitadmin_valid_email_unituser(client): ).first() assert user_project_key_row -def revoking_access_to_existing_user(client): - pass def revoking_access_to_unacepted_invite(client): - pass \ No newline at end of file + project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + + # invite a new user to an existing project so they receive a new invite + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + assert invited_user + + # check row was added to project invite keys table + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project.id + ).one_or_none() + assert project_invite_keys + + # Now, revoke access to said user. The invite should be deleted + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project.public_id}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + # Check that the invite is deleted + invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + assert not invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project.id + ).one_or_none() + assert not project_invite_keys From a0d4aaae756b222a122c242df10c41e55b1f6d45 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 18 Sep 2023 13:57:15 +0200 Subject: [PATCH 005/155] sprintlog and prettier --- SPRINTLOG.md | 1 + tests/test_project_access.py | 1 + 2 files changed, 2 insertions(+) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 77700d4e5..896ad7267 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -300,3 +300,4 @@ _Nothing merged in CLI during this sprint_ - Dependency: Bump `MariaDB` to LTS version 10.11.5 ([#1465](https://github.com/ScilifelabDataCentre/dds_web/pull/1465)) - Bug fixed: Row in `ProjectUsers` should also be added if it doesn't exist when giving Researcher access to a specific project ([#1464](https://github.com/ScilifelabDataCentre/dds_web/pull/1464)) - Workflow: Update PR template and clarify sections ([#1467](https://github.com/ScilifelabDataCentre/dds_web/pull/1467)) +- Revoking access to unacepted invites removes the invites from the DB ([#1192])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13/backlog?epics=visible&selectedIssue=DDS-1192) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index ae0eedb13..fac3c2a0b 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -469,6 +469,7 @@ def revoking_access_to_unacepted_invite(client): ).one_or_none() assert not project_invite_keys + def test_fix_access_unitadmin_valid_email_unituser_no_project(client): """Unit Admin giving access to unituser - ok. No project.""" # Remove ProjectUserKeys row for specific project and user From eb0edf94b654b0fd7bc01421bdb4898eae9a6747 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 18 Sep 2023 14:31:15 +0200 Subject: [PATCH 006/155] testing msg fixed --- dds_web/api/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 41f83aad0..968b17d09 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -932,9 +932,9 @@ def post(self): ) from err if unanswered_invite: - msg = f"Invited user is no longer associated with project {project.public_id}." + msg = f"Invited user is no longer associated with {project.public_id}." else: - msg = f"User {existing_user.username} no longer associated with project {project.public_id}." + msg = f"User with email {user_email} no longer associated with {project.public_id}." flask.current_app.logger.debug(msg) From 1a69114958384f07dcdfd9c30463b0175fab8b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 18 Sep 2023 14:55:30 +0200 Subject: [PATCH 007/155] Update test_project_access.py --- tests/test_project_access.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index fac3c2a0b..095a5fa78 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -442,7 +442,7 @@ def revoking_access_to_unacepted_invite(client): ) assert response.status_code == http.HTTPStatus.OK - invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() assert invited_user # check row was added to project invite keys table @@ -461,7 +461,7 @@ def revoking_access_to_unacepted_invite(client): assert response.status_code == http.HTTPStatus.OK # Check that the invite is deleted - invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() assert not invited_user project_invite_keys = models.ProjectInviteKeys.query.filter_by( From 9e687680c3d2c045541bcde58d462d15f4c455df Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 18 Sep 2023 16:10:49 +0200 Subject: [PATCH 008/155] testing --- tests/test_project_access.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index fac3c2a0b..37fe98fd4 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -430,7 +430,7 @@ def test_fix_access_unitadmin_valid_email_unituser(client): assert user_project_key_row -def revoking_access_to_unacepted_invite(client): +def test_revoking_access_to_unacepted_invite(client): project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() # invite a new user to an existing project so they receive a new invite @@ -443,11 +443,12 @@ def revoking_access_to_unacepted_invite(client): assert response.status_code == http.HTTPStatus.OK invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + invited_user_id = invited_user.id assert invited_user # check row was added to project invite keys table project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user.id, project_id=project.id + invite_id=invited_user_id, project_id=project.id ).one_or_none() assert project_invite_keys @@ -460,12 +461,17 @@ def revoking_access_to_unacepted_invite(client): ) assert response.status_code == http.HTTPStatus.OK + assert ( + f"Invited user is no longer associated with {project.public_id}." + in response.json["message"] + ) + # Check that the invite is deleted invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() assert not invited_user project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user.id, project_id=project.id + invite_id=invited_user_id, project_id=project.id ).one_or_none() assert not project_invite_keys From 53c56ddf0d032326bf149f95ccb26c46ce86fdf9 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 18 Sep 2023 16:49:26 +0200 Subject: [PATCH 009/155] improved tests --- dds_web/api/user.py | 8 +++++ tests/test_project_access.py | 4 --- tests/test_user_remove_association.py | 48 +++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 968b17d09..cfa0a6720 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -890,9 +890,17 @@ def post(self): project_invite_key = models.ProjectInviteKeys.query.filter_by( invite_id=invite_id, project_id=project.id ).one_or_none() + + # if the unasnwsered invite was asociated in the project, remove the invite and the asociation if project_invite_key: db.session.delete(unanswered_invite) db.session.delete(project_invite_key) + else: + # the unanswred invite is not asociated with the project + raise ddserr.NoSuchUserError( + f"The user with email '{user_email}' does not have access to the specified project." + " Cannot remove non-existent project access." + ) else: user_in_project = False diff --git a/tests/test_project_access.py b/tests/test_project_access.py index beb1c412e..6be2f7850 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -442,12 +442,8 @@ def test_revoking_access_to_unacepted_invite(client): ) assert response.status_code == http.HTTPStatus.OK -<<<<<<< HEAD invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() invited_user_id = invited_user.id -======= - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() ->>>>>>> 1a69114958384f07dcdfd9c30463b0175fab8b0e assert invited_user # check row was added to project invite keys table diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 39c0ecbf5..79b3ac56a 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -7,6 +7,16 @@ from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins from dds_web.database import models +# CONFIG ################################################################################## CONFIG # + +# proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} +proj_query = {"project": "public_project_id"} +# proj_query_restricted = {"project": "restricted_project_id"} +first_new_email = {"email": "first_test_email@mailtrap.io"} +first_new_user = {**first_new_email, "role": "Researcher"} + +# TESTS ################################################################################## TEST # + def test_remove_user_from_project(client, boto3_session): """Remove an associated user from a project""" @@ -98,6 +108,44 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Cannot remove non-existent project access" in response.json["message"] +def test_remove_nonacepted_user_from_other_project(client, boto3_session): + """Try to remove an User with an unacepted invite from another project""" + + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins == 3 + + # create a new project + response = client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + json=proj_data_with_existing_users, + ) + assert response.status_code == http.HTTPStatus.OK + + project_id = response.json.get("project_id") + + # invite a new user to an existing project + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": "public_project_id"}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + # try to remove the user from the first project + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project_id}, + json=first_new_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Cannot remove non-existent project access" in response.json["message"] + + def test_remove_existing_user_from_nonexistent_proj(client, boto3_session): """Try to an existing user from a nonexistent project""" From 52b962d1889d4698c4533483854600415438cc81 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 18 Sep 2023 16:50:10 +0200 Subject: [PATCH 010/155] black --- dds_web/api/user.py | 4 ++-- tests/test_user_remove_association.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index cfa0a6720..4f7c244ed 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -891,7 +891,7 @@ def post(self): invite_id=invite_id, project_id=project.id ).one_or_none() - # if the unasnwsered invite was asociated in the project, remove the invite and the asociation + # if the unasnwsered invite was asociated in the project, remove the invite and the asociation if project_invite_key: db.session.delete(unanswered_invite) db.session.delete(project_invite_key) @@ -900,7 +900,7 @@ def post(self): raise ddserr.NoSuchUserError( f"The user with email '{user_email}' does not have access to the specified project." " Cannot remove non-existent project access." - ) + ) else: user_in_project = False diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 79b3ac56a..d49ae262e 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -108,6 +108,7 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Cannot remove non-existent project access" in response.json["message"] + def test_remove_nonacepted_user_from_other_project(client, boto3_session): """Try to remove an User with an unacepted invite from another project""" @@ -146,7 +147,6 @@ def test_remove_nonacepted_user_from_other_project(client, boto3_session): assert "Cannot remove non-existent project access" in response.json["message"] - def test_remove_existing_user_from_nonexistent_proj(client, boto3_session): """Try to an existing user from a nonexistent project""" From bff698e93bc2df1e4f8ce385024b4e5bd6ed8f84 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 19 Sep 2023 10:08:45 +0200 Subject: [PATCH 011/155] Added more comments --- tests/test_project_access.py | 1 + tests/test_user_remove_association.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 6be2f7850..637fb8ad9 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -431,6 +431,7 @@ def test_fix_access_unitadmin_valid_email_unituser(client): def test_revoking_access_to_unacepted_invite(client): + """ Revoking access to an unacepted invite for an existing project should delete the invite from the db """ project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() # invite a new user to an existing project so they receive a new invite diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index d49ae262e..902de8922 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -110,7 +110,7 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): def test_remove_nonacepted_user_from_other_project(client, boto3_session): - """Try to remove an User with an unacepted invite from another project""" + """ Try to remove an User with an unacepted invite from another project should result in an error""" create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() From 20fcf2eff72095abe334a569574d7083fd12f779 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 19 Sep 2023 10:09:20 +0200 Subject: [PATCH 012/155] black --- tests/test_project_access.py | 2 +- tests/test_user_remove_association.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 637fb8ad9..4a6ce5362 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -431,7 +431,7 @@ def test_fix_access_unitadmin_valid_email_unituser(client): def test_revoking_access_to_unacepted_invite(client): - """ Revoking access to an unacepted invite for an existing project should delete the invite from the db """ + """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() # invite a new user to an existing project so they receive a new invite diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 902de8922..b097b3951 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -110,7 +110,7 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): def test_remove_nonacepted_user_from_other_project(client, boto3_session): - """ Try to remove an User with an unacepted invite from another project should result in an error""" + """Try to remove an User with an unacepted invite from another project should result in an error""" create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() From a1f9a76304b6cc3d47a96ccea7ede7d68c4e1089 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 19 Sep 2023 14:42:38 +0200 Subject: [PATCH 013/155] checks that it wasn't the only invite --- dds_web/api/user.py | 7 +++- tests/test_project_access.py | 62 ++++++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 4f7c244ed..3130012e3 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -893,8 +893,13 @@ def post(self): # if the unasnwsered invite was asociated in the project, remove the invite and the asociation if project_invite_key: - db.session.delete(unanswered_invite) db.session.delete(project_invite_key) + + # if such invite was only associated with a single project, delete the invite. Otherwise leave the invite + project_invite_key = models.ProjectInviteKeys.query.filter_by( + invite_id=invite_id).one_or_none() + if not project_invite_key: + db.session.delete(unanswered_invite) else: # the unanswred invite is not asociated with the project raise ddserr.NoSuchUserError( diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 4a6ce5362..fe9ce5d97 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -430,6 +430,64 @@ def test_fix_access_unitadmin_valid_email_unituser(client): assert user_project_key_row +def test_remove_access_invite_associated_several_projects(client): + """If an invite is associated with several projects then a single revoke access should not delete the invite""" + + project_1 = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + project_2 = models.Project.query.filter_by(public_id="second_public_project_id").one_or_none() + + def add_to_project(project): + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project.id + ).one_or_none() + assert project_invite_keys + + return invited_user + + # invite a new user to both projects + invited_user = add_to_project(project_1) + _ = add_to_project(project_2) + + # Now revoke access for the first project + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project_1.public_id}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + assert ( + f"Invited user is no longer associated with {project_1.public_id}." + in response.json["message"] + ) + + # The project invite row should only be deleted for project 1 and the invite should still exist + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project_2.id + ).one_or_none() + assert project_invite_keys + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project_1.id + ).one_or_none() + assert not project_invite_keys + + def test_revoking_access_to_unacepted_invite(client): """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() @@ -443,7 +501,7 @@ def test_revoking_access_to_unacepted_invite(client): ) assert response.status_code == http.HTTPStatus.OK - invited_user = models.Invite.query.filter_by(email=first_new_email["email"]).one_or_none() + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() invited_user_id = invited_user.id assert invited_user @@ -456,7 +514,7 @@ def test_revoking_access_to_unacepted_invite(client): # Now, revoke access to said user. The invite should be deleted response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project.public_id}, json=first_new_user, ) From ac7e825e4b2015b6b0d4d554efaf0481f9fc5633 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 19 Sep 2023 14:43:55 +0200 Subject: [PATCH 014/155] checks that it wasn't the only invite --- dds_web/api/user.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 3130012e3..207eaaf76 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -894,10 +894,11 @@ def post(self): # if the unasnwsered invite was asociated in the project, remove the invite and the asociation if project_invite_key: db.session.delete(project_invite_key) - + # if such invite was only associated with a single project, delete the invite. Otherwise leave the invite project_invite_key = models.ProjectInviteKeys.query.filter_by( - invite_id=invite_id).one_or_none() + invite_id=invite_id + ).one_or_none() if not project_invite_key: db.session.delete(unanswered_invite) else: From 01f6309d035dd3d8ded81371f5699c1728f94afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:41:33 +0200 Subject: [PATCH 015/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 207eaaf76..4bd7603b5 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -882,7 +882,7 @@ def post(self): if not unanswered_invite: # The user doesn't exist and doesnt have a pending invite either raise ddserr.NoSuchUserError( - f"The user with email '{user_email}' does not have access to the specified project." + f"The user / invite with email '{user_email}' does not have access to the specified project." " Cannot remove non-existent project access." ) else: From 8577bc71a247a5e1f86deeeb5baa51ebfe87cf2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:41:42 +0200 Subject: [PATCH 016/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 4bd7603b5..9bf4d848e 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -904,7 +904,7 @@ def post(self): else: # the unanswred invite is not asociated with the project raise ddserr.NoSuchUserError( - f"The user with email '{user_email}' does not have access to the specified project." + f"The user / invite with email '{user_email}' does not have access to the specified project." " Cannot remove non-existent project access." ) From 4e0aa632fc4817858efbc38b1624bc0d19c144c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:41:50 +0200 Subject: [PATCH 017/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 9bf4d848e..2a494be06 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -922,7 +922,7 @@ def post(self): if not user_in_project: raise ddserr.NoSuchUserError( - f"The user with email '{user_email}' does not have access to the specified project." + f"The user / invite with email '{user_email}' does not have access to the specified project." " Cannot remove non-existent project access." ) From 988597cbb30e71ab1e57f4ba6b1acad6988d4f4b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 21 Sep 2023 10:51:10 +0200 Subject: [PATCH 018/155] refactoring --- dds_web/api/user.py | 85 ++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 2a494be06..fee0a2c4f 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -871,59 +871,66 @@ def post(self): if not (user_email := json_input.get("email")): raise ddserr.DDSArgumentError(message="User email missing.") - # Check if email is registered to a user + # Check if the user exists or has a pending invite try: existing_user = user_schemas.UserSchema().load({"email": user_email}) unanswered_invite = user_schemas.UnansweredInvite().load({"email": user_email}) except sqlalchemy.exc.OperationalError as err: raise ddserr.DatabaseError(message=str(err), alt_message="Unexpected database error.") - if not existing_user: - if not unanswered_invite: - # The user doesn't exist and doesnt have a pending invite either - raise ddserr.NoSuchUserError( - f"The user / invite with email '{user_email}' does not have access to the specified project." - " Cannot remove non-existent project access." - ) - else: - invite_id = unanswered_invite.id + """ + TODO before proceding with the deletion: We need to ensure that the hierarchy of roles is ensured + i.e That: + + Researchers only and just only revoke access for Researchers + ProjectOwners revoke access for ProjectOwners (in the same project) and Researchers (in the same project) + Unit Personnel & Unit Admins: Researchers and Project Owners (any). + """ + + # If the user doesn't exist and doesn't have a pending invite + if not existing_user and not unanswered_invite: + raise ddserr.NoSuchUserError( + f"The user / invite with email '{user_email}' does not have access to the specified project. " + "Cannot remove non-existent project access." + ) + + if unanswered_invite: + invite_id = unanswered_invite.id + + # Check if the unanswered invite is associated with the project + project_invite_key = models.ProjectInviteKeys.query.filter_by( + invite_id=invite_id, project_id=project.id + ).one_or_none() + + if project_invite_key: + # Remove the association if it exists + db.session.delete(project_invite_key) + + # Check if the invite is associated with only one project, then delete it project_invite_key = models.ProjectInviteKeys.query.filter_by( - invite_id=invite_id, project_id=project.id + invite_id=invite_id ).one_or_none() - # if the unasnwsered invite was asociated in the project, remove the invite and the asociation - if project_invite_key: - db.session.delete(project_invite_key) - - # if such invite was only associated with a single project, delete the invite. Otherwise leave the invite - project_invite_key = models.ProjectInviteKeys.query.filter_by( - invite_id=invite_id - ).one_or_none() - if not project_invite_key: - db.session.delete(unanswered_invite) - else: - # the unanswred invite is not asociated with the project - raise ddserr.NoSuchUserError( - f"The user / invite with email '{user_email}' does not have access to the specified project." - " Cannot remove non-existent project access." - ) + if not project_invite_key: + db.session.delete(unanswered_invite) + else: + # The unanswered invite is not associated with the project + raise ddserr.NoSuchUserError( + f"The user / invite with email '{user_email}' does not have access to the specified project. " + "Cannot remove non-existent project access." + ) else: - user_in_project = False - for user_association in project.researchusers: - if user_association.user_id == existing_user.username: - user_in_project = True - db.session.delete(user_association) - project_user_key = models.ProjectUserKeys.query.filter_by( - project_id=project.id, user_id=existing_user.username - ).first() - if project_user_key: - db.session.delete(project_user_key) + # User exists, check if they are associated with the project + user_in_project = any( + user_association.user_id == existing_user.username + for user_association in project.researchusers + ) if not user_in_project: raise ddserr.NoSuchUserError( - f"The user / invite with email '{user_email}' does not have access to the specified project." - " Cannot remove non-existent project access." + f"The user / invite with email '{user_email}' does not have access to the specified project. " + "Cannot remove non-existent project access." ) try: From 6f7d890b8df2240f158d4bb3de217d061a002bbb Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 21 Sep 2023 14:25:24 +0200 Subject: [PATCH 019/155] added logic for hierarchy --- dds_web/api/user.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index fee0a2c4f..bbb3254e7 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -885,8 +885,23 @@ def post(self): Researchers only and just only revoke access for Researchers ProjectOwners revoke access for ProjectOwners (in the same project) and Researchers (in the same project) Unit Personnel & Unit Admins: Researchers and Project Owners (any). + + rules = { + "Researcher": {"Researcher"}, + "Project Owner": {"Project Owner", "Researcher"}, + "Unit Admin": {"Project Owner", "Researcher"}, + "Unit Personnel": {"Project Owner", "Researcher"} + } + + allowed_users = rules.get(role) # user executing the function + + if existing_user.role not in allowed_users: + raise Exception ("Not enough privileges to remove such user") + """ + + # If the user doesn't exist and doesn't have a pending invite if not existing_user and not unanswered_invite: raise ddserr.NoSuchUserError( From c83e6f3c8709e37d8beb23b5f40fc35b2f356f8b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 21 Sep 2023 15:08:59 +0200 Subject: [PATCH 020/155] hierarchy functionaly logic added, todo to implement --- dds_web/api/user.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index bbb3254e7..1d4a93ea1 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -898,6 +898,24 @@ def post(self): if existing_user.role not in allowed_users: raise Exception ("Not enough privileges to remove such user") + + ---- + We need to add functionality to check for the user exists in the project and unit + + 1. Pos and Researchers only delete access in the project they are + 2. Unit Admin and Personal delete access for every project in the unit + + user_unit = # query to get the unit(s) the user executing the function is asociated + user_project = # query to get the projects the user executing the function is asociated from the previous units (only for pos and researchers) + So, for everyone: role.unit != project.unit_id -> Exception + pos and researchers: if project.id not in user_project -> Exception + + if user_unit != project.unit_id: + raise Exception("Not enough privileges to remove such user") + + if role in {"Project Owner", "Researcher"} and project.id not in user_project: + raise Exception("Not enough privileges to remove such user") + """ @@ -909,6 +927,7 @@ def post(self): "Cannot remove non-existent project access." ) + if unanswered_invite: invite_id = unanswered_invite.id From 7f43395bea00a8aef9f373a901cbcb1cedf5ea5d Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 22 Sep 2023 12:10:52 +0200 Subject: [PATCH 021/155] hierarchy --- dds_web/api/user.py | 95 +++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 1d4a93ea1..836f06d12 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -878,48 +878,7 @@ def post(self): except sqlalchemy.exc.OperationalError as err: raise ddserr.DatabaseError(message=str(err), alt_message="Unexpected database error.") - """ - TODO before proceding with the deletion: We need to ensure that the hierarchy of roles is ensured - i.e That: - - Researchers only and just only revoke access for Researchers - ProjectOwners revoke access for ProjectOwners (in the same project) and Researchers (in the same project) - Unit Personnel & Unit Admins: Researchers and Project Owners (any). - - rules = { - "Researcher": {"Researcher"}, - "Project Owner": {"Project Owner", "Researcher"}, - "Unit Admin": {"Project Owner", "Researcher"}, - "Unit Personnel": {"Project Owner", "Researcher"} - } - - allowed_users = rules.get(role) # user executing the function - - if existing_user.role not in allowed_users: - raise Exception ("Not enough privileges to remove such user") - - - ---- - We need to add functionality to check for the user exists in the project and unit - - 1. Pos and Researchers only delete access in the project they are - 2. Unit Admin and Personal delete access for every project in the unit - - user_unit = # query to get the unit(s) the user executing the function is asociated - user_project = # query to get the projects the user executing the function is asociated from the previous units (only for pos and researchers) - So, for everyone: role.unit != project.unit_id -> Exception - pos and researchers: if project.id not in user_project -> Exception - - if user_unit != project.unit_id: - raise Exception("Not enough privileges to remove such user") - - if role in {"Project Owner", "Researcher"} and project.id not in user_project: - raise Exception("Not enough privileges to remove such user") - - """ - - - + # If the user doesn't exist and doesn't have a pending invite if not existing_user and not unanswered_invite: raise ddserr.NoSuchUserError( @@ -927,7 +886,44 @@ def post(self): "Cannot remove non-existent project access." ) + """ + We also need to ensure the hierarchy of roles is ensured: + + Researchers only revoke access for Researchers + ProjectOwners revoke access for ProjectOwners (in the same project) and Researchers (in the same project) + Unit Personnel & Unit Admins: Researchers and Project Owners (any project). + + Because the access to the project has already being verified, and we are revoking access for a specific project + The only condition to check is that a Project Owner should not be deleted by a Researcher that is not PO + """ + + # role of the user calling the function + role = auth.current_user().role + + # check if the user/invite trying to get revoked access is a PO + is_po = False + if unanswered_invite: + is_po = models.ProjectInviteKeys.query.filter_by( + project_id = project.id, + invite_id = unanswered_invite.id, + owner = 1 + ) + else: + is_po = models.ProjectUsers.query.filter_by( + project_id = project.id, + user_id = existing_user.username, + owner = 1 + ).one_or_none() + + if is_po: + """ + If the user trying to be deleted is a PO -> can only be deleted by a user with higher credentials than a Researcher + """ + is_po = models.ProjectUsers.query.filter_by(user_id = auth.current_user().username, project_id = project.id, owner = 1).one_or_none() + if role == "Researcher" and not is_po: + raise ddserr.AccessDeniedError() + if unanswered_invite: invite_id = unanswered_invite.id @@ -955,11 +951,16 @@ def post(self): ) else: - # User exists, check if they are associated with the project - user_in_project = any( - user_association.user_id == existing_user.username - for user_association in project.researchusers - ) + user_in_project = False + for user_association in project.researchusers: + if user_association.user_id == existing_user.username: + user_in_project = True + db.session.delete(user_association) + project_user_key = models.ProjectUserKeys.query.filter_by( + project_id=project.id, user_id=existing_user.username + ).first() + if project_user_key: + db.session.delete(project_user_key) if not user_in_project: raise ddserr.NoSuchUserError( From 8e4b175794daa38821f241611aad89474e85f019 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 22 Sep 2023 12:11:47 +0200 Subject: [PATCH 022/155] black --- dds_web/api/user.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 836f06d12..6bf4dce03 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -878,7 +878,6 @@ def post(self): except sqlalchemy.exc.OperationalError as err: raise ddserr.DatabaseError(message=str(err), alt_message="Unexpected database error.") - # If the user doesn't exist and doesn't have a pending invite if not existing_user and not unanswered_invite: raise ddserr.NoSuchUserError( @@ -904,15 +903,11 @@ def post(self): is_po = False if unanswered_invite: is_po = models.ProjectInviteKeys.query.filter_by( - project_id = project.id, - invite_id = unanswered_invite.id, - owner = 1 + project_id=project.id, invite_id=unanswered_invite.id, owner=1 ) else: is_po = models.ProjectUsers.query.filter_by( - project_id = project.id, - user_id = existing_user.username, - owner = 1 + project_id=project.id, user_id=existing_user.username, owner=1 ).one_or_none() if is_po: @@ -920,10 +915,12 @@ def post(self): If the user trying to be deleted is a PO -> can only be deleted by a user with higher credentials than a Researcher """ - is_po = models.ProjectUsers.query.filter_by(user_id = auth.current_user().username, project_id = project.id, owner = 1).one_or_none() + is_po = models.ProjectUsers.query.filter_by( + user_id=auth.current_user().username, project_id=project.id, owner=1 + ).one_or_none() if role == "Researcher" and not is_po: raise ddserr.AccessDeniedError() - + if unanswered_invite: invite_id = unanswered_invite.id @@ -960,7 +957,7 @@ def post(self): project_id=project.id, user_id=existing_user.username ).first() if project_user_key: - db.session.delete(project_user_key) + db.session.delete(project_user_key) if not user_in_project: raise ddserr.NoSuchUserError( From e9f1e0a85641f3eba75f122e9a92bd12922651de Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 22 Sep 2023 16:31:24 +0200 Subject: [PATCH 023/155] added --- dds_web/api/user.py | 8 +++++--- tests/test_user_remove_association.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 6bf4dce03..3578f72ff 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -859,7 +859,7 @@ def delete_invite(email): class RemoveUserAssociation(flask_restful.Resource): - @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) + @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner"]) @logging_bind_request @json_required @handle_validation_errors @@ -919,7 +919,7 @@ def post(self): user_id=auth.current_user().username, project_id=project.id, owner=1 ).one_or_none() if role == "Researcher" and not is_po: - raise ddserr.AccessDeniedError() + raise ddserr.AccessDeniedError("Insufficient credentials") if unanswered_invite: invite_id = unanswered_invite.id @@ -949,7 +949,7 @@ def post(self): else: user_in_project = False - for user_association in project.researchusers: + for user_association in project.researchusers: # TODO Possible optimization -> comprehesion list if user_association.user_id == existing_user.username: user_in_project = True db.session.delete(user_association) @@ -958,6 +958,7 @@ def post(self): ).first() if project_user_key: db.session.delete(project_user_key) + break if not user_in_project: raise ddserr.NoSuchUserError( @@ -994,6 +995,7 @@ def post(self): return {"message": msg} + class EncryptedToken(flask_restful.Resource): """Generates encrypted token for the user.""" diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index b097b3951..651403e4a 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -163,3 +163,24 @@ def test_remove_existing_user_from_nonexistent_proj(client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "The specified project does not exist" in response.json["message"] + + +def test_researcher_removes_project_owner(client): + """ + A Researcher who is not a PO should not be able to delete a PO + """ + + project_id = "public_project_id" + email = "projectowner@mailtrap.io" + + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), + query_string={"project": project_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert "You do not have the necessary permissions" in response.json["message"] + From 3cb1d28b5f553edefec7671d9dae645d28196030 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 08:04:30 +0200 Subject: [PATCH 024/155] more tests and removed unnecesary code --- SPRINTLOG.md | 1 - dds_web/api/user.py | 39 ++---------------------- tests/test_user_remove_association.py | 44 +++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 343683442..a7afd821b 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -302,7 +302,6 @@ _Nothing merged in CLI during this sprint_ - Workflow: Update PR template and clarify sections ([#1467](https://github.com/ScilifelabDataCentre/dds_web/pull/1467)) - Revoking access to unacepted invites removes the invites from the DB ([#1192])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13/backlog?epics=visible&selectedIssue=DDS-1192) - # 2023-09-18 - 2023-09-29 - Column `sto4_start_time` is automatically set when the create-unit command is run ([#1668])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1668) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 24fa51537..686fd332c 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -890,42 +890,6 @@ def post(self): "Cannot remove non-existent project access." ) - """ - We also need to ensure the hierarchy of roles is ensured: - - Researchers only revoke access for Researchers - ProjectOwners revoke access for ProjectOwners (in the same project) and Researchers (in the same project) - Unit Personnel & Unit Admins: Researchers and Project Owners (any project). - - Because the access to the project has already being verified, and we are revoking access for a specific project - The only condition to check is that a Project Owner should not be deleted by a Researcher that is not PO - """ - - # role of the user calling the function - role = auth.current_user().role - - # check if the user/invite trying to get revoked access is a PO - is_po = False - if unanswered_invite: - is_po = models.ProjectInviteKeys.query.filter_by( - project_id=project.id, invite_id=unanswered_invite.id, owner=1 - ) - else: - is_po = models.ProjectUsers.query.filter_by( - project_id=project.id, user_id=existing_user.username, owner=1 - ).one_or_none() - - if is_po: - """ - If the user trying to be deleted is a PO -> can only be deleted by a user with higher credentials than a Researcher - """ - - is_po = models.ProjectUsers.query.filter_by( - user_id=auth.current_user().username, project_id=project.id, owner=1 - ).one_or_none() - if role == "Researcher" and not is_po: - raise ddserr.AccessDeniedError("Insufficient credentials") - if unanswered_invite: invite_id = unanswered_invite.id @@ -953,6 +917,9 @@ def post(self): ) else: + if(auth.current_user().username == existing_user.username): + raise ddserr.AccessDeniedError(message="You cannot renew your own access.") + user_in_project = False for user_association in project.researchusers: # TODO Possible optimization -> comprehesion list if user_association.user_id == existing_user.username: diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 651403e4a..3deca25f9 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -184,3 +184,47 @@ def test_researcher_removes_project_owner(client): assert response.status_code == http.HTTPStatus.FORBIDDEN assert "You do not have the necessary permissions" in response.json["message"] + +def test_user_personal_removed(client): + """ + User personal cannot be deleted from individual projects (they should be removed from the unit) + """ + + project_id = "public_project_id" + email = "unituser2@mailtrap.io" + + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # Should give error because a unit personal cannot be granted access to individual projects + assert "Cannot remove non-existent project access." in response.json["message"] + +def test_removed_myself(client): + """ + An User cannot remove themselves from a project + """ + + project_id = "public_project_id" + email = "projectowner@mailtrap.io" + + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), + query_string={"project": project_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + # Should give error because a unit personal cannot be granted access to individual projects + assert "You cannot renew your own access." in response.json["message"] + + + + From 4f26365f88f7c9cf6171626953ebe95208fb0d67 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 08:04:45 +0200 Subject: [PATCH 025/155] more tests and removed unnecesary code --- dds_web/api/user.py | 11 ++++++----- tests/test_user_remove_association.py | 5 +---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 686fd332c..19232c4ee 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -917,11 +917,13 @@ def post(self): ) else: - if(auth.current_user().username == existing_user.username): - raise ddserr.AccessDeniedError(message="You cannot renew your own access.") - + if auth.current_user().username == existing_user.username: + raise ddserr.AccessDeniedError(message="You cannot renew your own access.") + user_in_project = False - for user_association in project.researchusers: # TODO Possible optimization -> comprehesion list + for ( + user_association + ) in project.researchusers: # TODO Possible optimization -> comprehesion list if user_association.user_id == existing_user.username: user_in_project = True db.session.delete(user_association) @@ -967,7 +969,6 @@ def post(self): return {"message": msg} - class EncryptedToken(flask_restful.Resource): """Generates encrypted token for the user.""" diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 3deca25f9..ac25fee29 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -205,6 +205,7 @@ def test_user_personal_removed(client): # Should give error because a unit personal cannot be granted access to individual projects assert "Cannot remove non-existent project access." in response.json["message"] + def test_removed_myself(client): """ An User cannot remove themselves from a project @@ -224,7 +225,3 @@ def test_removed_myself(client): assert response.status_code == http.HTTPStatus.FORBIDDEN # Should give error because a unit personal cannot be granted access to individual projects assert "You cannot renew your own access." in response.json["message"] - - - - From 7e437c28f3457d0329e2102970c84784e139df60 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 08:30:34 +0200 Subject: [PATCH 026/155] forgot to change testfile --- tests/test_user_remove_association.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index ac25fee29..c46b12f0f 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -182,7 +182,7 @@ def test_researcher_removes_project_owner(client): ) assert response.status_code == http.HTTPStatus.FORBIDDEN - assert "You do not have the necessary permissions" in response.json["message"] + assert "Insufficient credentials" in response.json["message"] def test_user_personal_removed(client): From c7a3577ea6247069534a280c3f15c9f56aeb2921 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 11:15:27 +0200 Subject: [PATCH 027/155] checks for unanswered invites of personel --- dds_web/api/user.py | 7 +++- tests/test_user_remove_association.py | 60 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 19232c4ee..315c8862d 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -891,6 +891,10 @@ def post(self): ) if unanswered_invite: + + if (unanswered_invite.unit_id): + raise ddserr.UserDeletionError("Cannot delete Unit Admin / Unit User") + invite_id = unanswered_invite.id # Check if the unanswered invite is associated with the project @@ -898,11 +902,12 @@ def post(self): invite_id=invite_id, project_id=project.id ).one_or_none() + # if the invite has an unit id -> it is a unit personel invite, dont remove if project_invite_key: # Remove the association if it exists db.session.delete(project_invite_key) - # Check if the invite is associated with only one project, then delete it + # Check if the invite is associated with only one project, if it is -> delete the invite project_invite_key = models.ProjectInviteKeys.query.filter_by( invite_id=invite_id ).one_or_none() diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index c46b12f0f..191c7ffbd 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -14,6 +14,8 @@ # proj_query_restricted = {"project": "restricted_project_id"} first_new_email = {"email": "first_test_email@mailtrap.io"} first_new_user = {**first_new_email, "role": "Researcher"} +first_new_user_unit_admin = {**first_new_email, "role": "Unit Admin"} +first_new_user_unit_personel = {**first_new_email, "role": "Unit Personnel"} # TESTS ################################################################################## TEST # @@ -225,3 +227,61 @@ def test_removed_myself(client): assert response.status_code == http.HTTPStatus.FORBIDDEN # Should give error because a unit personal cannot be granted access to individual projects assert "You cannot renew your own access." in response.json["message"] + +def test_remove_invite_unit_admin(client): + """ + A project removal request for an unanswered invite of unit admin should not work + """ + + project_id = "public_project_id" + + # invite a new unit admin to the system + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_unit_admin, + ) + assert response.status_code == http.HTTPStatus.OK + + # try to remove the unitadmin for a specific project within their unit -> should not work + email = first_new_user_unit_admin["email"] + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # Should give error because a unit personal cannot be granted access to individual projects + assert "Cannot delete Unit Admin / Unit User" in response.json["message"] + +def test_invite_unit_user(client): + """ + A project removal request for an unanswered invite of unit admin should not work + """ + + project_id = "public_project_id" + + # invite a new unit user to the system + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_unit_personel, + ) + assert response.status_code == http.HTTPStatus.OK + + # try to remove the unit personal for a specific project within their unit -> should not work + email = first_new_user_unit_personel["email"] + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # Should give error because a unit personal cannot be granted access to individual projects + assert "Cannot delete Unit Admin / Unit User" in response.json["message"] From 5bb88f5f6d0b99da5ca2e08744e23f9041097930 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 11:16:34 +0200 Subject: [PATCH 028/155] black --- dds_web/api/user.py | 2 +- tests/test_user_remove_association.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 315c8862d..cc95b3104 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -892,7 +892,7 @@ def post(self): if unanswered_invite: - if (unanswered_invite.unit_id): + if unanswered_invite.unit_id: raise ddserr.UserDeletionError("Cannot delete Unit Admin / Unit User") invite_id = unanswered_invite.id diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 191c7ffbd..4c8bf01f4 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -228,6 +228,7 @@ def test_removed_myself(client): # Should give error because a unit personal cannot be granted access to individual projects assert "You cannot renew your own access." in response.json["message"] + def test_remove_invite_unit_admin(client): """ A project removal request for an unanswered invite of unit admin should not work @@ -257,6 +258,7 @@ def test_remove_invite_unit_admin(client): # Should give error because a unit personal cannot be granted access to individual projects assert "Cannot delete Unit Admin / Unit User" in response.json["message"] + def test_invite_unit_user(client): """ A project removal request for an unanswered invite of unit admin should not work From 94bc466f98adfb02980b866701c7cffc0bc6f862 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 11:24:19 +0200 Subject: [PATCH 029/155] black --- dds_web/api/user.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index cc95b3104..82b727439 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -891,7 +891,6 @@ def post(self): ) if unanswered_invite: - if unanswered_invite.unit_id: raise ddserr.UserDeletionError("Cannot delete Unit Admin / Unit User") From c0ecaae6abd678f536aff55ce4ab1e3aa3d03ec0 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 14:07:44 +0200 Subject: [PATCH 030/155] more explicate error message --- dds_web/api/user.py | 3 ++- tests/test_user_remove_association.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 82b727439..efc2f08ea 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -892,7 +892,8 @@ def post(self): if unanswered_invite: if unanswered_invite.unit_id: - raise ddserr.UserDeletionError("Cannot delete Unit Admin / Unit User") + raise ddserr.UserDeletionError( + "Cannot remove a Unit Admin / Unit User from individual projects") invite_id = unanswered_invite.id diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 4c8bf01f4..bef154145 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -256,7 +256,10 @@ def test_remove_invite_unit_admin(client): assert response.status_code == http.HTTPStatus.BAD_REQUEST # Should give error because a unit personal cannot be granted access to individual projects - assert "Cannot delete Unit Admin / Unit User" in response.json["message"] + assert ( + "Cannot remove a Unit Admin / Unit User from individual projects" + in response.json["message"] + ) def test_invite_unit_user(client): @@ -286,4 +289,7 @@ def test_invite_unit_user(client): assert response.status_code == http.HTTPStatus.BAD_REQUEST # Should give error because a unit personal cannot be granted access to individual projects - assert "Cannot delete Unit Admin / Unit User" in response.json["message"] + assert ( + "Cannot remove a Unit Admin / Unit User from individual projects" + in response.json["message"] + ) From 91159fe326dd4977bfaf62a44dc57cc75d8fb5e8 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 14:09:54 +0200 Subject: [PATCH 031/155] more explicate error message --- dds_web/api/user.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index efc2f08ea..2549c159f 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -893,7 +893,8 @@ def post(self): if unanswered_invite: if unanswered_invite.unit_id: raise ddserr.UserDeletionError( - "Cannot remove a Unit Admin / Unit User from individual projects") + "Cannot remove a Unit Admin / Unit User from individual projects" + ) invite_id = unanswered_invite.id From 84a92c5462c9fcfaf34809f6d688bff847cf555d Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 14:17:28 +0200 Subject: [PATCH 032/155] better coments --- dds_web/api/user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 2549c159f..a3c7a86cb 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -891,6 +891,8 @@ def post(self): ) if unanswered_invite: + # If there is a unit_id value, it means the invite was associated to a unit + # i.e The invite is for a Unit Personel which shouldn't be removed from individual projects if unanswered_invite.unit_id: raise ddserr.UserDeletionError( "Cannot remove a Unit Admin / Unit User from individual projects" @@ -903,7 +905,6 @@ def post(self): invite_id=invite_id, project_id=project.id ).one_or_none() - # if the invite has an unit id -> it is a unit personel invite, dont remove if project_invite_key: # Remove the association if it exists db.session.delete(project_invite_key) @@ -926,6 +927,7 @@ def post(self): if auth.current_user().username == existing_user.username: raise ddserr.AccessDeniedError(message="You cannot renew your own access.") + # Search the user in the project, when found delete from the database all references to them user_in_project = False for ( user_association From bd6dcd0f3b6691ca39763b82cddd984e13b9062b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:08:54 +0200 Subject: [PATCH 033/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index a3c7a86cb..b5e46d95d 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -895,7 +895,7 @@ def post(self): # i.e The invite is for a Unit Personel which shouldn't be removed from individual projects if unanswered_invite.unit_id: raise ddserr.UserDeletionError( - "Cannot remove a Unit Admin / Unit User from individual projects" + "Cannot remove a Unit Admin / Unit Personnel from individual projects." ) invite_id = unanswered_invite.id From f95bc061e0bc4a298f919bcfd875885d28938204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:09:11 +0200 Subject: [PATCH 034/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index b5e46d95d..245a44208 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -925,7 +925,7 @@ def post(self): else: if auth.current_user().username == existing_user.username: - raise ddserr.AccessDeniedError(message="You cannot renew your own access.") + raise ddserr.AccessDeniedError(message="You cannot revoke your own access.") # Search the user in the project, when found delete from the database all references to them user_in_project = False From 95a36e8c392ddc134d8e3840459578c97c1052ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:09:26 +0200 Subject: [PATCH 035/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 245a44208..70c71b68f 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -919,7 +919,7 @@ def post(self): else: # The unanswered invite is not associated with the project raise ddserr.NoSuchUserError( - f"The user / invite with email '{user_email}' does not have access to the specified project. " + f"The invite with email '{user_email}' does not have access to the specified project. " "Cannot remove non-existent project access." ) From ffd00cba5c22f6ca9843faad66217220c013399e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:09:38 +0200 Subject: [PATCH 036/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 70c71b68f..c70ee9602 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -944,7 +944,7 @@ def post(self): if not user_in_project: raise ddserr.NoSuchUserError( - f"The user / invite with email '{user_email}' does not have access to the specified project. " + f"The user with email '{user_email}' does not have access to the specified project. " "Cannot remove non-existent project access." ) From 6d96fb147e81d7e24f16ead2ce06b9634bb08c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:10:26 +0200 Subject: [PATCH 037/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index c70ee9602..f7588bcb4 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -972,7 +972,6 @@ def post(self): else: msg = f"User with email {user_email} no longer associated with {project.public_id}." - flask.current_app.logger.debug(msg) return {"message": msg} From 822b080e4db6913b0bafe110ee28322c2d815163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:10:34 +0200 Subject: [PATCH 038/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index f7588bcb4..23456f97f 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -967,10 +967,6 @@ def post(self): ), ) from err - if unanswered_invite: - msg = f"Invited user is no longer associated with {project.public_id}." - else: - msg = f"User with email {user_email} no longer associated with {project.public_id}." return {"message": msg} From dced7de1cdbd28fb3d1ac60682364dd67a7f9429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:10:42 +0200 Subject: [PATCH 039/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 23456f97f..98e062a81 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -947,6 +947,7 @@ def post(self): f"The user with email '{user_email}' does not have access to the specified project. " "Cannot remove non-existent project access." ) + msg = f"User with email {user_email} no longer associated with {project.public_id}." try: db.session.commit() From c4821dcb03be6b8f146e1611abe2128c5db8189f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:10:53 +0200 Subject: [PATCH 040/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 98e062a81..889692f18 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -906,6 +906,7 @@ def post(self): ).one_or_none() if project_invite_key: + msg = f"Invited user is no longer associated with {project.public_id}." # Remove the association if it exists db.session.delete(project_invite_key) From ebf8ff04763db2cb5486b16f09ac2c8e253ce2ab Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 16:12:17 +0200 Subject: [PATCH 041/155] black --- dds_web/api/user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 889692f18..020b3a3ab 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -969,8 +969,6 @@ def post(self): ), ) from err - - return {"message": msg} From cf974837d48e9fe064366bdc119c2f1428ff947b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 25 Sep 2023 16:38:58 +0200 Subject: [PATCH 042/155] new test comment --- tests/test_user_remove_association.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index bef154145..db6c996c4 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -257,7 +257,7 @@ def test_remove_invite_unit_admin(client): assert response.status_code == http.HTTPStatus.BAD_REQUEST # Should give error because a unit personal cannot be granted access to individual projects assert ( - "Cannot remove a Unit Admin / Unit User from individual projects" + "Cannot remove a Unit Admin / Unit Personnel from individual projects" in response.json["message"] ) @@ -290,6 +290,6 @@ def test_invite_unit_user(client): assert response.status_code == http.HTTPStatus.BAD_REQUEST # Should give error because a unit personal cannot be granted access to individual projects assert ( - "Cannot remove a Unit Admin / Unit User from individual projects" + "Cannot remove a Unit Admin / Unit Personnel from individual projects" in response.json["message"] ) From 0a3a35afc934281928978b3faa431691a59aca26 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 26 Sep 2023 09:24:19 +0200 Subject: [PATCH 043/155] fixed strings --- tests/test_user_remove_association.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index db6c996c4..c2e730581 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -226,7 +226,7 @@ def test_removed_myself(client): assert response.status_code == http.HTTPStatus.FORBIDDEN # Should give error because a unit personal cannot be granted access to individual projects - assert "You cannot renew your own access." in response.json["message"] + assert "You cannot revoke your own access" in response.json["message"] def test_remove_invite_unit_admin(client): From f952f3039c13ed36a2c0b782d561810b7a3bbed1 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 26 Sep 2023 10:18:41 +0200 Subject: [PATCH 044/155] refactoring test --- tests/test_project_access.py | 62 +++++++++++---------------- tests/test_user_remove_association.py | 58 +++++++++---------------- 2 files changed, 45 insertions(+), 75 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index fe9ce5d97..e49dbe000 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -21,6 +21,26 @@ # UTILITY FUNCTIONS ############################################################ UTILITY FUNCTIONS # +def add_to_project(project, client, json_query): + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=json_query, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=json_query["email"]).one_or_none() + assert invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project.id + ).one_or_none() + assert project_invite_keys + + return invited_user + + def delete_project_user(project_id, user_id, table_to_use): """Delete row in either ProjectUsers or ProjectUserKeys.""" # Get project from database @@ -436,28 +456,9 @@ def test_remove_access_invite_associated_several_projects(client): project_1 = models.Project.query.filter_by(public_id="public_project_id").one_or_none() project_2 = models.Project.query.filter_by(public_id="second_public_project_id").one_or_none() - def add_to_project(project): - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project.public_id}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user - - project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user.id, project_id=project.id - ).one_or_none() - assert project_invite_keys - - return invited_user - # invite a new user to both projects - invited_user = add_to_project(project_1) - _ = add_to_project(project_2) + invited_user = add_to_project(project=project_1, client=client, json_query=first_new_user) + _ = add_to_project(project=project_2, client=client, json_query=first_new_user) # Now revoke access for the first project response = client.post( @@ -492,24 +493,9 @@ def test_revoking_access_to_unacepted_invite(client): """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() - # invite a new user to an existing project so they receive a new invite - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project.public_id}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + # Invite a new user to the project + invited_user = add_to_project(project=project, client=client, json_query=first_new_user) invited_user_id = invited_user.id - assert invited_user - - # check row was added to project invite keys table - project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user_id, project_id=project.id - ).one_or_none() - assert project_invite_keys # Now, revoke access to said user. The invite should be deleted response = client.post( diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index c2e730581..b8f051dc7 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -6,6 +6,7 @@ import tests from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins from dds_web.database import models +from tests.test_project_access import add_to_project # CONFIG ################################################################################## CONFIG # @@ -17,6 +18,10 @@ first_new_user_unit_admin = {**first_new_email, "role": "Unit Admin"} first_new_user_unit_personel = {**first_new_email, "role": "Unit Personnel"} +remove_user_project_owner = {"email": "projectowner@mailtrap.io"} +remove_user_unit_user = {"email": "unituser2@mailtrap.io"} +remove_user_project_owner = {"email": "projectowner@mailtrap.io"} + # TESTS ################################################################################## TEST # @@ -114,6 +119,8 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): def test_remove_nonacepted_user_from_other_project(client, boto3_session): """Try to remove an User with an unacepted invite from another project should result in an error""" + existing_project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() assert current_unit_admins == 3 @@ -125,23 +132,16 @@ def test_remove_nonacepted_user_from_other_project(client, boto3_session): json=proj_data_with_existing_users, ) assert response.status_code == http.HTTPStatus.OK - - project_id = response.json.get("project_id") + new_project_id = response.json.get("project_id") # invite a new user to an existing project - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": "public_project_id"}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK + add_to_project(project=existing_project, client=client, json_query=first_new_user) # try to remove the user from the first project response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project_id}, + query_string={"project": new_project_id}, json=first_new_user, ) @@ -172,15 +172,12 @@ def test_researcher_removes_project_owner(client): A Researcher who is not a PO should not be able to delete a PO """ - project_id = "public_project_id" - email = "projectowner@mailtrap.io" - - rem_user = {"email": email} + # Research user trying to delete PO response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), - query_string={"project": project_id}, - json=rem_user, + query_string=proj_query, + json=remove_user_project_owner, ) assert response.status_code == http.HTTPStatus.FORBIDDEN @@ -189,18 +186,14 @@ def test_researcher_removes_project_owner(client): def test_user_personal_removed(client): """ - User personal cannot be deleted from individual projects (they should be removed from the unit) + User personal cannot be deleted from individual projects (they should be removed from the unit instead) """ - project_id = "public_project_id" - email = "unituser2@mailtrap.io" - - rem_user = {"email": email} response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project_id}, - json=rem_user, + query_string=proj_query, + json=remove_user_unit_user, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST @@ -213,19 +206,14 @@ def test_removed_myself(client): An User cannot remove themselves from a project """ - project_id = "public_project_id" - email = "projectowner@mailtrap.io" - - rem_user = {"email": email} response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), - query_string={"project": project_id}, - json=rem_user, + query_string=proj_query, + json=remove_user_project_owner, ) assert response.status_code == http.HTTPStatus.FORBIDDEN - # Should give error because a unit personal cannot be granted access to individual projects assert "You cannot revoke your own access" in response.json["message"] @@ -234,8 +222,6 @@ def test_remove_invite_unit_admin(client): A project removal request for an unanswered invite of unit admin should not work """ - project_id = "public_project_id" - # invite a new unit admin to the system response = client.post( tests.DDSEndpoint.USER_ADD, @@ -250,7 +236,7 @@ def test_remove_invite_unit_admin(client): response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project_id}, + query_string=proj_query, json=rem_user, ) @@ -264,11 +250,9 @@ def test_remove_invite_unit_admin(client): def test_invite_unit_user(client): """ - A project removal request for an unanswered invite of unit admin should not work + A project removal request for an unanswered invite of unit personel should not work """ - project_id = "public_project_id" - # invite a new unit user to the system response = client.post( tests.DDSEndpoint.USER_ADD, @@ -283,7 +267,7 @@ def test_invite_unit_user(client): response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project_id}, + query_string=proj_query, json=rem_user, ) From 8f8001f84a9765c17721f5206dfa8fd50a765988 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 26 Sep 2023 11:12:09 +0200 Subject: [PATCH 045/155] refactoring more test --- tests/test_project_access.py | 28 +++++++++++++++++++-------- tests/test_user_remove_association.py | 28 +++++++++------------------ 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index e49dbe000..7616bc99a 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -17,11 +17,21 @@ first_new_email = {"email": "first_test_email@mailtrap.io"} first_new_user = {**first_new_email, "role": "Researcher"} - # UTILITY FUNCTIONS ############################################################ UTILITY FUNCTIONS # -def add_to_project(project, client, json_query): +def get_existing_projects(): + """Return existing projects for the tests""" + existing_project_1 = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + existing_project_2 = models.Project.query.filter_by( + public_id="second_public_project_id" + ).one_or_none() + + return [existing_project_1, existing_project_2] + + +def invite_to_project(project, client, json_query): + """Create a invitation of a user for a project""" response = client.post( tests.DDSEndpoint.USER_ADD, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), @@ -453,12 +463,13 @@ def test_fix_access_unitadmin_valid_email_unituser(client): def test_remove_access_invite_associated_several_projects(client): """If an invite is associated with several projects then a single revoke access should not delete the invite""" - project_1 = models.Project.query.filter_by(public_id="public_project_id").one_or_none() - project_2 = models.Project.query.filter_by(public_id="second_public_project_id").one_or_none() + projects = get_existing_projects() + project_1 = projects[0] + project_2 = projects[1] # invite a new user to both projects - invited_user = add_to_project(project=project_1, client=client, json_query=first_new_user) - _ = add_to_project(project=project_2, client=client, json_query=first_new_user) + invited_user = invite_to_project(project=project_1, client=client, json_query=first_new_user) + _ = invite_to_project(project=project_2, client=client, json_query=first_new_user) # Now revoke access for the first project response = client.post( @@ -491,10 +502,11 @@ def test_remove_access_invite_associated_several_projects(client): def test_revoking_access_to_unacepted_invite(client): """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" - project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + + project = get_existing_projects()[0] # Invite a new user to the project - invited_user = add_to_project(project=project, client=client, json_query=first_new_user) + invited_user = invite_to_project(project=project, client=client, json_query=first_new_user) invited_user_id = invited_user.id # Now, revoke access to said user. The invite should be deleted diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index b8f051dc7..13ee20977 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -6,7 +6,7 @@ import tests from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins from dds_web.database import models -from tests.test_project_access import add_to_project +from tests.test_project_access import invite_to_project, get_existing_projects # CONFIG ################################################################################## CONFIG # @@ -22,6 +22,7 @@ remove_user_unit_user = {"email": "unituser2@mailtrap.io"} remove_user_project_owner = {"email": "projectowner@mailtrap.io"} + # TESTS ################################################################################## TEST # @@ -119,29 +120,18 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): def test_remove_nonacepted_user_from_other_project(client, boto3_session): """Try to remove an User with an unacepted invite from another project should result in an error""" - existing_project = models.Project.query.filter_by(public_id="public_project_id").one_or_none() - - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins == 3 - - # create a new project - response = client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - json=proj_data_with_existing_users, - ) - assert response.status_code == http.HTTPStatus.OK - new_project_id = response.json.get("project_id") + projects = get_existing_projects() + project_1 = projects[0] + project_2 = projects[1] - # invite a new user to an existing project - add_to_project(project=existing_project, client=client, json_query=first_new_user) + # invite a new user to a project + invite_to_project(project=project_1, client=client, json_query=first_new_user) - # try to remove the user from the first project + # try to remove the same user from a different one response = client.post( tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": new_project_id}, + query_string={"project": project_2.public_id}, json=first_new_user, ) From f70f5ba5ce865f9bd71fab43a805fc894cc5ef05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:52:11 +0200 Subject: [PATCH 046/155] Update SPRINTLOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- SPRINTLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index a7afd821b..d965cd8b5 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -300,7 +300,7 @@ _Nothing merged in CLI during this sprint_ - Dependency: Bump `MariaDB` to LTS version 10.11.5 ([#1465](https://github.com/ScilifelabDataCentre/dds_web/pull/1465)) - Bug fixed: Row in `ProjectUsers` should also be added if it doesn't exist when giving Researcher access to a specific project ([#1464](https://github.com/ScilifelabDataCentre/dds_web/pull/1464)) - Workflow: Update PR template and clarify sections ([#1467](https://github.com/ScilifelabDataCentre/dds_web/pull/1467)) -- Revoking access to unacepted invites removes the invites from the DB ([#1192])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13/backlog?epics=visible&selectedIssue=DDS-1192) +- Revoke project access for unaccepted invites ([#1192])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13/backlog?epics=visible&selectedIssue=DDS-1192) # 2023-09-18 - 2023-09-29 From e90f7cba3ee2fd5abb3ed4505a560fa649d6ed3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:52:57 +0200 Subject: [PATCH 047/155] Update dds_web/api/user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 020b3a3ab..3031b4a0f 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -906,7 +906,7 @@ def post(self): ).one_or_none() if project_invite_key: - msg = f"Invited user is no longer associated with {project.public_id}." + msg = f"Invited user is no longer associated with the project '{project.public_id}'." # Remove the association if it exists db.session.delete(project_invite_key) From 1c49497907f334d7ea5d71ecf6077664e3398001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:53:39 +0200 Subject: [PATCH 048/155] Update tests/test_project_access.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/test_project_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 7616bc99a..b311fb39e 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -27,7 +27,7 @@ def get_existing_projects(): public_id="second_public_project_id" ).one_or_none() - return [existing_project_1, existing_project_2] + return existing_project_1, existing_project_2 def invite_to_project(project, client, json_query): From 4c09789ebd591fd7374f7958e9e4d900cab0ed35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:53:49 +0200 Subject: [PATCH 049/155] Update tests/test_project_access.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/test_project_access.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index b311fb39e..7b4d43be9 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -463,9 +463,7 @@ def test_fix_access_unitadmin_valid_email_unituser(client): def test_remove_access_invite_associated_several_projects(client): """If an invite is associated with several projects then a single revoke access should not delete the invite""" - projects = get_existing_projects() - project_1 = projects[0] - project_2 = projects[1] + project_1, project_2 = get_existing_projects() # invite a new user to both projects invited_user = invite_to_project(project=project_1, client=client, json_query=first_new_user) From 1c3bab60f30e4b41522c6123cdcaf1ac42f0c4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 13:54:32 +0200 Subject: [PATCH 050/155] Update tests/test_project_access.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/test_project_access.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 7b4d43be9..6b2c098ae 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -501,7 +501,7 @@ def test_remove_access_invite_associated_several_projects(client): def test_revoking_access_to_unacepted_invite(client): """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" - project = get_existing_projects()[0] + project, _ = get_existing_projects() # Invite a new user to the project invited_user = invite_to_project(project=project, client=client, json_query=first_new_user) From 241647b5e8d7ca74275b97b4ecbf388ee6cb87dd Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 27 Sep 2023 14:17:29 +0200 Subject: [PATCH 051/155] black and comments --- dds_web/api/user.py | 6 ++++-- tests/test_project_access.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index 3031b4a0f..d8fb964aa 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -906,7 +906,9 @@ def post(self): ).one_or_none() if project_invite_key: - msg = f"Invited user is no longer associated with the project '{project.public_id}'." + msg = ( + f"Invited user is no longer associated with the project '{project.public_id}'." + ) # Remove the association if it exists db.session.delete(project_invite_key) @@ -941,7 +943,7 @@ def post(self): ).first() if project_user_key: db.session.delete(project_user_key) - break + break if not user_in_project: raise ddserr.NoSuchUserError( diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 6b2c098ae..59364a60a 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -479,7 +479,7 @@ def test_remove_access_invite_associated_several_projects(client): assert response.status_code == http.HTTPStatus.OK assert ( - f"Invited user is no longer associated with {project_1.public_id}." + f"Invited user is no longer associated with the project {project_1.public_id}." in response.json["message"] ) @@ -517,7 +517,7 @@ def test_revoking_access_to_unacepted_invite(client): assert response.status_code == http.HTTPStatus.OK assert ( - f"Invited user is no longer associated with {project.public_id}." + f"Invited user is no longer associated with the project {project.public_id}." in response.json["message"] ) From cd891b2a355781134a8e747262a0645613685dd8 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 27 Sep 2023 14:44:56 +0200 Subject: [PATCH 052/155] moved tests --- tests/api/test_user.py | 255 ++++++++++++++++++++++++++ tests/test_project_access.py | 102 ----------- tests/test_user_remove_association.py | 142 -------------- 3 files changed, 255 insertions(+), 244 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 3473a96eb..f9fdba731 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -16,11 +16,15 @@ import werkzeug import time +# CONFIG ################################################################################## CONFIG # + existing_project = "public_project_id" existing_project_2 = "second_public_project_id" first_new_email = {"email": "first_test_email@mailtrap.io"} first_new_user = {**first_new_email, "role": "Researcher"} first_new_owner = {**first_new_email, "role": "Project Owner"} +first_new_user_unit_admin = {**first_new_email, "role": "Unit Admin"} +first_new_user_unit_personel = {**first_new_email, "role": "Unit Personnel"} first_new_user_existing_project = {**first_new_user, "project": "public_project_id"} first_new_user_extra_args = {**first_new_user, "extra": "test"} first_new_user_invalid_role = {**first_new_email, "role": "Invalid Role"} @@ -48,6 +52,43 @@ **existing_research_user_owner, "project": "second_public_project_id", } +remove_user_project_owner = {"email": "projectowner@mailtrap.io"} +remove_user_unit_user = {"email": "unituser2@mailtrap.io"} +remove_user_project_owner = {"email": "projectowner@mailtrap.io"} + +# UTILITY FUNCTIONS ############################################################ UTILITY FUNCTIONS # + + +def get_existing_projects(): + """Return existing projects for the tests""" + existing_project_1 = models.Project.query.filter_by(public_id="public_project_id").one_or_none() + existing_project_2 = models.Project.query.filter_by( + public_id="second_public_project_id" + ).one_or_none() + + return existing_project_1, existing_project_2 + + +def invite_to_project(project, client, json_query): + """Create a invitation of a user for a project""" + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=json_query, + ) + assert response.status_code == http.HTTPStatus.OK + + invited_user = models.Invite.query.filter_by(email=json_query["email"]).one_or_none() + assert invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project.id + ).one_or_none() + assert project_invite_keys + + return invited_user + # AddUser ################################################################# AddUser # @@ -1258,3 +1299,217 @@ def get_list(as_user) -> dict: assert response.status_code == http.HTTPStatus.FORBIDDEN assert not response.json.get("invites") assert not response.json.get("keys") + + +##### Test for RemoveUserAssociation + + +def test_remove_access_invite_associated_several_projects(client): + """If an invite is associated with several projects then a single revoke access should not delete the invite""" + + project_1, project_2 = get_existing_projects() + + # invite a new user to both projects + invited_user = invite_to_project(project=project_1, client=client, json_query=first_new_user) + _ = invite_to_project(project=project_2, client=client, json_query=first_new_user) + + # Now revoke access for the first project + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project_1.public_id}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + assert ( + f"Invited user is no longer associated with the project '{project_1.public_id}'." + in response.json["message"] + ) + + # The project invite row should only be deleted for project 1 and the invite should still exist + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project_2.id + ).one_or_none() + assert project_invite_keys + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user.id, project_id=project_1.id + ).one_or_none() + assert not project_invite_keys + + +def test_revoking_access_to_unacepted_invite(client): + """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" + + project, _ = get_existing_projects() + + # Invite a new user to the project + invited_user = invite_to_project(project=project, client=client, json_query=first_new_user) + invited_user_id = invited_user.id + + # Now, revoke access to said user. The invite should be deleted + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=first_new_user, + ) + assert response.status_code == http.HTTPStatus.OK + + assert ( + f"Invited user is no longer associated with the project {project.public_id}." + in response.json["message"] + ) + + # Check that the invite is deleted + invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() + assert not invited_user + + project_invite_keys = models.ProjectInviteKeys.query.filter_by( + invite_id=invited_user_id, project_id=project.id + ).one_or_none() + assert not project_invite_keys + + +def test_remove_nonacepted_user_from_other_project(client, boto3_session): + """Try to remove an User with an unacepted invite from another project should result in an error""" + + projects = get_existing_projects() + project_1 = projects[0] + project_2 = projects[1] + + # invite a new user to a project + invite_to_project(project=project_1, client=client, json_query=first_new_user) + + # try to remove the same user from a different one + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": project_2.public_id}, + json=first_new_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Cannot remove non-existent project access" in response.json["message"] + + +def test_researcher_removes_project_owner(client): + """ + A Researcher who is not a PO should not be able to delete a PO + """ + + project, _ = get_existing_projects() + + # Research user trying to delete PO + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), + query_string={"project": project.public_id}, + json=remove_user_project_owner, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert "Insufficient credentials" in response.json["message"] + + +def test_user_personal_removed(client): + """ + User personal cannot be deleted from individual projects (they should be removed from the unit instead) + """ + project, _ = get_existing_projects() + + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=remove_user_unit_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # Should give error because a unit personal cannot be granted access to individual projects + assert "Cannot remove non-existent project access." in response.json["message"] + + +def test_removed_myself(client): + """ + An User cannot remove themselves from a project + """ + project, _ = get_existing_projects() + + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), + query_string={"project": project.public_id}, + json=remove_user_project_owner, + ) + + assert response.status_code == http.HTTPStatus.FORBIDDEN + assert "You cannot revoke your own access" in response.json["message"] + + +def test_remove_invite_unit_admin(client): + """ + A project removal request for an unanswered invite of unit admin should not work + """ + project, _ = get_existing_projects() + + # invite a new unit admin to the system + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_unit_admin, + ) + assert response.status_code == http.HTTPStatus.OK + + # try to remove the unitadmin for a specific project within their unit -> should not work + email = first_new_user_unit_admin["email"] + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # Should give error because a unit personal cannot be granted access to individual projects + assert ( + "Cannot remove a Unit Admin / Unit Personnel from individual projects" + in response.json["message"] + ) + + +def test_invite_unit_user(client): + """ + A project removal request for an unanswered invite of unit personel should not work + """ + project, _ = get_existing_projects() + + # invite a new unit user to the system + response = client.post( + tests.DDSEndpoint.USER_ADD, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=first_new_user_unit_personel, + ) + assert response.status_code == http.HTTPStatus.OK + + # try to remove the unit personal for a specific project within their unit -> should not work + email = first_new_user_unit_personel["email"] + rem_user = {"email": email} + response = client.post( + tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": project.public_id}, + json=rem_user, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + # Should give error because a unit personal cannot be granted access to individual projects + assert ( + "Cannot remove a Unit Admin / Unit Personnel from individual projects" + in response.json["message"] + ) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index 59364a60a..f551feeab 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -20,37 +20,6 @@ # UTILITY FUNCTIONS ############################################################ UTILITY FUNCTIONS # -def get_existing_projects(): - """Return existing projects for the tests""" - existing_project_1 = models.Project.query.filter_by(public_id="public_project_id").one_or_none() - existing_project_2 = models.Project.query.filter_by( - public_id="second_public_project_id" - ).one_or_none() - - return existing_project_1, existing_project_2 - - -def invite_to_project(project, client, json_query): - """Create a invitation of a user for a project""" - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project.public_id}, - json=json_query, - ) - assert response.status_code == http.HTTPStatus.OK - - invited_user = models.Invite.query.filter_by(email=json_query["email"]).one_or_none() - assert invited_user - - project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user.id, project_id=project.id - ).one_or_none() - assert project_invite_keys - - return invited_user - - def delete_project_user(project_id, user_id, table_to_use): """Delete row in either ProjectUsers or ProjectUserKeys.""" # Get project from database @@ -460,77 +429,6 @@ def test_fix_access_unitadmin_valid_email_unituser(client): assert user_project_key_row -def test_remove_access_invite_associated_several_projects(client): - """If an invite is associated with several projects then a single revoke access should not delete the invite""" - - project_1, project_2 = get_existing_projects() - - # invite a new user to both projects - invited_user = invite_to_project(project=project_1, client=client, json_query=first_new_user) - _ = invite_to_project(project=project_2, client=client, json_query=first_new_user) - - # Now revoke access for the first project - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project_1.public_id}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - assert ( - f"Invited user is no longer associated with the project {project_1.public_id}." - in response.json["message"] - ) - - # The project invite row should only be deleted for project 1 and the invite should still exist - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert invited_user - - project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user.id, project_id=project_2.id - ).one_or_none() - assert project_invite_keys - - project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user.id, project_id=project_1.id - ).one_or_none() - assert not project_invite_keys - - -def test_revoking_access_to_unacepted_invite(client): - """Revoking access to an unacepted invite for an existing project should delete the invite from the db""" - - project, _ = get_existing_projects() - - # Invite a new user to the project - invited_user = invite_to_project(project=project, client=client, json_query=first_new_user) - invited_user_id = invited_user.id - - # Now, revoke access to said user. The invite should be deleted - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string={"project": project.public_id}, - json=first_new_user, - ) - assert response.status_code == http.HTTPStatus.OK - - assert ( - f"Invited user is no longer associated with the project {project.public_id}." - in response.json["message"] - ) - - # Check that the invite is deleted - invited_user = models.Invite.query.filter_by(email=first_new_user["email"]).one_or_none() - assert not invited_user - - project_invite_keys = models.ProjectInviteKeys.query.filter_by( - invite_id=invited_user_id, project_id=project.id - ).one_or_none() - assert not project_invite_keys - - def test_fix_access_unitadmin_valid_email_unituser_no_project(client): """Unit Admin giving access to unituser - ok. No project.""" # Remove ProjectUserKeys row for specific project and user diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index 13ee20977..eb79c449d 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -6,7 +6,6 @@ import tests from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins from dds_web.database import models -from tests.test_project_access import invite_to_project, get_existing_projects # CONFIG ################################################################################## CONFIG # @@ -15,13 +14,6 @@ # proj_query_restricted = {"project": "restricted_project_id"} first_new_email = {"email": "first_test_email@mailtrap.io"} first_new_user = {**first_new_email, "role": "Researcher"} -first_new_user_unit_admin = {**first_new_email, "role": "Unit Admin"} -first_new_user_unit_personel = {**first_new_email, "role": "Unit Personnel"} - -remove_user_project_owner = {"email": "projectowner@mailtrap.io"} -remove_user_unit_user = {"email": "unituser2@mailtrap.io"} -remove_user_project_owner = {"email": "projectowner@mailtrap.io"} - # TESTS ################################################################################## TEST # @@ -117,28 +109,6 @@ def test_remove_nonexistent_user_from_project(client, boto3_session): assert "Cannot remove non-existent project access" in response.json["message"] -def test_remove_nonacepted_user_from_other_project(client, boto3_session): - """Try to remove an User with an unacepted invite from another project should result in an error""" - - projects = get_existing_projects() - project_1 = projects[0] - project_2 = projects[1] - - # invite a new user to a project - invite_to_project(project=project_1, client=client, json_query=first_new_user) - - # try to remove the same user from a different one - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project_2.public_id}, - json=first_new_user, - ) - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Cannot remove non-existent project access" in response.json["message"] - - def test_remove_existing_user_from_nonexistent_proj(client, boto3_session): """Try to an existing user from a nonexistent project""" @@ -155,115 +125,3 @@ def test_remove_existing_user_from_nonexistent_proj(client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "The specified project does not exist" in response.json["message"] - - -def test_researcher_removes_project_owner(client): - """ - A Researcher who is not a PO should not be able to delete a PO - """ - - # Research user trying to delete PO - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client), - query_string=proj_query, - json=remove_user_project_owner, - ) - - assert response.status_code == http.HTTPStatus.FORBIDDEN - assert "Insufficient credentials" in response.json["message"] - - -def test_user_personal_removed(client): - """ - User personal cannot be deleted from individual projects (they should be removed from the unit instead) - """ - - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string=proj_query, - json=remove_user_unit_user, - ) - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - # Should give error because a unit personal cannot be granted access to individual projects - assert "Cannot remove non-existent project access." in response.json["message"] - - -def test_removed_myself(client): - """ - An User cannot remove themselves from a project - """ - - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client), - query_string=proj_query, - json=remove_user_project_owner, - ) - - assert response.status_code == http.HTTPStatus.FORBIDDEN - assert "You cannot revoke your own access" in response.json["message"] - - -def test_remove_invite_unit_admin(client): - """ - A project removal request for an unanswered invite of unit admin should not work - """ - - # invite a new unit admin to the system - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=first_new_user_unit_admin, - ) - assert response.status_code == http.HTTPStatus.OK - - # try to remove the unitadmin for a specific project within their unit -> should not work - email = first_new_user_unit_admin["email"] - rem_user = {"email": email} - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string=proj_query, - json=rem_user, - ) - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - # Should give error because a unit personal cannot be granted access to individual projects - assert ( - "Cannot remove a Unit Admin / Unit Personnel from individual projects" - in response.json["message"] - ) - - -def test_invite_unit_user(client): - """ - A project removal request for an unanswered invite of unit personel should not work - """ - - # invite a new unit user to the system - response = client.post( - tests.DDSEndpoint.USER_ADD, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - json=first_new_user_unit_personel, - ) - assert response.status_code == http.HTTPStatus.OK - - # try to remove the unit personal for a specific project within their unit -> should not work - email = first_new_user_unit_personel["email"] - rem_user = {"email": email} - response = client.post( - tests.DDSEndpoint.REMOVE_USER_FROM_PROJ, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), - query_string=proj_query, - json=rem_user, - ) - - assert response.status_code == http.HTTPStatus.BAD_REQUEST - # Should give error because a unit personal cannot be granted access to individual projects - assert ( - "Cannot remove a Unit Admin / Unit Personnel from individual projects" - in response.json["message"] - ) From 8685e4af7ecac9f7c0d61437fba3e872bc83b99a Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 27 Sep 2023 14:50:41 +0200 Subject: [PATCH 053/155] updated tes --- tests/api/test_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index f9fdba731..f9ceebc1e 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1361,7 +1361,7 @@ def test_revoking_access_to_unacepted_invite(client): assert response.status_code == http.HTTPStatus.OK assert ( - f"Invited user is no longer associated with the project {project.public_id}." + f"Invited user is no longer associated with the project '{project.public_id}'." in response.json["message"] ) From 4b6c10249b4de6473acf2eafa77658dc5e29912a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:39:25 +0200 Subject: [PATCH 054/155] Update tests/api/test_user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/api/test_user.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index f9ceebc1e..6bb5ecf9b 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1378,9 +1378,7 @@ def test_revoking_access_to_unacepted_invite(client): def test_remove_nonacepted_user_from_other_project(client, boto3_session): """Try to remove an User with an unacepted invite from another project should result in an error""" - projects = get_existing_projects() - project_1 = projects[0] - project_2 = projects[1] + project_1, project_2 = get_existing_projects() # invite a new user to a project invite_to_project(project=project_1, client=client, json_query=first_new_user) From 26a88f1b25687f1cefa8be19a3c7a08ecfe842b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:39:39 +0200 Subject: [PATCH 055/155] Update tests/api/test_user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/api/test_user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 6bb5ecf9b..b2f9a24aa 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1414,9 +1414,9 @@ def test_researcher_removes_project_owner(client): assert "Insufficient credentials" in response.json["message"] -def test_user_personal_removed(client): +def test_unit_personnel_removed(client): """ - User personal cannot be deleted from individual projects (they should be removed from the unit instead) + Unit Personnel cannot be deleted from individual projects (they should be removed from the unit instead) """ project, _ = get_existing_projects() From 6a28caea8ef3eeb9e1d4a42b5b56cae8722b8480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:39:51 +0200 Subject: [PATCH 056/155] Update tests/api/test_user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/api/test_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index b2f9a24aa..5dc0b9600 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1506,7 +1506,7 @@ def test_invite_unit_user(client): ) assert response.status_code == http.HTTPStatus.BAD_REQUEST - # Should give error because a unit personal cannot be granted access to individual projects + # Should give error because a Unit Personnel cannot be granted access to individual projects assert ( "Cannot remove a Unit Admin / Unit Personnel from individual projects" in response.json["message"] From daaeeed87fd3f80bb0a22b177a281c50ef143859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:39:59 +0200 Subject: [PATCH 057/155] Update tests/test_user_remove_association.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/test_user_remove_association.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_user_remove_association.py b/tests/test_user_remove_association.py index eb79c449d..34b726886 100644 --- a/tests/test_user_remove_association.py +++ b/tests/test_user_remove_association.py @@ -7,14 +7,6 @@ from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins from dds_web.database import models -# CONFIG ################################################################################## CONFIG # - -# proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} -proj_query = {"project": "public_project_id"} -# proj_query_restricted = {"project": "restricted_project_id"} -first_new_email = {"email": "first_test_email@mailtrap.io"} -first_new_user = {**first_new_email, "role": "Researcher"} - # TESTS ################################################################################## TEST # From 38d42680bcaaeb01b25c921ab07ec5281843faf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:40:07 +0200 Subject: [PATCH 058/155] Update tests/test_project_access.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/test_project_access.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_project_access.py b/tests/test_project_access.py index f551feeab..9466f7c73 100644 --- a/tests/test_project_access.py +++ b/tests/test_project_access.py @@ -14,8 +14,6 @@ # proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} proj_query = {"project": "public_project_id"} # proj_query_restricted = {"project": "restricted_project_id"} -first_new_email = {"email": "first_test_email@mailtrap.io"} -first_new_user = {**first_new_email, "role": "Researcher"} # UTILITY FUNCTIONS ############################################################ UTILITY FUNCTIONS # From e5bfb52c0c25e2029340decb908b30943cf4a2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:40:17 +0200 Subject: [PATCH 059/155] Update tests/api/test_user.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/api/test_user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 5dc0b9600..615590e5a 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1495,7 +1495,7 @@ def test_invite_unit_user(client): ) assert response.status_code == http.HTTPStatus.OK - # try to remove the unit personal for a specific project within their unit -> should not work + # try to remove the Unit Personnel for a specific project within their unit -> should not work email = first_new_user_unit_personel["email"] rem_user = {"email": email} response = client.post( From 88254d2f799f1c9e509ac36e3e1b2e91b9e44ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= Date: Thu, 28 Sep 2023 14:58:44 +0200 Subject: [PATCH 060/155] comment about whitelisted IPs --- dds_web/database/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 104fa932d..8d27ea779 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -195,7 +195,7 @@ class Unit(db.Model): sto2_access = db.Column(db.String(255), unique=False, nullable=True) # unique=True later sto2_secret = db.Column(db.String(255), unique=False, nullable=True) # unique=True later - # New safespring storage + # New safespring storage - NOTE: MAKE SURE IPS ARE WHITELISTED ON UPPMAX AND OTHER SERVERS sto4_start_time = db.Column(db.DateTime(), nullable=True) sto4_endpoint = db.Column(db.String(255), unique=False, nullable=True) # unique=True later sto4_name = db.Column(db.String(255), unique=False, nullable=True) # unique=True later From 70e5d6045441d72fb78f71ff2741a1baca6b948e Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 3 Oct 2023 10:43:44 +0200 Subject: [PATCH 061/155] new info --- dds_web/api/user.py | 4 ++++ dds_web/templates/mail/project_release.html | 16 +++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/dds_web/api/user.py b/dds_web/api/user.py index d8fb964aa..35ec90405 100644 --- a/dds_web/api/user.py +++ b/dds_web/api/user.py @@ -423,6 +423,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): unit_email = None project_id = None + project_title = None deadline = None # Don't display unit admins or personnels name @@ -440,6 +441,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): elif mail_type == "project_release": subject = f"Project made available by {displayed_sender} in the SciLifeLab Data Delivery System" project_id = project.public_id + project_title = project.title deadline = project.current_deadline.astimezone(datetime.timezone.utc).strftime( "%Y-%m-%d %H:%M:%S %Z" ) @@ -470,6 +472,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): displayed_sender=displayed_sender, unit_email=unit_email, project_id=project_id, + project_title=project_title, deadline=deadline, ) msg.html = flask.render_template( @@ -478,6 +481,7 @@ def compose_and_send_email_to_user(userobj, mail_type, link=None, project=None): displayed_sender=displayed_sender, unit_email=unit_email, project_id=project_id, + project_title=project_title, deadline=deadline, ) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 24262a413..1a8ef6272 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -10,11 +10,13 @@ -

The - project {{project_id}} is now available for your access in the SciLifeLab Data Delivery System (DDS).

-

- The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple - way.

+

+ The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can now download your data. +

    +
  • Project Title: {{project_title}}
  • +
  • DDS project ID: {{project_id}}
  • +
+

{% if unit_email %} You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}). @@ -28,6 +30,10 @@ The DDS CLI command dds data get -p {{project_id}} -a can be used to download all the files in this project to your current directory.

Your access to this project will expire on {{deadline}}

+

+ What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple + way.

+ From 30465f27978f53a98f4bdc912644db6db0fe8bd0 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 3 Oct 2023 11:31:08 +0200 Subject: [PATCH 062/155] txt templatw --- dds_web/templates/mail/project_release.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt index 121d5ea6f..a7cf4774c 100644 --- a/dds_web/templates/mail/project_release.txt +++ b/dds_web/templates/mail/project_release.txt @@ -1,5 +1,6 @@ -The project {{project_id}} is now available for your access in the SciLifeLab Data Delivery System (DDS). -The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way. +The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can now download your data. + - Project Title: {{project_title}} + - DDS project ID: {{project_id}} {% if unit_email %} You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}). @@ -12,3 +13,6 @@ The DDS CLI command 'dds ls -p {{project_id}}' can be used to list the files in The DDS CLI command 'dds data get -p {{project_id}} -a' can be used to download all the files in this project to your current directory. Your access to this project will expire on {{deadline}} + +What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way. + From 2364ced2e80fe99d5617bac497e77fbb17b3ec58 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 4 Oct 2023 12:57:19 +0200 Subject: [PATCH 063/155] test email --- tests/api/test_project.py | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index cd1d7f740..81a3a7b9a 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1388,3 +1388,46 @@ def test_project_usage(module_client): # Call project_usage() for the project and check if cost is calculated correctly proj_bhours, proj_cost = UserProjects.project_usage(project=project_0) assert (proj_bhours / 1e9) * cost_gbhour == proj_cost + + +def test_email_project_release(module_client): + """Test that the email to the researches is sent when the project has been released + Function is compose_and_send_email_to_user used at project.py + """ + + # Create unit admins to allow project creation + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + # Create project + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + + project_id = response.json.get("project_id") + + # Release project and check email + with unittest.mock.patch.object(flask_mail.Mail) as mock_mail: + with mock_mail.record_messages() as outbox: + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available"}, + ) + assert len(outbox) == 1 + assert "Project made available by" in outbox[0].subject + + assert response.status_code == http.HTTPStatus.OK + + +# msg = outbox[-1] +# assert msg.subject == const.RESET_EMAIL_SUBJECT +# assert 'Reset Password' in msg.html +# assert 'Reset Password' in msg.body From c8378300aee63c9171db6d36ad9149c776282ec6 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 4 Oct 2023 13:30:48 +0200 Subject: [PATCH 064/155] changed client --- tests/api/test_project.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 81a3a7b9a..fd909f3c1 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1390,37 +1390,35 @@ def test_project_usage(module_client): assert (proj_bhours / 1e9) * cost_gbhour == proj_cost -def test_email_project_release(module_client): +def test_email_project_release(client): """Test that the email to the researches is sent when the project has been released Function is compose_and_send_email_to_user used at project.py """ - - # Create unit admins to allow project creation + create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 + assert current_unit_admins == 3 - # Create project - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), json=proj_data, ) assert response.status_code == http.HTTPStatus.OK project_id = response.json.get("project_id") + assert response.status_code == http.HTTPStatus.OK + # Release project and check email with unittest.mock.patch.object(flask_mail.Mail) as mock_mail: with mock_mail.record_messages() as outbox: - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), query_string={"project": project_id}, json={"new_status": "Available"}, ) + assert len(outbox) == 1 assert "Project made available by" in outbox[0].subject From c45a2e5edf385440c474f7e3b41371c3a44e45e8 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 4 Oct 2023 15:32:52 +0200 Subject: [PATCH 065/155] added tests --- tests/api/test_project.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index fd909f3c1..27966c903 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -16,7 +16,7 @@ # Own import dds_web -from dds_web import db +from dds_web import auth, mail, db, basic_auth, limiter from dds_web.errors import BucketNotFoundError, DatabaseError, DeletionError import tests from tests.test_files_new import project_row, file_in_db, FIRST_NEW_FILE @@ -1390,7 +1390,7 @@ def test_project_usage(module_client): assert (proj_bhours / 1e9) * cost_gbhour == proj_cost -def test_email_project_release(client): +def test_email_project_release(client,boto3_session): """Test that the email to the researches is sent when the project has been released Function is compose_and_send_email_to_user used at project.py """ @@ -1401,31 +1401,28 @@ def test_email_project_release(client): response = client.post( tests.DDSEndpoint.PROJECT_CREATE, headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - json=proj_data, + json=proj_data_with_existing_users, ) assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - - assert response.status_code == http.HTTPStatus.OK + public_project_id = response.json.get("project_id") # Release project and check email - with unittest.mock.patch.object(flask_mail.Mail) as mock_mail: - with mock_mail.record_messages() as outbox: - response = client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), - query_string={"project": project_id}, - json={"new_status": "Available"}, - ) - - assert len(outbox) == 1 - assert "Project made available by" in outbox[0].subject + with mail.record_messages() as outbox: + response = client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + query_string={"project": public_project_id}, + json={"new_status": "Available", "deadline": 10, "send_email": True}, + ) + assert len(outbox) == 3 + assert "Project made available by" in outbox[-1].subject + # TODO check the body of the email + # msg = outbox[-1] +# assert msg.subject == const.RESET_EMAIL_SUBJECT +# assert 'Reset Password' in msg.html +# assert 'Reset Password' in msg.body assert response.status_code == http.HTTPStatus.OK -# msg = outbox[-1] -# assert msg.subject == const.RESET_EMAIL_SUBJECT -# assert 'Reset Password' in msg.html -# assert 'Reset Password' in msg.body From e5bbc5d6bbad21890b153804c71a55a4f925d1b4 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 09:50:27 +0200 Subject: [PATCH 066/155] tryit --- tests/api/test_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 27966c903..23375d5dd 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -16,7 +16,7 @@ # Own import dds_web -from dds_web import auth, mail, db, basic_auth, limiter +from dds_web import mail, db from dds_web.errors import BucketNotFoundError, DatabaseError, DeletionError import tests from tests.test_files_new import project_row, file_in_db, FIRST_NEW_FILE @@ -1421,7 +1421,7 @@ def test_email_project_release(client,boto3_session): # msg = outbox[-1] # assert msg.subject == const.RESET_EMAIL_SUBJECT # assert 'Reset Password' in msg.html -# assert 'Reset Password' in msg.body +# assert 'Reset Password' in msg.body -> plain text assert response.status_code == http.HTTPStatus.OK From 353ad737a121a17f167501bbe5c3643bd0ed7813 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 09:55:43 +0200 Subject: [PATCH 067/155] black --- tests/api/test_project.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 23375d5dd..1fc557fa5 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1390,7 +1390,7 @@ def test_project_usage(module_client): assert (proj_bhours / 1e9) * cost_gbhour == proj_cost -def test_email_project_release(client,boto3_session): +def test_email_project_release(client, boto3_session): """Test that the email to the researches is sent when the project has been released Function is compose_and_send_email_to_user used at project.py """ @@ -1419,10 +1419,8 @@ def test_email_project_release(client,boto3_session): assert "Project made available by" in outbox[-1].subject # TODO check the body of the email # msg = outbox[-1] -# assert msg.subject == const.RESET_EMAIL_SUBJECT -# assert 'Reset Password' in msg.html -# assert 'Reset Password' in msg.body -> plain text + # assert msg.subject == const.RESET_EMAIL_SUBJECT + # assert 'Reset Password' in msg.html + # assert 'Reset Password' in msg.body -> plain text assert response.status_code == http.HTTPStatus.OK - - From 9ee7ae2dce933c00ccdd182525393fad3f1298cf Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 10:23:12 +0200 Subject: [PATCH 068/155] changes to module client --- tests/api/test_project.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 1fc557fa5..854373c81 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1390,7 +1390,7 @@ def test_project_usage(module_client): assert (proj_bhours / 1e9) * cost_gbhour == proj_cost -def test_email_project_release(client, boto3_session): +def test_email_project_release(module_client, boto3_session): """Test that the email to the researches is sent when the project has been released Function is compose_and_send_email_to_user used at project.py """ @@ -1398,9 +1398,9 @@ def test_email_project_release(client, boto3_session): current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() assert current_unit_admins == 3 - response = client.post( + response = module_client.post( tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), json=proj_data_with_existing_users, ) assert response.status_code == http.HTTPStatus.OK @@ -1409,9 +1409,9 @@ def test_email_project_release(client, boto3_session): # Release project and check email with mail.record_messages() as outbox: - response = client.post( + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), query_string={"project": public_project_id}, json={"new_status": "Available", "deadline": 10, "send_email": True}, ) From dd394b72348ec0e7e5214948a43e08bba3be95f1 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 10:57:51 +0200 Subject: [PATCH 069/155] completed test --- tests/api/test_project.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 854373c81..8ef851f1d 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1396,11 +1396,13 @@ def test_email_project_release(module_client, boto3_session): """ create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins == 3 + assert current_unit_admins >= 3 + + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client) response = module_client.post( tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + headers=token, json=proj_data_with_existing_users, ) assert response.status_code == http.HTTPStatus.OK @@ -1411,16 +1413,25 @@ def test_email_project_release(module_client, boto3_session): with mail.record_messages() as outbox: response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + headers=token, query_string={"project": public_project_id}, json={"new_status": "Available", "deadline": 10, "send_email": True}, ) assert len(outbox) == 3 assert "Project made available by" in outbox[-1].subject - # TODO check the body of the email - # msg = outbox[-1] - # assert msg.subject == const.RESET_EMAIL_SUBJECT - # assert 'Reset Password' in msg.html - # assert 'Reset Password' in msg.body -> plain text + + body = outbox[-1].body #plain text + html = outbox[-1].html + + project_title = proj_data_with_existing_users["title"] + + ## check plain text message + assert f"- Project Title: {project_title}" in outbox[-1].body + assert f"- DDS project ID: {public_project_id}" in outbox[-1].body + + ## check html + + assert f"
  • Project Title: {project_title}
  • " + assert f"
  • DDS project ID: {public_project_id}
  • " assert response.status_code == http.HTTPStatus.OK From 7ba770375995a706150919c30ac03bada453f89e Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 11:00:16 +0200 Subject: [PATCH 070/155] black --- tests/api/test_project.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 8ef851f1d..dbb6812a1 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1397,7 +1397,7 @@ def test_email_project_release(module_client, boto3_session): create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() assert current_unit_admins >= 3 - + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client) response = module_client.post( @@ -1419,8 +1419,8 @@ def test_email_project_release(module_client, boto3_session): ) assert len(outbox) == 3 assert "Project made available by" in outbox[-1].subject - - body = outbox[-1].body #plain text + + body = outbox[-1].body # plain text html = outbox[-1].html project_title = proj_data_with_existing_users["title"] From 4eee28e501e4f9d36def9fdd145eea59a87aeedd Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 11:13:27 +0200 Subject: [PATCH 071/155] files --- dds_web/api/superadmin_only.py | 2 +- tests/api/test_superadmin_only.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/dds_web/api/superadmin_only.py b/dds_web/api/superadmin_only.py index 44fae0fa0..3c73305f1 100644 --- a/dds_web/api/superadmin_only.py +++ b/dds_web/api/superadmin_only.py @@ -164,7 +164,7 @@ def post(self): # Create email content # put motd_obj.message etc in there etc - subject: str = "DDS Important Information" + subject: str = "Important Information: Data Delivery System" body: str = flask.render_template(f"mail/motd.txt", motd=motd_obj.message) html = flask.render_template(f"mail/motd.html", motd=motd_obj.message) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 022c537a7..54d600058 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -20,7 +20,7 @@ import click # Own -from dds_web import db +from dds_web import db, mail from dds_web.database import models import tests from dds_web.commands import collect_stats @@ -155,9 +155,15 @@ def test_create_motd_as_superadmin_empty_message(client: flask.testing.FlaskClie def test_create_motd_as_superadmin_success(client: flask.testing.FlaskClient) -> None: """Create a new message of the day, using a Super Admin account.""" token: typing.Dict = get_token(username=users["Super Admin"], client=client) - response: werkzeug.test.WrapperTestResponse = client.post( - tests.DDSEndpoint.MOTD, headers=token, json={"message": "test"} - ) + + with mail.record_messages() as outbox: + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD, headers=token, json={"message": "test"} + ) + + assert len(outbox) == 1 + assert "Important Information: Data Delivery System" in outbox[-1].subject + assert response.status_code == http.HTTPStatus.OK assert "The MOTD was successfully added to the database." in response.json.get("message") From 5527e1288cdfc8227ae532bf1c756c98f889bca8 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 11:23:13 +0200 Subject: [PATCH 072/155] sprintlog and change int --- SPRINTLOG.md | 4 ++++ tests/api/test_project.py | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index d965cd8b5..3b0a4e900 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -307,3 +307,7 @@ _Nothing merged in CLI during this sprint_ - Column `sto4_start_time` is automatically set when the create-unit command is run ([#1668])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1668) - Replace expired invites when there's a new invitation attempt ([#1466](https://github.com/ScilifelabDataCentre/dds_web/pull/1466)) - New version: 2.5.1 ([#1471](https://github.com/ScilifelabDataCentre/dds_web/pull/1471)) + +# 2023-10-02 - 2023-10-13 + +- Added the project title aling with the internal project ID in the email sent when a project is released ([#1537])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1537) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index dbb6812a1..e798960f8 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1417,7 +1417,7 @@ def test_email_project_release(module_client, boto3_session): query_string={"project": public_project_id}, json={"new_status": "Available", "deadline": 10, "send_email": True}, ) - assert len(outbox) == 3 + assert len(outbox) == 2 # Emails informing researchers assert "Project made available by" in outbox[-1].subject body = outbox[-1].body # plain text @@ -1426,12 +1426,12 @@ def test_email_project_release(module_client, boto3_session): project_title = proj_data_with_existing_users["title"] ## check plain text message - assert f"- Project Title: {project_title}" in outbox[-1].body - assert f"- DDS project ID: {public_project_id}" in outbox[-1].body + assert f"- Project Title: {project_title}" in body + assert f"- DDS project ID: {public_project_id}" in body ## check html - assert f"
  • Project Title: {project_title}
  • " - assert f"
  • DDS project ID: {public_project_id}
  • " + assert f"
  • Project Title: {project_title}
  • " in html + assert f"
  • DDS project ID: {public_project_id}
  • " in html assert response.status_code == http.HTTPStatus.OK From f7a770bccab7ba9e619cc839af8989fdb0a97ff5 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 11:24:18 +0200 Subject: [PATCH 073/155] black --- tests/api/test_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index e798960f8..a73cda918 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1417,7 +1417,7 @@ def test_email_project_release(module_client, boto3_session): query_string={"project": public_project_id}, json={"new_status": "Available", "deadline": 10, "send_email": True}, ) - assert len(outbox) == 2 # Emails informing researchers + assert len(outbox) == 2 # Emails informing researchers assert "Project made available by" in outbox[-1].subject body = outbox[-1].body # plain text From caba69046dbbc5de4f16d50c9add98ad667a286d Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 11:54:59 +0200 Subject: [PATCH 074/155] sprintlog and move test --- SPRINTLOG.md | 4 ++++ tests/api/test_superadmin_only.py | 17 ++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index d965cd8b5..bdf8ee865 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -307,3 +307,7 @@ _Nothing merged in CLI during this sprint_ - Column `sto4_start_time` is automatically set when the create-unit command is run ([#1668])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1668) - Replace expired invites when there's a new invitation attempt ([#1466](https://github.com/ScilifelabDataCentre/dds_web/pull/1466)) - New version: 2.5.1 ([#1471](https://github.com/ScilifelabDataCentre/dds_web/pull/1471)) + +# 2023-10-02 - 2023-10-13 + +- Changed email subject when motd is release to send the whole DDS name and not just the acronynm ([#1422])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1422) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index 54d600058..d06df07b0 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -155,15 +155,9 @@ def test_create_motd_as_superadmin_empty_message(client: flask.testing.FlaskClie def test_create_motd_as_superadmin_success(client: flask.testing.FlaskClient) -> None: """Create a new message of the day, using a Super Admin account.""" token: typing.Dict = get_token(username=users["Super Admin"], client=client) - - with mail.record_messages() as outbox: - response: werkzeug.test.WrapperTestResponse = client.post( - tests.DDSEndpoint.MOTD, headers=token, json={"message": "test"} - ) - - assert len(outbox) == 1 - assert "Important Information: Data Delivery System" in outbox[-1].subject - + response: werkzeug.test.WrapperTestResponse = client.post( + tests.DDSEndpoint.MOTD, headers=token, json={"message": "test"} + ) assert response.status_code == http.HTTPStatus.OK assert "The MOTD was successfully added to the database." in response.json.get("message") @@ -611,12 +605,13 @@ def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: num_users = models.User.query.count() # Attempt request - with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: + with mail.record_messages() as outbox: response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id} ) assert response.status_code == http.HTTPStatus.OK - assert mock_mail_send.call_count == num_users + assert len(outbox) == num_users + assert "Important Information: Data Delivery System" in outbox[-1].subject # Maintenance ###################################################################################### From 9eb94c5d4a434926be94fe352223d485400fc667 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 12:48:12 +0200 Subject: [PATCH 075/155] last test --- tests/api/test_superadmin_only.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index d06df07b0..c3e4e44b8 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -577,13 +577,14 @@ def test_send_motd_no_primary_email(client: flask.testing.FlaskClient) -> None: assert not models.Email.query.filter_by(email=email).one_or_none() assert not models.User.query.filter_by(username=username).one().primary_email - # Attempt request - with unittest.mock.patch.object(flask_mail.Connection, "send") as mock_mail_send: + # Attempt request and catch email + with mail.record_messages() as outbox: response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id} ) assert response.status_code == http.HTTPStatus.OK - assert mock_mail_send.call_count == num_users - 1 + assert len(outbox) == num_users - 1 + assert "Important Information: Data Delivery System" in outbox[-1].subject def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: @@ -604,7 +605,7 @@ def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: # Get number of users num_users = models.User.query.count() - # Attempt request + # Attempt request and catch email with mail.record_messages() as outbox: response: werkzeug.test.WrapperTestResponse = client.post( tests.DDSEndpoint.MOTD_SEND, headers=token, json={"motd_id": created_motd.id} From 2038b39855aea4eb37229549cb7fa78c3c55d0b3 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 5 Oct 2023 13:08:17 +0200 Subject: [PATCH 076/155] refactoring --- tests/api/test_project.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index a73cda918..867da5ef3 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1391,23 +1391,19 @@ def test_project_usage(module_client): def test_email_project_release(module_client, boto3_session): - """Test that the email to the researches is sent when the project has been released - Function is compose_and_send_email_to_user used at project.py - """ + """Test that check that the email sent to the researchers when project is released is correct""" + public_project_id = "public_project_id" + create_unit_admins(num_admins=2) current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() assert current_unit_admins >= 3 + # user to perfrom the operation token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client) - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=token, - json=proj_data_with_existing_users, - ) - assert response.status_code == http.HTTPStatus.OK - - public_project_id = response.json.get("project_id") + # project to be released + project = models.Project.query.filter_by(public_id=public_project_id).first() + # num of researchers that will receive email + num_users = models.ProjectUsers.query.filter_by(project_id=project.id).count() # Release project and check email with mail.record_messages() as outbox: @@ -1417,13 +1413,13 @@ def test_email_project_release(module_client, boto3_session): query_string={"project": public_project_id}, json={"new_status": "Available", "deadline": 10, "send_email": True}, ) - assert len(outbox) == 2 # Emails informing researchers + assert len(outbox) == num_users # nº of Emails informing researchers assert "Project made available by" in outbox[-1].subject body = outbox[-1].body # plain text html = outbox[-1].html - project_title = proj_data_with_existing_users["title"] + project_title = project.title ## check plain text message assert f"- Project Title: {project_title}" in body From 55727c54baf42d1732625e68d1c125f4868e0c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:39:28 +0200 Subject: [PATCH 077/155] Update SPRINTLOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- SPRINTLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 3b0a4e900..d8c6aa2c7 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -310,4 +310,4 @@ _Nothing merged in CLI during this sprint_ # 2023-10-02 - 2023-10-13 -- Added the project title aling with the internal project ID in the email sent when a project is released ([#1537])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1537) +- Project title displayed along with the internal project ID email sent when a project is released ([#1537])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1537) From ca812f327d7ce37f9461e92ae0378f761eafd141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:39:34 +0200 Subject: [PATCH 078/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 1a8ef6272..45f6ad4cd 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -11,7 +11,7 @@

    - The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can now download your data. + The following project is now available for your access in the SciLifeLab Data Delivery System (DDS) and you can download your data.

    • Project Title: {{project_title}}
    • DDS project ID: {{project_id}}
    • From 264fbe2f93816b88ab2f7bd69498e71641968ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:39:46 +0200 Subject: [PATCH 079/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 45f6ad4cd..1e96f384c 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -31,7 +31,7 @@

      Your access to this project will expire on {{deadline}}

      - What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple + What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way.

      From 7894f51b2681cb9b9328880cc7801fe7a228d4f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:42:02 +0200 Subject: [PATCH 080/155] Update SPRINTLOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- SPRINTLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index bdf8ee865..5e2242f7b 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -310,4 +310,4 @@ _Nothing merged in CLI during this sprint_ # 2023-10-02 - 2023-10-13 -- Changed email subject when motd is release to send the whole DDS name and not just the acronynm ([#1422])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1422) +- Use full DDS name in MOTD email subject ([#1422])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1422) From c61513665111c3f8132f4f2fbe1f1784d54bd178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Thu, 5 Oct 2023 17:42:10 +0200 Subject: [PATCH 081/155] Update tests/api/test_superadmin_only.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/api/test_superadmin_only.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/test_superadmin_only.py b/tests/api/test_superadmin_only.py index c3e4e44b8..ca1d5d428 100644 --- a/tests/api/test_superadmin_only.py +++ b/tests/api/test_superadmin_only.py @@ -585,6 +585,7 @@ def test_send_motd_no_primary_email(client: flask.testing.FlaskClient) -> None: assert response.status_code == http.HTTPStatus.OK assert len(outbox) == num_users - 1 assert "Important Information: Data Delivery System" in outbox[-1].subject + assert "incorrect subject" not in outbox[-1].subject def test_send_motd_ok(client: flask.testing.FlaskClient) -> None: From fbd6efec87324dfe0c2af4db4d2f18c52a26a42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 6 Oct 2023 11:01:10 +0200 Subject: [PATCH 082/155] Update SPRINTLOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- SPRINTLOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 7ddbbd5d9..09dc78670 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -310,5 +310,5 @@ _Nothing merged in CLI during this sprint_ # 2023-10-02 - 2023-10-13 -- Use full DDS name in MOTD email subject ([#1422])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1422) -- Project title displayed along with the internal project ID email sent when a project is released ([#1537])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1537) +- Project title displayed along with the internal project ID email sent when a project is released ([#1475](https://github.com/ScilifelabDataCentre/dds_web/pull/1475)) +- Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) From bc389e90d1af250a1fd96e70ab9c8f26fb66d09e Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 6 Oct 2023 12:02:37 +0200 Subject: [PATCH 083/155] new templates --- dds_web/templates/mail/project_release.html | 2 +- dds_web/templates/mail/project_release.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 1e96f384c..4183de5fa 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -27,7 +27,7 @@

      The DDS CLI command dds ls -p {{project_id}} can be used to list the files in this project.

      - The DDS CLI command dds data get -p {{project_id}} -a can be used to download all the files in this project to your current directory.

      + The DDS CLI command dds data get -p {{project_id}} -a --verify-checksum can be used to download all the files in this project to your current directory.

      Your access to this project will expire on {{deadline}}

      diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt index a7cf4774c..162f0a803 100644 --- a/dds_web/templates/mail/project_release.txt +++ b/dds_web/templates/mail/project_release.txt @@ -10,7 +10,7 @@ You were added to this project by {{displayed_sender}}. The DDS CLI command 'dds ls -p {{project_id}}' can be used to list the files in this project. -The DDS CLI command 'dds data get -p {{project_id}} -a' can be used to download all the files in this project to your current directory. +The DDS CLI command 'dds data get -p {{project_id}} -a --verify-checksum' can be used to download all the files in this project to your current directory. Your access to this project will expire on {{deadline}} From 972f9d706ee09bc1b98f284e824dce34cafda1e4 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 6 Oct 2023 12:42:27 +0200 Subject: [PATCH 084/155] springlog --- SPRINTLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 09dc78670..c96aac575 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -310,5 +310,6 @@ _Nothing merged in CLI during this sprint_ # 2023-10-02 - 2023-10-13 +- Add flag --verify-checksum to the comand in email template ([#1409])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1409) - Project title displayed along with the internal project ID email sent when a project is released ([#1475](https://github.com/ScilifelabDataCentre/dds_web/pull/1475)) - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) From 2bdd4ce6d30414d61edbbe8cf3b28936794cc51c Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 6 Oct 2023 15:27:05 +0200 Subject: [PATCH 085/155] correct sprintlog --- SPRINTLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index c96aac575..ee717956f 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -310,6 +310,6 @@ _Nothing merged in CLI during this sprint_ # 2023-10-02 - 2023-10-13 -- Add flag --verify-checksum to the comand in email template ([#1409])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1409) - Project title displayed along with the internal project ID email sent when a project is released ([#1475](https://github.com/ScilifelabDataCentre/dds_web/pull/1475)) - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) +- Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) From e986301602345d1051cb448c82710dd318c71f4d Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 9 Oct 2023 10:59:30 +0200 Subject: [PATCH 086/155] modified files for functionality --- dds_web/templates/mail/mail_base.html | 23 ++++++++++++++++++- dds_web/templates/mail/project_release.html | 25 +++++++++++++-------- dds_web/templates/mail/project_release.txt | 18 ++++++++++----- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/dds_web/templates/mail/mail_base.html b/dds_web/templates/mail/mail_base.html index d0dd52e7f..5fbafcd8a 100644 --- a/dds_web/templates/mail/mail_base.html +++ b/dds_web/templates/mail/mail_base.html @@ -1,6 +1,23 @@ - + + @@ -122,6 +139,10 @@ border-color: #a00202 !important; } } + mark { + background-color: pink; + color: black; +} diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 4183de5fa..ae49f4b70 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -18,22 +18,29 @@

    + You were added to this project on behalf of {{displayed_sender}}. +

    +

    + To list the files in this project, run:
    + dds ls -p {{project_id}}.

    +

    + To download all the files in this project to your current directory, run:
    + dds data get -p {{project_id}} -a --verify-checksum.

    +

    + For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.

    +

    + {% if unit_email %} - You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}). + if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at ({{unit_email}}). {% else %} - You were added to this project by {{displayed_sender}}. + if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. {% endif %}

    -

    - The DDS CLI command dds ls -p {{project_id}} can be used to list the files in this project.

    -

    - The DDS CLI command dds data get -p {{project_id}} -a --verify-checksum can be used to download all the files in this project to your current directory.

    -

    - Your access to this project will expire on {{deadline}}

    +

    + Your access to this project will expire on: {{deadline}}

    What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way.

    - diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt index 162f0a803..e8d88dda9 100644 --- a/dds_web/templates/mail/project_release.txt +++ b/dds_web/templates/mail/project_release.txt @@ -2,16 +2,22 @@ The following project is now available for your access in the SciLifeLab Data De - Project Title: {{project_title}} - DDS project ID: {{project_id}} +You were added to this project on behalf of {{displayed_sender}}. + +To list the files in this project, run: + dds ls -p {{project_id}} + +To download all the files in this project to your current directory, run: + dds data get -p {{project_id}} -a --verify-checksum. + +For more information (including an installation guide), see the DDS CLI documentation here: https://scilifelabdatacentre.github.io/dds_cli/ + {% if unit_email %} -You were added to this project on behalf of {{displayed_sender}} ({{unit_email}}). +if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}at ({{unit_email}}). {% else %} -You were added to this project by {{displayed_sender}}. +if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. {% endif %} -The DDS CLI command 'dds ls -p {{project_id}}' can be used to list the files in this project. - -The DDS CLI command 'dds data get -p {{project_id}} -a --verify-checksum' can be used to download all the files in this project to your current directory. - Your access to this project will expire on {{deadline}} What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way. From 720e5042837fe234454d2d93df71921f01d25f1e Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 9 Oct 2023 11:14:54 +0200 Subject: [PATCH 087/155] new tests --- dds_web/templates/mail/project_release.html | 3 +-- tests/api/test_project.py | 9 ++++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index ae49f4b70..c5ed54bf3 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -39,8 +39,7 @@

    Your access to this project will expire on: {{deadline}}

    - What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple - way.

    + What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way.

    diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 867da5ef3..1032d31e8 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1424,10 +1424,17 @@ def test_email_project_release(module_client, boto3_session): ## check plain text message assert f"- Project Title: {project_title}" in body assert f"- DDS project ID: {public_project_id}" in body + assert f"dds ls -p {public_project_id}" in body + assert f"dds data get -p {public_project_id} -a --verify-checksum" in body + assert "if you experience issues, please contact the SciLifeLab unit" in body + assert "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way" in body ## check html - assert f"
  • Project Title: {project_title}
  • " in html assert f"
  • DDS project ID: {public_project_id}
  • " in html + assert f"dds ls -p {public_project_id}" in html + assert f"dds data get -p {public_project_id} -a --verify-checksum" in html + assert "if you experience issues, please contact the SciLifeLab unit" in html + assert "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way." in html assert response.status_code == http.HTTPStatus.OK From 43c45e20a3125e2f21a0e53297334288fed9abf7 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 9 Oct 2023 11:17:38 +0200 Subject: [PATCH 088/155] test --- tests/api/test_project.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 1032d31e8..89d5e1c2a 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1427,7 +1427,10 @@ def test_email_project_release(module_client, boto3_session): assert f"dds ls -p {public_project_id}" in body assert f"dds data get -p {public_project_id} -a --verify-checksum" in body assert "if you experience issues, please contact the SciLifeLab unit" in body - assert "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way" in body + assert ( + "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way" + in body + ) ## check html assert f"
  • Project Title: {project_title}
  • " in html @@ -1435,6 +1438,9 @@ def test_email_project_release(module_client, boto3_session): assert f"dds ls -p {public_project_id}" in html assert f"dds data get -p {public_project_id} -a --verify-checksum" in html assert "if you experience issues, please contact the SciLifeLab unit" in html - assert "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way." in html + assert ( + "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way." + in html + ) assert response.status_code == http.HTTPStatus.OK From c49699b62be0c6c17044d5ed9f667791e8f3c23b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 9 Oct 2023 11:18:59 +0200 Subject: [PATCH 089/155] springlog --- SPRINTLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index ee717956f..32341599a 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -313,3 +313,4 @@ _Nothing merged in CLI during this sprint_ - Project title displayed along with the internal project ID email sent when a project is released ([#1475](https://github.com/ScilifelabDataCentre/dds_web/pull/1475)) - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) - Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) +- Improved on email layout when project release ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) From 724322dad09ed48a050e370b806d8e86a170f4c8 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 9 Oct 2023 15:53:36 +0200 Subject: [PATCH 090/155] new functionality --- dds_web/api/project.py | 96 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 4b00c8042..6f0cf572b 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -188,6 +188,102 @@ def post(self): return {"message": return_message} + @auth.login_required(role=["Unit Admin", "Unit Personnel"]) + @logging_bind_request + @json_required + @handle_validation_errors + def patch(self): + """Partially update a the project status""" + # Get project ID, project and verify access + project_id = dds_web.utils.get_required_item(obj=flask.request.args, req="project") + project = dds_web.utils.collect_project(project_id=project_id) + dds_web.utils.verify_project_access(project=project) + + # Cannot change project status if project is busy + if project.busy: + raise ProjectBusyError( + message=( + f"The status for the project '{project_id}' is already in the process of being changed. " + "Please try again later. \n\nIf you know the project is not busy, contact support." + ) + ) + + self.set_busy(project=project, busy=True) + + try: + # get atributes + json_input = flask.request.get_json(silent=True) # Already checked by json_required + extend_deadline = json_input.get("extend_deadline", False) # False by default + + # some variable definition + curr_date = dds_web.utils.current_time() + send_email = False + + if extend_deadline: + # deadline can only be extended from Available + if not project.current_status == "Available": + raise DDSArgumentError("Can only extend deadline if the project is available") + + new_deadline_in = json_input.get("new_deadline_in") + if not new_deadline_in: + raise DDSArgumentError( + message="No new deadline provived, cannot perform operation." + ) + if new_deadline_in > 90: + raise DDSArgumentError( + message="The deadline needs to be less than (or equal to) 90 days." + ) + + if project.times_expired > 2: + raise DDSArgumentError( + "Project availability limit: Project cannot have an extended deadline any more times" + ) + + try: + # add a fake archived status to mimick a re-release in order to have an udpated deadline + new_status_row = self.expire_project( + project=project, + current_time=curr_date, + deadline_in=project.responsible_unit.days_in_expired, + ) + project.project_statuses.append(new_status_row) + db.session.commit() + + new_status_row = self.release_project( + project=project, current_time=curr_date, deadline_in=new_deadline_in + ) + project.project_statuses.append(new_status_row) + + project.busy = False # return to not busy + db.session.commit() + + except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: + flask.current_app.logger.exception(err) + db.session.rollback() + raise DatabaseError( + message=str(err), + alt_message=( + "Status was not updated" + + ( + ": Database malfunction." + if isinstance(err, sqlalchemy.exc.OperationalError) + else ": Server Error." + ) + ), + ) from err + + return_message = f"{project.public_id} has been given a new deadline" + + return_message += ( + f". An e-mail notification has{' not ' if not send_email else ' '}been sent." + ) + + except: + self.set_busy(project=project, busy=False) + raise + + return {"message": return_message} + @staticmethod @dbsession def set_busy(project: models.Project, busy: bool) -> None: From ef340d2d10c2944282a0a09ea591abcf4dff694b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 9 Oct 2023 16:24:26 +0200 Subject: [PATCH 091/155] started tests --- dds_web/api/project.py | 14 +++++++++--- tests/api/test_project.py | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 6f0cf572b..015a3d8a5 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -225,13 +225,19 @@ def patch(self): raise DDSArgumentError("Can only extend deadline if the project is available") new_deadline_in = json_input.get("new_deadline_in") + current_deadline = (project.current_deadline - curr_date).days + if not new_deadline_in: raise DDSArgumentError( message="No new deadline provived, cannot perform operation." ) - if new_deadline_in > 90: + if current_deadline > 10: + raise DDSArgumentError( + message=f"There are still {current_deadline} days left, it is not possible to extend deadline yet" + ) + if new_deadline_in + current_deadline > 90: raise DDSArgumentError( - message="The deadline needs to be less than (or equal to) 90 days." + message="The new deadline needs to be less than (or equal to) 90 days." ) if project.times_expired > 2: @@ -250,7 +256,9 @@ def patch(self): db.session.commit() new_status_row = self.release_project( - project=project, current_time=curr_date, deadline_in=new_deadline_in + project=project, + current_time=curr_date, + deadline_in=new_deadline_in + current_deadline, ) project.project_statuses.append(new_status_row) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 867da5ef3..bff12417d 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1079,6 +1079,53 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session assert "The deadline needs to be less than (or equal to) 30 days." in response.json["message"] +def test_extend_deadline(module_client, boto3_session): + """Extend a project deadline of a project already release""" + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) + + # Release project with a small deadline + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available", "deadline": 5}, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + deadline = project.current_deadline + + time.sleep(1) # tests are too fast + + # extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"extend_deadline": True, "new_deadline_in": 20}, + ) + assert response.status_code == http.HTTPStatus.OK + assert project.times_expired == 1 + assert not project.current_deadline == deadline + + assert f"{project_id} has been given a new deadline" in response.json["message"] + assert "An e-mail notification has not been sent." in response.json["message"] + + def test_projectstatus_post_deletion_and_archivation_errors(module_client, boto3_session): """Mock the different expections that can occur when deleting project.""" current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() From ffa340bc6ea95889b34413a0fbbf72ced38d7d51 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 11:49:05 +0200 Subject: [PATCH 092/155] status code --- dds_web/api/project.py | 20 ++++++++++++++++---- doc/status_codes_api.md | 5 +++++ tests/api/test_project.py | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 015a3d8a5..fa6cb0ee8 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -199,6 +199,17 @@ def patch(self): project = dds_web.utils.collect_project(project_id=project_id) dds_web.utils.verify_project_access(project=project) + # get atributes + json_input = flask.request.get_json(silent=True) # Already checked by json_required + + # false by default - operation must be confirmed by the user + confirmed_operation = json_input.get("confirmed", False) + if not isinstance(confirmed_operation, bool): + raise DDSArgumentError(message="`confirmed` is a boolean value: True or False.") + if not confirmed_operation: + warning_message = "Operation must be confirmed before proceding" + return {"warning": warning_message} + # Cannot change project status if project is busy if project.busy: raise ProjectBusyError( @@ -211,18 +222,19 @@ def patch(self): self.set_busy(project=project, busy=True) try: - # get atributes - json_input = flask.request.get_json(silent=True) # Already checked by json_required extend_deadline = json_input.get("extend_deadline", False) # False by default # some variable definition curr_date = dds_web.utils.current_time() send_email = False + # Update the deadline if extend_deadline: # deadline can only be extended from Available if not project.current_status == "Available": - raise DDSArgumentError("Can only extend deadline if the project is available") + raise DDSArgumentError( + "you can only extend the deadline for a project that has the status Available" + ) new_deadline_in = json_input.get("new_deadline_in") current_deadline = (project.current_deadline - curr_date).days @@ -242,7 +254,7 @@ def patch(self): if project.times_expired > 2: raise DDSArgumentError( - "Project availability limit: Project cannot have an extended deadline any more times" + "The project has already been available for download 3 times. cannot extend the deadline again" ) try: diff --git a/doc/status_codes_api.md b/doc/status_codes_api.md index 2bd2bc1a1..58aa04f35 100644 --- a/doc/status_codes_api.md +++ b/doc/status_codes_api.md @@ -237,6 +237,11 @@ - `archive_project` - Database or S3 issues +#### `patch` + +- [Authentication errors](#authentication) + + ### GetPublic - [Authentication errors](#authentication) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index bff12417d..53a5a09ad 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1116,7 +1116,7 @@ def test_extend_deadline(module_client, boto3_session): tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"extend_deadline": True, "new_deadline_in": 20}, + json={"extend_deadline": True, "new_deadline_in": 20, "confirmed": True}, ) assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 1 From 096ba14b26f325e49798c9f5faa45b58c65610c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 11:49:28 +0200 Subject: [PATCH 093/155] Update SPRINTLOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- SPRINTLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 32341599a..8001f5ca5 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -313,4 +313,4 @@ _Nothing merged in CLI during this sprint_ - Project title displayed along with the internal project ID email sent when a project is released ([#1475](https://github.com/ScilifelabDataCentre/dds_web/pull/1475)) - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) - Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) -- Improved on email layout when project release ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) +- Improved email layout; Highlighted information and commands when project is released ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) From 44bdf81db10b5d53cf48be10bb28dcd28a9a74e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 11:49:46 +0200 Subject: [PATCH 094/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index c5ed54bf3..1c3e50346 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -31,7 +31,7 @@

    {% if unit_email %} - if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at ({{unit_email}}). + if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}. {% else %} if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. {% endif %} From 492b790afdc0815f7ae974f3f46a2c72c8de0f66 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 12:04:27 +0200 Subject: [PATCH 095/155] added feedback --- dds_web/templates/mail/mail_base.html | 2 +- dds_web/templates/mail/project_release.html | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dds_web/templates/mail/mail_base.html b/dds_web/templates/mail/mail_base.html index 5fbafcd8a..a9531bcb7 100644 --- a/dds_web/templates/mail/mail_base.html +++ b/dds_web/templates/mail/mail_base.html @@ -139,7 +139,7 @@ border-color: #a00202 !important; } } - mark { + code { background-color: pink; color: black; } diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 1c3e50346..da99d68e9 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -22,10 +22,11 @@

    To list the files in this project, run:
    - dds ls -p {{project_id}}.

    + dds ls -p {{project_id}}.

    +

    To download all the files in this project to your current directory, run:
    - dds data get -p {{project_id}} -a --verify-checksum.

    + dds data get -p {{project_id}} -a --verify-checksum.

    For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.

    @@ -37,7 +38,7 @@ {% endif %}

    - Your access to this project will expire on: {{deadline}}

    + Your access to this project will expire on:
    {{deadline}}

    What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way.

    From 700f8c1ca82e73148f1548349ba0b81f28e3d21e Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 12:07:05 +0200 Subject: [PATCH 096/155] feedback --- dds_web/templates/mail/project_release.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt index e8d88dda9..1813881e1 100644 --- a/dds_web/templates/mail/project_release.txt +++ b/dds_web/templates/mail/project_release.txt @@ -13,7 +13,7 @@ To download all the files in this project to your current directory, run: For more information (including an installation guide), see the DDS CLI documentation here: https://scilifelabdatacentre.github.io/dds_cli/ {% if unit_email %} -if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}at ({{unit_email}}). +if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at ({{unit_email}}). {% else %} if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. {% endif %} From 5df28d6d0400f4e2c77192466942d70c0b3436d5 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 14:05:59 +0200 Subject: [PATCH 097/155] finalized tests --- dds_web/api/project.py | 10 +- doc/status_codes_api.md | 22 ++- tests/api/test_project.py | 328 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 339 insertions(+), 21 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index fa6cb0ee8..a0e24e17f 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -207,14 +207,14 @@ def patch(self): if not isinstance(confirmed_operation, bool): raise DDSArgumentError(message="`confirmed` is a boolean value: True or False.") if not confirmed_operation: - warning_message = "Operation must be confirmed before proceding" + warning_message = "Operation must be confirmed before proceding." return {"warning": warning_message} # Cannot change project status if project is busy if project.busy: raise ProjectBusyError( message=( - f"The status for the project '{project_id}' is already in the process of being changed. " + f"The deadline for the project '{project_id}' is already in the process of being changed. " "Please try again later. \n\nIf you know the project is not busy, contact support." ) ) @@ -233,7 +233,7 @@ def patch(self): # deadline can only be extended from Available if not project.current_status == "Available": raise DDSArgumentError( - "you can only extend the deadline for a project that has the status Available" + "you can only extend the deadline for a project that has the status Available." ) new_deadline_in = json_input.get("new_deadline_in") @@ -245,7 +245,7 @@ def patch(self): ) if current_deadline > 10: raise DDSArgumentError( - message=f"There are still {current_deadline} days left, it is not possible to extend deadline yet" + message=f"There are still {current_deadline} days left, it is not possible to extend deadline yet." ) if new_deadline_in + current_deadline > 90: raise DDSArgumentError( @@ -254,7 +254,7 @@ def patch(self): if project.times_expired > 2: raise DDSArgumentError( - "The project has already been available for download 3 times. cannot extend the deadline again" + "Project availability limit: Project cannot be made Available any more times." ) try: diff --git a/doc/status_codes_api.md b/doc/status_codes_api.md index 58aa04f35..84fb085c0 100644 --- a/doc/status_codes_api.md +++ b/doc/status_codes_api.md @@ -240,7 +240,27 @@ #### `patch` - [Authentication errors](#authentication) - +- `400 Bad Request` + - Decorators + - Json required but not provided + - Validation error + - Schemas + - Project does not exist + - Project is busy + - `extend_deadline` + - Invalid deadline + - No deadline provided + - Project is not in Available state + - Max number of times available reached +- `403 Forbidden` + - Schemas + - User does not have access to project +- `500 Internal Server Error` + - Database errors + - `delete_project` + - Database or S3 issues + - `archive_project` + - Database or S3 issues ### GetPublic diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 53a5a09ad..1eeb787f9 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -21,6 +21,7 @@ import tests from tests.test_files_new import project_row, file_in_db, FIRST_NEW_FILE from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins +from tests.api.test_user import get_existing_projects from dds_web.database import models from dds_web.api.project import UserProjects @@ -44,6 +45,15 @@ # "date_updated", ] +release_project_small_deadline = {"new_status": "Available", "deadline": 5} + +extend_deadline_data_no_confirmed = { + "extend_deadline": True, + "new_deadline_in": 20, +} + +extend_deadline_data = {**extend_deadline_data_no_confirmed, "confirmed": True} + @pytest.fixture(scope="module") def test_project(module_client): @@ -1079,36 +1089,290 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session assert "The deadline needs to be less than (or equal to) 30 days." in response.json["message"] -def test_extend_deadline(module_client, boto3_session): - """Extend a project deadline of a project already release""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 +def test_extend_deadline_bad_confirmed(module_client, boto3_session): + """Try to extend a deadline and send a not boolean for confirmation""" + username = "researchuser" + # Get user + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project + project = user.projects[0] + assert project + project_id = project.public_id + + # Release project response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available"}, ) assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + json_data = {**extend_deadline_data, "confirmed": "true"} + # extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=json_data, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "`confirmed` is a boolean value: True or False." in response.json["message"] + + +def test_extend_deadline_no_confirmed(module_client, boto3_session): + """Try to extend a deadline before confirmation should sent a warning and no operation perfrom""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + # extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=extend_deadline_data_no_confirmed, + ) + # status code is ok but no operation perfrom + assert response.status_code == http.HTTPStatus.OK + assert project.times_expired == 0 + + assert "Operation must be confirmed before proceding." in response.json["warning"] + + +def test_extend_deadline_when_busy(module_client, boto3_session): + """Request should not be possible when project is busy.""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + # set to busy + project.busy = True + db.session.commit() + assert project.busy + + time.sleep(1) # tests are too fast + + # attempt to extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=extend_deadline_data, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + assert ( + f"The deadline for the project '{project_id}' is already in the process of being changed. " + in response.json["message"] + ) + assert ( + "Please try again later. \n\nIf you know the project is not busy, contact support." + in response.json["message"] + ) + + +def test_extend_deadline_project_not_available(module_client, boto3_session): + """Is not possible to extend deadline to project in other status than available.""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # attempt to extend deadline - project is in progress + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=extend_deadline_data, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + + assert ( + "you can only extend the deadline for a project that has the status Available." + in response.json["message"] + ) + + +def test_extend_deadline_no_deadline(module_client, boto3_session): + """If no deadline has been provided it should fail""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available"}, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + # try to extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"extend_deadline": True, "confirmed": True}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "No new deadline provived, cannot perform operation." in response.json["message"] + + +def test_extend_deadline_not_enough_time_left(module_client, boto3_session): + """If there are more than 10 days left, extend deadline should not be possible""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project with a big deadline + current_deadline = 30 + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"new_status": "Available", "deadline": current_deadline}, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + # try to extend upon such deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"extend_deadline": True, "new_deadline_in": 20, "confirmed": True}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert ( + f"There are still {current_deadline} days left, it is not possible to extend deadline yet." + in response.json["message"] + ) + + +def test_extend_deadline_too_much_days(module_client, boto3_session): + """If the new deadline together with the time left already is more than 90 days it should not work""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project with a small deadline + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=release_project_small_deadline, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + # try to extend deadline a lot of days + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"extend_deadline": True, "new_deadline_in": 90, "confirmed": True}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert ( + "The new deadline needs to be less than (or equal to) 90 days." in response.json["message"] + ) + + +def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): + """If the deadline has been extended more than 2 times it should not work""" + + project, _ = get_existing_projects() + project_id = project.public_id # Release project with a small deadline response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"new_status": "Available", "deadline": 5}, + json=release_project_small_deadline, ) assert response.status_code == http.HTTPStatus.OK # hasnt been expired or extended deadline yet assert project.times_expired == 0 deadline = project.current_deadline + new_deadline_in = 1 + + for i in range(1, 4): + time.sleep(1) # tests are too fast + + # extend deadline with a small new deadline so we can do it several times + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={**extend_deadline_data, "new_deadline_in": new_deadline_in}, + ) + if i < 3: + assert response.status_code == http.HTTPStatus.OK + assert project.times_expired == i + assert project.current_deadline == deadline + datetime.timedelta(days=new_deadline_in) + deadline = project.current_deadline + assert f"{project_id} has been given a new deadline" in response.json["message"] + assert "An e-mail notification has not been sent." in response.json["message"] + else: + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert ( + "Project availability limit: Project cannot be made Available any more times" + in response.json["message"] + ) + + +def test_extend_deadline_ok(module_client, boto3_session): + """Extend a project deadline of a project already release""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project with a small deadline + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=release_project_small_deadline, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + deadline = project.current_deadline time.sleep(1) # tests are too fast # extend deadline @@ -1116,16 +1380,50 @@ def test_extend_deadline(module_client, boto3_session): tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"extend_deadline": True, "new_deadline_in": 20, "confirmed": True}, + json=extend_deadline_data, ) assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 1 - assert not project.current_deadline == deadline + assert project.current_deadline == deadline + datetime.timedelta( + days=extend_deadline_data.get("new_deadline_in") + ) assert f"{project_id} has been given a new deadline" in response.json["message"] assert "An e-mail notification has not been sent." in response.json["message"] +def test_extend_deadline_mock_database_error(module_client, boto3_session): + """Mock error when updating the database""" + + project, _ = get_existing_projects() + project_id = project.public_id + + # Release project with a small deadline + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=release_project_small_deadline, + ) + assert response.status_code == http.HTTPStatus.OK + # hasnt been expired or extended deadline yet + assert project.times_expired == 0 + + time.sleep(1) # tests are too fast + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client) + with unittest.mock.patch("dds_web.db.session.commit", mock_sqlalchemyerror): + # extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=token, + query_string={"project": project_id}, + json=extend_deadline_data, + ) + assert response.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR + assert "Saving database changes failed." in response.json["message"] + + def test_projectstatus_post_deletion_and_archivation_errors(module_client, boto3_session): """Mock the different expections that can occur when deleting project.""" current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() From a4c0885d1981f0b68e8e03aede049c0d0555fd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:07:11 +0200 Subject: [PATCH 098/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index da99d68e9..588b4732b 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -18,7 +18,7 @@

    - You were added to this project on behalf of {{displayed_sender}}. + You were added to this project {% if unit_email %} on behalf of {% else %} by {% endif %} {{displayed_sender}}.

    To list the files in this project, run:
    From 138fd01857faace723ca9bb6831ca0ac2736547f Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 14:13:25 +0200 Subject: [PATCH 099/155] feedback --- dds_web/templates/mail/mail_base.html | 2 ++ dds_web/templates/mail/project_release.html | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dds_web/templates/mail/mail_base.html b/dds_web/templates/mail/mail_base.html index a9531bcb7..816e6fbf3 100644 --- a/dds_web/templates/mail/mail_base.html +++ b/dds_web/templates/mail/mail_base.html @@ -142,6 +142,8 @@ code { background-color: pink; color: black; + font-weight: normal; + font-style: normal; } diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 588b4732b..25dbfe448 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -22,11 +22,11 @@

    To list the files in this project, run:
    - dds ls -p {{project_id}}.

    + dds ls -p {{project_id}}


    To download all the files in this project to your current directory, run:
    - dds data get -p {{project_id}} -a --verify-checksum.

    + dds data get -p {{project_id}} -a --verify-checksum

    For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.

    From 33c3cbba78898f2f5cf6bf0f370775469ad6e093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:20:40 +0200 Subject: [PATCH 100/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 25dbfe448..af605b7ad 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -22,7 +22,7 @@

    To list the files in this project, run:
    - dds ls -p {{project_id}}

    + dds ls -p {{project_id}}


    To download all the files in this project to your current directory, run:
    From ddc25b744afd4cdf71ac08e74f9270ebfaf2dcdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:21:08 +0200 Subject: [PATCH 101/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index af605b7ad..307be8dd7 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -26,7 +26,7 @@

    To download all the files in this project to your current directory, run:
    - dds data get -p {{project_id}} -a --verify-checksum

    + dds data get -p {{project_id}} -a --verify-checksum

    For more information (including an installation guide), see the DDS CLI documentation here: scilifelabdatacentre.github.io/dds_cli.

    From fc6efa41dca224e9fe249cf229dbfc47b9233415 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 14:23:40 +0200 Subject: [PATCH 102/155] feedback --- dds_web/templates/mail/mail_base.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/dds_web/templates/mail/mail_base.html b/dds_web/templates/mail/mail_base.html index 816e6fbf3..a9531bcb7 100644 --- a/dds_web/templates/mail/mail_base.html +++ b/dds_web/templates/mail/mail_base.html @@ -142,8 +142,6 @@ code { background-color: pink; color: black; - font-weight: normal; - font-style: normal; } From 294d9b7e7097d178c514a673253bf069ac6d8ca2 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 14:26:45 +0200 Subject: [PATCH 103/155] fix test --- tests/api/test_project.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 1eeb787f9..7e140af7b 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1092,14 +1092,7 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session def test_extend_deadline_bad_confirmed(module_client, boto3_session): """Try to extend a deadline and send a not boolean for confirmation""" - username = "researchuser" - # Get user - user = models.User.query.filter_by(username=username).one_or_none() - assert user - - # Get project - project = user.projects[0] - assert project + project, _ = get_existing_projects() project_id = project.public_id # Release project @@ -1113,13 +1106,12 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): # hasnt been expired or extended deadline yet assert project.times_expired == 0 - json_data = {**extend_deadline_data, "confirmed": "true"} # extend deadline response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json=json_data, + json={**extend_deadline_data, "confirmed": "true"}, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "`confirmed` is a boolean value: True or False." in response.json["message"] From 739bbb483777c26c6d7e5a0fb2fa995f375bb758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:34:15 +0200 Subject: [PATCH 104/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 307be8dd7..701ad8b21 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -32,7 +32,7 @@

    {% if unit_email %} - if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}. + If you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}. {% else %} if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. {% endif %} From 3db8026bd734eff0ecbd5f7ebb5b8d59eadbc2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:34:25 +0200 Subject: [PATCH 105/155] Update dds_web/templates/mail/project_release.html MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/templates/mail/project_release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/templates/mail/project_release.html b/dds_web/templates/mail/project_release.html index 701ad8b21..06d536664 100644 --- a/dds_web/templates/mail/project_release.html +++ b/dds_web/templates/mail/project_release.html @@ -34,7 +34,7 @@ {% if unit_email %} If you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}. {% else %} - if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. + If you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. {% endif %}

    From ca1fd4263504d8b1a4da38d79a6cdaaeccf0eacc Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 14:36:48 +0200 Subject: [PATCH 106/155] updated txt --- dds_web/templates/mail/project_release.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dds_web/templates/mail/project_release.txt b/dds_web/templates/mail/project_release.txt index 1813881e1..9b6dd9220 100644 --- a/dds_web/templates/mail/project_release.txt +++ b/dds_web/templates/mail/project_release.txt @@ -2,7 +2,7 @@ The following project is now available for your access in the SciLifeLab Data De - Project Title: {{project_title}} - DDS project ID: {{project_id}} -You were added to this project on behalf of {{displayed_sender}}. +You were added to this project {% if unit_email %} on behalf of {% else %} by {% endif %} {{displayed_sender}}. To list the files in this project, run: dds ls -p {{project_id}} @@ -12,11 +12,11 @@ To download all the files in this project to your current directory, run: For more information (including an installation guide), see the DDS CLI documentation here: https://scilifelabdatacentre.github.io/dds_cli/ -{% if unit_email %} -if you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at ({{unit_email}}). -{% else %} -if you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. -{% endif %} +{% if unit_email %} +If you experience issues, please contact the SciLifeLab unit {{displayed_sender}} at {{unit_email}}. +{% else %} +If you experience issues, please contact the SciLifeLab unit {{displayed_sender}}. +{% endif %} Your access to this project will expire on {{deadline}} From fdca870051595692ffb740af97fb50e01a23cf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 10 Oct 2023 15:00:33 +0200 Subject: [PATCH 107/155] Update test_project.py --- tests/api/test_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 89d5e1c2a..7ac6ce020 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1426,7 +1426,7 @@ def test_email_project_release(module_client, boto3_session): assert f"- DDS project ID: {public_project_id}" in body assert f"dds ls -p {public_project_id}" in body assert f"dds data get -p {public_project_id} -a --verify-checksum" in body - assert "if you experience issues, please contact the SciLifeLab unit" in body + assert "If you experience issues, please contact the SciLifeLab unit" in body assert ( "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way" in body @@ -1437,7 +1437,7 @@ def test_email_project_release(module_client, boto3_session): assert f"

  • DDS project ID: {public_project_id}
  • " in html assert f"dds ls -p {public_project_id}" in html assert f"dds data get -p {public_project_id} -a --verify-checksum" in html - assert "if you experience issues, please contact the SciLifeLab unit" in html + assert "If you experience issues, please contact the SciLifeLab unit" in html assert ( "What is the DDS? The DDS is a system for SciLifeLab infrastructures to deliver data to researchers in a fast, secure and simple way." in html From 9c3805a1c7fc1bee6ecf0615972f1c5b5f8bca5c Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 15:23:09 +0200 Subject: [PATCH 108/155] change fixture --- tests/api/test_project.py | 104 +++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 7e140af7b..a98892931 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1089,16 +1089,16 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session assert "The deadline needs to be less than (or equal to) 30 days." in response.json["message"] -def test_extend_deadline_bad_confirmed(module_client, boto3_session): +def test_extend_deadline_bad_confirmed(client, boto3_session): """Try to extend a deadline and send a not boolean for confirmation""" project, _ = get_existing_projects() project_id = project.public_id # Release project - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"new_status": "Available"}, ) @@ -1106,10 +1106,12 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast + # extend deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={**extend_deadline_data, "confirmed": "true"}, ) @@ -1117,16 +1119,16 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): assert "`confirmed` is a boolean value: True or False." in response.json["message"] -def test_extend_deadline_no_confirmed(module_client, boto3_session): +def test_extend_deadline_no_confirmed(client, boto3_session): """Try to extend a deadline before confirmation should sent a warning and no operation perfrom""" project, _ = get_existing_projects() project_id = project.public_id # Release project - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"new_status": "Available"}, ) @@ -1134,10 +1136,12 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast + # extend deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=extend_deadline_data_no_confirmed, ) @@ -1148,16 +1152,16 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): assert "Operation must be confirmed before proceding." in response.json["warning"] -def test_extend_deadline_when_busy(module_client, boto3_session): +def test_extend_deadline_when_busy(client, boto3_session): """Request should not be possible when project is busy.""" project, _ = get_existing_projects() project_id = project.public_id # Release project - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"new_status": "Available"}, ) @@ -1173,9 +1177,9 @@ def test_extend_deadline_when_busy(module_client, boto3_session): time.sleep(1) # tests are too fast # attempt to extend deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=extend_deadline_data, ) @@ -1191,16 +1195,16 @@ def test_extend_deadline_when_busy(module_client, boto3_session): ) -def test_extend_deadline_project_not_available(module_client, boto3_session): +def test_extend_deadline_project_not_available(client, boto3_session): """Is not possible to extend deadline to project in other status than available.""" project, _ = get_existing_projects() project_id = project.public_id # attempt to extend deadline - project is in progress - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=extend_deadline_data, ) @@ -1212,16 +1216,16 @@ def test_extend_deadline_project_not_available(module_client, boto3_session): ) -def test_extend_deadline_no_deadline(module_client, boto3_session): +def test_extend_deadline_no_deadline(client, boto3_session): """If no deadline has been provided it should fail""" project, _ = get_existing_projects() project_id = project.public_id # Release project - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"new_status": "Available"}, ) @@ -1229,10 +1233,12 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast + # try to extend deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"extend_deadline": True, "confirmed": True}, ) @@ -1240,7 +1246,7 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): assert "No new deadline provived, cannot perform operation." in response.json["message"] -def test_extend_deadline_not_enough_time_left(module_client, boto3_session): +def test_extend_deadline_not_enough_time_left(client, boto3_session): """If there are more than 10 days left, extend deadline should not be possible""" project, _ = get_existing_projects() @@ -1248,9 +1254,9 @@ def test_extend_deadline_not_enough_time_left(module_client, boto3_session): # Release project with a big deadline current_deadline = 30 - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"new_status": "Available", "deadline": current_deadline}, ) @@ -1258,10 +1264,12 @@ def test_extend_deadline_not_enough_time_left(module_client, boto3_session): # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast + # try to extend upon such deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={"extend_deadline": True, "new_deadline_in": 20, "confirmed": True}, ) @@ -1272,16 +1280,16 @@ def test_extend_deadline_not_enough_time_left(module_client, boto3_session): ) -def test_extend_deadline_too_much_days(module_client, boto3_session): +def test_extend_deadline_too_much_days(client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" project, _ = get_existing_projects() project_id = project.public_id # Release project with a small deadline - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=release_project_small_deadline, ) @@ -1289,8 +1297,10 @@ def test_extend_deadline_too_much_days(module_client, boto3_session): # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast + # try to extend deadline a lot of days - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, @@ -1302,16 +1312,16 @@ def test_extend_deadline_too_much_days(module_client, boto3_session): ) -def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): +def test_extend_deadline_maxium_number_available_exceded(client, boto3_session): """If the deadline has been extended more than 2 times it should not work""" project, _ = get_existing_projects() project_id = project.public_id # Release project with a small deadline - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=release_project_small_deadline, ) @@ -1326,9 +1336,9 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se time.sleep(1) # tests are too fast # extend deadline with a small new deadline so we can do it several times - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json={**extend_deadline_data, "new_deadline_in": new_deadline_in}, ) @@ -1347,16 +1357,16 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se ) -def test_extend_deadline_ok(module_client, boto3_session): +def test_extend_deadline_ok(client, boto3_session): """Extend a project deadline of a project already release""" project, _ = get_existing_projects() project_id = project.public_id # Release project with a small deadline - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=release_project_small_deadline, ) @@ -1368,9 +1378,9 @@ def test_extend_deadline_ok(module_client, boto3_session): time.sleep(1) # tests are too fast # extend deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=extend_deadline_data, ) @@ -1384,16 +1394,16 @@ def test_extend_deadline_ok(module_client, boto3_session): assert "An e-mail notification has not been sent." in response.json["message"] -def test_extend_deadline_mock_database_error(module_client, boto3_session): +def test_extend_deadline_mock_database_error(client, boto3_session): """Mock error when updating the database""" project, _ = get_existing_projects() project_id = project.public_id # Release project with a small deadline - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=release_project_small_deadline, ) @@ -1403,10 +1413,10 @@ def test_extend_deadline_mock_database_error(module_client, boto3_session): time.sleep(1) # tests are too fast - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client) + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) with unittest.mock.patch("dds_web.db.session.commit", mock_sqlalchemyerror): # extend deadline - response = module_client.patch( + response = client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=token, query_string={"project": project_id}, From 996cc0813b73d8844b5bc52f34fa46471b400909 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 10 Oct 2023 16:19:31 +0200 Subject: [PATCH 109/155] go back to first approach of test --- tests/api/test_project.py | 324 +++++++++++++++++++++++++++----------- 1 file changed, 233 insertions(+), 91 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index a98892931..076a78abe 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -55,6 +55,39 @@ extend_deadline_data = {**extend_deadline_data_no_confirmed, "confirmed": True} +# HELPER FUNCTIONS ################################################################################## CONFIG # + + +def create_and_release_project(module_client, proj_data, release_data): + """Helper function that creates a project and set it ups as available""" + + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) + + # Release project + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=release_data, + ) + assert response.status_code == http.HTTPStatus.OK + + return project_id, project + + @pytest.fixture(scope="module") def test_project(module_client): """Create a shared test project""" @@ -1089,29 +1122,40 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session assert "The deadline needs to be less than (or equal to) 30 days." in response.json["message"] -def test_extend_deadline_bad_confirmed(client, boto3_session): +def test_extend_deadline_bad_confirmed(module_client, boto3_session): """Try to extend a deadline and send a not boolean for confirmation""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project - response = client.post( + release_data = {"new_status": "Available"} + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"new_status": "Available"}, + json=release_data, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - time.sleep(1) # tests are too fast # extend deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json={**extend_deadline_data, "confirmed": "true"}, ) @@ -1119,29 +1163,40 @@ def test_extend_deadline_bad_confirmed(client, boto3_session): assert "`confirmed` is a boolean value: True or False." in response.json["message"] -def test_extend_deadline_no_confirmed(client, boto3_session): +def test_extend_deadline_no_confirmed(module_client, boto3_session): """Try to extend a deadline before confirmation should sent a warning and no operation perfrom""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project - response = client.post( + release_data = {"new_status": "Available"} + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"new_status": "Available"}, + json=release_data, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - time.sleep(1) # tests are too fast # extend deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=extend_deadline_data_no_confirmed, ) @@ -1152,22 +1207,35 @@ def test_extend_deadline_no_confirmed(client, boto3_session): assert "Operation must be confirmed before proceding." in response.json["warning"] -def test_extend_deadline_when_busy(client, boto3_session): +def test_extend_deadline_when_busy(module_client, boto3_session): """Request should not be possible when project is busy.""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project - response = client.post( + release_data = {"new_status": "Available"} + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"new_status": "Available"}, + json=release_data, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast # set to busy project.busy = True @@ -1177,9 +1245,9 @@ def test_extend_deadline_when_busy(client, boto3_session): time.sleep(1) # tests are too fast # attempt to extend deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=extend_deadline_data, ) @@ -1195,16 +1263,27 @@ def test_extend_deadline_when_busy(client, boto3_session): ) -def test_extend_deadline_project_not_available(client, boto3_session): +def test_extend_deadline_project_not_available(module_client, boto3_session): """Is not possible to extend deadline to project in other status than available.""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") # attempt to extend deadline - project is in progress - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=extend_deadline_data, ) @@ -1216,29 +1295,40 @@ def test_extend_deadline_project_not_available(client, boto3_session): ) -def test_extend_deadline_no_deadline(client, boto3_session): +def test_extend_deadline_no_deadline(module_client, boto3_session): """If no deadline has been provided it should fail""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project - response = client.post( + release_data = {"new_status": "Available"} + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"new_status": "Available"}, + json=release_data, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - time.sleep(1) # tests are too fast # try to extend deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json={"extend_deadline": True, "confirmed": True}, ) @@ -1246,30 +1336,41 @@ def test_extend_deadline_no_deadline(client, boto3_session): assert "No new deadline provived, cannot perform operation." in response.json["message"] -def test_extend_deadline_not_enough_time_left(client, boto3_session): +def test_extend_deadline_not_enough_time_left(module_client, boto3_session): """If there are more than 10 days left, extend deadline should not be possible""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) - # Release project with a big deadline + # Release project current_deadline = 30 - response = client.post( + release_data = {"new_status": "Available", "deadline": current_deadline} + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"new_status": "Available", "deadline": current_deadline}, + json=release_data, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - time.sleep(1) # tests are too fast # try to extend upon such deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json={"extend_deadline": True, "new_deadline_in": 20, "confirmed": True}, ) @@ -1280,27 +1381,37 @@ def test_extend_deadline_not_enough_time_left(client, boto3_session): ) -def test_extend_deadline_too_much_days(client, boto3_session): +def test_extend_deadline_too_much_days(module_client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project with a small deadline - response = client.post( + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=release_project_small_deadline, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - time.sleep(1) # tests are too fast # try to extend deadline a lot of days - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, @@ -1312,33 +1423,44 @@ def test_extend_deadline_too_much_days(client, boto3_session): ) -def test_extend_deadline_maxium_number_available_exceded(client, boto3_session): +def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): """If the deadline has been extended more than 2 times it should not work""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project with a small deadline - response = client.post( + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=release_project_small_deadline, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 + time.sleep(1) # tests are too fast deadline = project.current_deadline new_deadline_in = 1 - for i in range(1, 4): time.sleep(1) # tests are too fast # extend deadline with a small new deadline so we can do it several times - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json={**extend_deadline_data, "new_deadline_in": new_deadline_in}, ) @@ -1357,30 +1479,40 @@ def test_extend_deadline_maxium_number_available_exceded(client, boto3_session): ) -def test_extend_deadline_ok(client, boto3_session): +def test_extend_deadline_ok(module_client, boto3_session): """Extend a project deadline of a project already release""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project with a small deadline - response = client.post( + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=release_project_small_deadline, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - - deadline = project.current_deadline time.sleep(1) # tests are too fast + deadline = project.current_deadline # extend deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=extend_deadline_data, ) @@ -1394,29 +1526,39 @@ def test_extend_deadline_ok(client, boto3_session): assert "An e-mail notification has not been sent." in response.json["message"] -def test_extend_deadline_mock_database_error(client, boto3_session): +def test_extend_deadline_mock_database_error(module_client, boto3_session): """Mock error when updating the database""" - project, _ = get_existing_projects() - project_id = project.public_id + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + if current_unit_admins < 3: + create_unit_admins(num_admins=2) + current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() + assert current_unit_admins >= 3 + + response = module_client.post( + tests.DDSEndpoint.PROJECT_CREATE, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + json=proj_data, + ) + assert response.status_code == http.HTTPStatus.OK + project_id = response.json.get("project_id") + project = project_row(project_id=project_id) # Release project with a small deadline - response = client.post( + response = module_client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=release_project_small_deadline, ) assert response.status_code == http.HTTPStatus.OK - # hasnt been expired or extended deadline yet assert project.times_expired == 0 - time.sleep(1) # tests are too fast - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client) with unittest.mock.patch("dds_web.db.session.commit", mock_sqlalchemyerror): # extend deadline - response = client.patch( + response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=token, query_string={"project": project_id}, From c410d464dfcd6cdf4f8a1c7f6f0bc1f252cfd38f Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 11 Oct 2023 11:01:35 +0200 Subject: [PATCH 110/155] improved --- tests/api/test_project.py | 242 +++++--------------------------------- 1 file changed, 29 insertions(+), 213 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 076a78abe..e2d96efd0 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -21,7 +21,6 @@ import tests from tests.test_files_new import project_row, file_in_db, FIRST_NEW_FILE from tests.test_project_creation import proj_data_with_existing_users, create_unit_admins -from tests.api.test_user import get_existing_projects from dds_web.database import models from dds_web.api.project import UserProjects @@ -58,7 +57,7 @@ # HELPER FUNCTIONS ################################################################################## CONFIG # -def create_and_release_project(module_client, proj_data, release_data): +def create_and_release_project(client, proj_data, release_data): """Helper function that creates a project and set it ups as available""" current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() @@ -67,9 +66,9 @@ def create_and_release_project(module_client, proj_data, release_data): current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() assert current_unit_admins >= 3 - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client), json=proj_data, ) assert response.status_code == http.HTTPStatus.OK @@ -77,9 +76,9 @@ def create_and_release_project(module_client, proj_data, release_data): project = project_row(project_id=project_id) # Release project - response = module_client.post( + response = client.post( tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), query_string={"project": project_id}, json=release_data, ) @@ -1125,30 +1124,9 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session def test_extend_deadline_bad_confirmed(module_client, boto3_session): """Try to extend a deadline and send a not boolean for confirmation""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project - release_data = {"new_status": "Available"} - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_data, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1166,30 +1144,9 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): def test_extend_deadline_no_confirmed(module_client, boto3_session): """Try to extend a deadline before confirmation should sent a warning and no operation perfrom""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project - release_data = {"new_status": "Available"} - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_data, - ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1210,30 +1167,9 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): def test_extend_deadline_when_busy(module_client, boto3_session): """Request should not be possible when project is busy.""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project - release_data = {"new_status": "Available"} - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_data, - ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1242,8 +1178,6 @@ def test_extend_deadline_when_busy(module_client, boto3_session): db.session.commit() assert project.busy - time.sleep(1) # tests are too fast - # attempt to extend deadline response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, @@ -1266,6 +1200,7 @@ def test_extend_deadline_when_busy(module_client, boto3_session): def test_extend_deadline_project_not_available(module_client, boto3_session): """Is not possible to extend deadline to project in other status than available.""" + # create a new project and never release it current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() if current_unit_admins < 3: create_unit_admins(num_admins=2) @@ -1298,30 +1233,9 @@ def test_extend_deadline_project_not_available(module_client, boto3_session): def test_extend_deadline_no_deadline(module_client, boto3_session): """If no deadline has been provided it should fail""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project - release_data = {"new_status": "Available"} - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_data, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1339,31 +1253,11 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): def test_extend_deadline_not_enough_time_left(module_client, boto3_session): """If there are more than 10 days left, extend deadline should not be possible""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, + current_deadline = 90 + release_data_long_deadline = {"new_status": "Available", "deadline": current_deadline} + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data=release_data_long_deadline ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project - current_deadline = 30 - release_data = {"new_status": "Available", "deadline": current_deadline} - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_data, - ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1384,29 +1278,9 @@ def test_extend_deadline_not_enough_time_left(module_client, boto3_session): def test_extend_deadline_too_much_days(module_client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project with a small deadline - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_project_small_deadline, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1426,34 +1300,14 @@ def test_extend_deadline_too_much_days(module_client, boto3_session): def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): """If the deadline has been extended more than 2 times it should not work""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project with a small deadline - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_project_small_deadline, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast deadline = project.current_deadline - new_deadline_in = 1 + new_deadline_in = 1 # small new deadline for i in range(1, 4): time.sleep(1) # tests are too fast @@ -1482,33 +1336,15 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se def test_extend_deadline_ok(module_client, boto3_session): """Extend a project deadline of a project already release""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, + # release with a small deadline + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project with a small deadline - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_project_small_deadline, - ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast deadline = project.current_deadline + # extend deadline response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, @@ -1529,29 +1365,9 @@ def test_extend_deadline_ok(module_client, boto3_session): def test_extend_deadline_mock_database_error(module_client, boto3_session): """Mock error when updating the database""" - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - if current_unit_admins < 3: - create_unit_admins(num_admins=2) - current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() - assert current_unit_admins >= 3 - - response = module_client.post( - tests.DDSEndpoint.PROJECT_CREATE, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client), - json=proj_data, - ) - assert response.status_code == http.HTTPStatus.OK - project_id = response.json.get("project_id") - project = project_row(project_id=project_id) - - # Release project with a small deadline - response = module_client.post( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json=release_project_small_deadline, + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) - assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 time.sleep(1) # tests are too fast From 0ad3d5da9e59b09e2af5f08f68137cacd8903365 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Wed, 11 Oct 2023 14:25:55 +0200 Subject: [PATCH 111/155] better comments --- tests/api/test_project.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 5700e16ce..59146f695 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1130,7 +1130,7 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): assert project.times_expired == 0 time.sleep(1) # tests are too fast - # extend deadline + # try to extend deadline with a string as confirmed - should fail response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), @@ -1142,7 +1142,7 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): def test_extend_deadline_no_confirmed(module_client, boto3_session): - """Try to extend a deadline before confirmation should sent a warning and no operation perfrom""" + """Try to extend a deadline before confirmation - should sent a warning and no operation is perfrom""" project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} @@ -1150,14 +1150,14 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): assert project.times_expired == 0 time.sleep(1) # tests are too fast - # extend deadline + # try to extend deadline response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, json=extend_deadline_data_no_confirmed, ) - # status code is ok but no operation perfrom + # status code is ok but no operation perform assert response.status_code == http.HTTPStatus.OK assert project.times_expired == 0 @@ -1198,7 +1198,7 @@ def test_extend_deadline_when_busy(module_client, boto3_session): def test_extend_deadline_project_not_available(module_client, boto3_session): - """Is not possible to extend deadline to project in other status than available.""" + """Is not possible to extend deadline to a project in another status than available.""" # create a new project and never release it current_unit_admins = models.UnitUser.query.filter_by(unit_id=1, is_admin=True).count() @@ -1239,7 +1239,7 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): assert project.times_expired == 0 time.sleep(1) # tests are too fast - # try to extend deadline + # try to extend deadline - no new deadline provided response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), @@ -1251,8 +1251,9 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): def test_extend_deadline_not_enough_time_left(module_client, boto3_session): - """If there are more than 10 days left, extend deadline should not be possible""" + """If there are still too much days left, extend deadline should not be yet possible""" + # create and release a new project with a long time left as available current_deadline = 90 release_data_long_deadline = {"new_status": "Available", "deadline": current_deadline} project_id, project = create_and_release_project( @@ -1278,13 +1279,14 @@ def test_extend_deadline_not_enough_time_left(module_client, boto3_session): def test_extend_deadline_too_much_days(module_client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" + # create project with small deadline so it can be extended project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) assert project.times_expired == 0 time.sleep(1) # tests are too fast - # try to extend deadline a lot of days + # try to extend deadline by a lot of days response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), @@ -1300,18 +1302,18 @@ def test_extend_deadline_too_much_days(module_client, boto3_session): def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): """If the deadline has been extended more than 2 times it should not work""" + # create project with small deadline so it can be extended project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) assert project.times_expired == 0 - time.sleep(1) # tests are too fast - - deadline = project.current_deadline + deadline = project.current_deadline # current deadline new_deadline_in = 1 # small new deadline + for i in range(1, 4): time.sleep(1) # tests are too fast - # extend deadline with a small new deadline so we can do it several times + # extend deadline by a small new deadline so we can do it several times response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), @@ -1322,7 +1324,7 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se assert response.status_code == http.HTTPStatus.OK assert project.times_expired == i assert project.current_deadline == deadline + datetime.timedelta(days=new_deadline_in) - deadline = project.current_deadline + deadline = project.current_deadline # update current deadline assert f"{project_id} has been given a new deadline" in response.json["message"] assert "An e-mail notification has not been sent." in response.json["message"] else: @@ -1334,16 +1336,16 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se def test_extend_deadline_ok(module_client, boto3_session): - """Extend a project deadline of a project already release""" + """Extend a project deadline of a project - it should work ok""" - # release with a small deadline + # release with a small deadline so it can be extended project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) assert project.times_expired == 0 time.sleep(1) # tests are too fast - deadline = project.current_deadline + deadline = project.current_deadline # save current deadline # extend deadline response = module_client.patch( @@ -1363,7 +1365,7 @@ def test_extend_deadline_ok(module_client, boto3_session): def test_extend_deadline_mock_database_error(module_client, boto3_session): - """Mock error when updating the database""" + """Mock error when performing the request""" project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline From c7976cb12187b569a81ebd31f93d0658baca1904 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 12 Oct 2023 11:56:23 +0200 Subject: [PATCH 112/155] more coverage --- dds_web/api/project.py | 17 +---------------- tests/api/test_project.py | 8 +++++++- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index a0e24e17f..c6eea1d41 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -252,11 +252,6 @@ def patch(self): message="The new deadline needs to be less than (or equal to) 90 days." ) - if project.times_expired > 2: - raise DDSArgumentError( - "Project availability limit: Project cannot be made Available any more times." - ) - try: # add a fake archived status to mimick a re-release in order to have an udpated deadline new_status_row = self.expire_project( @@ -280,17 +275,7 @@ def patch(self): except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: flask.current_app.logger.exception(err) db.session.rollback() - raise DatabaseError( - message=str(err), - alt_message=( - "Status was not updated" - + ( - ": Database malfunction." - if isinstance(err, sqlalchemy.exc.OperationalError) - else ": Server Error." - ) - ), - ) from err + raise err return_message = f"{project.public_id} has been given a new deadline" diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 59146f695..ed79d7707 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -4,6 +4,7 @@ import http from sqlite3 import OperationalError import pytest +from _pytest.logging import LogCaptureFixture import datetime import time import unittest.mock @@ -1364,7 +1365,9 @@ def test_extend_deadline_ok(module_client, boto3_session): assert "An e-mail notification has not been sent." in response.json["message"] -def test_extend_deadline_mock_database_error(module_client, boto3_session): +def test_extend_deadline_mock_database_error( + module_client, boto3_session, capfd: LogCaptureFixture +): """Mock error when performing the request""" project_id, project = create_and_release_project( @@ -1385,6 +1388,9 @@ def test_extend_deadline_mock_database_error(module_client, boto3_session): assert response.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR assert "Saving database changes failed." in response.json["message"] + _, err = capfd.readouterr() + assert "500 Internal Server Error: Saving database changes failed." in err + def test_projectstatus_post_deletion_and_archivation_errors(module_client, boto3_session): """Mock the different expections that can occur when deleting project.""" From 19ec5e22ea07d8a84168be7ac5fcd8604bcf80e9 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 12 Oct 2023 14:34:25 +0200 Subject: [PATCH 113/155] coverage --- dds_web/api/project.py | 2 +- tests/api/test_project.py | 38 +++++++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index c6eea1d41..96856b988 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -273,7 +273,7 @@ def patch(self): db.session.commit() except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: - flask.current_app.logger.exception(err) + flask.current_app.logger.exception("Failed to extend deadline") db.session.rollback() raise err diff --git a/tests/api/test_project.py b/tests/api/test_project.py index ed79d7707..f4c8b3518 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -5,6 +5,7 @@ from sqlite3 import OperationalError import pytest from _pytest.logging import LogCaptureFixture +import logging import datetime import time import unittest.mock @@ -1368,7 +1369,7 @@ def test_extend_deadline_ok(module_client, boto3_session): def test_extend_deadline_mock_database_error( module_client, boto3_session, capfd: LogCaptureFixture ): - """Mock error when performing the request""" + """Operation fails when trying to save in the Database""" project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline @@ -1377,19 +1378,30 @@ def test_extend_deadline_mock_database_error( time.sleep(1) # tests are too fast token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client) - with unittest.mock.patch("dds_web.db.session.commit", mock_sqlalchemyerror): - # extend deadline - response = module_client.patch( - tests.DDSEndpoint.PROJECT_STATUS, - headers=token, - query_string={"project": project_id}, - json=extend_deadline_data, - ) - assert response.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR - assert "Saving database changes failed." in response.json["message"] - _, err = capfd.readouterr() - assert "500 Internal Server Error: Saving database changes failed." in err + with unittest.mock.patch.object(db.session, "rollback") as rollback: + with unittest.mock.patch("dds_web.db.session.commit") as mock_commit: + # we need this because the first time the commit function is called is when set_busy() + def side_effect_generator(): + yield None # First call, no exception + while True: + yield sqlalchemy.exc.SQLAlchemyError() # Subsequent calls, exception + + mock_commit.side_effect = side_effect_generator() + + # extend deadline + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=token, + query_string={"project": project_id}, + json=extend_deadline_data, + ) + assert response.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR + assert "Saving database changes failed." in response.json["message"] + + assert rollback.called + _, err = capfd.readouterr() + assert "Failed to extend deadline" in err def test_projectstatus_post_deletion_and_archivation_errors(module_client, boto3_session): From fa4c46c63ce14aff39e24d419bfed5d3f0011b27 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 13 Oct 2023 09:23:35 +0200 Subject: [PATCH 114/155] sprintlog --- SPRINTLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 8001f5ca5..b6c399177 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -314,3 +314,4 @@ _Nothing merged in CLI during this sprint_ - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) - Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) - Improved email layout; Highlighted information and commands when project is released ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) +- Added new API endpoint ProjectStatus.patch to extend the deadline ([#1480])(https://github.com/ScilifelabDataCentre/dds_web/pull/1480) From 261fbcfc5e48deb6ea64d9b923e6271e05e847e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:27:11 +0200 Subject: [PATCH 115/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 96856b988..c2a3af5af 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -233,7 +233,7 @@ def patch(self): # deadline can only be extended from Available if not project.current_status == "Available": raise DDSArgumentError( - "you can only extend the deadline for a project that has the status Available." + "You can only extend the deadline for a project that has the status 'Available'." ) new_deadline_in = json_input.get("new_deadline_in") From 0b531b57896fe4498649276d7bd1925e4a37a714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:27:22 +0200 Subject: [PATCH 116/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index c2a3af5af..6ed141edb 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -243,10 +243,6 @@ def patch(self): raise DDSArgumentError( message="No new deadline provived, cannot perform operation." ) - if current_deadline > 10: - raise DDSArgumentError( - message=f"There are still {current_deadline} days left, it is not possible to extend deadline yet." - ) if new_deadline_in + current_deadline > 90: raise DDSArgumentError( message="The new deadline needs to be less than (or equal to) 90 days." From 5cca377ce59a17a14736d60cf9536eeca7004520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:27:34 +0200 Subject: [PATCH 117/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 6ed141edb..196ceebff 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -271,7 +271,7 @@ def patch(self): except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: flask.current_app.logger.exception("Failed to extend deadline") db.session.rollback() - raise err + raise return_message = f"{project.public_id} has been given a new deadline" From a8efc2e19b966cf26cf8203ca0248f44ef3979d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:27:45 +0200 Subject: [PATCH 118/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 196ceebff..4b4c5e23c 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -199,7 +199,7 @@ def patch(self): project = dds_web.utils.collect_project(project_id=project_id) dds_web.utils.verify_project_access(project=project) - # get atributes + # Get json input from request json_input = flask.request.get_json(silent=True) # Already checked by json_required # false by default - operation must be confirmed by the user From 7cb1d43ef175ffe40608f28b09f2675afeceacc6 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 13 Oct 2023 09:28:37 +0200 Subject: [PATCH 119/155] modified test according to comments --- tests/api/test_project.py | 29 +---------------------------- 1 file changed, 1 insertion(+), 28 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index f4c8b3518..c87e8cc4e 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1227,7 +1227,7 @@ def test_extend_deadline_project_not_available(module_client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( - "you can only extend the deadline for a project that has the status Available." + "You can only extend the deadline for a project that has the status 'Available'." in response.json["message"] ) @@ -1251,33 +1251,6 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "No new deadline provived, cannot perform operation." in response.json["message"] - -def test_extend_deadline_not_enough_time_left(module_client, boto3_session): - """If there are still too much days left, extend deadline should not be yet possible""" - - # create and release a new project with a long time left as available - current_deadline = 90 - release_data_long_deadline = {"new_status": "Available", "deadline": current_deadline} - project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data=release_data_long_deadline - ) - assert project.times_expired == 0 - time.sleep(1) # tests are too fast - - # try to extend upon such deadline - response = module_client.patch( - tests.DDSEndpoint.PROJECT_STATUS, - headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), - query_string={"project": project_id}, - json={"extend_deadline": True, "new_deadline_in": 20, "confirmed": True}, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert ( - f"There are still {current_deadline} days left, it is not possible to extend deadline yet." - in response.json["message"] - ) - - def test_extend_deadline_too_much_days(module_client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" From a3939148d5f4f815c06aacbed59e54cca970b4bc Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 13 Oct 2023 09:28:56 +0200 Subject: [PATCH 120/155] modified test according to comments --- tests/api/test_project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index c87e8cc4e..d437dd592 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1251,6 +1251,7 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "No new deadline provived, cannot perform operation." in response.json["message"] + def test_extend_deadline_too_much_days(module_client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" From 125e6651b77f581fc539c51f296cc09d20aac39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:29:19 +0200 Subject: [PATCH 121/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 4b4c5e23c..89ddffd3a 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -202,7 +202,7 @@ def patch(self): # Get json input from request json_input = flask.request.get_json(silent=True) # Already checked by json_required - # false by default - operation must be confirmed by the user + # Operation must be confirmed by the user - False by default confirmed_operation = json_input.get("confirmed", False) if not isinstance(confirmed_operation, bool): raise DDSArgumentError(message="`confirmed` is a boolean value: True or False.") From 96e736826f8bfc153c71518e645357664c4e3133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:11:03 +0200 Subject: [PATCH 122/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 89ddffd3a..b7f06a443 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -221,6 +221,7 @@ def patch(self): self.set_busy(project=project, busy=True) + # Extend deadline try: extend_deadline = json_input.get("extend_deadline", False) # False by default From 55d40a9f4395aaf69128e8a30ff672b97fe6b79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:11:39 +0200 Subject: [PATCH 123/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index b7f06a443..cefdcdd5c 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -246,7 +246,7 @@ def patch(self): ) if new_deadline_in + current_deadline > 90: raise DDSArgumentError( - message="The new deadline needs to be less than (or equal to) 90 days." + message=f"You requested the deadline to be extended with {new_deadline_in} days (from {current_deadline}), giving a new total deadline of {new_deadline_in + current_deadline} days. The new deadline needs to be less than (or equal to) 90 days." ) try: From 4f26b2dc005015905bbd7a9f2e3adfb738487acd Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 13 Oct 2023 11:45:34 +0200 Subject: [PATCH 124/155] modified acording to comments --- dds_web/api/project.py | 24 ++++++++------- tests/api/test_project.py | 63 +++++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index cefdcdd5c..a16f6a726 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -223,32 +223,33 @@ def patch(self): # Extend deadline try: - extend_deadline = json_input.get("extend_deadline", False) # False by default + new_deadline_in = json_input.get( + "new_deadline_in", None + ) # if not provided is None -> deadlie is not updated # some variable definition curr_date = dds_web.utils.current_time() send_email = False - # Update the deadline - if extend_deadline: + # Update the deadline functionality + if new_deadline_in: # deadline can only be extended from Available if not project.current_status == "Available": raise DDSArgumentError( "You can only extend the deadline for a project that has the status 'Available'." ) - new_deadline_in = json_input.get("new_deadline_in") + # it shouldnt surpass 90 days current_deadline = (project.current_deadline - curr_date).days - - if not new_deadline_in: - raise DDSArgumentError( - message="No new deadline provived, cannot perform operation." - ) if new_deadline_in + current_deadline > 90: raise DDSArgumentError( message=f"You requested the deadline to be extended with {new_deadline_in} days (from {current_deadline}), giving a new total deadline of {new_deadline_in + current_deadline} days. The new deadline needs to be less than (or equal to) 90 days." ) + if type(new_deadline_in) is not int: + raise DDSArgumentError( + message=" The deadline atribute passed should be of type Int (i.e a number)." + ) try: # add a fake archived status to mimick a re-release in order to have an udpated deadline new_status_row = self.expire_project( @@ -257,7 +258,6 @@ def patch(self): deadline_in=project.responsible_unit.days_in_expired, ) project.project_statuses.append(new_status_row) - db.session.commit() new_status_row = self.release_project( project=project, @@ -279,7 +279,9 @@ def patch(self): return_message += ( f". An e-mail notification has{' not ' if not send_email else ' '}been sent." ) - + else: + # leave it for future new functionality of updating the status + return_message = "Nothing to update." except: self.set_busy(project=project, busy=False) raise diff --git a/tests/api/test_project.py b/tests/api/test_project.py index d437dd592..a44b4cedd 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -49,7 +49,6 @@ release_project_small_deadline = {"new_status": "Available", "deadline": 5} extend_deadline_data_no_confirmed = { - "extend_deadline": True, "new_deadline_in": 20, } @@ -1126,6 +1125,7 @@ def test_projectstatus_post_invalid_deadline_expire(module_client, boto3_session def test_extend_deadline_bad_confirmed(module_client, boto3_session): """Try to extend a deadline and send a not boolean for confirmation""" + # create project and release it project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) @@ -1146,6 +1146,7 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): def test_extend_deadline_no_confirmed(module_client, boto3_session): """Try to extend a deadline before confirmation - should sent a warning and no operation is perfrom""" + # create project and release it project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) @@ -1169,6 +1170,7 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): def test_extend_deadline_when_busy(module_client, boto3_session): """Request should not be possible when project is busy.""" + # create project and release it project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} ) @@ -1199,6 +1201,28 @@ def test_extend_deadline_when_busy(module_client, boto3_session): ) +def test_extend_deadline_no_deadline(module_client, boto3_session): + """If no deadline has been provided it should not be executed anything""" + + # create project and release it + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} + ) + assert project.times_expired == 0 + time.sleep(1) # tests are too fast + + # try to extend deadline - no new deadline provided + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json={"confirmed": True}, + ) + assert response.status_code == http.HTTPStatus.OK + assert project.times_expired == 0 + assert "Nothing to update." in response.json["message"] + + def test_extend_deadline_project_not_available(module_client, boto3_session): """Is not possible to extend deadline to a project in another status than available.""" @@ -1232,42 +1256,47 @@ def test_extend_deadline_project_not_available(module_client, boto3_session): ) -def test_extend_deadline_no_deadline(module_client, boto3_session): - """If no deadline has been provided it should fail""" +def test_extend_deadline_too_much_days(module_client, boto3_session): + """If the new deadline together with the time left already is more than 90 days it should not work""" + # create project and release it project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} + client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) assert project.times_expired == 0 time.sleep(1) # tests are too fast - # try to extend deadline - no new deadline provided + # try to extend deadline by a lot of days + extend_deadline_data = {**extend_deadline_data, "new_deadline_in": 90} response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"extend_deadline": True, "confirmed": True}, + json=extend_deadline_data, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "No new deadline provived, cannot perform operation." in response.json["message"] + assert ( + "The new deadline needs to be less than (or equal to) 90 days." in response.json["message"] + ) -def test_extend_deadline_too_much_days(module_client, boto3_session): - """If the new deadline together with the time left already is more than 90 days it should not work""" +def test_extend_deadline_bad_new_deadline(module_client, boto3_session): + """If the new deadlien provided is not an integer it should fail""" - # create project with small deadline so it can be extended + # create project and release it project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) assert project.times_expired == 0 time.sleep(1) # tests are too fast - # try to extend deadline by a lot of days + # try to extend deadline with a bad new deadline + extend_deadline_data = {**extend_deadline_data, "new_deadline_in": "20"} response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={"extend_deadline": True, "new_deadline_in": 90, "confirmed": True}, + json=extend_deadline_data, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( @@ -1278,7 +1307,7 @@ def test_extend_deadline_too_much_days(module_client, boto3_session): def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): """If the deadline has been extended more than 2 times it should not work""" - # create project with small deadline so it can be extended + # create project and release it project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) @@ -1290,11 +1319,15 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se time.sleep(1) # tests are too fast # extend deadline by a small new deadline so we can do it several times + extend_deadline_data_small_deadline = { + **extend_deadline_data, + "new_deadline_in": new_deadline_in, + } response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json={**extend_deadline_data, "new_deadline_in": new_deadline_in}, + json=extend_deadline_data_small_deadline, ) if i < 3: assert response.status_code == http.HTTPStatus.OK @@ -1314,7 +1347,7 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se def test_extend_deadline_ok(module_client, boto3_session): """Extend a project deadline of a project - it should work ok""" - # release with a small deadline so it can be extended + # create project and release it project_id, project = create_and_release_project( client=module_client, proj_data=proj_data, release_data=release_project_small_deadline ) From 4141b12c0178b2cb04e6de2b925223593ead4d77 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 13 Oct 2023 11:52:46 +0200 Subject: [PATCH 125/155] small fix --- dds_web/api/project.py | 9 +++++---- tests/api/test_project.py | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index a16f6a726..e99f090da 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -239,6 +239,11 @@ def patch(self): "You can only extend the deadline for a project that has the status 'Available'." ) + if type(new_deadline_in) is not int: + raise DDSArgumentError( + message="The deadline atribute passed should be of type Int (i.e a number)." + ) + # it shouldnt surpass 90 days current_deadline = (project.current_deadline - curr_date).days if new_deadline_in + current_deadline > 90: @@ -246,10 +251,6 @@ def patch(self): message=f"You requested the deadline to be extended with {new_deadline_in} days (from {current_deadline}), giving a new total deadline of {new_deadline_in + current_deadline} days. The new deadline needs to be less than (or equal to) 90 days." ) - if type(new_deadline_in) is not int: - raise DDSArgumentError( - message=" The deadline atribute passed should be of type Int (i.e a number)." - ) try: # add a fake archived status to mimick a re-release in order to have an udpated deadline new_status_row = self.expire_project( diff --git a/tests/api/test_project.py b/tests/api/test_project.py index a44b4cedd..9cdfddb00 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1267,12 +1267,12 @@ def test_extend_deadline_too_much_days(module_client, boto3_session): time.sleep(1) # tests are too fast # try to extend deadline by a lot of days - extend_deadline_data = {**extend_deadline_data, "new_deadline_in": 90} + extend_deadline_data_big_deadline = {**extend_deadline_data, "new_deadline_in": 90} response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json=extend_deadline_data, + json=extend_deadline_data_big_deadline, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( @@ -1291,16 +1291,17 @@ def test_extend_deadline_bad_new_deadline(module_client, boto3_session): time.sleep(1) # tests are too fast # try to extend deadline with a bad new deadline - extend_deadline_data = {**extend_deadline_data, "new_deadline_in": "20"} + extend_deadline_data_bad_deadline = {**extend_deadline_data, "new_deadline_in": "20"} response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), query_string={"project": project_id}, - json=extend_deadline_data, + json=extend_deadline_data_bad_deadline, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( - "The new deadline needs to be less than (or equal to) 90 days." in response.json["message"] + "The deadline atribute passed should be of type Int (i.e a number)." + in response.json["message"] ) From 1d0478cadcf47cd623b6bf1e7d21acb95a2f03c7 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Fri, 13 Oct 2023 11:53:10 +0200 Subject: [PATCH 126/155] black --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index e99f090da..977fad7ab 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -243,7 +243,7 @@ def patch(self): raise DDSArgumentError( message="The deadline atribute passed should be of type Int (i.e a number)." ) - + # it shouldnt surpass 90 days current_deadline = (project.current_deadline - curr_date).days if new_deadline_in + current_deadline > 90: From 0f0bb1f9d6b7dbb54cde4691c06a5b591be847b4 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 16 Oct 2023 15:16:05 +0200 Subject: [PATCH 127/155] unconfirmed returns project info --- dds_web/api/project.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 977fad7ab..b40b7df01 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -208,7 +208,15 @@ def patch(self): raise DDSArgumentError(message="`confirmed` is a boolean value: True or False.") if not confirmed_operation: warning_message = "Operation must be confirmed before proceding." - return {"warning": warning_message} + project_info = ProjectInfo().get() + project_status = self.get() + json_returned = { + **project_info, + "project_status": project_status, + "warning": warning_message, + "default_unit_days": project.responsible_unit.days_in_expired, + } + return json_returned # Cannot change project status if project is busy if project.busy: From 0cab328da2c844cf18419a7502b28b58c5c18297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= Date: Tue, 17 Oct 2023 11:23:37 +0200 Subject: [PATCH 128/155] changelog --- CHANGELOG.rst | 8 ++++++++ SPRINTLOG.md | 4 ++-- doc/procedures/new_release.md | 6 +++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d9b86cf5f..69752ed7b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========== +.. _2.5.2: + +2.5.2 - 2023-10-25 +~~~~~~~~~~~~~~~~~~~~~ + +- Users can revoke project access given to unaccepted invites (e.g. after a mistake). +- Email layout changed. When project is released, important information is now highlighted, and the Project Title is displayed along with the DDS project ID. + .. _2.5.1: 2.5.1 - 2023-09-27 diff --git a/SPRINTLOG.md b/SPRINTLOG.md index 8001f5ca5..cf0c7e19e 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -300,13 +300,13 @@ _Nothing merged in CLI during this sprint_ - Dependency: Bump `MariaDB` to LTS version 10.11.5 ([#1465](https://github.com/ScilifelabDataCentre/dds_web/pull/1465)) - Bug fixed: Row in `ProjectUsers` should also be added if it doesn't exist when giving Researcher access to a specific project ([#1464](https://github.com/ScilifelabDataCentre/dds_web/pull/1464)) - Workflow: Update PR template and clarify sections ([#1467](https://github.com/ScilifelabDataCentre/dds_web/pull/1467)) -- Revoke project access for unaccepted invites ([#1192])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13/backlog?epics=visible&selectedIssue=DDS-1192) # 2023-09-18 - 2023-09-29 -- Column `sto4_start_time` is automatically set when the create-unit command is run ([#1668])(https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13?selectedIssue=DDS-1668) +- Column `sto4_start_time` is automatically set when the create-unit command is run ([#1469](https://github.com/ScilifelabDataCentre/dds_web/pull/1469)) - Replace expired invites when there's a new invitation attempt ([#1466](https://github.com/ScilifelabDataCentre/dds_web/pull/1466)) - New version: 2.5.1 ([#1471](https://github.com/ScilifelabDataCentre/dds_web/pull/1471)) +- Revoke project access for unaccepted invites ([#1468](https://github.com/ScilifelabDataCentre/dds_web/pull/1468)) # 2023-10-02 - 2023-10-13 diff --git a/doc/procedures/new_release.md b/doc/procedures/new_release.md index cdb83dc91..8fc7b1387 100644 --- a/doc/procedures/new_release.md +++ b/doc/procedures/new_release.md @@ -1,14 +1,14 @@ # How to create a new release 1. Create a PR from `dev` to `master`: "New release" -2. Confirm that the development instance works and that the newest changes have been deployed +2. Confirm that the development instance works and that the newest changes have been deployed. If not, make a new redeployment of dds-dev (via argocd). 1. _In general_, e.g. that it's up and running 2. _Specific feature has been added or changed:_ Confirm that it also works in the development instance 3. _The change is in the API:_ Confirm that the development instance works together with the CLI -3. Fork a new branch from `dev` -4. Update the version [changelog](../../CHANGELOG.rst) +3. Fork a new branch from `dev` (locally) +4. Update the version [changelog](../../CHANGELOG.rst), located at `dds_web/CHANGELOG.rst` **Tip:** Use the PR to `master` to see all changes since last release. From ee23e090d8f83fad63ed96b2740f3adc9d81a61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= Date: Tue, 17 Oct 2023 11:27:44 +0200 Subject: [PATCH 129/155] update release info --- doc/procedures/new_release.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/procedures/new_release.md b/doc/procedures/new_release.md index 8fc7b1387..9a62b54d4 100644 --- a/doc/procedures/new_release.md +++ b/doc/procedures/new_release.md @@ -20,7 +20,7 @@ - _Minor changes, e.g. bug fix_: Minor version upgrade, e.g. `1.0.1 --> 1.0.2` - _Small changes, e.g. new feature_: Mid version upgrade, e.g. `1.1.0 --> 1.2.0` - - _Breaking changes or large new feature(s)_: Major version upgrade, e.g. `1.0.0 --> 2.0.0` + - _Breaking changes or large new feature(s)_: Major version upgrade, e.g. `1.0.0 --> 2.0.0` _AVOID THIS -- NEED TO INFORM USERS WELL IN ADVANCE IN THAT CASE SINCE IT WILL BLOCK THE USERS FROM USING ANY OLDER VERSIONS_ > Will break if CLI version not bumped as well From 40696ed1a74bd8a38f40e2f107b57fc1b567fb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= Date: Tue, 17 Oct 2023 11:31:33 +0200 Subject: [PATCH 130/155] version --- dds_web/version.py | 2 +- tests/test_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/version.py b/dds_web/version.py index 7a2056f56..667b52f95 100644 --- a/dds_web/version.py +++ b/dds_web/version.py @@ -1 +1 @@ -__version__ = "2.5.1" +__version__ = "2.5.2" diff --git a/tests/test_version.py b/tests/test_version.py index 02a5bc16a..534eab711 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,4 +2,4 @@ def test_version(): - assert version.__version__ == "2.5.1" + assert version.__version__ == "2.5.2" From e042eca19cf92ca8662c620a10b88052fbe542d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= Date: Tue, 17 Oct 2023 11:34:23 +0200 Subject: [PATCH 131/155] sprintlog --- SPRINTLOG.md | 1 + doc/procedures/new_release.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index cf0c7e19e..a5afa6219 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -314,3 +314,4 @@ _Nothing merged in CLI during this sprint_ - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) - Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) - Improved email layout; Highlighted information and commands when project is released ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) +- New version: 2.5.2 ([#1482](https://github.com/ScilifelabDataCentre/dds_web/pull/1482)) diff --git a/doc/procedures/new_release.md b/doc/procedures/new_release.md index 9a62b54d4..82bd4fbe4 100644 --- a/doc/procedures/new_release.md +++ b/doc/procedures/new_release.md @@ -25,7 +25,7 @@ > Will break if CLI version not bumped as well 6. Push version change to branch -7. Create a new PR from `` to `dev` +7. Create a new PR from `` to `dev`: "New version & changelog" Wait for approval and merge by Product Owner or admin. From 5df20c92a9d25362b317ac900950a62cc870fa4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:42:59 +0200 Subject: [PATCH 132/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index b40b7df01..ff086f0ff 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -233,7 +233,7 @@ def patch(self): try: new_deadline_in = json_input.get( "new_deadline_in", None - ) # if not provided is None -> deadlie is not updated + ) # if not provided --> is None -> deadline is not updated # some variable definition curr_date = dds_web.utils.current_time() From 69693d5c9974e197c594642579ed91374be96f1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:43:24 +0200 Subject: [PATCH 133/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index ff086f0ff..044803b72 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -192,6 +192,7 @@ def post(self): @logging_bind_request @json_required @handle_validation_errors + @handle_db_error def patch(self): """Partially update a the project status""" # Get project ID, project and verify access From 470b3a126fb2e3ced68ca6476b44a6b9e5aa5b02 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 17 Oct 2023 15:11:52 +0200 Subject: [PATCH 134/155] moved set busy --- dds_web/api/project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 044803b72..873c5e661 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -228,10 +228,10 @@ def patch(self): ) ) - self.set_busy(project=project, busy=True) - # Extend deadline try: + self.set_busy(project=project, busy=True) + new_deadline_in = json_input.get( "new_deadline_in", None ) # if not provided --> is None -> deadline is not updated From 29d8eda1b2cf6dd406ebb7936e64a0b5b3bbb62b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Thu, 19 Oct 2023 16:07:40 +0200 Subject: [PATCH 135/155] rollout --- dds_web/api/project.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 873c5e661..af42d42fd 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -209,6 +209,7 @@ def patch(self): raise DDSArgumentError(message="`confirmed` is a boolean value: True or False.") if not confirmed_operation: warning_message = "Operation must be confirmed before proceding." + # When not confirmed, return information about the project project_info = ProjectInfo().get() project_status = self.get() json_returned = { @@ -228,10 +229,10 @@ def patch(self): ) ) + self.set_busy(project=project, busy=True) + # Extend deadline try: - self.set_busy(project=project, busy=True) - new_deadline_in = json_input.get( "new_deadline_in", None ) # if not provided --> is None -> deadline is not updated From 4f4ff9155e71cf84dc7e9bc728242b5993cc819e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Fri, 20 Oct 2023 13:40:37 +0200 Subject: [PATCH 136/155] Update SPRINTLOG.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- SPRINTLOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SPRINTLOG.md b/SPRINTLOG.md index b6c399177..065b8b294 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -314,4 +314,7 @@ _Nothing merged in CLI during this sprint_ - Use full DDS name in MOTD email subject ([#1477](https://github.com/ScilifelabDataCentre/dds_web/pull/1477)) - Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) - Improved email layout; Highlighted information and commands when project is released ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) + +# 2023-10-16 - 2023-10-27 + - Added new API endpoint ProjectStatus.patch to extend the deadline ([#1480])(https://github.com/ScilifelabDataCentre/dds_web/pull/1480) From ca9709459b3c304c0daaddb2498dc072b52c17e0 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 10:42:48 +0200 Subject: [PATCH 137/155] new exceptions --- dds_web/api/project.py | 9 ++++++- tests/api/test_project.py | 52 +++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index af42d42fd..423e9453b 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -240,6 +240,7 @@ def patch(self): # some variable definition curr_date = dds_web.utils.current_time() send_email = False + default_unit_days = project.responsible_unit.days_in_expired # Update the deadline functionality if new_deadline_in: @@ -254,7 +255,13 @@ def patch(self): message="The deadline atribute passed should be of type Int (i.e a number)." ) - # it shouldnt surpass 90 days + # New deadline shouldnt surpass the default unit days + if new_deadline_in > default_unit_days: + raise DDSArgumentError( + message=f"You requested the deadline to be extended {new_deadline_in}. The number of days has to be lower than the default deadline extension number of {default_unit_days} days" + ) + + # the new deadline + days left shouldnt surpass 90 days current_deadline = (project.current_deadline - curr_date).days if new_deadline_in + current_deadline > 90: raise DDSArgumentError( diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 9cdfddb00..3bfb18519 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -46,7 +46,9 @@ # "date_updated", ] -release_project_small_deadline = {"new_status": "Available", "deadline": 5} +release_project = {"new_status": "Available"} +release_project_small_deadline = {**release_project, "deadline": 5} +release_project_big_deadline = {**release_project, "deadline": 80} extend_deadline_data_no_confirmed = { "new_deadline_in": 20, @@ -1127,7 +1129,7 @@ def test_extend_deadline_bad_confirmed(module_client, boto3_session): # create project and release it project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} + client=module_client, proj_data=proj_data, release_data=release_project ) assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1148,7 +1150,7 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): # create project and release it project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} + client=module_client, proj_data=proj_data, release_data=release_project ) assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1172,7 +1174,7 @@ def test_extend_deadline_when_busy(module_client, boto3_session): # create project and release it project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} + client=module_client, proj_data=proj_data, release_data=release_project ) assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1206,7 +1208,7 @@ def test_extend_deadline_no_deadline(module_client, boto3_session): # create project and release it project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data={"new_status": "Available"} + client=module_client, proj_data=proj_data, release_data=release_project ) assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1259,15 +1261,15 @@ def test_extend_deadline_project_not_available(module_client, boto3_session): def test_extend_deadline_too_much_days(module_client, boto3_session): """If the new deadline together with the time left already is more than 90 days it should not work""" - # create project and release it + # create project and release it with big dealdine project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data=release_project_small_deadline + client=module_client, proj_data=proj_data, release_data=release_project_big_deadline ) assert project.times_expired == 0 time.sleep(1) # tests are too fast - # try to extend deadline by a lot of days - extend_deadline_data_big_deadline = {**extend_deadline_data, "new_deadline_in": 90} + # try to extend deadline -> 80 + 11 > 90 + extend_deadline_data_big_deadline = {**extend_deadline_data, "new_deadline_in": 11} response = module_client.patch( tests.DDSEndpoint.PROJECT_STATUS, headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), @@ -1285,7 +1287,7 @@ def test_extend_deadline_bad_new_deadline(module_client, boto3_session): # create project and release it project_id, project = create_and_release_project( - client=module_client, proj_data=proj_data, release_data=release_project_small_deadline + client=module_client, proj_data=proj_data, release_data=release_project ) assert project.times_expired == 0 time.sleep(1) # tests are too fast @@ -1305,6 +1307,36 @@ def test_extend_deadline_bad_new_deadline(module_client, boto3_session): ) +def test_extend_deadline_more_than_default(module_client, boto3_session): + """If the new deadline provided is more than the default unit days to release a project it should fail""" + + # create project and release it + project_id, project = create_and_release_project( + client=module_client, proj_data=proj_data, release_data=release_project_small_deadline + ) + assert project.times_expired == 0 + time.sleep(1) # tests are too fast + + default_unit_days = project.responsible_unit.days_in_expired + + # try to extend deadline with a bigger deadline that it is suppose to have + extend_deadline_data_bad_deadline = { + **extend_deadline_data, + "new_deadline_in": default_unit_days + 1, + } + response = module_client.patch( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client), + query_string={"project": project_id}, + json=extend_deadline_data_bad_deadline, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert ( + "The number of days has to be lower than the default deadline extension number" + in response.json["message"] + ) + + def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_session): """If the deadline has been extended more than 2 times it should not work""" From 7cc7b17ee948fc28f023049c58a20b164918ab7d Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 10:59:02 +0200 Subject: [PATCH 138/155] new exceptions --- dds_web/api/project.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 423e9453b..8967ee650 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -216,7 +216,6 @@ def patch(self): **project_info, "project_status": project_status, "warning": warning_message, - "default_unit_days": project.responsible_unit.days_in_expired, } return json_returned From 806e12cd6a87bfec3540f6600fd5298c69a42874 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 11:20:20 +0200 Subject: [PATCH 139/155] default unti days --- dds_web/api/project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 8967ee650..423e9453b 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -216,6 +216,7 @@ def patch(self): **project_info, "project_status": project_status, "warning": warning_message, + "default_unit_days": project.responsible_unit.days_in_expired, } return json_returned From 181ebbc9c7cbce15bec5b48cc81a17193d662911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 23 Oct 2023 12:51:30 +0200 Subject: [PATCH 140/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 423e9453b..7f601bc6a 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -252,7 +252,7 @@ def patch(self): if type(new_deadline_in) is not int: raise DDSArgumentError( - message="The deadline atribute passed should be of type Int (i.e a number)." + message="The deadline attribute passed should be of type Int (i.e a number)." ) # New deadline shouldnt surpass the default unit days From 681b4af920349c45acd412cd238477e08afaf205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 23 Oct 2023 12:51:40 +0200 Subject: [PATCH 141/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 7f601bc6a..7132ccd2d 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -225,7 +225,7 @@ def patch(self): raise ProjectBusyError( message=( f"The deadline for the project '{project_id}' is already in the process of being changed. " - "Please try again later. \n\nIf you know the project is not busy, contact support." + "Please try again later. \n\nIf you know that the project is not busy, contact support." ) ) From de4b7c505f594a66e4004bf898247b7dd2bca676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 23 Oct 2023 12:51:57 +0200 Subject: [PATCH 142/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 7132ccd2d..3ff013968 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -292,10 +292,9 @@ def patch(self): db.session.rollback() raise - return_message = f"{project.public_id} has been given a new deadline" - - return_message += ( - f". An e-mail notification has{' not ' if not send_email else ' '}been sent." + return_message = ( + f"{project.public_id} has been given a new deadline. " + f"An e-mail notification has{' not ' if not send_email else ' '}been sent." ) else: # leave it for future new functionality of updating the status From 1cc9c4d6f68b1b16747d9c66f8514c0824b21ecf Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 12:56:31 +0200 Subject: [PATCH 143/155] test to match changes --- tests/api/test_project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 3bfb18519..a1479eebc 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1198,7 +1198,7 @@ def test_extend_deadline_when_busy(module_client, boto3_session): in response.json["message"] ) assert ( - "Please try again later. \n\nIf you know the project is not busy, contact support." + "Please try again later. \n\nIf you know that the project is not busy, contact support." in response.json["message"] ) @@ -1302,7 +1302,7 @@ def test_extend_deadline_bad_new_deadline(module_client, boto3_session): ) assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( - "The deadline atribute passed should be of type Int (i.e a number)." + "The deadline attribute passed should be of type Int (i.e a number)." in response.json["message"] ) From a4fb0f5dec253244399b38c4a546ad7a516b5b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 23 Oct 2023 15:49:51 +0200 Subject: [PATCH 144/155] Update tests/api/test_project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- tests/api/test_project.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index a1479eebc..944683d1d 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1167,6 +1167,7 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): assert project.times_expired == 0 assert "Operation must be confirmed before proceding." in response.json["warning"] + assert all(item in response.json for item in ["project_info", "project_status", "warning", "default_unit_days"]) def test_extend_deadline_when_busy(module_client, boto3_session): From 703fa91124249cd9d8cb801f8399a4653852c0d9 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 16:14:23 +0200 Subject: [PATCH 145/155] feedback --- dds_web/api/project.py | 9 +++++++-- tests/api/test_project.py | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 3ff013968..d4cc65c43 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -267,9 +267,14 @@ def patch(self): raise DDSArgumentError( message=f"You requested the deadline to be extended with {new_deadline_in} days (from {current_deadline}), giving a new total deadline of {new_deadline_in + current_deadline} days. The new deadline needs to be less than (or equal to) 90 days." ) - + # the dealine has changed at least two times, next time it expires + # wont change again -> error + if project.times_expired >= 2: + raise DDSArgumentError( + "Project availability limit: Project cannot be made Available any more times" + ) try: - # add a fake archived status to mimick a re-release in order to have an udpated deadline + # add a fake expire status to mimick a re-release in order to have an udpated deadline new_status_row = self.expire_project( project=project, current_time=curr_date, diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 944683d1d..638bb25d9 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1167,7 +1167,10 @@ def test_extend_deadline_no_confirmed(module_client, boto3_session): assert project.times_expired == 0 assert "Operation must be confirmed before proceding." in response.json["warning"] - assert all(item in response.json for item in ["project_info", "project_status", "warning", "default_unit_days"]) + assert all( + item in response.json + for item in ["project_info", "project_status", "warning", "default_unit_days"] + ) def test_extend_deadline_when_busy(module_client, boto3_session): From b3f478b8759ddd7d2a345f9626e8513fb4466913 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 16:27:04 +0200 Subject: [PATCH 146/155] more explicative error --- dds_web/api/project.py | 2 +- tests/api/test_project.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index d4cc65c43..5bacb7069 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -271,7 +271,7 @@ def patch(self): # wont change again -> error if project.times_expired >= 2: raise DDSArgumentError( - "Project availability limit: Project cannot be made Available any more times" + "Project availability limit: The maximun number of changes in data availability has been reached." ) try: # add a fake expire status to mimick a re-release in order to have an udpated deadline diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 638bb25d9..99531c792 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1376,7 +1376,7 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se else: assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( - "Project availability limit: Project cannot be made Available any more times" + "Project availability limit: The maximun number of changes in data availability has been reached." in response.json["message"] ) From 75987af295a2d89db6ae15075b3231437467f70b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 16:40:58 +0200 Subject: [PATCH 147/155] move check for data availability --- dds_web/api/project.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 5bacb7069..cb63a2714 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -203,6 +203,13 @@ def patch(self): # Get json input from request json_input = flask.request.get_json(silent=True) # Already checked by json_required + # the status has changed at least two times, + # next time the project expires it wont change again -> error + if project.times_expired >= 2: + raise DDSArgumentError( + "Project availability limit: The maximun number of changes in data availability has been reached." + ) + # Operation must be confirmed by the user - False by default confirmed_operation = json_input.get("confirmed", False) if not isinstance(confirmed_operation, bool): @@ -267,12 +274,6 @@ def patch(self): raise DDSArgumentError( message=f"You requested the deadline to be extended with {new_deadline_in} days (from {current_deadline}), giving a new total deadline of {new_deadline_in + current_deadline} days. The new deadline needs to be less than (or equal to) 90 days." ) - # the dealine has changed at least two times, next time it expires - # wont change again -> error - if project.times_expired >= 2: - raise DDSArgumentError( - "Project availability limit: The maximun number of changes in data availability has been reached." - ) try: # add a fake expire status to mimick a re-release in order to have an udpated deadline new_status_row = self.expire_project( From bbffbd59396d56abb9379abfb1f271e61fb81e5b Mon Sep 17 00:00:00 2001 From: rv0lt Date: Mon, 23 Oct 2023 16:43:12 +0200 Subject: [PATCH 148/155] typo --- dds_web/api/project.py | 2 +- tests/api/test_project.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index cb63a2714..a2cbf94ff 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -207,7 +207,7 @@ def patch(self): # next time the project expires it wont change again -> error if project.times_expired >= 2: raise DDSArgumentError( - "Project availability limit: The maximun number of changes in data availability has been reached." + "Project availability limit: The maximum number of changes in data availability has been reached." ) # Operation must be confirmed by the user - False by default diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 99531c792..e7d614607 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1376,7 +1376,7 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se else: assert response.status_code == http.HTTPStatus.BAD_REQUEST assert ( - "Project availability limit: The maximun number of changes in data availability has been reached." + "Project availability limit: The maximum number of changes in data availability has been reached." in response.json["message"] ) From 118708c10246d0546ab46b56c5d4a1c502140ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:09:30 +0200 Subject: [PATCH 149/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index a2cbf94ff..12e6cd421 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -223,7 +223,7 @@ def patch(self): **project_info, "project_status": project_status, "warning": warning_message, - "default_unit_days": project.responsible_unit.days_in_expired, + "default_unit_days": project.responsible_unit.days_in_available, } return json_returned From a5ab01adfb29b740fd97af36de2d3d6408865eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:05:25 +0200 Subject: [PATCH 150/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 12e6cd421..a568dd8de 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -247,7 +247,7 @@ def patch(self): # some variable definition curr_date = dds_web.utils.current_time() send_email = False - default_unit_days = project.responsible_unit.days_in_expired + default_unit_days = project.responsible_unit.days_in_available # Update the deadline functionality if new_deadline_in: From 78f7418e5845fac95f245d8a4d3a126533de8259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:05:35 +0200 Subject: [PATCH 151/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index a568dd8de..541dda856 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -299,7 +299,7 @@ def patch(self): raise return_message = ( - f"{project.public_id} has been given a new deadline. " + f"The project '{project.public_id}' has been given a new deadline. " f"An e-mail notification has{' not ' if not send_email else ' '}been sent." ) else: From dd50770446a60851e830a2b61412573d03e8f40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Revuelta?= <46089290+rv0lt@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:05:48 +0200 Subject: [PATCH 152/155] Update dds_web/api/project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+i-oden@users.noreply.github.com> --- dds_web/api/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 541dda856..9a016c350 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -265,7 +265,7 @@ def patch(self): # New deadline shouldnt surpass the default unit days if new_deadline_in > default_unit_days: raise DDSArgumentError( - message=f"You requested the deadline to be extended {new_deadline_in}. The number of days has to be lower than the default deadline extension number of {default_unit_days} days" + message=f"You requested the deadline to be extended {new_deadline_in} days. The number of days has to be lower than the default deadline extension number of {default_unit_days} days" ) # the new deadline + days left shouldnt surpass 90 days From 33e78efe8094c0276961e25a3529013a00d45102 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 24 Oct 2023 09:49:53 +0200 Subject: [PATCH 153/155] fix tests --- tests/api/test_project.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index e7d614607..6baee43af 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1321,7 +1321,7 @@ def test_extend_deadline_more_than_default(module_client, boto3_session): assert project.times_expired == 0 time.sleep(1) # tests are too fast - default_unit_days = project.responsible_unit.days_in_expired + default_unit_days = project.responsible_unit.days_in_available # try to extend deadline with a bigger deadline that it is suppose to have extend_deadline_data_bad_deadline = { @@ -1371,7 +1371,10 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se assert project.times_expired == i assert project.current_deadline == deadline + datetime.timedelta(days=new_deadline_in) deadline = project.current_deadline # update current deadline - assert f"{project_id} has been given a new deadline" in response.json["message"] + assert ( + f"The project '{project_id}' has been given a new deadline" + in response.json["message"] + ) assert "An e-mail notification has not been sent." in response.json["message"] else: assert response.status_code == http.HTTPStatus.BAD_REQUEST @@ -1406,7 +1409,7 @@ def test_extend_deadline_ok(module_client, boto3_session): days=extend_deadline_data.get("new_deadline_in") ) - assert f"{project_id} has been given a new deadline" in response.json["message"] + assert f"The project '{project_id}' has been given a new deadline" in response.json["message"] assert "An e-mail notification has not been sent." in response.json["message"] From 494b7bc4afbec6e42b6ffb821856280a27b54b25 Mon Sep 17 00:00:00 2001 From: rv0lt Date: Tue, 24 Oct 2023 10:32:48 +0200 Subject: [PATCH 154/155] feedback --- dds_web/api/project.py | 10 ++++++++-- tests/api/test_project.py | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 9a016c350..ffe2c95ef 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -245,7 +245,6 @@ def patch(self): ) # if not provided --> is None -> deadline is not updated # some variable definition - curr_date = dds_web.utils.current_time() send_email = False default_unit_days = project.responsible_unit.days_in_available @@ -269,6 +268,7 @@ def patch(self): ) # the new deadline + days left shouldnt surpass 90 days + curr_date = dds_web.utils.current_time() current_deadline = (project.current_deadline - curr_date).days if new_deadline_in + current_deadline > 90: raise DDSArgumentError( @@ -276,13 +276,19 @@ def patch(self): ) try: # add a fake expire status to mimick a re-release in order to have an udpated deadline + curr_date = ( + dds_web.utils.current_time() + ) # call current_time before each call so it is stored with different timestamps new_status_row = self.expire_project( project=project, current_time=curr_date, - deadline_in=project.responsible_unit.days_in_expired, + deadline_in=1, # some dummy deadline bc it will re-release now again ) project.project_statuses.append(new_status_row) + curr_date = ( + dds_web.utils.current_time() + ) # call current_time before each call so it is stored with different timestamps new_status_row = self.release_project( project=project, current_time=curr_date, diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 6baee43af..18fcfcb10 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1371,6 +1371,7 @@ def test_extend_deadline_maxium_number_available_exceded(module_client, boto3_se assert project.times_expired == i assert project.current_deadline == deadline + datetime.timedelta(days=new_deadline_in) deadline = project.current_deadline # update current deadline + assert project.current_status == "Available" assert ( f"The project '{project_id}' has been given a new deadline" in response.json["message"] @@ -1408,6 +1409,7 @@ def test_extend_deadline_ok(module_client, boto3_session): assert project.current_deadline == deadline + datetime.timedelta( days=extend_deadline_data.get("new_deadline_in") ) + assert project.current_status == "Available" assert f"The project '{project_id}' has been given a new deadline" in response.json["message"] assert "An e-mail notification has not been sent." in response.json["message"] From 59e44d5cf6f4eea1dc5cec8cec5da94d44549362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ina=20Od=C3=A9n=20=C3=96sterbo?= Date: Tue, 24 Oct 2023 11:44:06 +0200 Subject: [PATCH 155/155] changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 69752ed7b..30c19b0dc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Changelog - Users can revoke project access given to unaccepted invites (e.g. after a mistake). - Email layout changed. When project is released, important information is now highlighted, and the Project Title is displayed along with the DDS project ID. +- New endpoint `ProjectStatus.patch`: Unit Admins / Personnel can extend the project deadline. .. _2.5.1: