From 094451d50da32b639898f9db10d2ac245edf52e3 Mon Sep 17 00:00:00 2001 From: Sasha Dresden Date: Thu, 12 Dec 2024 15:12:30 -0500 Subject: [PATCH] Added tests to make sure all Views use CommitteeOwnedViewMixin and all models use CommitteeOwnedModel, with some exceptions --- .../committee_accounts/test_models.py | 42 ++++++++++- .../fecfiler/committee_accounts/test_views.py | 75 ++++++++++++++++++- django-backend/fecfiler/memo_text/views.py | 3 +- django-backend/fecfiler/reports/views.py | 2 +- django-backend/fecfiler/web_services/views.py | 4 +- 5 files changed, 115 insertions(+), 11 deletions(-) diff --git a/django-backend/fecfiler/committee_accounts/test_models.py b/django-backend/fecfiler/committee_accounts/test_models.py index 9faa4b6ee1..fa7357ac5b 100644 --- a/django-backend/fecfiler/committee_accounts/test_models.py +++ b/django-backend/fecfiler/committee_accounts/test_models.py @@ -1,5 +1,6 @@ +from django.apps import apps from django.test import TestCase -from .models import CommitteeAccount +from .models import CommitteeAccount, CommitteeOwnedModel class CommitteeAccountTestCase(TestCase): @@ -16,9 +17,7 @@ def test_get_contact(self): def test_save_and_delete(self): self.valid_committee_account.save() - committee_account_from_db = CommitteeAccount.objects.get( - committee_id="C87654321" - ) + committee_account_from_db = CommitteeAccount.objects.get(committee_id="C87654321") self.assertIsInstance(committee_account_from_db, CommitteeAccount) self.assertEquals(committee_account_from_db.committee_id, "C87654321") committee_account_from_db.delete() @@ -39,3 +38,38 @@ def test_save_and_delete(self): CommitteeAccount.all_objects.get, committee_id="C87654321", ) + + def test_all_models_include_committee_owned_mixin(self): + ignore_list = [ + "Permission", + "Group", + "ContentType", + "Session", + "CommitteeAccount", + "Membership", + "Form3X", + "Form24", + "Form99", + "Form1M", + "ReportTransaction", + "ScheduleA", + "ScheduleB", + "ScheduleC", + "ScheduleC1", + "ScheduleC2", + "ScheduleD", + "ScheduleE", + "DotFEC", + "UploadSubmission", + "WebPrintSubmission", + "User", + ] + app_models = apps.get_models() + + for model in app_models: + with self.subTest(model=model): + if model.__name__ not in ignore_list: + self.assertTrue( + issubclass(model, CommitteeOwnedModel), + f"{model.__name__} does not include CommitteeOwnedModel mixin.", + ) diff --git a/django-backend/fecfiler/committee_accounts/test_views.py b/django-backend/fecfiler/committee_accounts/test_views.py index bd7780e4d6..8d320629dc 100644 --- a/django-backend/fecfiler/committee_accounts/test_views.py +++ b/django-backend/fecfiler/committee_accounts/test_views.py @@ -1,8 +1,15 @@ +from importlib import import_module +import inspect +import os from uuid import UUID from django.test import RequestFactory, TestCase from fecfiler.committee_accounts.models import Membership -from fecfiler.committee_accounts.views import CommitteeMembershipViewSet, CommitteeViewSet - +from fecfiler.committee_accounts.views import ( + CommitteeMembershipViewSet, + CommitteeOwnedViewMixin, + CommitteeViewSet, +) +from rest_framework.viewsets import ViewSetMixin from fecfiler.user.models import User from unittest.mock import Mock, patch @@ -221,3 +228,67 @@ def test_get_committee_account_data_from_redis(self): self.assertNotEqual(len(was_called_with), 0) self.assertIn("C12345678", was_called_with) self.assertEqual(response.data["name"], "TEST") + + def test_all_viewsets_include_mixin(self): + script_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.join(script_dir, "../..") + exclude_dirs = ["venv", "__pycache__"] + + exclude_list = [ + "CommitteeViewSet", + "UserViewSet", + "SystemStatusViewSet", + "WebServicesViewSet", + "SummaryViewSet", + "FeedbackViewSet", + ] + + subclasses = find_subclasses(ViewSetMixin, project_root, exclude_dirs) + missing_mixin = [ + subclass + for subclass in subclasses + if not issubclass(subclass, CommitteeOwnedViewMixin) + and subclass.__name__ not in exclude_list + ] + + self.assertEqual( + len(missing_mixin), + 0, + f"The following subclasses of GenericViewSet " + f"do not include CommitteeOwnedViewMixin:" + f"{', '.join([f'{cls.__module__}.{cls.__name__}' for cls in missing_mixin])}", + ) + + +def find_subclasses(base_class, project_path, exclude_dirs=None): + subclasses = [] + exclude_dirs = exclude_dirs or [] + + for root, dirs, files in os.walk(project_path): + dirs[:] = [d for d in dirs if d not in exclude_dirs] + + for file in files: + if ( + file.endswith("views.py") + and not file.startswith("__init__") + and "test_views" not in file + ): + module_path = os.path.join(root, file) + module_name = ( + module_path.replace(project_path, "") + .replace(os.sep, ".") + .lstrip(".") + .replace(".py", "") + ) + try: + module = import_module(module_name) + except (ModuleNotFoundError, ImportError): + print(f"module not found: {module_name}") + continue + + for name, obj in inspect.getmembers(module, inspect.isclass): + if obj.__module__ == module.__name__: + if issubclass(obj, base_class) and obj != base_class: + subclasses.append(obj) + + return subclasses diff --git a/django-backend/fecfiler/memo_text/views.py b/django-backend/fecfiler/memo_text/views.py index d7c22ec563..2397260a85 100644 --- a/django-backend/fecfiler/memo_text/views.py +++ b/django-backend/fecfiler/memo_text/views.py @@ -1,11 +1,10 @@ from .models import MemoText from .serializers import MemoTextSerializer -from fecfiler.committee_accounts.views import CommitteeOwnedViewMixin from fecfiler.reports.views import ReportViewMixin from rest_framework.viewsets import ModelViewSet -class MemoTextViewSet(CommitteeOwnedViewMixin, ReportViewMixin, ModelViewSet): +class MemoTextViewSet(ReportViewMixin, ModelViewSet): def get_queryset(self): memos = super().get_queryset() query_params = self.request.query_params.keys() diff --git a/django-backend/fecfiler/reports/views.py b/django-backend/fecfiler/reports/views.py index eb6db1ecf8..6a83461803 100644 --- a/django-backend/fecfiler/reports/views.py +++ b/django-backend/fecfiler/reports/views.py @@ -183,7 +183,7 @@ def list(self, request, *args, **kwargs): return super().list(request, args, kwargs) -class ReportViewMixin(GenericViewSet): +class ReportViewMixin(CommitteeOwnedViewMixin, GenericViewSet): def get_queryset(self): return filter_by_report(super().get_queryset(), self) diff --git a/django-backend/fecfiler/web_services/views.py b/django-backend/fecfiler/web_services/views.py index cd8b3f7c03..e55b123a03 100644 --- a/django-backend/fecfiler/web_services/views.py +++ b/django-backend/fecfiler/web_services/views.py @@ -17,13 +17,13 @@ from .models import DotFEC, UploadSubmission, WebPrintSubmission from fecfiler.reports.models import Report, FORMS_TO_CALCULATE from celery.result import AsyncResult - +from fecfiler.committee_accounts.views import CommitteeOwnedViewMixin import structlog logger = structlog.get_logger(__name__) -class WebServicesViewSet(viewsets.ViewSet): +class WebServicesViewSet(CommitteeOwnedViewMixin, viewsets.ViewSet): """ A viewset that provides actions to start web service tasks and retrieve thier statuses and results