import logging from collections.abc import Callable from itertools import chain from typing import TypedDict from django.contrib.auth import get_user_model from django.contrib.auth.models import AbstractBaseUser, Permission from django.contrib.contenttypes.models import ContentType from django.db import models from django.test import Client from hypothesis import given from hypothesis import strategies as st from hypothesis.extra.django import TestCase, from_model from paperwork.models import ( Certification, CertificationDefinition, CertificationVersion, Department, InstructorOrVendor, Waiver, ) class PermissionLookup(TypedDict): codename: str model: type[models.Model] class PermissionRequiredViewTestCaseMixin: permissions: list[PermissionLookup] = [] path: str client: Client user_with_permission: AbstractBaseUser user_without_permission: AbstractBaseUser assertEqual: Callable @classmethod def setUpTestData(cls): User = get_user_model() cls.client = Client() cls.user_without_permission = User.objects.create_user( username="user_without_permission" ) cls.user_with_permission = User.objects.create_user( username="user_with_permission" ) resolved_permissions = [ Permission.objects.get( content_type=ContentType.objects.get_for_model(permission["model"]), codename=permission["codename"], ) for permission in cls.permissions ] cls.user_with_permission.user_permissions.add(*resolved_permissions) def test_missing_permission(self) -> None: # suppress PermissionDenied messages logger = logging.getLogger("django.request") previous_log_level = logger.getEffectiveLevel() logger.setLevel(logging.ERROR) self.client.force_login(self.user_without_permission) response = self.client.get(self.path) self.assertEqual(response.status_code, 403) logger.setLevel(previous_log_level) class WaiverReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase): permissions = [{"model": Waiver, "codename": "view_waiver"}] path = "/paperwork/waivers" @given(waivers=st.lists(from_model(Waiver, number=st.none()))) def test_waiver_report(self, waivers: list[Waiver]) -> None: self.client.force_login(self.user_with_permission) response = self.client.get(self.path) self.assertEqual(response.status_code, 200) class InstructorOrVendorReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase): permissions = [{"model": InstructorOrVendor, "codename": "view_instructororvendor"}] path = "/paperwork/instructors-and-vendors" @given( instructors_or_vendors=st.lists( from_model(InstructorOrVendor, serial=st.none()) ) ) def test_waiver_report( self, instructors_or_vendors: list[InstructorOrVendor] ) -> None: self.client.force_login(self.user_with_permission) response = self.client.get(self.path) self.assertEqual(response.status_code, 200) @st.composite def random_certifications( draw, ) -> list[Certification]: def certifications(version: CertificationVersion): return st.lists( from_model( Certification, number=st.none(), certification_version=st.just(version), ), max_size=10, ) def versions_with_certifications(definition: CertificationDefinition): return st.lists( from_model(CertificationVersion, definition=st.just(definition)).flatmap( certifications ), max_size=2, ) def definitions_with_versions(department: Department): return st.lists( from_model(CertificationDefinition, department=st.just(department)).flatmap( versions_with_certifications ), max_size=2, ) return draw( st.lists( from_model(Department).flatmap(definitions_with_versions), max_size=2, ).map( lambda x: list( chain.from_iterable(chain.from_iterable(chain.from_iterable(x))) ) ) ) class CertifiersReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase): permissions = [{"model": Certification, "codename": "view_certification"}] path = "/paperwork/certifiers" @given(certifications=random_certifications()) def test_certifiers_report(self, certifications: list[Certification]) -> None: self.client.force_login(self.user_with_permission) response = self.client.get(self.path) self.assertEqual(response.status_code, 200) class CertificationCountReportTestCase(PermissionRequiredViewTestCaseMixin, TestCase): permissions = [{"model": Certification, "codename": "view_certification"}] path = "/paperwork/certification-count" @given(certifications=random_certifications()) def test_certification_count_report( self, certifications: list[Certification] ) -> None: self.client.force_login(self.user_with_permission) response = self.client.get(self.path) self.assertEqual(response.status_code, 200)