diff --git a/backend/api/projects/teams.py b/backend/api/projects/teams.py index ed4828d527..aaa6003adc 100644 --- a/backend/api/projects/teams.py +++ b/backend/api/projects/teams.py @@ -31,7 +31,7 @@ def get(self, project_id): responses: 200: description: Teams listed successfully - 403: + 401: description: Forbidden, if user is not authenticated 404: description: Not found @@ -41,6 +41,8 @@ def get(self, project_id): try: teams_dto = TeamService.get_project_teams_as_dto(project_id) return teams_dto.to_primitive(), 200 + except NotFound: + return {"Error": "Project Not Found", "SubCode": "NotFound"}, 404 except Exception as e: error_msg = f"Team GET - unhandled error: {str(e)}" current_app.logger.critical(error_msg) @@ -85,9 +87,9 @@ def post(self, team_id, project_id): 201: description: Team project assignment created 401: - description: Forbidden, if user is not a manager of the project - 403: description: Forbidden, if user is not authenticated + 403: + description: Forbidden, if user is not a manager of the project or team 404: description: Not found 500: @@ -97,7 +99,7 @@ def post(self, team_id, project_id): return { "Error": "User is not an admin or a manager for the team", "SubCode": "UserPermissionError", - }, 401 + }, 403 try: role = request.get_json(force=True)["role"] @@ -107,7 +109,7 @@ def post(self, team_id, project_id): try: if not ProjectAdminService.is_user_action_permitted_on_project( - token_auth.current_user, project_id + token_auth.current_user(), project_id ): raise ValueError() TeamService.add_team_project(team_id, project_id, role) @@ -119,6 +121,8 @@ def post(self, team_id, project_id): }, 201, ) + except NotFound: + return {"Error": "No Project Found", "SubCode": "NotFound"}, 404 except ValueError: return { "Error": "User is not a manager of the project", @@ -184,11 +188,11 @@ def patch(self, team_id, project_id): try: if not ProjectAdminService.is_user_action_permitted_on_project( - token_auth.current_user, project_id + token_auth.current_user(), project_id ): raise ValueError() TeamService.change_team_role(team_id, project_id, role) - return {"Status": "Team role updated successfully."}, 200 + return {"Status": "Team role updated successfully"}, 200 except NotFound as e: return {"Error": str(e), "SubCode": "NotFound"}, 404 except ValueError: @@ -229,9 +233,9 @@ def delete(self, team_id, project_id): 200: description: Team unassigned of the project 401: - description: Forbidden, if user is not a manager of the project - 403: description: Forbidden, if user is not authenticated + 403: + description: Forbidden, if user is not a manager of the project 404: description: Not found 500: @@ -239,7 +243,7 @@ def delete(self, team_id, project_id): """ try: if not ProjectAdminService.is_user_action_permitted_on_project( - token_auth.current_user, project_id + token_auth.current_user(), project_id ): raise ValueError() TeamService.delete_team_project(team_id, project_id) diff --git a/backend/services/team_service.py b/backend/services/team_service.py index 605d9773fc..7bbcc2fef6 100644 --- a/backend/services/team_service.py +++ b/backend/services/team_service.py @@ -16,7 +16,7 @@ from backend.models.dtos.stats_dto import Pagination from backend.models.postgis.message import Message, MessageType from backend.models.postgis.team import Team, TeamMembers -from backend.models.postgis.project import ProjectTeams +from backend.models.postgis.project import Project, ProjectTeams from backend.models.postgis.project_info import ProjectInfo from backend.models.postgis.utils import NotFound from backend.models.postgis.statuses import ( @@ -388,8 +388,12 @@ def get_projects_by_team_id(team_id: int): @staticmethod def get_project_teams_as_dto(project_id: int) -> TeamsListDTO: """Gets all the teams for a specified project""" + project = Project.get(project_id) + if project is None: + raise NotFound() + project_teams = ProjectTeams.query.filter( - ProjectTeams.project_id == project_id + ProjectTeams.project_id == project.id ).all() teams_list_dto = TeamsListDTO() diff --git a/tests/backend/integration/api/projects/test_teams.py b/tests/backend/integration/api/projects/test_teams.py new file mode 100644 index 0000000000..ebc9abd6d1 --- /dev/null +++ b/tests/backend/integration/api/projects/test_teams.py @@ -0,0 +1,238 @@ +from tests.backend.base import BaseTestCase +from tests.backend.helpers.test_helpers import ( + assign_team_to_project, + create_canned_project, + create_canned_team, + return_canned_user, + generate_encoded_token, + TEST_TEAM_NAME, +) +from backend.models.postgis.statuses import UserRole, TeamRoles + + +class TestProjectsTeamsAPI(BaseTestCase): + def setUp(self): + super().setUp() + self.test_project, self.test_author = create_canned_project() + self.test_author.role = UserRole.ADMIN.value + self.test_team = create_canned_team() + self.test_user = return_canned_user("test_user", 11111111) + self.test_user.create() + self.test_author_session_token = generate_encoded_token(self.test_author.id) + self.test_user_session_token = generate_encoded_token(self.test_user.id) + self.all_project_teams_url = f"/api/v2/projects/{self.test_project.id}/teams/" + self.single_project_team_url = ( + f"/api/v2/projects/{self.test_project.id}/teams/{self.test_team.id}/" + ) + self.non_existent_project_team_url = "/api/v2/projects/99/teams/99/" + + # get + def test_get_project_teams_by_unauthenticated_user_fails(self): + """ + Test that endpoint returns 401 when an unauthenticated user retrieves teams + """ + response = self.client.get(self.all_project_teams_url) + response_body = response.get_json() + self.assertEqual(response.status_code, 401) + self.assertEqual(response_body["SubCode"], "InvalidToken") + + def test_get_project_teams_for_non_existent_project_fails(self): + """ + Test that endpoint returns 404 when retrieving teams for non-existent projects + """ + response = self.client.get( + "/api/v2/projects/99/teams/", + headers={"Authorization": self.test_user_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 404) + self.assertEqual(response_body["Error"], "Project Not Found") + self.assertEqual(response_body["SubCode"], "NotFound") + + def test_get_project_teams_passes(self): + """ + Test that endpoint returns 200 when an authenticated user retrieves teams + """ + response = self.client.get( + self.all_project_teams_url, + headers={"Authorization": self.test_user_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response_body["teams"]), 0) + self.assertEqual(response_body["teams"], []) + # setup: add team to project + assign_team_to_project(project=self.test_project, team=self.test_team, role=0) + response = self.client.get( + self.all_project_teams_url, + headers={"Authorization": self.test_user_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response_body["teams"]), 1) + self.assertEqual(response_body["teams"][0]["name"], TEST_TEAM_NAME) + self.assertEqual(response_body["teams"][0]["role"], 0) + + # post + def test_assign_team_to_project_by_unauthenticated_user_fails(self): + """ + Test that endpoint returns 401 when unauthenticated user assigns team to project + """ + response = self.client.post( + self.single_project_team_url, json={"role": TeamRoles.MAPPER.name} + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 401) + self.assertEqual(response_body["SubCode"], "InvalidToken") + + def test_assign_team_to_project_by_non_admin_fails(self): + """ + Test that endpoint returns 403 when non admin assigns team to a project + """ + response = self.client.post( + self.single_project_team_url, + json={"role": TeamRoles.MAPPER.name}, + headers={"Authorization": self.test_user_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 403) + self.assertEqual( + response_body["Error"], "User is not an admin or a manager for the team" + ) + self.assertEqual(response_body["SubCode"], "UserPermissionError") + + def test_assign_team_to_non_existent_project_fails(self): + """ + Test that endpoint returns 404 when admin assigns a team to a non-existent project + """ + response = self.client.post( + f"/api/v2/projects/99/teams/{self.test_team.id}/", + json={"role": TeamRoles.MAPPER.name}, + headers={"Authorization": self.test_author_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 404) + self.assertEqual(response_body["Error"], "No Project Found") + self.assertEqual(response_body["SubCode"], "NotFound") + + def test_assign_team_to_project_by_admin_passes(self): + """ + Test that endpoint returns 201 when admin successfully assigns a team to a project + """ + response = self.client.post( + self.single_project_team_url, + json={"role": TeamRoles.MAPPER.name}, + headers={"Authorization": self.test_author_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 201) + self.assertEqual( + response_body["Success"], + f"Team {self.test_team.id} assigned to project {self.test_project.id} with role MAPPER", + ) + + # patch + def test_update_team_role_by_unauthenticated_user_fails(self): + """ + Test that endpoint returns 401 when unauthenticated user updates project team role + """ + response = self.client.patch( + self.single_project_team_url, json={"role": TeamRoles.MAPPER.name} + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 401) + self.assertEqual(response_body["SubCode"], "InvalidToken") + + def test_update_team_role_by_non_admin_fails(self): + """ + Test that endpoint returns 403 when non admin updates project team role + """ + response = self.client.patch( + self.single_project_team_url, + json={"role": TeamRoles.MAPPER.name}, + headers={"Authorization": self.test_user_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 403) + self.assertEqual(response_body["Error"], "User is not a manager of the project") + self.assertEqual(response_body["SubCode"], "UserPermissionError") + + def test_update_team_role_of_non_existent_project_fails(self): + """ + Test that endpoint returns 404 when admin updates non-existent project team role + """ + response = self.client.patch( + self.non_existent_project_team_url, + json={"role": TeamRoles.MAPPER.name}, + headers={"Authorization": self.test_author_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 404) + self.assertEqual(response_body["SubCode"], "NotFound") + + def test_update_team_role_by_admin_passes(self): + """ + Test that endpoint returns 200 when admin successfully updates project team role + """ + assign_team_to_project( + self.test_project, self.test_team, TeamRoles.MAPPER.value + ) + response = self.client.patch( + self.single_project_team_url, + json={"role": TeamRoles.VALIDATOR.name}, + headers={"Authorization": self.test_author_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 200) + self.assertEqual(response_body["Status"], "Team role updated successfully") + + # delete + def test_delete_project_team_by_unauthenticated_user_fails(self): + """ + Test that endpoint returns 401 when unauthenticated user deletes project team + """ + response = self.client.delete(self.single_project_team_url) + response_body = response.get_json() + self.assertEqual(response.status_code, 401) + self.assertEqual(response_body["SubCode"], "InvalidToken") + + def test_delete_project_team_by_non_admin_fails(self): + """ + Test that endpoint returns 403 when non admin deletes project team + """ + response = self.client.delete( + self.single_project_team_url, + headers={"Authorization": self.test_user_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 403) + self.assertEqual(response_body["Error"], "User is not a manager of the project") + self.assertEqual(response_body["SubCode"], "UserPermissionError") + + def test_delete_non_existent_project_team_fails(self): + """ + Test that endpoint returns 404 when admin deletes non-existent project team + """ + response = self.client.delete( + self.non_existent_project_team_url, + headers={"Authorization": self.test_author_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 404) + self.assertEqual(response_body["Error"], "No team found") + self.assertEqual(response_body["SubCode"], "NotFound") + + def test_delete_project_team_by_admin_passes(self): + """ + Test that endpoint returns 200 when admin successfully deletes project team + """ + assign_team_to_project( + self.test_project, self.test_team, TeamRoles.MAPPER.value + ) + response = self.client.delete( + self.single_project_team_url, + headers={"Authorization": self.test_author_session_token}, + ) + response_body = response.get_json() + self.assertEqual(response.status_code, 200) + self.assertEqual(response_body["Success"], True)