Skip to content

Commit 896d7f5

Browse files
committed
Add CS Package Deprecate form and view
1 parent 77ccb9b commit 896d7f5

File tree

7 files changed

+326
-0
lines changed

7 files changed

+326
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import json
2+
3+
import pytest
4+
from rest_framework.test import APIClient
5+
6+
from thunderstore.core.types import UserType
7+
from thunderstore.repository.models import Package
8+
from thunderstore.repository.models.team import TeamMember
9+
10+
11+
@pytest.mark.django_db
12+
def test_package_deprecate_api_view__succeeds(
13+
api_client: APIClient,
14+
package: Package,
15+
team_member: TeamMember,
16+
) -> None:
17+
api_client.force_authenticate(team_member.user)
18+
19+
assert Package.objects.get(pk=package.pk).is_deprecated == False
20+
21+
response = api_client.post(
22+
f"/api/cyberstorm/package/{package.namespace}/{package.name}/deprecate/",
23+
json.dumps({"is_deprecated": True}),
24+
content_type="application/json",
25+
)
26+
actual = response.json()
27+
28+
assert actual["is_deprecated"] == True
29+
assert Package.objects.get(pk=package.pk).is_deprecated == True
30+
31+
response = api_client.post(
32+
f"/api/cyberstorm/package/{package.namespace}/{package.name}/deprecate/",
33+
json.dumps({"is_deprecated": False}),
34+
content_type="application/json",
35+
)
36+
actual = response.json()
37+
38+
assert actual["is_deprecated"] == False
39+
assert Package.objects.get(pk=package.pk).is_deprecated == False
40+
41+
42+
@pytest.mark.django_db
43+
def test_package_deprecate_api_view__returns_error_for_non_existent_package(
44+
api_client: APIClient,
45+
user: UserType,
46+
) -> None:
47+
api_client.force_authenticate(user)
48+
response = api_client.post(
49+
f"/api/cyberstorm/package/BAD/BAD/deprecate/",
50+
json.dumps({"is_deprecated": True}),
51+
content_type="application/json",
52+
)
53+
actual = response.json()
54+
55+
assert actual["detail"] == "Not found."
56+
57+
58+
@pytest.mark.django_db
59+
def test_package_deprecate_api_view__returns_error_for_no_user(
60+
api_client: APIClient,
61+
) -> None:
62+
response = api_client.post(
63+
f"/api/cyberstorm/package/BAD/BAD/deprecate/",
64+
json.dumps({"is_deprecated": True}),
65+
content_type="application/json",
66+
)
67+
actual = response.json()
68+
69+
assert actual["detail"] == "Authentication credentials were not provided."
70+
71+
72+
@pytest.mark.django_db
73+
def test_package_deprecate_api_view__returns_error_for_bad_data(
74+
api_client: APIClient,
75+
package: Package,
76+
user: UserType,
77+
) -> None:
78+
api_client.force_authenticate(user)
79+
package.is_active = False
80+
package.save()
81+
82+
response = api_client.post(
83+
f"/api/cyberstorm/package/{package.namespace}/{package.name}/deprecate/",
84+
json.dumps({"bad_data": True}),
85+
content_type="application/json",
86+
)
87+
actual = response.json()
88+
89+
assert actual["is_deprecated"] == ["This field is required."]
90+
91+
response = api_client.post(
92+
f"/api/cyberstorm/package/{package.namespace}/{package.name}/deprecate/",
93+
json.dumps({"is_deprecated": "bad"}),
94+
content_type="application/json",
95+
)
96+
actual = response.json()
97+
98+
assert actual["is_deprecated"] == ["Must be a valid boolean."]

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

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .community_filters import CommunityFiltersAPIView
33
from .community_list import CommunityListAPIView
44
from .markdown import PackageVersionChangelogAPIView, PackageVersionReadmeAPIView
5+
from .package import PackageDeprecateAPIView
56
from .package_listing import PackageListingAPIView
67
from .package_listing_list import (
78
PackageListingByCommunityListAPIView,
@@ -33,4 +34,5 @@
3334
"TeamMemberListAPIView",
3435
"TeamServiceAccountListAPIView",
3536
"PackageRatingRateAPIView",
37+
"PackageDeprecateAPIView",
3638
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from django.http import HttpRequest
2+
from rest_framework import serializers
3+
from rest_framework.exceptions import ValidationError
4+
from rest_framework.generics import get_object_or_404
5+
from rest_framework.permissions import IsAuthenticated
6+
from rest_framework.response import Response
7+
from rest_framework.views import APIView
8+
9+
from thunderstore.api.utils import conditional_swagger_auto_schema
10+
from thunderstore.repository.forms import DeprecateForm
11+
from thunderstore.repository.models import Package
12+
13+
14+
class CyberstormDeprecatePackageRequestSerialiazer(serializers.Serializer):
15+
is_deprecated = serializers.BooleanField()
16+
17+
18+
class CyberstormDeprecatePackageResponseSerialiazer(serializers.Serializer):
19+
is_deprecated = serializers.BooleanField()
20+
21+
22+
class PackageDeprecateAPIView(APIView):
23+
permission_classes = [IsAuthenticated]
24+
25+
@conditional_swagger_auto_schema(
26+
request_body=CyberstormDeprecatePackageRequestSerialiazer,
27+
responses={200: CyberstormDeprecatePackageResponseSerialiazer},
28+
operation_id="cyberstorm.package.deprecate",
29+
tags=["cyberstorm"],
30+
)
31+
def post(self, request: HttpRequest, namespace_id: str, package_name: str):
32+
serializer = CyberstormDeprecatePackageRequestSerialiazer(data=request.data)
33+
serializer.is_valid(raise_exception=True)
34+
package = get_object_or_404(
35+
Package,
36+
namespace__name=namespace_id,
37+
name__iexact=package_name,
38+
)
39+
form = DeprecateForm(
40+
user=request.user,
41+
instance=package,
42+
data=serializer.validated_data,
43+
)
44+
if form.is_valid():
45+
package = form.execute()
46+
return Response(CyberstormDeprecatePackageResponseSerialiazer(package).data)
47+
else:
48+
raise ValidationError(form.errors)

django/thunderstore/api/urls.py

+6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
CommunityAPIView,
55
CommunityFiltersAPIView,
66
CommunityListAPIView,
7+
PackageDeprecateAPIView,
78
PackageListingAPIView,
89
PackageListingByCommunityListAPIView,
910
PackageListingByDependencyListAPIView,
@@ -84,6 +85,11 @@
8485
PackageRatingRateAPIView.as_view(),
8586
name="cyberstorm.package_rating.rate",
8687
),
88+
path(
89+
"package/<str:namespace_id>/<str:package_name>/deprecate/",
90+
PackageDeprecateAPIView.as_view(),
91+
name="cyberstorm.package.deprecate",
92+
),
8793
path(
8894
"team/<str:team_id>/",
8995
TeamAPIView.as_view(),
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
from .package import *
12
from .package_rating import *
23
from .team import *
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import Optional
2+
3+
from django import forms
4+
from django.contrib.auth import get_user_model
5+
6+
from thunderstore.core.types import UserType
7+
from thunderstore.repository.models import Package
8+
9+
User = get_user_model()
10+
11+
12+
class DeprecateForm(forms.ModelForm):
13+
class Meta:
14+
model = Package
15+
fields = ["is_deprecated"]
16+
17+
def __init__(self, user: Optional[UserType], *args, **kwargs):
18+
super().__init__(*args, **kwargs)
19+
self.user = user
20+
21+
def clean(self):
22+
self.instance.ensure_user_can_manage_deprecation(self.user)
23+
value = self.data.get("is_deprecated", None)
24+
if not isinstance(value, bool):
25+
raise forms.ValidationError("Given value for is_deprecated is invalid.")
26+
return super().clean()
27+
28+
def execute(self):
29+
desired_state = self.cleaned_data.get("is_deprecated")
30+
if desired_state:
31+
self.instance.deprecate()
32+
else:
33+
self.instance.undeprecate()
34+
return self.instance
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import pytest
2+
from django.forms import ValidationError
3+
4+
from thunderstore.account.models.service_account import ServiceAccount
5+
from thunderstore.repository.forms import DeprecateForm
6+
from thunderstore.repository.models import Package
7+
from thunderstore.repository.models.team import TeamMember
8+
9+
10+
@pytest.mark.django_db
11+
def test_package_deprecate_form__correct_values__succeeds(
12+
team_member: TeamMember, package: Package
13+
) -> None:
14+
# Deprecate
15+
p = Package.objects.get(pk=package.pk)
16+
form = DeprecateForm(
17+
user=team_member.user,
18+
instance=p,
19+
data={"is_deprecated": True},
20+
)
21+
assert form.is_valid() is True
22+
pkg = form.execute()
23+
assert pkg.is_deprecated == True
24+
# Undeprecate
25+
p = Package.objects.get(pk=package.pk)
26+
form = DeprecateForm(
27+
user=team_member.user,
28+
instance=p,
29+
data={"is_deprecated": False},
30+
)
31+
assert form.is_valid() is True
32+
pkg = form.execute()
33+
assert pkg.is_deprecated == False
34+
35+
36+
@pytest.mark.django_db
37+
def test_package_deprecate_form__already_on_state__succeeds(
38+
team_member: TeamMember, package: Package
39+
) -> None:
40+
# Deprecate
41+
p = Package.objects.get(pk=package.pk)
42+
form = DeprecateForm(
43+
user=team_member.user,
44+
instance=p,
45+
data={"is_deprecated": True},
46+
)
47+
assert form.is_valid() is True
48+
pkg = form.execute()
49+
assert pkg.is_deprecated == True
50+
# Second time
51+
p = Package.objects.get(pk=package.pk)
52+
form = DeprecateForm(
53+
user=team_member.user,
54+
instance=p,
55+
data={"is_deprecated": True},
56+
)
57+
assert form.is_valid() is True
58+
pkg = form.execute()
59+
assert pkg.is_deprecated == True
60+
# Undeprecate
61+
p = Package.objects.get(pk=package.pk)
62+
form = DeprecateForm(
63+
user=team_member.user,
64+
instance=p,
65+
data={"is_deprecated": False},
66+
)
67+
assert form.is_valid() is True
68+
pkg = form.execute()
69+
assert pkg.is_deprecated == False
70+
# Second time
71+
p = Package.objects.get(pk=package.pk)
72+
form = DeprecateForm(
73+
user=team_member.user,
74+
instance=p,
75+
data={"is_deprecated": False},
76+
)
77+
assert form.is_valid() is True
78+
pkg = form.execute()
79+
assert pkg.is_deprecated == False
80+
81+
82+
@pytest.mark.django_db
83+
def test_package_deprecate_form__bad_value__fails(
84+
team_member: TeamMember, package: Package
85+
) -> None:
86+
error = "Given value for is_deprecated is invalid."
87+
form = DeprecateForm(
88+
user=team_member.user,
89+
instance=package,
90+
data={"is_deprecated": "bad"},
91+
)
92+
assert form.is_valid() is False
93+
assert error in str(repr(form.errors))
94+
95+
96+
@pytest.mark.django_db
97+
def test_package_deprecate_form__user_none__fails(
98+
package: Package,
99+
) -> None:
100+
form = DeprecateForm(
101+
user=None,
102+
instance=package,
103+
data={"is_deprecated": True},
104+
)
105+
with pytest.raises(ValidationError) as e:
106+
form.clean()
107+
assert "Must be authenticated" in str(e.value)
108+
109+
110+
@pytest.mark.django_db
111+
def test_package_deprecate_form__user_deactivated__fails(
112+
team_member: TeamMember, package: Package
113+
) -> None:
114+
team_member.user.is_active = False
115+
team_member.user.save()
116+
form = DeprecateForm(
117+
user=team_member.user,
118+
instance=package,
119+
data={"is_deprecated": True},
120+
)
121+
with pytest.raises(ValidationError) as e:
122+
form.clean()
123+
assert "User has been deactivated" in str(e.value)
124+
125+
126+
@pytest.mark.django_db
127+
def test_package_deprecate_form__user_is_service_account__fails(
128+
service_account: ServiceAccount, package: Package
129+
) -> None:
130+
form = DeprecateForm(
131+
user=service_account.user,
132+
instance=package,
133+
data={"is_deprecated": True},
134+
)
135+
with pytest.raises(ValidationError) as e:
136+
form.clean()
137+
assert "Service accounts are unable to perform this action" in str(e.value)

0 commit comments

Comments
 (0)