Skip to content

Commit 5f2bb6d

Browse files
committed
Implement cyberstorm view for removing team member
Implement a APIView for removing team members from teams in the cyberstom API. Utilize the service layer function for removing team members in the view. Add URL and implement tests. Refs. TS-2315
1 parent 3205c6d commit 5f2bb6d

File tree

4 files changed

+181
-4
lines changed

4 files changed

+181
-4
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import pytest
2+
from rest_framework.test import APIClient
3+
4+
from conftest import TestUserTypes
5+
from thunderstore.core.types import UserType
6+
from thunderstore.repository.factories import TeamFactory, TeamMemberFactory
7+
from thunderstore.repository.models.team import Team, TeamMember, TeamMemberRole
8+
9+
10+
def get_remove_team_member_url(team_name: str, team_member: str) -> str:
11+
return f"/api/cyberstorm/team/{team_name}/member/{team_member}/remove/"
12+
13+
14+
def make_request(api_client: APIClient, team_name: str, team_member: str):
15+
return api_client.delete(
16+
get_remove_team_member_url(team_name, team_member),
17+
content_type="application/json",
18+
)
19+
20+
21+
@pytest.mark.django_db
22+
@pytest.mark.parametrize("user_type", TestUserTypes.options())
23+
def test_remove_team_member_user_roles(user_type, api_client, team_member):
24+
user = TestUserTypes.get_user_by_type(user_type)
25+
team_member_pk = team_member.pk
26+
27+
user_type_result = {
28+
TestUserTypes.no_user: 401,
29+
TestUserTypes.unauthenticated: 401,
30+
TestUserTypes.regular_user: 204,
31+
TestUserTypes.deactivated_user: 400,
32+
TestUserTypes.service_account: 400,
33+
TestUserTypes.site_admin: 204,
34+
TestUserTypes.superuser: 204,
35+
}
36+
37+
if not user_type in [TestUserTypes.no_user, TestUserTypes.unauthenticated]:
38+
team_member.team.add_member(user=user, role=TeamMemberRole.owner)
39+
api_client.force_authenticate(user)
40+
41+
response = make_request(
42+
api_client, team_member.team.name, team_member.user.username
43+
)
44+
45+
assert response.status_code == user_type_result[user_type]
46+
47+
if response.status_code == 204:
48+
assert not TeamMember.objects.filter(pk=team_member_pk).exists()
49+
else:
50+
assert TeamMember.objects.filter(pk=team_member_pk).exists()
51+
52+
53+
@pytest.mark.django_db
54+
def test_remove_team_member_success(api_client: APIClient, user: UserType, team: Team):
55+
TeamMemberFactory(team=team, user=user, role="owner")
56+
api_client.force_authenticate(user)
57+
58+
just_a_member = TeamMemberFactory(team=team, role="member")
59+
response = make_request(api_client, team.name, just_a_member.user.username)
60+
61+
assert response.status_code == 204
62+
63+
64+
@pytest.mark.django_db
65+
def test_remove_member_fail_because_user_is_not_a_member_in_team(
66+
api_client: APIClient,
67+
user: UserType,
68+
team: Team,
69+
):
70+
TeamMemberFactory(team=team, user=user, role="owner")
71+
api_client.force_authenticate(user)
72+
73+
another_team = TeamFactory()
74+
member_in_another_team = TeamMemberFactory(team=another_team, role="owner")
75+
76+
response = make_request(api_client, team.name, member_in_another_team.user.username)
77+
78+
assert response.status_code == 404
79+
assert response.json() == {"detail": "Not found."}
80+
81+
82+
@pytest.mark.django_db
83+
def test_remove_fail_team_does_not_exist(
84+
api_client: APIClient,
85+
user: UserType,
86+
):
87+
api_client.force_authenticate(user)
88+
89+
response = make_request(api_client, "nonexistent", user.username)
90+
91+
assert response.status_code == 404
92+
assert response.json() == {"detail": "Not found."}
93+
94+
95+
@pytest.mark.django_db
96+
def test_remove_fail_user_is_not_authenticated(
97+
api_client: APIClient,
98+
user: UserType,
99+
team: Team,
100+
):
101+
TeamMemberFactory(team=team, user=user, role="owner")
102+
just_a_member = TeamMemberFactory(team=team, role="member")
103+
104+
response = make_request(api_client, team.name, just_a_member.user.username)
105+
expected_response = {"detail": "Authentication credentials were not provided."}
106+
107+
assert response.status_code == 401
108+
assert response.json() == expected_response
109+
110+
111+
@pytest.mark.django_db
112+
def test_remove_fail_no_permission_to_access_team(
113+
api_client: APIClient, user: UserType, team: Team
114+
):
115+
api_client.force_authenticate(user)
116+
owner = TeamMemberFactory(team=team, role="owner")
117+
118+
response = make_request(api_client, team.name, owner.user.username)
119+
expected_response = {"non_field_errors": ["Must be a member to access team"]}
120+
121+
assert response.status_code == 400
122+
assert response.json() == expected_response
123+
124+
125+
@pytest.mark.django_db
126+
def test_remove_fail_last_owner(api_client: APIClient, user: UserType, team: Team):
127+
TeamMemberFactory(team=team, user=user, role="owner")
128+
api_client.force_authenticate(user)
129+
130+
response = make_request(api_client, team.name, user.username)
131+
expected_response = {"non_field_errors": ["Cannot remove last owner from team"]}
132+
133+
assert response.status_code == 400
134+
assert response.json() == expected_response

django/thunderstore/api/cyberstorm/views/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .package_version_list import PackageVersionListAPIView
2020
from .team import (
2121
DisbandTeamAPIView,
22+
RemoveTeamMemberAPIView,
2223
TeamAPIView,
2324
TeamCreateAPIView,
2425
TeamMemberAddAPIView,
@@ -49,4 +50,5 @@
4950
"UpdatePackageListingCategoriesAPIView",
5051
"RejectPackageListingAPIView",
5152
"ApprovePackageListingAPIView",
53+
"RemoveTeamMemberAPIView",
5254
]

django/thunderstore/api/cyberstorm/views/team.py

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from django.core.exceptions import ValidationError as DjangoValidationError
21
from django.db.models import Q, QuerySet
32
from rest_framework import status
43
from rest_framework.exceptions import PermissionDenied, ValidationError
@@ -17,7 +16,11 @@
1716
CyberstormTeamMemberSerializer,
1817
CyberstormTeamSerializer,
1918
)
20-
from thunderstore.api.cyberstorm.services import team as team_services
19+
from thunderstore.api.cyberstorm.services.team import (
20+
create_team,
21+
disband_team,
22+
remove_team_member,
23+
)
2124
from thunderstore.api.ordering import StrictOrderingFilter
2225
from thunderstore.api.utils import (
2326
CyberstormAutoSchemaMixin,
@@ -27,6 +30,21 @@
2730
from thunderstore.repository.models.team import Team, TeamMember
2831

2932

33+
def get_team_object_or_404(team_name: str) -> Team:
34+
return get_object_or_404(
35+
Team.objects.exclude(is_active=False),
36+
name=team_name,
37+
)
38+
39+
40+
def get_team_member_object_or_404(team_name: str, team_member: str) -> TeamMember:
41+
return get_object_or_404(
42+
TeamMember.objects.real_users(),
43+
team__name=team_name,
44+
user__username=team_member,
45+
)
46+
47+
3048
class TeamPermissionsMixin:
3149
permission_classes = [IsAuthenticated]
3250

@@ -66,7 +84,7 @@ def post(self, request, *args, **kwargs):
6684
serializer = CyberstormCreateTeamSerializer(data=request.data)
6785
serializer.is_valid(raise_exception=True)
6886
team_name = serializer.validated_data["name"]
69-
team = team_services.create_team(user=request.user, team_name=team_name)
87+
team = create_team(user=request.user, team_name=team_name)
7088
return_data = CyberstormTeamSerializer(team).data
7189
return Response(return_data, status=status.HTTP_201_CREATED)
7290

@@ -135,5 +153,22 @@ class DisbandTeamAPIView(APIView):
135153
)
136154
def delete(self, request, *args, **kwargs):
137155
team_name = kwargs["team_name"]
138-
team_services.disband_team(user=request.user, team_name=team_name)
156+
disband_team(user=request.user, team_name=team_name)
157+
return Response(status=status.HTTP_204_NO_CONTENT)
158+
159+
160+
class RemoveTeamMemberAPIView(APIView):
161+
permission_classes = [IsAuthenticated]
162+
163+
@conditional_swagger_auto_schema(
164+
operation_id="cyberstorm.team.member.remove",
165+
tags=["cyberstorm"],
166+
responses={status.HTTP_204_NO_CONTENT: ""},
167+
)
168+
def delete(self, request, *args, **kwargs):
169+
team_member = get_team_member_object_or_404(
170+
team_name=kwargs["team_name"],
171+
team_member=kwargs["team_member"],
172+
)
173+
remove_team_member(agent=request.user, team_member=team_member)
139174
return Response(status=status.HTTP_204_NO_CONTENT)

django/thunderstore/api/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
PackageVersionReadmeAPIView,
1818
RatePackageAPIView,
1919
RejectPackageListingAPIView,
20+
RemoveTeamMemberAPIView,
2021
TeamAPIView,
2122
TeamCreateAPIView,
2223
TeamMemberAddAPIView,
@@ -141,6 +142,11 @@
141142
TeamMemberAddAPIView.as_view(),
142143
name="cyberstorm.team.member.add",
143144
),
145+
path(
146+
"team/<str:team_name>/member/<str:team_member>/remove/",
147+
RemoveTeamMemberAPIView.as_view(),
148+
name="cyberstorm.team.member.remove",
149+
),
144150
path(
145151
"team/<str:team_id>/service-account/",
146152
TeamServiceAccountListAPIView.as_view(),

0 commit comments

Comments
 (0)