Skip to content

Commit

Permalink
Merge pull request #57 from Alschn/feature/standardized-errors
Browse files Browse the repository at this point in the history
Setup exception handler from drf-standardized-errors, adjust unit tests to changes, add some extra exceptions codes, clean up tests
  • Loading branch information
Alschn authored May 12, 2024
2 parents 41d5d4d + 4de6011 commit da70821
Show file tree
Hide file tree
Showing 18 changed files with 1,104 additions and 560 deletions.
1 change: 1 addition & 0 deletions backend/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ drf-spectacular = "==0.27.2"
django-storages = "==1.14.2"
boto3 = "==1.34.93"
django-countries = "==7.6.1"
drf-standardized-errors = "==0.13.0"

[dev-packages]
coverage = "==7.5.0"
Expand Down
304 changes: 157 additions & 147 deletions backend/Pipfile.lock

Large diffs are not rendered by default.

40 changes: 27 additions & 13 deletions backend/beers/tests/test_beer_styles_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from beers.models import BeerStyle
from beers.serializers import BeerStyleListSerializer, BeerStyleDetailSerializer
from core.shared.factories import BeerStyleFactory
from core.shared.unit_tests import APITestCase


Expand All @@ -17,16 +18,19 @@ class BeerStylesAPIViewsTests(APITestCase):
@classmethod
def setUpTestData(cls) -> None:
super().setUpTestData()
cls.style_ipa = BeerStyle.objects.create(name='India Pale Ale')
cls.style_apa = BeerStyle.objects.create(name='American Pale Ale')
cls.beer_style_to_delete = BeerStyle.objects.create(name='DDH APA')
cls.style_ipa = BeerStyleFactory(name='IPA')
cls.style_apa = BeerStyleFactory(name='APA')
cls.beer_style_to_delete = BeerStyleFactory(name='DDH APA')

def test_list_beer_styles(self):
queryset = BeerStyle.objects.order_by('-id')

response = self.client.get(self.styles_url)
response_json = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
first=response.json()['results'],
second=BeerStyleListSerializer(BeerStyle.objects.order_by('-id'), many=True).data
response_json['results'],
BeerStyleListSerializer(queryset, many=True).data
)

@unittest.skip('Currently disabled')
Expand All @@ -35,10 +39,12 @@ def test_create_beer_style(self):
response = self.client.post(self.styles_url, {
'name': 'DDH Hazy IPA'
})
response_json = response.json()
beer_style = BeerStyle.objects.get(name='DDH Hazy IPA')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(
first=response.json(),
second=BeerStyleListSerializer(BeerStyle.objects.get(name='DDH Hazy IPA')).data
response_json,
BeerStyleListSerializer(beer_style).data
)

@unittest.skip('Currently disabled')
Expand All @@ -64,24 +70,32 @@ def test_get_update_delete_beer_style_not_exists(self):
# self.assertEqual(response_delete.status_code, status.HTTP_404_NOT_FOUND)

def test_get_beer_style_by_id(self):
response = self.client.get(f'/api/styles/{self.style_apa.id}/')
response = self.client.get(
reverse_lazy('styles-detail', args=(self.style_apa.id,))
)
response_json = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
first=response.json(),
second=BeerStyleDetailSerializer(self.style_apa).data
response_json,
BeerStyleDetailSerializer(self.style_apa).data
)

@unittest.skip('Currently disabled')
def test_update_beer_style_by_id(self):
self._require_login_and_auth()
response = self.client.put(f'/api/styles/{self.style_apa.id}/', data={
payload = {
'name': 'APA',
'description': 'Very nice beer style',
})
}
response = self.client.put(
reverse_lazy('styles-detail', args=(self.style_apa.id,)),
data=payload
)
response_json = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.style_apa.refresh_from_db()
self.assertEqual(
response.json(),
response_json,
BeerStyleDetailSerializer(self.style_apa).data
)

Expand Down
124 changes: 77 additions & 47 deletions backend/beers/tests/test_beers_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from string import ascii_letters

from django.core.exceptions import ObjectDoesNotExist
from drf_standardized_errors.openapi_serializers import ValidationErrorEnum, ClientErrorEnum, ErrorCode404Enum
from rest_framework import status
from rest_framework.reverse import reverse_lazy

from beers.models import (
Beer, BeerStyle,
Expand All @@ -13,10 +15,11 @@
BeerDetailedSerializer,
BeerSerializer,
)
from core.shared.unit_tests import APITestCase
from core.shared.unit_tests import APITestCase, ExceptionResponse


class BeersAPIViewsTest(APITestCase):
list_url = reverse_lazy('beers-list')

@classmethod
def setUpTestData(cls) -> None:
Expand Down Expand Up @@ -51,99 +54,126 @@ def setUpTestData(cls) -> None:
)

def test_list_beers(self):
response = self.client.get('/api/beers/')
json_response = response.json()
beers = Beer.objects.order_by('-id')

response = self.client.get(self.list_url)
response_json = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(json_response['count'], beers.count())
self.assertEqual(response_json['count'], beers.count())
self.assertEqual(
first=BeerDetailedSerializer(beers, many=True).data,
second=json_response['results']
response_json['results'],
BeerDetailedSerializer(beers, many=True).data
)

def test_create_beer(self):
self._require_login_and_auth()
response = self.client.post('/api/beers/', data={
payload = {
'name': "a'la Grodziskie",
'percentage': 5,
'volume_ml': 500,
})
}
self._require_login_and_auth()
response = self.client.post(self.list_url, payload)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
beer = Beer.objects.get(name=payload['name'])
self.assertEqual(
response.json(),
BeerSerializer(Beer.objects.get(name="a'la Grodziskie")).data
BeerSerializer(beer).data
)

def test_create_beer_missing_data(self):
self._require_login_and_auth()
response = self.client.post('/api/beers/', data={
payload = {
'name': "Random name",
'volume_ml': 750,
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('percentage', response.json())
}
self._require_login_and_auth()
response = self.client.post(self.list_url, payload)
res = ExceptionResponse.from_response(response)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(res.type, ValidationErrorEnum.VALIDATION_ERROR)
err = res.get_error_by_attr('percentage')
self.assertEqual(err.code, 'required')

def test_create_beer_negative_percentage(self):
self._require_login_and_auth()
response = self.client.post('/api/beers/', data={
payload = {
'name': "Negative",
'percentage': -1,
'volume_ml': 500,
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('percentage', response.json())
}
self._require_login_and_auth()
response = self.client.post(self.list_url, payload)
res = ExceptionResponse.from_response(response)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(res.type, ValidationErrorEnum.VALIDATION_ERROR)
err = res.get_error_by_attr('percentage')
self.assertEqual(err.code, 'min_value')

# todo: more create beer tests (including base64 image upload)

def test_retrieve_beer(self):
beer = Beer.objects.create(name='Kwas Theta', percentage=10.2, volume_ml=500)
response = self.client.get(f'/api/beers/{beer.id}/')
response = self.client.get(
reverse_lazy('beers-detail', args=(beer.id,))
)
response_json = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), BeerSerializer(beer).data)
self.assertEqual(
response_json,
BeerSerializer(beer).data
)

# todo: more retrieve beer tests

def test_get_update_delete_beer_not_exists(self):
response_get = self.client.get('/api/beers/200/')
self.assertEqual(response_get.status_code, status.HTTP_404_NOT_FOUND)

# self._require_login_and_auth()
# response_put = self.client.put('/api/beers/200/', {})
# self.assertEqual(response_put.status_code, status.HTTP_404_NOT_FOUND)
#
# response_patch = self.client.patch('/api/beers/200/', {})
# self.assertEqual(response_patch.status_code, status.HTTP_404_NOT_FOUND)
#
# response_delete = self.client.delete('/api/beers/200/')
# self.assertEqual(response_delete.status_code, status.HTTP_404_NOT_FOUND)
def test_get_beer_does_not_exists(self):
beer_id = 200
response = self.client.get(
reverse_lazy('beers-detail', args=(beer_id,))
)
res = ExceptionResponse.from_response(response)
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(res.type, ClientErrorEnum.CLIENT_ERROR)
self.assertIn(ErrorCode404Enum.NOT_FOUND, res.codes)

@unittest.skip('Currently disabled')
def test_update_beer_by_id(self):
self._require_login_and_auth()
self.assertIsNone(self.beer_to_update.description)
response = self.client.patch(f"/api/beers/{self.beer_to_update.id}/", {
"description": 'Very nice beer',
})
payload = {
'description': 'Very nice beer',
}
response = self.client.patch(
reverse_lazy('beers-detail', args=(self.beer_to_update.id,)),
payload
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
beer = Beer.objects.get(name='PanIIPAni')
self.assertEqual(beer.description, "Very nice beer")
self.beer_to_update.refresh_from_db()
self.assertEqual(self.beer_to_update.description, payload['description'])

@unittest.skip('Currently disabled')
def test_update_beer_too_long_data(self):
self._require_login_and_auth()
response = self.client.patch(f"/api/beers/{self.beer_to_update.id}/", {
"name": ''.join(choice(ascii_letters) for _ in range(101)),
})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
payload = {
'name': ''.join(choice(ascii_letters) for _ in range(101)),
}
response = self.client.patch(
reverse_lazy('beers-detail', args=(self.beer_to_update.id,)),
payload
)
res = ExceptionResponse.from_response(response)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(res.type, ValidationErrorEnum.VALIDATION_ERROR)
err = res.get_error_by_attr('name')
self.assertEqual(err.code, 'max_length')

@unittest.skip('Currently disabled')
def test_delete_beer_by_id(self):
self._require_login_and_auth()
qs_len_before = Beer.objects.all().count()
qs_len_before = Beer.objects.count()
lookup_id = self.beer_to_delete.id
response = self.client.delete(f"/api/beers/{lookup_id}/")
response = self.client.delete(
reverse_lazy('beers-detail', args=(lookup_id,))
)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(qs_len_before - 1, Beer.objects.all().count())
self.assertEqual(qs_len_before - 1, Beer.objects.count())
with self.assertRaises(ObjectDoesNotExist):
Beer.objects.get(id=lookup_id)

Expand Down
Loading

0 comments on commit da70821

Please sign in to comment.