From 4c255c3fa6b2f2c78f893e7fdf4bdac37c664d06 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 7 Sep 2022 11:25:06 +0200 Subject: [PATCH 01/25] new endpoint for setting project as busy --- dds_web/api/__init__.py | 1 + dds_web/api/project.py | 23 +++++++++++++++ dds_web/database/models.py | 1 + dds_web/errors.py | 9 ++++++ .../6e9b74878553_add_busy_to_project.py | 28 +++++++++++++++++++ 5 files changed, 62 insertions(+) create mode 100644 migrations/versions/6e9b74878553_add_busy_to_project.py diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index f7931a190..3d8f23f07 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -60,6 +60,7 @@ def output_json(data, code, headers=None): api.add_resource(project.ProjectUsers, "/proj/users", endpoint="list_project_users") api.add_resource(project.ProjectStatus, "/proj/status", endpoint="project_status") api.add_resource(project.ProjectAccess, "/proj/access", endpoint="project_access") +api.add_resource(project.ProjectBusy, "/proj/busy", endpoint="project_busy") # User management ################################################################ User management # api.add_resource(user.RetrieveUserInfo, "/user/info", endpoint="user_info") diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 928a37b10..1c5fb2c96 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -34,6 +34,8 @@ DeletionError, BucketNotFoundError, KeyNotFoundError, + NoSuchProjectError, + ProjectBusyError, S3ConnectionError, NoSuchUserError, ) @@ -867,3 +869,24 @@ def give_project_access(project_list, current_user, user): ] = "You do not have access to this project. Please contact the responsible unit." return fix_errors + +class ProjectBusy(flask_restful.Resource): + @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) + @logging_bind_request + @dbsession + @json_required + def put(self): + """Set project to busy / not busy.""" + # 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) + + # Check if project is busy + if project.busy: + raise ProjectBusyError(message="The project is already busy, cannot proceed.") + + # Set project as busy + project.busy = True + + return {"ok": True, "message": f"Project {project_id} was set to busy."} diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 1459f23a3..053797ae6 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -251,6 +251,7 @@ class Project(db.Model): released = db.Column(db.DateTime(), nullable=True) is_active = db.Column(db.Boolean, unique=False, nullable=False, default=True, index=True) done = db.Column(db.Boolean, unique=False, nullable=False, default=False) + busy = db.Column(db.Boolean, unique=False, nullable=False, default=False) # Foreign keys & relationships unit_id = db.Column(db.Integer, db.ForeignKey("units.id", ondelete="RESTRICT"), nullable=True) diff --git a/dds_web/errors.py b/dds_web/errors.py index 2b69aeb48..3842a8e86 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -291,6 +291,15 @@ def __init__(self, message="Project ID missing!"): general_logger.warning(message) +class ProjectBusyError(LoggedHTTPException): + """Something has gone wrong when transitioning between busy and not busy project.""" + + code = http.HTTPStatus.BAD_REQUEST + + def __init__(self, message): + super().__init__(message) + + general_logger.warning(message) class DDSArgumentError(LoggedHTTPException): """Base class for errors occurring due to missing request arguments.""" diff --git a/migrations/versions/6e9b74878553_add_busy_to_project.py b/migrations/versions/6e9b74878553_add_busy_to_project.py new file mode 100644 index 000000000..dd9755cd3 --- /dev/null +++ b/migrations/versions/6e9b74878553_add_busy_to_project.py @@ -0,0 +1,28 @@ +"""add-busy-to-project + +Revision ID: 6e9b74878553 +Revises: 02acefb3057a +Create Date: 2022-09-07 09:09:06.081829 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '6e9b74878553' +down_revision = '02acefb3057a' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('projects', sa.Column('busy', sa.Boolean(), nullable=False)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('projects', 'busy') + # ### end Alembic commands ### From 7e0e1d77e2e9db6bfd76035c9b7c5f37055272ea Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 7 Sep 2022 11:35:35 +0200 Subject: [PATCH 02/25] allow set to busy / not busy --- dds_web/api/project.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 1c5fb2c96..5ee757aa5 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -882,11 +882,24 @@ def put(self): project = dds_web.utils.collect_project(project_id=project_id) dds_web.utils.verify_project_access(project=project) - # Check if project is busy - if project.busy: - raise ProjectBusyError(message="The project is already busy, cannot proceed.") + # Get busy or not busy + set_to_busy = flask.request.json.get("busy") + if set_to_busy is None: + raise DDSArgumentError(message="Are you trying to set the project as busy or not busy?") + + if set_to_busy: + # Check if project is busy + if project.busy: + raise ProjectBusyError(message="The project is already busy, cannot proceed.") - # Set project as busy - project.busy = True - - return {"ok": True, "message": f"Project {project_id} was set to busy."} + # Set project as busy + project.busy = True + else: + # Check if project is not busy + if not project.busy: + raise ProjectBusyError(message="The project is not busy, cannot proceed.") + + # Set project to not busy + project.busy = False + + return {"ok": True, "message": f"Project {project_id} was set to {'busy' if set_to_busy else 'not busy'}."} From e8ee0a11fd98d6992afd9165bced4ddbd4d952b2 Mon Sep 17 00:00:00 2001 From: Ina Date: Wed, 7 Sep 2022 11:38:21 +0200 Subject: [PATCH 03/25] black --- dds_web/api/project.py | 12 ++++++++---- dds_web/errors.py | 2 ++ .../versions/6e9b74878553_add_busy_to_project.py | 8 ++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 5ee757aa5..e8fd228cb 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -870,6 +870,7 @@ def give_project_access(project_list, current_user, user): return fix_errors + class ProjectBusy(flask_restful.Resource): @auth.login_required(role=["Unit Admin", "Unit Personnel", "Project Owner", "Researcher"]) @logging_bind_request @@ -891,15 +892,18 @@ def put(self): # Check if project is busy if project.busy: raise ProjectBusyError(message="The project is already busy, cannot proceed.") - + # Set project as busy project.busy = True - else: + else: # Check if project is not busy if not project.busy: raise ProjectBusyError(message="The project is not busy, cannot proceed.") - + # Set project to not busy project.busy = False - return {"ok": True, "message": f"Project {project_id} was set to {'busy' if set_to_busy else 'not busy'}."} + return { + "ok": True, + "message": f"Project {project_id} was set to {'busy' if set_to_busy else 'not busy'}.", + } diff --git a/dds_web/errors.py b/dds_web/errors.py index 3842a8e86..37bf4608f 100644 --- a/dds_web/errors.py +++ b/dds_web/errors.py @@ -291,6 +291,7 @@ def __init__(self, message="Project ID missing!"): general_logger.warning(message) + class ProjectBusyError(LoggedHTTPException): """Something has gone wrong when transitioning between busy and not busy project.""" @@ -301,6 +302,7 @@ def __init__(self, message): general_logger.warning(message) + class DDSArgumentError(LoggedHTTPException): """Base class for errors occurring due to missing request arguments.""" diff --git a/migrations/versions/6e9b74878553_add_busy_to_project.py b/migrations/versions/6e9b74878553_add_busy_to_project.py index dd9755cd3..cd8a58011 100644 --- a/migrations/versions/6e9b74878553_add_busy_to_project.py +++ b/migrations/versions/6e9b74878553_add_busy_to_project.py @@ -10,19 +10,19 @@ from sqlalchemy.dialects import mysql # revision identifiers, used by Alembic. -revision = '6e9b74878553' -down_revision = '02acefb3057a' +revision = "6e9b74878553" +down_revision = "02acefb3057a" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.add_column('projects', sa.Column('busy', sa.Boolean(), nullable=False)) + op.add_column("projects", sa.Column("busy", sa.Boolean(), nullable=False)) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('projects', 'busy') + op.drop_column("projects", "busy") # ### end Alembic commands ### From cdc492b91205d57eeabf81b0743af7a6c60acadd Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 8 Sep 2022 10:40:17 +0200 Subject: [PATCH 04/25] dict instead of raise --- dds_web/api/project.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index e8fd228cb..771305336 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -886,19 +886,19 @@ def put(self): # Get busy or not busy set_to_busy = flask.request.json.get("busy") if set_to_busy is None: - raise DDSArgumentError(message="Are you trying to set the project as busy or not busy?") + raise DDSArgumentError(message="Missing information about setting busy or not busy.") if set_to_busy: # Check if project is busy if project.busy: - raise ProjectBusyError(message="The project is already busy, cannot proceed.") + return {"ok": False, "message": "The project is already busy, cannot proceed."} # Set project as busy project.busy = True else: # Check if project is not busy if not project.busy: - raise ProjectBusyError(message="The project is not busy, cannot proceed.") + return {"ok": False, "message": "The project is already not busy, cannot proceed."} # Set project to not busy project.busy = False From 9874dae0f668dabca323f698b887fc4caab53992 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 8 Sep 2022 11:14:45 +0200 Subject: [PATCH 05/25] check if project busy before status update --- dds_web/api/project.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 771305336..76b3fef4f 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -88,6 +88,16 @@ def post(self): project = dds_web.utils.collect_project(project_id=project_id) dds_web.utils.verify_project_access(project=project) + if project.busy: + raise ProjectBusyError( + message=( + f"Status change not possible at this time. The project '{project_id}' " + "is currently busy with upload/download/deletion. Please try again later. " + "\n\n" + "If you know the project is not busy, contact support." + ) + ) + # Check if valid status json_input = flask.request.json new_status = json_input.get("new_status") From 1766e461c10b84e00d4a284479ef06212eecd966 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 8 Sep 2022 11:27:09 +0200 Subject: [PATCH 06/25] set_as_busy static method --- dds_web/api/project.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 76b3fef4f..ed0b47f12 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -88,6 +88,7 @@ def post(self): 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=( @@ -97,6 +98,7 @@ def post(self): "If you know the project is not busy, contact support." ) ) + self.set_as_busy(project=project) # Check if valid status json_input = flask.request.json @@ -139,6 +141,7 @@ def post(self): try: project.project_statuses.append(new_status_row) + project.busy = False db.session.commit() except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: flask.current_app.logger.exception(err) @@ -174,6 +177,14 @@ def post(self): ) return {"message": return_message} + @dbsession + @staticmethod + def set_as_busy(project: models.Project) -> None: + """Set project as busy.""" + project.busy = True + db.session.commit() + flask.current_app.logger.info(f"Project '{project.public_id}' set as busy.") + def check_transition_possible(self, current_status, new_status): """Check if the transition is valid.""" valid_statuses = { From 8bf2fcc8fef8d5701f05ecf793e9ce8cc265b035 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 8 Sep 2022 11:46:22 +0200 Subject: [PATCH 07/25] set_as_busy renamed to set_busy and used for reset of busy after status update --- dds_web/api/project.py | 163 +++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 80 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index ed0b47f12..79ce4b7b6 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -92,98 +92,101 @@ def post(self): if project.busy: raise ProjectBusyError( message=( - f"Status change not possible at this time. The project '{project_id}' " - "is currently busy with upload/download/deletion. Please try again later. " - "\n\n" - "If you know the project is not busy, contact support." + f"The project '{project_id}' is currently busy with upload/download/deletion. " + "Please try again later. \n\nIf you know the project is not busy, contact support." ) ) - self.set_as_busy(project=project) - - # Check if valid status - json_input = flask.request.json - new_status = json_input.get("new_status") - if not new_status: - raise DDSArgumentError(message="No status transition provided. Specify the new status.") - - # Override default to send email - send_email = json_input.get("send_email", True) - - # Initial variable definition - curr_date = dds_web.utils.current_time() - delete_message = "" - is_aborted = False - - # Moving to Available - if new_status == "Available": - deadline_in = json_input.get("deadline", project.responsible_unit.days_in_available) - new_status_row = self.release_project( - project=project, current_time=curr_date, deadline_in=deadline_in - ) - elif new_status == "In Progress": - new_status_row = self.retract_project(project=project, current_time=curr_date) - elif new_status == "Expired": - deadline_in = json_input.get("deadline", project.responsible_unit.days_in_expired) - new_status_row = self.expire_project( - project=project, current_time=curr_date, deadline_in=deadline_in - ) - elif new_status == "Deleted": - new_status_row, delete_message = self.delete_project( - project=project, current_time=curr_date - ) - elif new_status == "Archived": - is_aborted = json_input.get("is_aborted", False) - new_status_row, delete_message = self.archive_project( - project=project, current_time=curr_date, aborted=is_aborted - ) - else: - raise DDSArgumentError(message="Invalid status") + self.set_busy(project=project, busy=True) try: - project.project_statuses.append(new_status_row) - project.busy = False - 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 - - # Mail users once project is made available - if new_status == "Available" and send_email: - for user in project.researchusers: - AddUser.compose_and_send_email_to_user( - userobj=user.researchuser, mail_type="project_release", project=project + # Check if valid status + json_input = flask.request.json + new_status = json_input.get("new_status") + if not new_status: + raise DDSArgumentError(message="No status transition provided. Specify the new status.") + + # Override default to send email + send_email = json_input.get("send_email", True) + + # Initial variable definition + curr_date = dds_web.utils.current_time() + delete_message = "" + is_aborted = False + + # Moving to Available + if new_status == "Available": + deadline_in = json_input.get("deadline", project.responsible_unit.days_in_available) + new_status_row = self.release_project( + project=project, current_time=curr_date, deadline_in=deadline_in + ) + elif new_status == "In Progress": + new_status_row = self.retract_project(project=project, current_time=curr_date) + elif new_status == "Expired": + deadline_in = json_input.get("deadline", project.responsible_unit.days_in_expired) + new_status_row = self.expire_project( + project=project, current_time=curr_date, deadline_in=deadline_in + ) + elif new_status == "Deleted": + new_status_row, delete_message = self.delete_project( + project=project, current_time=curr_date ) + elif new_status == "Archived": + is_aborted = json_input.get("is_aborted", False) + new_status_row, delete_message = self.archive_project( + project=project, current_time=curr_date, aborted=is_aborted + ) + else: + raise DDSArgumentError(message="Invalid status") - return_message = f"{project.public_id} updated to status {new_status}" + ( - " (aborted)" if new_status == "Archived" and is_aborted else "" - ) + try: + project.project_statuses.append(new_status_row) + project.busy = False + 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 - if new_status != "Available": - return_message += delete_message + "." - else: - return_message += ( - f". An e-mail notification has{' not ' if not send_email else ' '}been sent." + # Mail users once project is made available + if new_status == "Available" and send_email: + for user in project.researchusers: + AddUser.compose_and_send_email_to_user( + userobj=user.researchuser, mail_type="project_release", project=project + ) + + return_message = f"{project.public_id} updated to status {new_status}" + ( + " (aborted)" if new_status == "Archived" and is_aborted else "" ) + + if new_status != "Available": + return_message += delete_message + "." + else: + 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} - @dbsession @staticmethod - def set_as_busy(project: models.Project) -> None: - """Set project as busy.""" - project.busy = True + @dbsession + def set_busy(project: models.Project, busy: bool) -> None: + """Set project as not busy.""" + project.busy = busy db.session.commit() - flask.current_app.logger.info(f"Project '{project.public_id}' set as busy.") + flask.current_app.logger.info(f"Busy status set. Project: '{project.public_id}', Busy: {busy}") def check_transition_possible(self, current_status, new_status): """Check if the transition is valid.""" From 64f60ef2c716945f38e90b17ca1e5b25e62b6de9 Mon Sep 17 00:00:00 2001 From: Ina Date: Thu, 8 Sep 2022 11:50:21 +0200 Subject: [PATCH 08/25] changelog and linting --- CHANGELOG.md | 1 + dds_web/api/project.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ced2089..af9e31e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,3 +138,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe ## Sprint (2022-09-02 - 2022-09-16) - Add storage usage information in the Units listing table for Super Admin ([#1264](https://github.com/ScilifelabDataCentre/dds_web/pull/1264)) +- Check for if project busy before status change ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 79ce4b7b6..bd8b0cad5 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -103,7 +103,9 @@ def post(self): json_input = flask.request.json new_status = json_input.get("new_status") if not new_status: - raise DDSArgumentError(message="No status transition provided. Specify the new status.") + raise DDSArgumentError( + message="No status transition provided. Specify the new status." + ) # Override default to send email send_email = json_input.get("send_email", True) @@ -186,7 +188,9 @@ def set_busy(project: models.Project, busy: bool) -> None: """Set project as not busy.""" project.busy = busy db.session.commit() - flask.current_app.logger.info(f"Busy status set. Project: '{project.public_id}', Busy: {busy}") + flask.current_app.logger.info( + f"Busy status set. Project: '{project.public_id}', Busy: {busy}" + ) def check_transition_possible(self, current_status, new_status): """Check if the transition is valid.""" From 93e405bb43ec4fb1989e6fe5a7f02d7c55f9f9bd Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 8 Sep 2022 14:43:02 +0200 Subject: [PATCH 09/25] return instead for raise ProjectBusyError --- dds_web/api/project.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index e8fd228cb..9ebe2a6c5 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -891,7 +891,8 @@ def put(self): if set_to_busy: # Check if project is busy if project.busy: - raise ProjectBusyError(message="The project is already busy, cannot proceed.") + # raise ProjectBusyError(message="The project is already busy, cannot proceed.") + return {"ok": False, "message": "The project is already busy, cannot proceed."} # Set project as busy project.busy = True From 05bd2f86894e6f97c7ef21d433ffaaea10ea7b1e Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 8 Sep 2022 14:44:40 +0200 Subject: [PATCH 10/25] tests for ProjectBusy --- tests/__init__.py | 1 + tests/conftest.py | 9 +++ tests/test_project_busy.py | 123 +++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 tests/test_project_busy.py diff --git a/tests/__init__.py b/tests/__init__.py index ce6f46f03..5ba91be63 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -198,6 +198,7 @@ class DDSEndpoint: PROJECT_CREATE = BASE_ENDPOINT + "/proj/create" PROJECT_STATUS = BASE_ENDPOINT + "/proj/status" PROJECT_ACCESS = BASE_ENDPOINT + "/proj/access" + PROJECT_BUSY = BASE_ENDPOINT + "/proj/busy" # Listing urls LIST_PROJ = BASE_ENDPOINT + "/proj/list" diff --git a/tests/conftest.py b/tests/conftest.py index 4675167b2..e31b07935 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,6 +199,15 @@ def demo_data(): "from this project. Create a new project to test the entire system. ", pi="PI", bucket=f"publicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", + busy=False, + ), + Project( + public_id="busy_project_id", + title="busy project", + description="This is a test project with busy set to True. Can be used with unitadmin", + pi="PI", + bucket=f"busypublicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", + busy=True, ), Project( public_id="unused_project_id", diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py new file mode 100644 index 000000000..9f0240a8b --- /dev/null +++ b/tests/test_project_busy.py @@ -0,0 +1,123 @@ +# IMPORTS ################################################################################ IMPORTS # + +# Standard library +import http + +# Own +from dds_web import db +from dds_web.database import models +import tests + + +# CONFIG ################################################################################## CONFIG # + +proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} +proj_query = {"project": "public_project_id"} +busy_proj_query = {"project": "busy_project_id"} +# proj_query_restricted = {"project": "restricted_project_id"} + +# TESTS #################################################################################### TESTS # + + +def test_set_busy_no_token(client): + """Token required to set project busy/not busy.""" + response = client.put(tests.DDSEndpoint.PROJECT_BUSY, headers=tests.DEFAULT_HEADER) + assert response.status_code == http.HTTPStatus.UNAUTHORIZED + assert response.json.get("message") + assert "No token" in response.json.get("message") + + +def test_set_busy_superadmin_not_allowed(client): + """Super admin cannot set project busy/not busy.""" + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + +def test_set_busy_no_args(client): + """Args required to set busy/not busy.""" + # Unit Personnel + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing" in response.json.get("message") + + # Unit Personnel + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing" in response.json.get("message") + + # Researcher + token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing" in response.json.get("message") + + +def test_set_busy_no_busy(client): + """busy bool required.""" + token = tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string=proj_query, + json={"something": "notabool"}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Are you trying to set the project as busy or not busy?" in response.json.get("message") + + +def test_set_busy_true(client): + """Set project as busy.""" + token = tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string=proj_query, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + assert f"Project {proj_query.get('project')} was set to busy." in response.json.get("message") + + +def test_set_busy_false(client): + """Set project as not busy.""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string=busy_proj_query, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + assert f"Project {busy_proj_query.get('project')} was set to not busy." in response.json.get( + "message" + ) + + +def test_set_busy_project_already_busy(client): + """Set a busy project as busy.""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string=busy_proj_query, + json={"busy": True}, + ) + # assert response.status_code == http.HTTPStatus.OK + assert "The project is already busy, cannot proceed." in response.json.get("message") From a43f7966e6135877fd015439adbec1f175a128f3 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Thu, 8 Sep 2022 16:00:50 +0200 Subject: [PATCH 11/25] fix the tests --- tests/conftest.py | 17 +++++++++-------- tests/test_project_busy.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e31b07935..d30bb72b3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -201,14 +201,6 @@ def demo_data(): bucket=f"publicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", busy=False, ), - Project( - public_id="busy_project_id", - title="busy project", - description="This is a test project with busy set to True. Can be used with unitadmin", - pi="PI", - bucket=f"busypublicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", - busy=True, - ), Project( public_id="unused_project_id", title="unused project", @@ -222,6 +214,7 @@ def demo_data(): description="This is a test project without user access for the current research users", pi="PI", bucket=f"eliteprojectid-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", + busy=True, ), Project( public_id="second_public_project_id", @@ -244,6 +237,14 @@ def demo_data(): pi="unit 2 testing project PI", bucket="bucket-2", ), + # Project( + # public_id="busy_project_id", + # title="busy project", + # description="This is a test project with busy set to True. Can be used with unitadmin", + # pi="PI", + # bucket=f"busypublicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", + # busy=True, + # ), ] files_and_versions = [ diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py index 9f0240a8b..9575e0ec7 100644 --- a/tests/test_project_busy.py +++ b/tests/test_project_busy.py @@ -13,7 +13,7 @@ proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} proj_query = {"project": "public_project_id"} -busy_proj_query = {"project": "busy_project_id"} +busy_proj_query = {"project": "restricted_project_id"} # proj_query_restricted = {"project": "restricted_project_id"} # TESTS #################################################################################### TESTS # From bca392eaa2cb460d03bb90e7924cf607ebe2b8fb Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Fri, 9 Sep 2022 08:42:05 +0200 Subject: [PATCH 12/25] return instead of raise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ina Odén Österbo <35953392+inaod568@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 9ebe2a6c5..fbf70394a 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -899,7 +899,7 @@ def put(self): else: # Check if project is not busy if not project.busy: - raise ProjectBusyError(message="The project is not busy, cannot proceed.") + return {"ok": False, "message": "The project is already not busy, cannot proceed."} # Set project to not busy project.busy = False From b6e2fd9bdc6f746a27d27edfd7226d91125aca87 Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Fri, 9 Sep 2022 09:28:37 +0200 Subject: [PATCH 13/25] more tests fixes --- dds_web/api/project.py | 2 +- tests/test_project_busy.py | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index fbf70394a..3a333229a 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -886,7 +886,7 @@ def put(self): # Get busy or not busy set_to_busy = flask.request.json.get("busy") if set_to_busy is None: - raise DDSArgumentError(message="Are you trying to set the project as busy or not busy?") + raise DDSArgumentError(message="Missing information about setting busy or not busy?") if set_to_busy: # Check if project is busy diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py index 9575e0ec7..0c2aa5ad2 100644 --- a/tests/test_project_busy.py +++ b/tests/test_project_busy.py @@ -12,7 +12,7 @@ # CONFIG ################################################################################## CONFIG # proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} -proj_query = {"project": "public_project_id"} +not_busy_proj_query = {"project": "public_project_id"} busy_proj_query = {"project": "restricted_project_id"} # proj_query_restricted = {"project": "restricted_project_id"} @@ -48,7 +48,7 @@ def test_set_busy_no_args(client): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert "Required data missing" in response.json.get("message") - # Unit Personnel + # Unit Admin token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) response = client.put( tests.DDSEndpoint.PROJECT_BUSY, @@ -73,11 +73,11 @@ def test_set_busy_no_busy(client): response = client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, - query_string=proj_query, + query_string=not_busy_proj_query, json={"something": "notabool"}, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Are you trying to set the project as busy or not busy?" in response.json.get("message") + assert "Missing information about setting busy or not busy?" in response.json.get("message") def test_set_busy_true(client): @@ -86,11 +86,26 @@ def test_set_busy_true(client): response = client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, - query_string=proj_query, + query_string=not_busy_proj_query, json={"busy": True}, ) assert response.status_code == http.HTTPStatus.OK - assert f"Project {proj_query.get('project')} was set to busy." in response.json.get("message") + assert f"Project {not_busy_proj_query.get('project')} was set to busy." in response.json.get( + "message" + ) + + +def test_set_busy_false(client): + """Set project as busy.""" + token = tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string=not_busy_proj_query, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + assert f"The project is already not busy, cannot proceed." in response.json.get("message") def test_set_busy_false(client): @@ -119,5 +134,5 @@ def test_set_busy_project_already_busy(client): query_string=busy_proj_query, json={"busy": True}, ) - # assert response.status_code == http.HTTPStatus.OK + assert response.status_code == http.HTTPStatus.OK assert "The project is already busy, cannot proceed." in response.json.get("message") From 7db3391a19a9238d859f824a77bc5ef2d1f90adb Mon Sep 17 00:00:00 2001 From: valyo <582646+valyo@users.noreply.github.com> Date: Fri, 9 Sep 2022 10:05:54 +0200 Subject: [PATCH 14/25] tests --- dds_web/api/project.py | 9 ++++++--- tests/test_project_busy.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 3a333229a..1f2683993 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -886,20 +886,23 @@ def put(self): # Get busy or not busy set_to_busy = flask.request.json.get("busy") if set_to_busy is None: - raise DDSArgumentError(message="Missing information about setting busy or not busy?") + raise DDSArgumentError(message="Missing information about setting busy or not busy.") if set_to_busy: # Check if project is busy if project.busy: # raise ProjectBusyError(message="The project is already busy, cannot proceed.") - return {"ok": False, "message": "The project is already busy, cannot proceed."} + return {"ok": False, "message": "The project is already busy, cannot proceed."}, 200 # Set project as busy project.busy = True else: # Check if project is not busy if not project.busy: - return {"ok": False, "message": "The project is already not busy, cannot proceed."} + return { + "ok": False, + "message": "The project is already not busy, cannot proceed.", + }, 200 # Set project to not busy project.busy = False diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py index 0c2aa5ad2..ecf2eb9cf 100644 --- a/tests/test_project_busy.py +++ b/tests/test_project_busy.py @@ -77,7 +77,7 @@ def test_set_busy_no_busy(client): json={"something": "notabool"}, ) assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Missing information about setting busy or not busy?" in response.json.get("message") + assert "Missing information about setting busy or not busy." in response.json.get("message") def test_set_busy_true(client): From 387b724a4476ce5dfe54e0f9d72129f4c0b6b962 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 10:59:24 +0200 Subject: [PATCH 15/25] tests should be fixed now --- dds_web/api/project.py | 5 +- tests/api/test_project.py | 187 +++++++++++++++++++++++++++++++++++++ tests/conftest.py | 2 - tests/test_project_busy.py | 117 ----------------------- 4 files changed, 188 insertions(+), 123 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 1f2683993..82a0615d9 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -899,10 +899,7 @@ def put(self): else: # Check if project is not busy if not project.busy: - return { - "ok": False, - "message": "The project is already not busy, cannot proceed.", - }, 200 + return {"ok": False, "message": "The project is already not busy, cannot proceed."}, 200 # Set project to not busy project.busy = False diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 8687f6871..1f07d048b 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1235,3 +1235,190 @@ def test_project_public_facility_put(module_client): assert response.status_code == http.HTTPStatus.OK response_json = response.json assert response_json.get("public") + +# ProjectBusy + +def test_set_busy_no_token(client): + """Token required to set project busy/not busy.""" + response = client.put(tests.DDSEndpoint.PROJECT_BUSY, headers=tests.DEFAULT_HEADER) + assert response.status_code == http.HTTPStatus.UNAUTHORIZED + assert response.json.get("message") + assert "No token" in response.json.get("message") + + +def test_set_busy_superadmin_not_allowed(client): + """Super admin cannot set project busy/not busy.""" + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + + +def test_set_busy_no_args(client): + """Args required to set busy/not busy.""" + # Unit Personnel + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing" in response.json.get("message") + + # Unit Admin + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing" in response.json.get("message") + + # Researcher + token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Required data missing" in response.json.get("message") + + +def test_set_busy_no_busy(client): + """busy bool required.""" + for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: + # Get user + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project + project = user.projects[0] + assert project + + # Authenticate and run + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"something": "notabool"}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "Missing information about setting busy or not busy." in response.json.get("message") + + +def test_set_busy_true(client): + """Set project as busy.""" + for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: + # Get user + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project + project = user.projects[0] + assert project + + # Set project to not busy + project.busy = False + db.session.commit() + assert not project.busy + + # Authenticate and run + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + assert f"Project {project.public_id} was set to busy." in response.json.get( + "message" + ) + + +def test_set_not_busy_project_already_not_busy(client): + """Set project as busy.""" + for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: + # Get user + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project + project = user.projects[0] + assert project + + # Set project to not busy + project.busy = False + db.session.commit() + assert not project.busy + + # Authenticate and run + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + assert f"The project is already not busy, cannot proceed." in response.json.get("message") + + +def test_set_busy_false(client): + """Set project as not busy.""" + for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: + # Get user + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project + project = user.projects[0] + assert project + + # Set project to busy + project.busy = True + db.session.commit() + assert project.busy + + # Authenticate and run + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": False}, + ) + assert response.status_code == http.HTTPStatus.OK + assert f"Project {project.public_id} was set to not busy." in response.json.get( + "message" + ) + + +def test_set_busy_project_already_busy(client): + """Set a busy project as busy.""" + for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: + # Get user + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project + project = user.projects[0] + assert project + + # Set project to busy + project.busy = True + db.session.commit() + assert project.busy + + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) + response = client.put( + tests.DDSEndpoint.PROJECT_BUSY, + headers=token, + query_string={"project": project.public_id}, + json={"busy": True}, + ) + assert response.status_code == http.HTTPStatus.OK + assert "The project is already busy, cannot proceed." in response.json.get("message") diff --git a/tests/conftest.py b/tests/conftest.py index d30bb72b3..c6f75bc8f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,7 +199,6 @@ def demo_data(): "from this project. Create a new project to test the entire system. ", pi="PI", bucket=f"publicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", - busy=False, ), Project( public_id="unused_project_id", @@ -214,7 +213,6 @@ def demo_data(): description="This is a test project without user access for the current research users", pi="PI", bucket=f"eliteprojectid-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", - busy=True, ), Project( public_id="second_public_project_id", diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py index ecf2eb9cf..cae1f6ca1 100644 --- a/tests/test_project_busy.py +++ b/tests/test_project_busy.py @@ -19,120 +19,3 @@ # TESTS #################################################################################### TESTS # -def test_set_busy_no_token(client): - """Token required to set project busy/not busy.""" - response = client.put(tests.DDSEndpoint.PROJECT_BUSY, headers=tests.DEFAULT_HEADER) - assert response.status_code == http.HTTPStatus.UNAUTHORIZED - assert response.json.get("message") - assert "No token" in response.json.get("message") - - -def test_set_busy_superadmin_not_allowed(client): - """Super admin cannot set project busy/not busy.""" - token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - ) - assert response.status_code == http.HTTPStatus.FORBIDDEN - - -def test_set_busy_no_args(client): - """Args required to set busy/not busy.""" - # Unit Personnel - token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Required data missing" in response.json.get("message") - - # Unit Admin - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Required data missing" in response.json.get("message") - - # Researcher - token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Required data missing" in response.json.get("message") - - -def test_set_busy_no_busy(client): - """busy bool required.""" - token = tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - query_string=not_busy_proj_query, - json={"something": "notabool"}, - ) - assert response.status_code == http.HTTPStatus.BAD_REQUEST - assert "Missing information about setting busy or not busy." in response.json.get("message") - - -def test_set_busy_true(client): - """Set project as busy.""" - token = tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - query_string=not_busy_proj_query, - json={"busy": True}, - ) - assert response.status_code == http.HTTPStatus.OK - assert f"Project {not_busy_proj_query.get('project')} was set to busy." in response.json.get( - "message" - ) - - -def test_set_busy_false(client): - """Set project as busy.""" - token = tests.UserAuth(tests.USER_CREDENTIALS["projectowner"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - query_string=not_busy_proj_query, - json={"busy": False}, - ) - assert response.status_code == http.HTTPStatus.OK - assert f"The project is already not busy, cannot proceed." in response.json.get("message") - - -def test_set_busy_false(client): - """Set project as not busy.""" - - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - query_string=busy_proj_query, - json={"busy": False}, - ) - assert response.status_code == http.HTTPStatus.OK - assert f"Project {busy_proj_query.get('project')} was set to not busy." in response.json.get( - "message" - ) - - -def test_set_busy_project_already_busy(client): - """Set a busy project as busy.""" - - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) - response = client.put( - tests.DDSEndpoint.PROJECT_BUSY, - headers=token, - query_string=busy_proj_query, - json={"busy": True}, - ) - assert response.status_code == http.HTTPStatus.OK - assert "The project is already busy, cannot proceed." in response.json.get("message") From bf4a649d9412d29812ea071b648372cc8464fd80 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 11:00:13 +0200 Subject: [PATCH 16/25] linting --- dds_web/api/project.py | 5 ++++- tests/api/test_project.py | 32 +++++++++++++++----------------- tests/test_project_busy.py | 2 -- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index 82a0615d9..1f2683993 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -899,7 +899,10 @@ def put(self): else: # Check if project is not busy if not project.busy: - return {"ok": False, "message": "The project is already not busy, cannot proceed."}, 200 + return { + "ok": False, + "message": "The project is already not busy, cannot proceed.", + }, 200 # Set project to not busy project.busy = False diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 1f07d048b..82faa94dc 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1236,8 +1236,10 @@ def test_project_public_facility_put(module_client): response_json = response.json assert response_json.get("public") + # ProjectBusy + def test_set_busy_no_token(client): """Token required to set project busy/not busy.""" response = client.put(tests.DDSEndpoint.PROJECT_BUSY, headers=tests.DEFAULT_HEADER) @@ -1295,9 +1297,9 @@ def test_set_busy_no_busy(client): # Get project project = user.projects[0] - assert project + assert project - # Authenticate and run + # Authenticate and run token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) response = client.put( tests.DDSEndpoint.PROJECT_BUSY, @@ -1318,8 +1320,8 @@ def test_set_busy_true(client): # Get project project = user.projects[0] - assert project - + assert project + # Set project to not busy project.busy = False db.session.commit() @@ -1334,9 +1336,7 @@ def test_set_busy_true(client): json={"busy": True}, ) assert response.status_code == http.HTTPStatus.OK - assert f"Project {project.public_id} was set to busy." in response.json.get( - "message" - ) + assert f"Project {project.public_id} was set to busy." in response.json.get("message") def test_set_not_busy_project_already_not_busy(client): @@ -1348,8 +1348,8 @@ def test_set_not_busy_project_already_not_busy(client): # Get project project = user.projects[0] - assert project - + assert project + # Set project to not busy project.busy = False db.session.commit() @@ -1376,14 +1376,14 @@ def test_set_busy_false(client): # Get project project = user.projects[0] - assert project - + assert project + # Set project to busy project.busy = True db.session.commit() assert project.busy - # Authenticate and run + # Authenticate and run token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) response = client.put( tests.DDSEndpoint.PROJECT_BUSY, @@ -1392,9 +1392,7 @@ def test_set_busy_false(client): json={"busy": False}, ) assert response.status_code == http.HTTPStatus.OK - assert f"Project {project.public_id} was set to not busy." in response.json.get( - "message" - ) + assert f"Project {project.public_id} was set to not busy." in response.json.get("message") def test_set_busy_project_already_busy(client): @@ -1406,8 +1404,8 @@ def test_set_busy_project_already_busy(client): # Get project project = user.projects[0] - assert project - + assert project + # Set project to busy project.busy = True db.session.commit() diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py index cae1f6ca1..2b866fe4a 100644 --- a/tests/test_project_busy.py +++ b/tests/test_project_busy.py @@ -17,5 +17,3 @@ # proj_query_restricted = {"project": "restricted_project_id"} # TESTS #################################################################################### TESTS # - - From 6b1673e522612fafee7d72ddbbc3c279b4292a3d Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 11:01:12 +0200 Subject: [PATCH 17/25] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ced2089..4f085f43a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -138,3 +138,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe ## Sprint (2022-09-02 - 2022-09-16) - Add storage usage information in the Units listing table for Super Admin ([#1264](https://github.com/ScilifelabDataCentre/dds_web/pull/1264)) +- New endpoint for setting project as busy / not busy ([#1266](https://github.com/ScilifelabDataCentre/dds_web/pull/1266)) From 77f97b82e119556fba340413c7954134ca8bd3ed Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 11:01:49 +0200 Subject: [PATCH 18/25] removed 200 from return --- 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 1f2683993..b87ad70be 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -892,7 +892,7 @@ def put(self): # Check if project is busy if project.busy: # raise ProjectBusyError(message="The project is already busy, cannot proceed.") - return {"ok": False, "message": "The project is already busy, cannot proceed."}, 200 + return {"ok": False, "message": "The project is already busy, cannot proceed."} # Set project as busy project.busy = True @@ -902,7 +902,7 @@ def put(self): return { "ok": False, "message": "The project is already not busy, cannot proceed.", - }, 200 + } # Set project to not busy project.busy = False From 8e8c68682ee088d77abefa69a36c4e1a2fa031c4 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 11:03:34 +0200 Subject: [PATCH 19/25] commented project not needed --- tests/conftest.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c6f75bc8f..4675167b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -235,14 +235,6 @@ def demo_data(): pi="unit 2 testing project PI", bucket="bucket-2", ), - # Project( - # public_id="busy_project_id", - # title="busy project", - # description="This is a test project with busy set to True. Can be used with unitadmin", - # pi="PI", - # bucket=f"busypublicproj-{str(timestamp(ts_format='%Y%m%d%H%M%S'))}-{str(uuid.uuid4())}", - # busy=True, - # ), ] files_and_versions = [ From 0fc9b116a91774e82dc8e80dddd3174e7cc95920 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 11:04:23 +0200 Subject: [PATCH 20/25] remove test file since tests moved to api folder --- tests/test_project_busy.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 tests/test_project_busy.py diff --git a/tests/test_project_busy.py b/tests/test_project_busy.py deleted file mode 100644 index 2b866fe4a..000000000 --- a/tests/test_project_busy.py +++ /dev/null @@ -1,19 +0,0 @@ -# IMPORTS ################################################################################ IMPORTS # - -# Standard library -import http - -# Own -from dds_web import db -from dds_web.database import models -import tests - - -# CONFIG ################################################################################## CONFIG # - -proj_data = {"pi": "piName", "title": "Test proj", "description": "A longer project description"} -not_busy_proj_query = {"project": "public_project_id"} -busy_proj_query = {"project": "restricted_project_id"} -# proj_query_restricted = {"project": "restricted_project_id"} - -# TESTS #################################################################################### TESTS # From cbcd7b2d52d0ee5d45c42be8bdf228e74432fb2f Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 11:36:35 +0200 Subject: [PATCH 21/25] change to module_client --- tests/api/test_project.py | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 82faa94dc..e09ff9bc0 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -1240,29 +1240,29 @@ def test_project_public_facility_put(module_client): # ProjectBusy -def test_set_busy_no_token(client): +def test_set_busy_no_token(module_client): """Token required to set project busy/not busy.""" - response = client.put(tests.DDSEndpoint.PROJECT_BUSY, headers=tests.DEFAULT_HEADER) + response = module_client.put(tests.DDSEndpoint.PROJECT_BUSY, headers=tests.DEFAULT_HEADER) assert response.status_code == http.HTTPStatus.UNAUTHORIZED assert response.json.get("message") assert "No token" in response.json.get("message") -def test_set_busy_superadmin_not_allowed(client): +def test_set_busy_superadmin_not_allowed(module_client): """Super admin cannot set project busy/not busy.""" - token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS["superadmin"]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, ) assert response.status_code == http.HTTPStatus.FORBIDDEN -def test_set_busy_no_args(client): +def test_set_busy_no_args(module_client): """Args required to set busy/not busy.""" # Unit Personnel - token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, ) @@ -1270,8 +1270,8 @@ def test_set_busy_no_args(client): assert "Required data missing" in response.json.get("message") # Unit Admin - token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, ) @@ -1279,8 +1279,8 @@ def test_set_busy_no_args(client): assert "Required data missing" in response.json.get("message") # Researcher - token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, ) @@ -1288,7 +1288,7 @@ def test_set_busy_no_args(client): assert "Required data missing" in response.json.get("message") -def test_set_busy_no_busy(client): +def test_set_busy_no_busy(module_client): """busy bool required.""" for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: # Get user @@ -1300,8 +1300,8 @@ def test_set_busy_no_busy(client): assert project # Authenticate and run - token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, @@ -1311,7 +1311,7 @@ def test_set_busy_no_busy(client): assert "Missing information about setting busy or not busy." in response.json.get("message") -def test_set_busy_true(client): +def test_set_busy_true(module_client): """Set project as busy.""" for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: # Get user @@ -1328,8 +1328,8 @@ def test_set_busy_true(client): assert not project.busy # Authenticate and run - token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, @@ -1339,7 +1339,7 @@ def test_set_busy_true(client): assert f"Project {project.public_id} was set to busy." in response.json.get("message") -def test_set_not_busy_project_already_not_busy(client): +def test_set_not_busy_project_already_not_busy(module_client): """Set project as busy.""" for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: # Get user @@ -1356,8 +1356,8 @@ def test_set_not_busy_project_already_not_busy(client): assert not project.busy # Authenticate and run - token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, @@ -1367,7 +1367,7 @@ def test_set_not_busy_project_already_not_busy(client): assert f"The project is already not busy, cannot proceed." in response.json.get("message") -def test_set_busy_false(client): +def test_set_busy_false(module_client): """Set project as not busy.""" for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: # Get user @@ -1384,8 +1384,8 @@ def test_set_busy_false(client): assert project.busy # Authenticate and run - token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, @@ -1395,7 +1395,7 @@ def test_set_busy_false(client): assert f"Project {project.public_id} was set to not busy." in response.json.get("message") -def test_set_busy_project_already_busy(client): +def test_set_busy_project_already_busy(module_client): """Set a busy project as busy.""" for username in ["researchuser", "projectowner", "unituser", "unitadmin"]: # Get user @@ -1411,8 +1411,8 @@ def test_set_busy_project_already_busy(client): db.session.commit() assert project.busy - token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(client) - response = client.put( + token = tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client) + response = module_client.put( tests.DDSEndpoint.PROJECT_BUSY, headers=token, query_string={"project": project.public_id}, From daa85a5c0137196d3c64728f303a4756df339288 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 13:55:42 +0200 Subject: [PATCH 22/25] unnecessary commit --- dds_web/api/project.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dds_web/api/project.py b/dds_web/api/project.py index dca458b9a..7dfd94de6 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -186,11 +186,10 @@ def post(self): @dbsession def set_busy(project: models.Project, busy: bool) -> None: """Set project as not busy.""" - project.busy = busy - db.session.commit() flask.current_app.logger.info( - f"Busy status set. Project: '{project.public_id}', Busy: {busy}" + f"Setting busy status. Project: '{project.public_id}', Busy: {busy}" ) + project.busy = busy def check_transition_possible(self, current_status, new_status): """Check if the transition is valid.""" From 53f673ee5dce5ec7e8c03c61a97810d0e9878ef5 Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 14:35:01 +0200 Subject: [PATCH 23/25] tests --- tests/api/test_project.py | 80 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index e09ff9bc0..dc075918b 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -184,8 +184,88 @@ def test_projectstatus_get_status_with_non_accessible_project(module_client, bot assert "Project access denied." in response.json["message"] +# set_busy + +def test_set_busy_true(module_client): + """Test set busy to true.""" + from dds_web.api import project + + # Get project + project_obj = models.Project.query.first() + assert project_obj + + # Set as not busy + project_obj.busy = False + db.session.commit() + + # Run function + project.ProjectStatus.set_busy(project=project_obj, busy=True) + assert project_obj.busy + +def test_set_busy_false(module_client): + """Test set busy to false.""" + from dds_web.api import project + + # Get project + project_obj = models.Project.query.first() + assert project_obj + + # Set as not busy + project_obj.busy = True + db.session.commit() + + # Run function + project.ProjectStatus.set_busy(project=project_obj, busy=False) + assert not project_obj.busy + # post +def test_projectstatus_when_busy(module_client): + """Status change should not be possible when project is busy.""" + # Get user + username = "unitadmin" + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project and set to busy + project = user.projects[0] + project.busy = True + db.session.commit() + assert project.busy + + # Attempt to change status + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client), + query_string={"project": project.public_id}, + json={"something": "something"}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert f"The project '{project.public_id}' is currently busy" in response.json.get("message") + +def test_projectstatus_when_not_busy_but_invalid(module_client): + """Status change which results in an exception should also reset busy to False.""" + # Get user + username = "unitadmin" + user = models.User.query.filter_by(username=username).one_or_none() + assert user + + # Get project and set as not busy + project = user.projects[0] + project.busy = False + db.session.commit() + assert not project.busy + + # Attempt to change status + response = module_client.post( + tests.DDSEndpoint.PROJECT_STATUS, + headers=tests.UserAuth(tests.USER_CREDENTIALS[username]).token(module_client), + query_string={"project": project.public_id}, + json={"new_status": ""}, + ) + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert "No status transition provided. Specify the new status." in response.json.get("message") + assert not project.busy def test_projectstatus_submit_request_with_invalid_args(module_client, boto3_session): """Submit status request with invalid arguments""" From 42de7ecaf38573764ae1c5f747bd360f9408bb5b Mon Sep 17 00:00:00 2001 From: Ina Date: Fri, 9 Sep 2022 14:35:59 +0200 Subject: [PATCH 24/25] linting --- 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 dc075918b..af38c1b9b 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -186,6 +186,7 @@ def test_projectstatus_get_status_with_non_accessible_project(module_client, bot # set_busy + def test_set_busy_true(module_client): """Test set busy to true.""" from dds_web.api import project @@ -199,9 +200,10 @@ def test_set_busy_true(module_client): db.session.commit() # Run function - project.ProjectStatus.set_busy(project=project_obj, busy=True) + project.ProjectStatus.set_busy(project=project_obj, busy=True) assert project_obj.busy + def test_set_busy_false(module_client): """Test set busy to false.""" from dds_web.api import project @@ -215,11 +217,13 @@ def test_set_busy_false(module_client): db.session.commit() # Run function - project.ProjectStatus.set_busy(project=project_obj, busy=False) + project.ProjectStatus.set_busy(project=project_obj, busy=False) assert not project_obj.busy + # post + def test_projectstatus_when_busy(module_client): """Status change should not be possible when project is busy.""" # Get user @@ -243,6 +247,7 @@ def test_projectstatus_when_busy(module_client): assert response.status_code == http.HTTPStatus.BAD_REQUEST assert f"The project '{project.public_id}' is currently busy" in response.json.get("message") + def test_projectstatus_when_not_busy_but_invalid(module_client): """Status change which results in an exception should also reset busy to False.""" # Get user @@ -267,6 +272,7 @@ def test_projectstatus_when_not_busy_but_invalid(module_client): assert "No status transition provided. Specify the new status." in response.json.get("message") assert not project.busy + def test_projectstatus_submit_request_with_invalid_args(module_client, boto3_session): """Submit status request with invalid arguments""" # Create unit admins to allow project creation From 8c2bcab2d4d79cbd7affa49e9915eba8eb073820 Mon Sep 17 00:00:00 2001 From: Ina Date: Tue, 13 Sep 2022 08:51:39 +0200 Subject: [PATCH 25/25] new 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 a82b376d2..72f26f596 100644 --- a/dds_web/version.py +++ b/dds_web/version.py @@ -1 +1 @@ -__version__ = "1.1.1" +__version__ = "1.1.2" diff --git a/tests/test_version.py b/tests/test_version.py index 0d1fc3019..be2d10076 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,4 +2,4 @@ def test_version(): - assert version.__version__ == "1.1.1" + assert version.__version__ == "1.1.2"