From e968a57c06a1b6e9af3836577643f9fcb5e9683d Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Wed, 17 Mar 2021 20:42:17 +0100 Subject: [PATCH] shopping list api tests and refactors --- .../migrations/0113_auto_20210317_2017.py | 21 ++++ cookbook/models.py | 20 ++- cookbook/serializer.py | 4 +- .../tests/api/test_api_shopping_list_entry.py | 0 .../api/test_api_shopping_list_recipe.py | 0 .../api/test_api_shopping_list_entry.py | 116 ++++++++++++++++++ .../api/test_api_shopping_list_recipe.py | 116 ++++++++++++++++++ cookbook/views/api.py | 4 +- 8 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 cookbook/migrations/0113_auto_20210317_2017.py delete mode 100644 cookbook/tests/api/test_api_shopping_list_entry.py delete mode 100644 cookbook/tests/api/test_api_shopping_list_recipe.py create mode 100644 cookbook/tests/pytest/api/test_api_shopping_list_entry.py create mode 100644 cookbook/tests/pytest/api/test_api_shopping_list_recipe.py diff --git a/cookbook/migrations/0113_auto_20210317_2017.py b/cookbook/migrations/0113_auto_20210317_2017.py new file mode 100644 index 00000000..29dd1596 --- /dev/null +++ b/cookbook/migrations/0113_auto_20210317_2017.py @@ -0,0 +1,21 @@ +# Generated by Django 3.1.7 on 2021-03-17 19:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0112_remove_synclog_space'), + ] + + operations = [ + migrations.RemoveField( + model_name='shoppinglistentry', + name='space', + ), + migrations.RemoveField( + model_name='shoppinglistrecipe', + name='space', + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index e0935a39..c99cb691 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -510,8 +510,14 @@ class ShoppingListRecipe(models.Model, PermissionModelMixin): recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, null=True, blank=True) servings = models.DecimalField(default=1, max_digits=8, decimal_places=4) - space = models.ForeignKey(Space, on_delete=models.CASCADE) - objects = ScopedManager(space='space') + objects = ScopedManager(space='shoppinglist__space') + + @staticmethod + def get_space_key(): + return 'shoppinglist', 'space' + + def get_space(self): + return self.shoppinglist_set.first().space def __str__(self): return f'Shopping list recipe {self.id} - {self.recipe}' @@ -531,8 +537,14 @@ class ShoppingListEntry(models.Model, PermissionModelMixin): order = models.IntegerField(default=0) checked = models.BooleanField(default=False) - space = models.ForeignKey(Space, on_delete=models.CASCADE) - objects = ScopedManager(space='space') + objects = ScopedManager(space='shoppinglist__space') + + @staticmethod + def get_space_key(): + return 'shoppinglist', 'space' + + def get_space(self): + return self.shoppinglist_set.first().space def __str__(self): return f'Shopping list entry {self.id}' diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 116329b0..57ddb143 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -360,7 +360,7 @@ class MealPlanSerializer(SpacedModelSerializer): read_only_fields = ('created_by',) -class ShoppingListRecipeSerializer(SpacedModelSerializer): +class ShoppingListRecipeSerializer(serializers.ModelSerializer): recipe_name = serializers.ReadOnlyField(source='recipe.name') servings = CustomDecimalField() @@ -370,7 +370,7 @@ class ShoppingListRecipeSerializer(SpacedModelSerializer): read_only_fields = ('id',) -class ShoppingListEntrySerializer(SpacedModelSerializer): +class ShoppingListEntrySerializer(WritableNestedModelSerializer): food = FoodSerializer(allow_null=True) unit = UnitSerializer(allow_null=True, required=False) amount = CustomDecimalField() diff --git a/cookbook/tests/api/test_api_shopping_list_entry.py b/cookbook/tests/api/test_api_shopping_list_entry.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbook/tests/api/test_api_shopping_list_recipe.py b/cookbook/tests/api/test_api_shopping_list_recipe.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbook/tests/pytest/api/test_api_shopping_list_entry.py b/cookbook/tests/pytest/api/test_api_shopping_list_entry.py new file mode 100644 index 00000000..323e2004 --- /dev/null +++ b/cookbook/tests/pytest/api/test_api_shopping_list_entry.py @@ -0,0 +1,116 @@ +import json + +import pytest +from django.contrib import auth +from django.forms import model_to_dict +from django.urls import reverse +from django_scopes import scopes_disabled + +from cookbook.models import RecipeBook, Storage, Sync, SyncLog, ShoppingList, ShoppingListEntry, Food + +LIST_URL = 'api:shoppinglistentry-list' +DETAIL_URL = 'api:shoppinglistentry-detail' + + +@pytest.fixture() +def obj_1(space_1, u1_s1): + e = ShoppingListEntry.objects.create(food=Food.objects.create(name='test 1', space=space_1)) + s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, ) + s.entries.add(e) + return e + + +@pytest.fixture +def obj_2(space_1, u1_s1): + e = ShoppingListEntry.objects.create(food=Food.objects.create(name='test 2', space=space_1)) + s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, ) + s.entries.add(e) + return e + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 200], + ['u1_s1', 200], + ['a1_s1', 200], +]) +def test_list_permission(arg, request): + c = request.getfixturevalue(arg[0]) + assert c.get(reverse(LIST_URL)).status_code == arg[1] + + +def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2): + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + with scopes_disabled(): + s = ShoppingList.objects.first() + s.space = space_2 + s.save() + + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 404], + ['u1_s1', 200], + ['a1_s1', 404], + ['g1_s2', 404], + ['u1_s2', 404], + ['a1_s2', 404], +]) +def test_update(arg, request, obj_1): + c = request.getfixturevalue(arg[0]) + r = c.patch( + reverse( + DETAIL_URL, + args={obj_1.id} + ), + {'amount': 2}, + content_type='application/json' + ) + assert r.status_code == arg[1] + if r.status_code == 200: + response = json.loads(r.content) + assert response['amount'] == 2 + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 201], + ['u1_s1', 201], + ['a1_s1', 201], +]) +def test_add(arg, request, obj_1): + c = request.getfixturevalue(arg[0]) + r = c.post( + reverse(LIST_URL), + {'food': model_to_dict(obj_1.food), 'amount': 1}, + content_type='application/json' + ) + response = json.loads(r.content) + print(r.content) + assert r.status_code == arg[1] + if r.status_code == 201: + assert response['food']['id'] == obj_1.food.pk + + +def test_delete(u1_s1, u1_s2, obj_1): + r = u1_s2.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + assert r.status_code == 404 + + r = u1_s1.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + + assert r.status_code == 204 diff --git a/cookbook/tests/pytest/api/test_api_shopping_list_recipe.py b/cookbook/tests/pytest/api/test_api_shopping_list_recipe.py new file mode 100644 index 00000000..f52de5d0 --- /dev/null +++ b/cookbook/tests/pytest/api/test_api_shopping_list_recipe.py @@ -0,0 +1,116 @@ +import json + +import pytest +from django.contrib import auth +from django.forms import model_to_dict +from django.urls import reverse +from django_scopes import scopes_disabled + +from cookbook.models import RecipeBook, Storage, Sync, SyncLog, ShoppingList, ShoppingListEntry, Food, ShoppingListRecipe + +LIST_URL = 'api:shoppinglistrecipe-list' +DETAIL_URL = 'api:shoppinglistrecipe-detail' + + +@pytest.fixture() +def obj_1(space_1, u1_s1, recipe_1_s1): + r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1) + s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, ) + s.recipes.add(r) + return r + + +@pytest.fixture +def obj_2(space_1, u1_s1,recipe_1_s1): + r = ShoppingListRecipe.objects.create(recipe=recipe_1_s1, servings=1) + s = ShoppingList.objects.create(created_by=auth.get_user(u1_s1), space=space_1, ) + s.recipes.add(r) + return r + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 200], + ['u1_s1', 200], + ['a1_s1', 200], +]) +def test_list_permission(arg, request): + c = request.getfixturevalue(arg[0]) + assert c.get(reverse(LIST_URL)).status_code == arg[1] + + +def test_list_space(obj_1, obj_2, u1_s1, u1_s2, space_2): + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 2 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + with scopes_disabled(): + s = ShoppingList.objects.first() + s.space = space_2 + s.save() + + assert len(json.loads(u1_s1.get(reverse(LIST_URL)).content)) == 1 + assert len(json.loads(u1_s2.get(reverse(LIST_URL)).content)) == 0 + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 404], + ['u1_s1', 200], + ['a1_s1', 404], + ['g1_s2', 404], + ['u1_s2', 404], + ['a1_s2', 404], +]) +def test_update(arg, request, obj_1): + c = request.getfixturevalue(arg[0]) + r = c.patch( + reverse( + DETAIL_URL, + args={obj_1.id} + ), + {'servings': 2}, + content_type='application/json' + ) + assert r.status_code == arg[1] + if r.status_code == 200: + response = json.loads(r.content) + assert response['servings'] == 2 + + +@pytest.mark.parametrize("arg", [ + ['a_u', 403], + ['g1_s1', 201], + ['u1_s1', 201], + ['a1_s1', 201], +]) +def test_add(arg, request, obj_1, recipe_1_s1): + c = request.getfixturevalue(arg[0]) + r = c.post( + reverse(LIST_URL), + {'recipe': recipe_1_s1.pk, 'servings': 1}, + content_type='application/json' + ) + response = json.loads(r.content) + print(r.content) + assert r.status_code == arg[1] + if r.status_code == 201: + assert response['recipe'] == recipe_1_s1.pk + + +def test_delete(u1_s1, u1_s2, obj_1): + r = u1_s2.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + assert r.status_code == 404 + + r = u1_s1.delete( + reverse( + DETAIL_URL, + args={obj_1.id} + ) + ) + + assert r.status_code == 204 diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 173b4a95..9e5cd0ab 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -340,7 +340,7 @@ class ShoppingListRecipeViewSet(viewsets.ModelViewSet): permission_classes = [CustomIsOwner, ] def get_queryset(self): - return self.queryset.filter(shoppinglist__created_by=self.request.user).filter(space=self.request.space).all() + return self.queryset.filter(shoppinglist__created_by=self.request.user, shoppinglist__space=self.request.space).all() class ShoppingListEntryViewSet(viewsets.ModelViewSet): @@ -349,7 +349,7 @@ class ShoppingListEntryViewSet(viewsets.ModelViewSet): permission_classes = [CustomIsOwner, ] def get_queryset(self): - return self.queryset.filter(shoppinglist__created_by=self.request.user).filter(space=self.request.space).all() + return self.queryset.filter(shoppinglist__created_by=self.request.user, shoppinglist__space=self.request.space).all() class ShoppingListViewSet(viewsets.ModelViewSet):