Skip to content

Commit

Permalink
improve coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
lsaavedr committed Oct 25, 2024
1 parent 2e574f0 commit 2c93e18
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 19 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![PyPI - Version](https://img.shields.io/pypi/v/drf-rules.svg)](https://pypi.org/project/drf-rules)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/drf-rules.svg)](https://pypi.org/project/drf-rules)
[![Coverage Status](https://coveralls.io/repos/github/lsaavedr/drf-rules/badge.svg)](https://coveralls.io/github/lsaavedr/drf-rules)

---

Expand Down
12 changes: 8 additions & 4 deletions src/drf_rules/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,15 @@ def has_object_permission(
if not perm_exists(name=perm):
logger.warning(error_message.format(perm))

if method_name not in crud_method_names:
raise ImproperlyConfigured(error_message.format(perm))
# already evaluated in has_permission
# if method_name not in crud_method_names:
# raise ImproperlyConfigured(error_message.format(perm))
assert method_name in crud_method_names

perm = self._permission(":default:", view)
if not perm_exists(name=perm):
raise ImproperlyConfigured(error_message.format(perm))
# already evaluated in has_permission
# if not perm_exists(name=perm):
# raise ImproperlyConfigured(error_message.format(perm))
assert perm_exists(name=perm)

return user.has_perm(perm, obj)
34 changes: 32 additions & 2 deletions tests/testapp/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 5.1.2 on 2024-10-24 01:30
# Generated by Django 5.1.2 on 2024-10-25 14:20

from typing import List, Tuple

Expand Down Expand Up @@ -27,7 +27,37 @@ class Migration(migrations.Migration):
),
("name", models.CharField(max_length=64)),
("age", models.IntegerField()),
("gender", models.CharField(max_length=32)),
(
"gender",
models.CharField(
choices=[("MALE", "Male"), ("FEMALE", "Female")],
max_length=6,
),
),
],
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
migrations.CreateModel(
name="Dog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=64)),
("age", models.IntegerField()),
(
"gender",
models.CharField(
choices=[("MALE", "Male"), ("FEMALE", "Female")],
max_length=6,
),
),
],
bases=(rules.contrib.models.RulesModelMixin, models.Model),
),
Expand Down
27 changes: 25 additions & 2 deletions tests/testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,44 @@
import rules
from django.db import models
from rules.contrib.models import RulesModel
from .rules import is_adult_cat


class Gender(models.TextChoices):
MALE = "MALE", "Male"
FEMALE = "FEMALE", "Female"


class Cat(RulesModel):
name = models.CharField(max_length=64)
age = models.IntegerField()
gender = models.CharField(max_length=32)
gender = models.CharField(max_length=6, choices=Gender.choices)

class Meta:
rules_permissions = {
"create": rules.always_true,
"retrieve": rules.always_true,
"destroy": rules.is_staff,
"partial_update": rules.always_true,
"custom_detail": rules.always_true,
"custom_nodetail": rules.always_true,
":default:": rules.is_staff,
":default:": is_adult_cat,
}

def __str__(self) -> str:
return self.name


class Dog(RulesModel):
name = models.CharField(max_length=64)
age = models.IntegerField()
gender = models.CharField(max_length=6, choices=Gender.choices)

class Meta:
rules_permissions = {
"create": rules.always_true,
"retrieve": rules.always_true,
"destroy": rules.is_staff,
}

def __str__(self) -> str:
Expand Down
9 changes: 9 additions & 0 deletions tests/testapp/rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import rules


@rules.predicate
def is_adult_cat(user, cat=None):
if cat is None:
return True

return cat.age >= 3
110 changes: 99 additions & 11 deletions tests/testapp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from rest_framework.serializers import ModelSerializer
from rest_framework.test import APITestCase, URLPatternsTestCase
from rest_framework.viewsets import ModelViewSet
from testapp.models import Cat
from testapp.models import Cat, Dog, Gender

from drf_rules.permissions import AutoRulesPermission

Expand All @@ -27,6 +27,11 @@ class Meta:
model = Cat
fields = "__all__"

class DogSerializer(ModelSerializer):
class Meta:
model = Dog
fields = "__all__"

class CatViewSet(ModelViewSet):
queryset = Cat.objects.all()
serializer_class = CatSerializer
Expand All @@ -40,51 +45,134 @@ def custom_detail(self, request, pk):
def custom_nodetail(self, request):
return Response()

@action(detail=True)
def unknown_detail(self, request, pk):
return Response()

@action(detail=False)
def unknown(self, request):
def unknown_nodetail(self, request):
return Response()

class DogViewSet(ModelViewSet):
queryset = Dog.objects.all()
serializer_class = DogSerializer
permission_classes = [AutoRulesPermission]

router = SimpleRouter()
router.register("cats", CatViewSet)
router.register("dogs", DogViewSet)
cls.urlpatterns = router.get_urls()

return super().setUpClass()

def test_predefined_actions(self):
def test_predefined_cat_actions(self):
url = reverse("cat-list")
url_1 = reverse("cat-detail", [1])

# Create should be allowed due to the create permission
# create
response = self.client.post(
url,
{"name": "michi", "age": 3, "gender": "femenino"},
{"name": "michi", "age": 3, "gender": Gender.FEMALE},
format="json",
)
self.assertEqual(response.status_code, 201)

# List should be forbidden due to missing list permission
# update
response = self.client.put(
url_1,
{"name": "michi", "age": 2, "gender": Gender.MALE},
format="json",
)
self.assertEqual(response.status_code, 200)

# update
response = self.client.put(
url_1,
{"name": "michi", "age": 4, "gender": Gender.MALE},
format="json",
)
self.assertEqual(response.status_code, 403)

# partial_update
response = self.client.patch(
url_1,
{"age": 6},
format="json",
)
self.assertEqual(response.status_code, 200)

# list
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, 200)

# retrieve
response = self.client.get(url_1, format="json")
self.assertEqual(response.status_code, 200)

# destroy
response = self.client.delete(url_1, format="json")
self.assertEqual(response.status_code, 403)

# Retrieve should be allowed due to the view permission
def test_predefined_dog_actions(self):
url = reverse("dog-list")
url_1 = reverse("dog-detail", [1])

# create
response = self.client.post(
url,
{"name": "puppy", "age": 3, "gender": Gender.FEMALE},
format="json",
)
self.assertEqual(response.status_code, 201)

# update
with self.assertRaises(ImproperlyConfigured):
self.client.put(
url_1,
{"name": "puppy", "age": 2, "gender": Gender.MALE},
format="json",
)

# partial_update
with self.assertRaises(ImproperlyConfigured):
self.client.patch(
url_1,
{"age": 6},
format="json",
)

# list
with self.assertRaises(ImproperlyConfigured):
self.client.get(url, format="json")

# retrieve
response = self.client.get(url_1, format="json")
self.assertEqual(response.status_code, 200)

# Destroy should be forbidden due to the destroy permission
# destroy
response = self.client.delete(url_1, format="json")
self.assertEqual(response.status_code, 403)

def test_custom_actions(self):
def test_custom_cat_actions(self):
url = reverse("cat-custom-nodetail")
url_1 = reverse("cat-custom-detail", [1])

# custom_nodetail
response = self.client.get(url, format="json")
self.assertEqual(response.status_code, 200)

# custom_detail
response = self.client.get(url_1, format="json")
self.assertEqual(response.status_code, 200)

def test_unknown_action(self):
url = reverse("cat-unknown")
def test_unknown_cat_action(self):
url = reverse("cat-unknown-nodetail")
url_1 = reverse("cat-unknown-detail", [1])

# unknow_nodetail
with self.assertRaises(ImproperlyConfigured):
self.client.get(url, format="json")

# unkown_detail
with self.assertRaises(ImproperlyConfigured):
self.client.get(url_1, format="json")

0 comments on commit 2c93e18

Please sign in to comment.