From 1b004bee2d831181b8c07fe4efeb8eecc17a8e67 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 14 Aug 2025 15:13:17 +0930 Subject: [PATCH] test(core): Notes Meta Models API Permissions Test cases for All Notes Models ref: #948 closes #778 --- ..._meta_model_note_tenant_permissions_api.py | 104 +++++++++ app/core/serializers/centurionmodelnote.py | 6 + ...ctional_meta_model_note_api_permissions.py | 218 ++++++++++++++++++ 3 files changed, 328 insertions(+) create mode 100644 app/access/tests/functional/additional_meta_model_note_tenant_permissions_api.py create mode 100644 app/core/tests/functional/centurion_model_note_meta/test_functional_meta_model_note_api_permissions.py diff --git a/app/access/tests/functional/additional_meta_model_note_tenant_permissions_api.py b/app/access/tests/functional/additional_meta_model_note_tenant_permissions_api.py new file mode 100644 index 00000000..29875847 --- /dev/null +++ b/app/access/tests/functional/additional_meta_model_note_tenant_permissions_api.py @@ -0,0 +1,104 @@ +import pytest + +from django.test import Client + + + +class AdditionalTestCases: + + + def test_permission_change(self, model_instance, api_request_permissions): + """ Check correct permission for change + + Make change with user who has change permission + """ + + client = Client() + + client.force_login( api_request_permissions['user']['change'] ) + + kwargs = self.kwargs_create_item.copy() + kwargs.update({ + 'organization': api_request_permissions['tenancy']['user'], + 'model': api_request_permissions['tenancy']['user'] + }) + + change_item = model_instance( + kwargs_create = kwargs, + ) + + response = client.patch( + path = change_item.get_url( many = False ), + data = self.change_data, + content_type = 'application/json' + ) + + if response.status_code == 405: + pytest.xfail( reason = 'ViewSet does not have this request method.' ) + + assert response.status_code == 200, response.content + + + + def test_permission_delete(self, model_instance, api_request_permissions): + """ Check correct permission for delete + + Delete item as user with delete permission + """ + + client = Client() + + client.force_login( api_request_permissions['user']['delete'] ) + + kwargs = self.kwargs_create_item + kwargs.update({ + 'organization': api_request_permissions['tenancy']['user'], + 'model': api_request_permissions['tenancy']['user'] + }) + + delete_item = model_instance( + kwargs_create = kwargs + ) + + response = client.delete( + path = delete_item.get_url( many = False ), + ) + + if response.status_code == 405: + pytest.xfail( reason = 'ViewSet does not have this request method.' ) + + assert response.status_code == 204, response.content + + def test_permission_view(self, model_instance, api_request_permissions): + """ Check correct permission for view + + Attempt to view as user with view permission + """ + + client = Client() + + client.force_login( api_request_permissions['user']['view'] ) + + kwargs = self.kwargs_create_item + kwargs.update({ + 'organization': api_request_permissions['tenancy']['user'], + 'model': api_request_permissions['tenancy']['user'] + }) + + view_item = model_instance( + kwargs_create = kwargs + ) + + response = client.get( + path = view_item.get_url( many = False ) + ) + + if response.status_code == 405: + pytest.xfail( reason = 'ViewSet does not have this request method.' ) + + assert response.status_code == 200, response.content + + + @pytest.mark.xfail( reason = 'model is not global based') + def test_returned_data_from_user_and_global_organizations_only(self ): + assert False \ No newline at end of file diff --git a/app/core/serializers/centurionmodelnote.py b/app/core/serializers/centurionmodelnote.py index 920540e4..3a6d2a12 100644 --- a/app/core/serializers/centurionmodelnote.py +++ b/app/core/serializers/centurionmodelnote.py @@ -94,6 +94,12 @@ class ModelSerializer( ] + def validate(self, attrs): + + attrs['created_by'] = self._context['request'].user + + return super().validate(attrs) + def is_valid(self, *, raise_exception=False) -> bool: diff --git a/app/core/tests/functional/centurion_model_note_meta/test_functional_meta_model_note_api_permissions.py b/app/core/tests/functional/centurion_model_note_meta/test_functional_meta_model_note_api_permissions.py new file mode 100644 index 00000000..21467d3a --- /dev/null +++ b/app/core/tests/functional/centurion_model_note_meta/test_functional_meta_model_note_api_permissions.py @@ -0,0 +1,218 @@ +import pytest + +from django.apps import apps +from django.conf import settings +from django.db import models +from django.utils.module_loading import import_string + +from core.models.centurion_notes import CenturionModelNote + +from api.tests.functional.test_functional_permissions_api import ( + APIPermissionsInheritedCases +) + + + +def get_models( excludes: list[ str ] = [] ) -> list[ tuple ]: + """Fetch models from Centurion Apps + + Args: + excludes (list[ str ]): Words that may be in a models name to exclude + + Returns: + list[ tuple ]: Centurion ERP Only models + """ + + models: list = [] + + model_apps: list = [] + + exclude_model_apps = [ + 'django', + 'django_celery_results', + 'django_filters', + 'drf_spectacular', + 'drf_spectacular_sidecar', + 'coresheaders', + 'corsheaders', + 'rest_framework', + 'rest_framework_json_api', + 'social_django', + ] + + for app in settings.INSTALLED_APPS: + + app = app.split('.')[0] + + if app in exclude_model_apps: + continue + + model_apps += [ app ] + + + for model in apps.get_models(): + + if model._meta.app_label not in model_apps: + continue + + skip = False + + for exclude in excludes: + + if exclude in str(model._meta.model_name): + skip = True + break + + if skip: + continue + + models += [ model ] + + return models + + + +class ModelNotesMetaAPIPermissionsTestCases( + APIPermissionsInheritedCases +): + """AuditHistory Meta Model Test Cases + + This test suite is the base for the dynamic tests that are created + during pytest discover. + """ + + @pytest.fixture( scope = 'class' ) + def note_model(self, request): + + return request.cls.note_model_class + + + @pytest.fixture( scope = 'class', autouse = True) + def model_kwargs(self, django_db_blocker, + request, note_model, kwargs_centurionmodelnotemeta + ): + + model_kwargs = kwargs_centurionmodelnotemeta.copy() + + with django_db_blocker.unblock(): + + note_model_kwargs = request.getfixturevalue('kwargs_' + note_model._meta.model_name) + + kwargs = {} + + many_field = {} + + for field, value in note_model_kwargs.items(): + + if not hasattr(getattr(note_model, field), 'field'): + continue + + if isinstance(getattr(note_model, field).field, models.ManyToManyField): + + if field in many_field: + + many_field[field] += [ value ] + + elif isinstance(value, list): + + value_list = [] + + for list_value in value: + + value_list += [ list_value ] + + + value = value_list + + else: + + many_field.update({ + field: [ + value + ] + }) + + continue + + kwargs.update({ + field: value + }) + + + model = note_model.objects.create( + **kwargs + ) + + + for field, values in many_field.items(): + + for value in values: + + getattr(model, field).add( value ) + + + model_kwargs.update({ + 'model': model + }) + request.cls.kwargs_create_item = model_kwargs + + yield model_kwargs + + with django_db_blocker.unblock(): + + model.delete() + + + @pytest.fixture( scope = 'class' ) + def model(self, request): + + return request.cls.model_class + + + @pytest.mark.skip( reason = 'ToDo: Figure out how to dynomagic add note_model instance' ) + def test_model_creation(self, model, user): + pass + + + +for model in get_models(): + + if( + not issubclass(model, CenturionModelNote) + or model == CenturionModelNote + ): + continue + + + cls_name: str = f"{model._meta.object_name}MetaAPIPermissionsPyTest" + + inc_classes = (ModelNotesMetaAPIPermissionsTestCases,) + try: + + additional_testcases = import_string( + model._meta.app_label + '.tests.functional.additional_meta_model_note_' + + str( model._meta.object_name ).replace('CenturionModelNote', '').lower() + '_permissions_api.AdditionalTestCases' + ) + + inc_classes = (additional_testcases, *inc_classes) + + except Exception as ex: + additional_testcases = None + + dynamic_class = type( + cls_name, + inc_classes, + { + 'note_model_class': apps.get_model( + app_label = model._meta.app_label, + model_name = str( model._meta.object_name ).replace('CenturionModelNote', '') + ), + 'model_class': model + } + ) + + dynamic_class = pytest.mark.__getattr__( + 'model_' + str(model._meta.model_name).replace('centurionmodelnote', ''))(dynamic_class) + dynamic_class = pytest.mark.__getattr__('module_' + model._meta.app_label)(dynamic_class) + + globals()[cls_name] = dynamic_class