From 70b1b996df13970edd9cfe96d359223a6bc4e3f9 Mon Sep 17 00:00:00 2001 From: Marie PUPO JEAMMET Date: Tue, 20 Feb 2024 16:32:00 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=EF=B8=8F(tests)=20separate=20team?= =?UTF-8?q?=20accesses=20tests=20by=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Small commit to separate team accesses tests into diferent files. --- .../test_api_team_accesses_create.py | 175 ++++ .../test_api_team_accesses_delete.py | 164 ++++ .../test_api_team_accesses_list.py | 99 ++ .../test_api_team_accesses_retrieve.py | 85 ++ .../test_api_team_accesses_update.py | 340 +++++++ .../core/tests/test_api_team_accesses.py | 846 ------------------ 6 files changed, 863 insertions(+), 846 deletions(-) create mode 100644 src/backend/core/tests/team_accesses/test_api_team_accesses_create.py create mode 100644 src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py create mode 100644 src/backend/core/tests/team_accesses/test_api_team_accesses_list.py create mode 100644 src/backend/core/tests/team_accesses/test_api_team_accesses_retrieve.py create mode 100644 src/backend/core/tests/team_accesses/test_api_team_accesses_update.py delete mode 100644 src/backend/core/tests/test_api_team_accesses.py diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_create.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_create.py new file mode 100644 index 0000000..5375b61 --- /dev/null +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_create.py @@ -0,0 +1,175 @@ +""" +Test for team accesses API endpoints in People's core app : create +""" +import random + +import pytest +from rest_framework.test import APIClient + +from core import factories, models + +pytestmark = pytest.mark.django_db + + +def test_api_team_accesses_create_anonymous(): + """Anonymous users should not be allowed to create team accesses.""" + user = factories.UserFactory() + team = factories.TeamFactory() + + response = APIClient().post( + f"/api/v1.0/teams/{team.id!s}/accesses/", + { + "user": str(user.id), + "team": str(team.id), + "role": random.choice(models.RoleChoices.choices)[0], + }, + format="json", + ) + assert response.status_code == 401 + assert response.json() == { + "detail": "Authentication credentials were not provided." + } + assert models.TeamAccess.objects.exists() is False + + +def test_api_team_accesses_create_authenticated_unrelated(): + """ + Authenticated users should not be allowed to create team accesses for a team to + which they are not related. + """ + identity = factories.IdentityFactory() + user = identity.user + + other_user = factories.UserFactory() + team = factories.TeamFactory() + + client = APIClient() + client.force_login(user) + response = APIClient().post( + f"/api/v1.0/teams/{team.id!s}/accesses/", + { + "user": str(other_user.id), + }, + format="json", + ) + + assert response.status_code == 403 + assert response.json() == { + "detail": "You are not allowed to manage accesses for this team." + } + assert not models.TeamAccess.objects.filter(user=other_user).exists() + + +def test_api_team_accesses_create_authenticated_member(): + """Members of a team should not be allowed to create team accesses.""" + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "member")]) + other_user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + for role in [role[0] for role in models.RoleChoices.choices]: + response = client.post( + f"/api/v1.0/teams/{team.id!s}/accesses/", + { + "user": str(other_user.id), + "role": role, + }, + format="json", + ) + + assert response.status_code == 403 + assert response.json() == { + "detail": "You are not allowed to manage accesses for this team." + } + + assert not models.TeamAccess.objects.filter(user=other_user).exists() + + +def test_api_team_accesses_create_authenticated_administrator(): + """ + Administrators of a team should be able to create team accesses except for the "owner" role. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "administrator")]) + other_user = factories.UserFactory() + + client = APIClient() + client.force_login(user) + + # It should not be allowed to create an owner access + response = client.post( + f"/api/v1.0/teams/{team.id!s}/accesses/", + { + "user": str(other_user.id), + "role": "owner", + }, + format="json", + ) + + assert response.status_code == 403 + assert response.json() == { + "detail": "Only owners of a team can assign other users as owners." + } + + # It should be allowed to create a lower access + role = random.choice( + [role[0] for role in models.RoleChoices.choices if role[0] != "owner"] + ) + + response = client.post( + f"/api/v1.0/teams/{team.id!s}/accesses/", + { + "user": str(other_user.id), + "role": role, + }, + format="json", + ) + + assert response.status_code == 201 + assert models.TeamAccess.objects.filter(user=other_user).count() == 1 + new_team_access = models.TeamAccess.objects.filter(user=other_user).get() + assert response.json() == { + "abilities": new_team_access.get_abilities(user), + "id": str(new_team_access.id), + "role": role, + "user": str(other_user.id), + } + + +def test_api_team_accesses_create_authenticated_owner(): + """ + Owners of a team should be able to create team accesses whatever the role. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "owner")]) + other_user = factories.UserFactory() + + role = random.choice([role[0] for role in models.RoleChoices.choices]) + + client = APIClient() + client.force_login(user) + response = APIClient().post( + f"/api/v1.0/teams/{team.id!s}/accesses/", + { + "user": str(other_user.id), + "role": role, + }, + format="json", + ) + + assert response.status_code == 201 + assert models.TeamAccess.objects.filter(user=other_user).count() == 1 + new_team_access = models.TeamAccess.objects.filter(user=other_user).get() + assert response.json() == { + "abilities": new_team_access.get_abilities(user), + "id": str(new_team_access.id), + "role": role, + "user": str(other_user.id), + } diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py new file mode 100644 index 0000000..767bb81 --- /dev/null +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_delete.py @@ -0,0 +1,164 @@ +""" +Test for team accesses API endpoints in People's core app : delete +""" +import random + +import pytest +from rest_framework.test import APIClient + +from core import factories, models + +pytestmark = pytest.mark.django_db + + +def test_api_team_accesses_delete_anonymous(): + """Anonymous users should not be allowed to destroy a team access.""" + access = factories.TeamAccessFactory() + + response = APIClient().delete( + f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 401 + assert models.TeamAccess.objects.count() == 1 + + +def test_api_team_accesses_delete_authenticated(): + """ + Authenticated users should not be allowed to delete a team access for a + team to which they are not related. + """ + identity = factories.IdentityFactory() + user = identity.user + + access = factories.TeamAccessFactory() + + client = APIClient() + client.force_login(user) + response = client.delete( + f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 403 + assert models.TeamAccess.objects.count() == 1 + + +def test_api_team_accesses_delete_member(): + """ + Authenticated users should not be allowed to delete a team access for a + team in which they are a simple member. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "member")]) + access = factories.TeamAccessFactory(team=team) + + assert models.TeamAccess.objects.count() == 2 + assert models.TeamAccess.objects.filter(user=access.user).exists() + + client = APIClient() + client.force_login(user) + response = client.delete( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 403 + assert models.TeamAccess.objects.count() == 2 + + +def test_api_team_accesses_delete_administrators(): + """ + Users who are administrators in a team should be allowed to delete an access + from the team provided it is not ownership. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "administrator")]) + access = factories.TeamAccessFactory( + team=team, role=random.choice(["member", "administrator"]) + ) + + assert models.TeamAccess.objects.count() == 2 + assert models.TeamAccess.objects.filter(user=access.user).exists() + + client = APIClient() + client.force_login(user) + response = client.delete( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 204 + assert models.TeamAccess.objects.count() == 1 + + +def test_api_team_accesses_delete_owners_except_owners(): + """ + Users should be able to delete the team access of another user + for a team of which they are owner provided it is not an owner access. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "owner")]) + access = factories.TeamAccessFactory( + team=team, role=random.choice(["member", "administrator"]) + ) + + assert models.TeamAccess.objects.count() == 2 + assert models.TeamAccess.objects.filter(user=access.user).exists() + + client = APIClient() + client.force_login(user) + response = client.delete( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 204 + assert models.TeamAccess.objects.count() == 1 + + +def test_api_team_accesses_delete_owners_for_owners(): + """ + Users should not be allowed to delete the team access of another owner + even for a team in which they are direct owner. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "owner")]) + access = factories.TeamAccessFactory(team=team, role="owner") + + assert models.TeamAccess.objects.count() == 2 + assert models.TeamAccess.objects.filter(user=access.user).exists() + + client = APIClient() + client.force_login(user) + response = client.delete( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 403 + assert models.TeamAccess.objects.count() == 2 + + +def test_api_team_accesses_delete_owners_last_owner(): + """ + It should not be possible to delete the last owner access from a team + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory() + access = factories.TeamAccessFactory(team=team, user=user, role="owner") + + assert models.TeamAccess.objects.count() == 1 + client = APIClient() + client.force_login(user) + response = client.delete( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 403 + assert models.TeamAccess.objects.count() == 1 diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py new file mode 100644 index 0000000..e69cf07 --- /dev/null +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_list.py @@ -0,0 +1,99 @@ +""" +Test for team accesses API endpoints in People's core app : list +""" +import pytest +from rest_framework.test import APIClient + +from core import factories, models + +pytestmark = pytest.mark.django_db + + +def test_api_team_accesses_list_anonymous(): + """Anonymous users should not be allowed to list team accesses.""" + team = factories.TeamFactory() + factories.TeamAccessFactory.create_batch(2, team=team) + + response = APIClient().get(f"/api/v1.0/teams/{team.id!s}/accesses/") + assert response.status_code == 401 + assert response.json() == { + "detail": "Authentication credentials were not provided." + } + + +def test_api_team_accesses_list_authenticated_unrelated(): + """ + Authenticated users should not be allowed to list team accesses for a team + to which they are not related. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory() + factories.TeamAccessFactory.create_batch(3, team=team) + + # Accesses for other teams to which the user is related should not be listed either + other_access = factories.TeamAccessFactory(user=user) + factories.TeamAccessFactory(team=other_access.team) + + client = APIClient() + client.force_login(user) + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/", + ) + assert response.status_code == 200 + assert response.json() == { + "count": 0, + "next": None, + "previous": None, + "results": [], + } + + +def test_api_team_accesses_list_authenticated_related(): + """ + Authenticated users should be able to list team accesses for a team + to which they are related, whatever their role in the team. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory() + user_access = models.TeamAccess.objects.create(team=team, user=user) # random role + access1, access2 = factories.TeamAccessFactory.create_batch(2, team=team) + + # Accesses for other teams to which the user is related should not be listed either + other_access = factories.TeamAccessFactory(user=user) + factories.TeamAccessFactory(team=other_access.team) + + client = APIClient() + client.force_login(user) + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/", + ) + + assert response.status_code == 200 + assert response.json()["count"] == 3 + assert sorted(response.json()["results"], key=lambda x: x["id"]) == sorted( + [ + { + "id": str(user_access.id), + "user": str(user.id), + "role": user_access.role, + "abilities": user_access.get_abilities(user), + }, + { + "id": str(access1.id), + "user": str(access1.user.id), + "role": access1.role, + "abilities": access1.get_abilities(user), + }, + { + "id": str(access2.id), + "user": str(access2.user.id), + "role": access2.role, + "abilities": access2.get_abilities(user), + }, + ], + key=lambda x: x["id"], + ) diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_retrieve.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_retrieve.py new file mode 100644 index 0000000..7c4aad1 --- /dev/null +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_retrieve.py @@ -0,0 +1,85 @@ +""" +Test for team accesses API endpoints in People's core app : retrieve +""" +import pytest +from rest_framework.test import APIClient + +from core import factories + +pytestmark = pytest.mark.django_db + + +def test_api_team_accesses_retrieve_anonymous(): + """ + Anonymous users should not be allowed to retrieve a team access. + """ + access = factories.TeamAccessFactory() + + response = APIClient().get( + f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 401 + assert response.json() == { + "detail": "Authentication credentials were not provided." + } + + +def test_api_team_accesses_retrieve_authenticated_unrelated(): + """ + Authenticated users should not be allowed to retrieve a team access for + a team to which they are not related. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory() + access = factories.TeamAccessFactory(team=team) + + client = APIClient() + client.force_login(user) + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + assert response.status_code == 403 + assert response.json() == { + "detail": "You do not have permission to perform this action." + } + + # Accesses related to another team should be excluded even if the user is related to it + for access in [ + factories.TeamAccessFactory(), + factories.TeamAccessFactory(user=user), + ]: + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 404 + assert response.json() == {"detail": "Not found."} + + +def test_api_team_accesses_retrieve_authenticated_related(): + """ + A user who is related to a team should be allowed to retrieve the + associated team user accesses. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[user]) + access = factories.TeamAccessFactory(team=team) + + client = APIClient() + client.force_login(user) + response = client.get( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + ) + + assert response.status_code == 200 + assert response.json() == { + "id": str(access.id), + "user": str(access.user.id), + "role": access.role, + "abilities": access.get_abilities(user), + } diff --git a/src/backend/core/tests/team_accesses/test_api_team_accesses_update.py b/src/backend/core/tests/team_accesses/test_api_team_accesses_update.py new file mode 100644 index 0000000..b955ba9 --- /dev/null +++ b/src/backend/core/tests/team_accesses/test_api_team_accesses_update.py @@ -0,0 +1,340 @@ +""" +Test for team accesses API endpoints in People's core app : update +""" +import random +from uuid import uuid4 + +import pytest +from rest_framework.test import APIClient + +from core import factories, models +from core.api import serializers + +pytestmark = pytest.mark.django_db + + +def test_api_team_accesses_update_anonymous(): + """Anonymous users should not be allowed to update a team access.""" + access = factories.TeamAccessFactory() + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user": factories.UserFactory().id, + "role": random.choice(models.RoleChoices.choices)[0], + } + + for field, value in new_values.items(): + response = APIClient().put( + f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", + {**old_values, field: value}, + format="json", + ) + assert response.status_code == 401 + + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + assert updated_values == old_values + + +def test_api_team_accesses_update_authenticated_unrelated(): + """ + Authenticated users should not be allowed to update a team access for a team to which + they are not related. + """ + identity = factories.IdentityFactory() + user = identity.user + + access = factories.TeamAccessFactory() + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user": factories.UserFactory().id, + "role": random.choice(models.RoleChoices.choices)[0], + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + response = client.put( + f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", + {**old_values, field: value}, + format="json", + ) + assert response.status_code == 403 + + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + assert updated_values == old_values + + +def test_api_team_accesses_update_authenticated_member(): + """Members of a team should not be allowed to update its accesses.""" + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "member")]) + access = factories.TeamAccessFactory(team=team) + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user": factories.UserFactory().id, + "role": random.choice(models.RoleChoices.choices)[0], + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + response = client.put( + f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", + {**old_values, field: value}, + format="json", + ) + assert response.status_code == 403 + + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + assert updated_values == old_values + + +def test_api_team_accesses_update_administrator_except_owner(): + """ + A user who is an administrator in a team should be allowed to update a user + access for this team, as long as they don't try to set the role to owner. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "administrator")]) + access = factories.TeamAccessFactory( + team=team, + role=random.choice(["administrator", "member"]), + ) + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user_id": factories.UserFactory().id, + "role": random.choice(["administrator", "member"]), + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + new_data = {**old_values, field: value} + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data=new_data, + format="json", + ) + + if ( + new_data["role"] == old_values["role"] + ): # we are not really updating the role + assert response.status_code == 403 + else: + assert response.status_code == 200 + + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + if field == "role": + assert updated_values == {**old_values, "role": new_values["role"]} + else: + assert updated_values == old_values + + +def test_api_team_accesses_update_administrator_from_owner(): + """ + A user who is an administrator in a team, should not be allowed to update + the user access of an "owner" for this team. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "administrator")]) + other_user = factories.UserFactory() + access = factories.TeamAccessFactory(team=team, user=other_user, role="owner") + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user_id": factories.UserFactory().id, + "role": random.choice(models.RoleChoices.choices)[0], + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data={**old_values, field: value}, + format="json", + ) + assert response.status_code == 403 + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + assert updated_values == old_values + + +def test_api_team_accesses_update_administrator_to_owner(): + """ + A user who is an administrator in a team, should not be allowed to update + the user access of another user to grant team ownership. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "administrator")]) + other_user = factories.UserFactory() + access = factories.TeamAccessFactory( + team=team, + user=other_user, + role=random.choice(["administrator", "member"]), + ) + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user_id": factories.UserFactory().id, + "role": "owner", + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + new_data = {**old_values, field: value} + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data=new_data, + format="json", + ) + # We are not allowed or not really updating the role + if field == "role" or new_data["role"] == old_values["role"]: + assert response.status_code == 403 + else: + assert response.status_code == 200 + + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + assert updated_values == old_values + + +def test_api_team_accesses_update_owner_except_owner(): + """ + A user who is an owner in a team should be allowed to update + a user access for this team except for existing "owner" accesses. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "owner")]) + factories.UserFactory() + access = factories.TeamAccessFactory( + team=team, + role=random.choice(["administrator", "member"]), + ) + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user_id": factories.UserFactory().id, + "role": random.choice(models.RoleChoices.choices)[0], + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + new_data = {**old_values, field: value} + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data=new_data, + format="json", + ) + + if ( + new_data["role"] == old_values["role"] + ): # we are not really updating the role + assert response.status_code == 403 + else: + assert response.status_code == 200 + + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + + if field == "role": + assert updated_values == {**old_values, "role": new_values["role"]} + else: + assert updated_values == old_values + + +def test_api_team_accesses_update_owner_for_owners(): + """ + A user who is "owner" of a team should not be allowed to update + an existing owner access for this team. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory(users=[(user, "owner")]) + access = factories.TeamAccessFactory(team=team, role="owner") + old_values = serializers.TeamAccessSerializer(instance=access).data + + new_values = { + "id": uuid4(), + "user_id": factories.UserFactory().id, + "role": random.choice(models.RoleChoices.choices)[0], + } + + client = APIClient() + client.force_login(user) + for field, value in new_values.items(): + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data={**old_values, field: value}, + content_type="application/json", + ) + assert response.status_code == 403 + access.refresh_from_db() + updated_values = serializers.TeamAccessSerializer(instance=access).data + assert updated_values == old_values + + +def test_api_team_accesses_update_owner_self(): + """ + A user who is owner of a team should be allowed to update + their own user access provided there are other owners in the team. + """ + identity = factories.IdentityFactory() + user = identity.user + + team = factories.TeamFactory() + access = factories.TeamAccessFactory(team=team, user=user, role="owner") + old_values = serializers.TeamAccessSerializer(instance=access).data + new_role = random.choice(["administrator", "member"]) + + client = APIClient() + client.force_login(user) + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data={**old_values, "role": new_role}, + format="json", + ) + + assert response.status_code == 403 + access.refresh_from_db() + assert access.role == "owner" + + # Add another owner and it should now work + factories.TeamAccessFactory(team=team, role="owner") + + response = client.put( + f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", + data={**old_values, "role": new_role}, + format="json", + ) + + assert response.status_code == 200 + access.refresh_from_db() + assert access.role == new_role diff --git a/src/backend/core/tests/test_api_team_accesses.py b/src/backend/core/tests/test_api_team_accesses.py deleted file mode 100644 index a113eec..0000000 --- a/src/backend/core/tests/test_api_team_accesses.py +++ /dev/null @@ -1,846 +0,0 @@ -""" -Test team accesses API endpoints for users in People's core app. -""" -import random -from uuid import uuid4 - -import pytest -from rest_framework.test import APIClient - -from core import factories, models -from core.api import serializers - -pytestmark = pytest.mark.django_db - - -def test_api_team_accesses_list_anonymous(): - """Anonymous users should not be allowed to list team accesses.""" - team = factories.TeamFactory() - factories.TeamAccessFactory.create_batch(2, team=team) - - response = APIClient().get(f"/api/v1.0/teams/{team.id!s}/accesses/") - assert response.status_code == 401 - assert response.json() == { - "detail": "Authentication credentials were not provided." - } - - -def test_api_team_accesses_list_authenticated_unrelated(): - """ - Authenticated users should not be allowed to list team accesses for a team - to which they are not related. - """ - identity = factories.IdentityFactory() - user = identity.user - team = factories.TeamFactory() - factories.TeamAccessFactory.create_batch(3, team=team) - - client = APIClient() - client.force_login(user) - - # Accesses for other teams to which the user is related should not be listed either - other_access = factories.TeamAccessFactory(user=user) - factories.TeamAccessFactory(team=other_access.team) - - response = client.get( - f"/api/v1.0/teams/{team.id!s}/accesses/", - ) - assert response.status_code == 200 - assert response.json() == { - "count": 0, - "next": None, - "previous": None, - "results": [], - } - - -def test_api_team_accesses_list_authenticated_related(): - """ - Authenticated users should be able to list team accesses for a team - to which they are related, whatever their role in the team. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory() - user_access = models.TeamAccess.objects.create(team=team, user=user) # random role - access1, access2 = factories.TeamAccessFactory.create_batch(2, team=team) - - # Accesses for other teams to which the user is related should not be listed either - other_access = factories.TeamAccessFactory(user=user) - factories.TeamAccessFactory(team=other_access.team) - - response = client.get( - f"/api/v1.0/teams/{team.id!s}/accesses/", - ) - - assert response.status_code == 200 - content = response.json() - assert len(content["results"]) == 3 - assert sorted(content["results"], key=lambda x: x["id"]) == sorted( - [ - { - "id": str(user_access.id), - "user": str(user.id), - "role": user_access.role, - "abilities": user_access.get_abilities(user), - }, - { - "id": str(access1.id), - "user": str(access1.user.id), - "role": access1.role, - "abilities": access1.get_abilities(user), - }, - { - "id": str(access2.id), - "user": str(access2.user.id), - "role": access2.role, - "abilities": access2.get_abilities(user), - }, - ], - key=lambda x: x["id"], - ) - - -def test_api_team_accesses_retrieve_anonymous(): - """ - Anonymous users should not be allowed to retrieve a team access. - """ - access = factories.TeamAccessFactory() - - response = APIClient().get( - f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 401 - assert response.json() == { - "detail": "Authentication credentials were not provided." - } - - -def test_api_team_accesses_retrieve_authenticated_unrelated(): - """ - Authenticated users should not be allowed to retrieve a team access for - a team to which they are not related. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory() - access = factories.TeamAccessFactory(team=team) - - response = client.get(f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/") - assert response.status_code == 403 - assert response.json() == { - "detail": "You do not have permission to perform this action." - } - - # Accesses related to another team should be excluded even if the user is related to it - for access in [ - factories.TeamAccessFactory(), - factories.TeamAccessFactory(user=user), - ]: - response = client.get( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 404 - assert response.json() == {"detail": "Not found."} - - -def test_api_team_accesses_retrieve_authenticated_related(): - """ - A user who is related to a team should be allowed to retrieve the - associated team user accesses. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[user]) - access = factories.TeamAccessFactory(team=team) - - response = client.get( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 200 - assert response.json() == { - "id": str(access.id), - "user": str(access.user.id), - "role": access.role, - "abilities": access.get_abilities(user), - } - - -def test_api_team_accesses_create_anonymous(): - """Anonymous users should not be allowed to create team accesses.""" - user = factories.UserFactory() - team = factories.TeamFactory() - - response = APIClient().post( - f"/api/v1.0/teams/{team.id!s}/accesses/", - { - "user": str(user.id), - "team": str(team.id), - "role": random.choice(models.RoleChoices.choices)[0], - }, - format="json", - ) - assert response.status_code == 401 - assert response.json() == { - "detail": "Authentication credentials were not provided." - } - assert models.TeamAccess.objects.exists() is False - - -def test_api_team_accesses_create_authenticated_unrelated(): - """ - Authenticated users should not be allowed to create team accesses for a team to - which they are not related. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - other_user = factories.UserFactory() - team = factories.TeamFactory() - - response = client.post( - f"/api/v1.0/teams/{team.id!s}/accesses/", - { - "user": str(other_user.id), - }, - format="json", - ) - - assert response.status_code == 403 - assert response.json() == { - "detail": "You are not allowed to manage accesses for this team." - } - assert not models.TeamAccess.objects.filter(user=other_user).exists() - - -def test_api_team_accesses_create_authenticated_member(): - """Members of a team should not be allowed to create team accesses.""" - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "member")]) - other_user = factories.UserFactory() - - for role in [role[0] for role in models.RoleChoices.choices]: - response = client.post( - f"/api/v1.0/teams/{team.id!s}/accesses/", - { - "user": str(other_user.id), - "role": role, - }, - format="json", - ) - - assert response.status_code == 403 - assert response.json() == { - "detail": "You are not allowed to manage accesses for this team." - } - - assert not models.TeamAccess.objects.filter(user=other_user).exists() - - -def test_api_team_accesses_create_authenticated_administrator(): - """ - Administrators of a team should be able to create team accesses except for the "owner" role. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "administrator")]) - other_user = factories.UserFactory() - - # It should not be allowed to create an owner access - response = client.post( - f"/api/v1.0/teams/{team.id!s}/accesses/", - { - "user": str(other_user.id), - "role": "owner", - }, - format="json", - ) - - assert response.status_code == 403 - assert response.json() == { - "detail": "Only owners of a team can assign other users as owners." - } - - # It should be allowed to create a lower access - role = random.choice( - [role[0] for role in models.RoleChoices.choices if role[0] != "owner"] - ) - - response = client.post( - f"/api/v1.0/teams/{team.id!s}/accesses/", - { - "user": str(other_user.id), - "role": role, - }, - format="json", - ) - - assert response.status_code == 201 - assert models.TeamAccess.objects.filter(user=other_user).count() == 1 - new_team_access = models.TeamAccess.objects.filter(user=other_user).get() - assert response.json() == { - "abilities": new_team_access.get_abilities(user), - "id": str(new_team_access.id), - "role": role, - "user": str(other_user.id), - } - - -def test_api_team_accesses_create_authenticated_owner(): - """ - Owners of a team should be able to create team accesses whatever the role. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "owner")]) - other_user = factories.UserFactory() - - role = random.choice([role[0] for role in models.RoleChoices.choices]) - - response = client.post( - f"/api/v1.0/teams/{team.id!s}/accesses/", - { - "user": str(other_user.id), - "role": role, - }, - format="json", - ) - - assert response.status_code == 201 - assert models.TeamAccess.objects.filter(user=other_user).count() == 1 - new_team_access = models.TeamAccess.objects.filter(user=other_user).get() - assert response.json() == { - "abilities": new_team_access.get_abilities(user), - "id": str(new_team_access.id), - "role": role, - "user": str(other_user.id), - } - - -def test_api_team_accesses_update_anonymous(): - """Anonymous users should not be allowed to update a team access.""" - access = factories.TeamAccessFactory() - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user": factories.UserFactory().id, - "role": random.choice(models.RoleChoices.choices)[0], - } - - api_client = APIClient() - for field, value in new_values.items(): - response = api_client.put( - f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", - {**old_values, field: value}, - format="json", - ) - assert response.status_code == 401 - - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - assert updated_values == old_values - - -def test_api_team_accesses_update_authenticated_unrelated(): - """ - Authenticated users should not be allowed to update a team access for a team to which - they are not related. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - access = factories.TeamAccessFactory() - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user": factories.UserFactory().id, - "role": random.choice(models.RoleChoices.choices)[0], - } - - for field, value in new_values.items(): - response = client.put( - f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", - {**old_values, field: value}, - format="json", - ) - assert response.status_code == 403 - - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - assert updated_values == old_values - - -def test_api_team_accesses_update_authenticated_member(): - """Members of a team should not be allowed to update its accesses.""" - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "member")]) - access = factories.TeamAccessFactory(team=team) - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user": factories.UserFactory().id, - "role": random.choice(models.RoleChoices.choices)[0], - } - - for field, value in new_values.items(): - response = client.put( - f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", - {**old_values, field: value}, - format="json", - ) - assert response.status_code == 403 - - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - assert updated_values == old_values - - -def test_api_team_accesses_update_administrator_except_owner(): - """ - A user who is an administrator in a team should be allowed to update a user - access for this team, as long as they don't try to set the role to owner. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "administrator")]) - access = factories.TeamAccessFactory( - team=team, - role=random.choice(["administrator", "member"]), - ) - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user_id": factories.UserFactory().id, - "role": random.choice(["administrator", "member"]), - } - - for field, value in new_values.items(): - new_data = {**old_values, field: value} - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data=new_data, - format="json", - ) - - if ( - new_data["role"] == old_values["role"] - ): # we are not really updating the role - assert response.status_code == 403 - else: - assert response.status_code == 200 - - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - if field == "role": - assert updated_values == {**old_values, "role": new_values["role"]} - else: - assert updated_values == old_values - - -def test_api_team_accesses_update_administrator_from_owner(): - """ - A user who is an administrator in a team, should not be allowed to update - the user access of an "owner" for this team. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "administrator")]) - other_user = factories.UserFactory() - access = factories.TeamAccessFactory(team=team, user=other_user, role="owner") - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user_id": factories.UserFactory().id, - "role": random.choice(models.RoleChoices.choices)[0], - } - - for field, value in new_values.items(): - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data={**old_values, field: value}, - format="json", - ) - assert response.status_code == 403 - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - assert updated_values == old_values - - -def test_api_team_accesses_update_administrator_to_owner(): - """ - A user who is an administrator in a team, should not be allowed to update - the user access of another user to grant team ownership. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "administrator")]) - other_user = factories.UserFactory() - access = factories.TeamAccessFactory( - team=team, - user=other_user, - role=random.choice(["administrator", "member"]), - ) - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user_id": factories.UserFactory().id, - "role": "owner", - } - - for field, value in new_values.items(): - new_data = {**old_values, field: value} - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data=new_data, - format="json", - ) - # We are not allowed or not really updating the role - if field == "role" or new_data["role"] == old_values["role"]: - assert response.status_code == 403 - else: - assert response.status_code == 200 - - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - assert updated_values == old_values - - -def test_api_team_accesses_update_owner_except_owner(): - """ - A user who is an owner in a team should be allowed to update - a user access for this team except for existing "owner" accesses. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "owner")]) - factories.UserFactory() - access = factories.TeamAccessFactory( - team=team, - role=random.choice(["administrator", "member"]), - ) - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user_id": factories.UserFactory().id, - "role": random.choice(models.RoleChoices.choices)[0], - } - - for field, value in new_values.items(): - new_data = {**old_values, field: value} - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data=new_data, - format="json", - ) - - if ( - new_data["role"] == old_values["role"] - ): # we are not really updating the role - assert response.status_code == 403 - else: - assert response.status_code == 200 - - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - - if field == "role": - assert updated_values == {**old_values, "role": new_values["role"]} - else: - assert updated_values == old_values - - -def test_api_team_accesses_update_owner_for_owners(): - """ - A user who is "owner" of a team should not be allowed to update - an existing owner access for this team. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "owner")]) - access = factories.TeamAccessFactory(team=team, role="owner") - old_values = serializers.TeamAccessSerializer(instance=access).data - - new_values = { - "id": uuid4(), - "user_id": factories.UserFactory().id, - "role": random.choice(models.RoleChoices.choices)[0], - } - - for field, value in new_values.items(): - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data={**old_values, field: value}, - content_type="application/json", - ) - assert response.status_code == 403 - access.refresh_from_db() - updated_values = serializers.TeamAccessSerializer(instance=access).data - assert updated_values == old_values - - -def test_api_team_accesses_update_owner_self(): - """ - A user who is owner of a team should be allowed to update - their own user access provided there are other owners in the team. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory() - access = factories.TeamAccessFactory(team=team, user=user, role="owner") - old_values = serializers.TeamAccessSerializer(instance=access).data - new_role = random.choice(["administrator", "member"]) - - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data={**old_values, "role": new_role}, - format="json", - ) - - assert response.status_code == 403 - access.refresh_from_db() - assert access.role == "owner" - - # Add another owner and it should now work - factories.TeamAccessFactory(team=team, role="owner") - - response = client.put( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - data={**old_values, "role": new_role}, - format="json", - ) - - assert response.status_code == 200 - access.refresh_from_db() - assert access.role == new_role - - -# Delete - - -def test_api_team_accesses_delete_anonymous(): - """Anonymous users should not be allowed to destroy a team access.""" - access = factories.TeamAccessFactory() - - response = APIClient().delete( - f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 401 - assert models.TeamAccess.objects.count() == 1 - - -def test_api_team_accesses_delete_authenticated(): - """ - Authenticated users should not be allowed to delete a team access for a - team to which they are not related. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - access = factories.TeamAccessFactory() - - response = client.delete( - f"/api/v1.0/teams/{access.team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 403 - assert models.TeamAccess.objects.count() == 1 - - -def test_api_team_accesses_delete_member(): - """ - Authenticated users should not be allowed to delete a team access for a - team in which they are a simple member. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "member")]) - access = factories.TeamAccessFactory(team=team) - - assert models.TeamAccess.objects.count() == 2 - assert models.TeamAccess.objects.filter(user=access.user).exists() - - response = client.delete( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 403 - assert models.TeamAccess.objects.count() == 2 - - -def test_api_team_accesses_delete_administrators(): - """ - Users who are administrators in a team should be allowed to delete an access - from the team provided it is not ownership. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "administrator")]) - access = factories.TeamAccessFactory( - team=team, role=random.choice(["member", "administrator"]) - ) - - assert models.TeamAccess.objects.count() == 2 - assert models.TeamAccess.objects.filter(user=access.user).exists() - - response = client.delete( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 204 - assert models.TeamAccess.objects.count() == 1 - - -def test_api_team_accesses_delete_owners_except_owners(): - """ - Users should be able to delete the team access of another user - for a team of which they are owner provided it is not an owner access. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "owner")]) - access = factories.TeamAccessFactory( - team=team, role=random.choice(["member", "administrator"]) - ) - - assert models.TeamAccess.objects.count() == 2 - assert models.TeamAccess.objects.filter(user=access.user).exists() - - response = client.delete( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 204 - assert models.TeamAccess.objects.count() == 1 - - -def test_api_team_accesses_delete_owners_for_owners(): - """ - Users should not be allowed to delete the team access of another owner - even for a team in which they are direct owner. - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory(users=[(user, "owner")]) - access = factories.TeamAccessFactory(team=team, role="owner") - - assert models.TeamAccess.objects.count() == 2 - assert models.TeamAccess.objects.filter(user=access.user).exists() - - response = client.delete( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 403 - assert models.TeamAccess.objects.count() == 2 - - -def test_api_team_accesses_delete_owners_last_owner(): - """ - It should not be possible to delete the last owner access from a team - """ - identity = factories.IdentityFactory() - user = identity.user - - client = APIClient() - client.force_login(user) - - team = factories.TeamFactory() - access = factories.TeamAccessFactory(team=team, user=user, role="owner") - - assert models.TeamAccess.objects.count() == 1 - response = client.delete( - f"/api/v1.0/teams/{team.id!s}/accesses/{access.id!s}/", - ) - - assert response.status_code == 403 - assert models.TeamAccess.objects.count() == 1