diff --git a/CHANGELOG.md b/CHANGELOG.md index ae1445504..5e4d574a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -165,3 +165,4 @@ Please add a _short_ line describing the PR you make, if the PR implements a spe - Add link to the dds instance to the end of all emails ([#1305](https://github.com/ScilifelabDataCentre/dds_web/pull/1305)) - Troubleshooting steps added to web page ([#1309](https://github.com/ScilifelabDataCentre/dds_web/pull/1309)) - Bug: Return instead of project creator if user has been deleted ([#1311](https://github.com/ScilifelabDataCentre/dds_web/pull/1311)) +- New endpoint: ProjectInfo - display project information ([#1310](https://github.com/ScilifelabDataCentre/dds_web/pull/1310)) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 5168f6beb..926c6ec39 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -61,6 +61,7 @@ def output_json(data, code, headers=None): 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") +api.add_resource(project.ProjectInfo, "/proj/info", endpoint="project_info") # 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 19deeab81..d07d2fb78 100644 --- a/dds_web/api/project.py +++ b/dds_web/api/project.py @@ -25,6 +25,7 @@ dbsession, json_required, handle_validation_errors, + handle_db_error, ) from dds_web.errors import ( AccessDeniedError, @@ -949,3 +950,30 @@ def put(self): "ok": True, "message": f"Project {project_id} was set to {'busy' if set_to_busy else 'not busy'}.", } + + +class ProjectInfo(flask_restful.Resource): + """Get information for a specific project.""" + + @auth.login_required + @logging_bind_request + @handle_db_error + def get(self): + # 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) + + # Construct a dict with info items + project_info = { + "Project ID": project.public_id, + "Created by": project.creator.name if project.creator else "Former User", + "Status": project.current_status, + "Last updated": project.date_updated, + "Size": project.size, + "Title": project.title, + "Description": project.description, + } + + return_info = {"project_info": project_info} + return return_info diff --git a/tests/__init__.py b/tests/__init__.py index 3055bfa83..0b8a93735 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -200,6 +200,7 @@ class DDSEndpoint: PROJECT_ACCESS = BASE_ENDPOINT + "/proj/access" PROJECT_BUSY = BASE_ENDPOINT + "/proj/busy" PROJECT_BUSY_ANY = BASE_ENDPOINT + "/proj/busy/any" + PROJECT_INFO = BASE_ENDPOINT + "/proj/info" # Listing urls LIST_PROJ = BASE_ENDPOINT + "/proj/list" diff --git a/tests/test_project_info_listing.py b/tests/test_project_info_listing.py new file mode 100644 index 000000000..14816a7e1 --- /dev/null +++ b/tests/test_project_info_listing.py @@ -0,0 +1,109 @@ +# IMPORTS ################################################################################ IMPORTS # + +# Standard library +import http +import unittest + +# Own +import tests + + +# CONFIG ################################################################################## CONFIG # + +proj_info_items = [ + "Project ID", + "Created by", + "Status", + "Last updated", + "Size", + "Title", + "Description", +] +proj_query = {"project": "public_project_id"} +proj_query_restricted = {"project": "restricted_project_id"} + +# TESTS #################################################################################### TESTS # + + +def test_list_proj_info_no_token(client): + """Token required to list project information""" + + response = client.get(tests.DDSEndpoint.PROJECT_INFO, headers=tests.DEFAULT_HEADER) + assert response.status_code == http.HTTPStatus.UNAUTHORIZED + response_json = response.json + assert response_json.get("message") + assert "No token" in response_json.get("message") + + +def test_list_proj_info_without_project(client): + """Attempting to get the project information without specifying a project""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["unituser"]).token(client) + response = client.get(tests.DDSEndpoint.PROJECT_INFO, headers=token) + response_json = response.json + assert "Missing required information: 'project'" in response_json.get("message") + + +def test_list_proj_info_access_granted(client): + """Researcher should be able to list project information""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client) + response = client.get(tests.DDSEndpoint.PROJECT_INFO, headers=token, query_string=proj_query) + assert response.status_code == http.HTTPStatus.OK + response_json = response.json + project_info = response_json.get("project_info") + + assert "public_project_id" == project_info.get("Project ID") + # check that endpoint returns dictionary and not a list + assert isinstance(project_info, dict) + + +def test_list_proj_info_unit_user(client): + """Unit user should be able to list project information""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.get(tests.DDSEndpoint.PROJECT_INFO, headers=token, query_string=proj_query) + assert response.status_code == http.HTTPStatus.OK + response_json = response.json + project_info = response_json.get("project_info") + + assert "public_project_id" == project_info.get("Project ID") + assert ( + "This is a test project. You will be able to upload to but NOT download" + in project_info.get("Description") + ) + assert "Size" in project_info.keys() and project_info["Size"] is not None + + +def test_list_proj_info_returned_items(client): + """Returned project information should contain certain items""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client) + response = client.get(tests.DDSEndpoint.PROJECT_INFO, headers=token, query_string=proj_query) + assert response.status_code == http.HTTPStatus.OK + response_json = response.json + project_info = response_json.get("project_info") + + assert all(item in project_info for item in proj_info_items) + + +def test_list_project_info_by_researchuser_not_in_project(client): + """Researchuser not in project should not be able to list project info""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser2"]).token(client) + response = client.get(tests.DDSEndpoint.PROJECT_INFO, query_string=proj_query, headers=token) + assert response.status_code == http.HTTPStatus.FORBIDDEN + response_json = response.json + assert "Project access denied" in response_json.get("message") + + +def test_list_proj_info_public_insufficient_credentials(client): + """If the project access has not been granted, the project info should not be provided.""" + + token = tests.UserAuth(tests.USER_CREDENTIALS["researchuser"]).token(client) + response = client.get( + tests.DDSEndpoint.PROJECT_INFO, query_string=proj_query_restricted, headers=token + ) + assert response.status_code == http.HTTPStatus.FORBIDDEN + response_json = response.json + assert "Project access denied" in response_json.get("message")