diff --git a/otisweb_testsuite/__init__.py b/otisweb_testsuite/__init__.py index 3577f93e..484b7d5b 100644 --- a/otisweb_testsuite/__init__.py +++ b/otisweb_testsuite/__init__.py @@ -5,7 +5,7 @@ with enhanced assertion methods and debugging capabilities. """ -from .testcase import EvanTestCase, UniqueFaker +from .faker import UniqueFaker -__all__ = ["EvanTestCase", "UniqueFaker"] +__all__ = ["UniqueFaker"] __version__ = "1.0.0" diff --git a/otisweb_testsuite/faker.py b/otisweb_testsuite/faker.py new file mode 100644 index 00000000..44e3bdce --- /dev/null +++ b/otisweb_testsuite/faker.py @@ -0,0 +1,19 @@ +"""Custom Faker utilities for factory_boy.""" + +from typing import Any + +import factory +import factory.random +from factory.faker import Faker + +# Seed factory_boy's random generator for reproducible tests +factory.random.reseed_random("otisweb") + + +# waiting on https://github.com/FactoryBoy/factory_boy/pull/820 ... +class UniqueFaker(Faker): + # based on factory.faker.Faker.generate + def generate(self, **params: Any) -> Any: + locale = params.pop("locale") + subfaker = self._get_faker(locale) + return subfaker.unique.format(self.provider, **params) diff --git a/otisweb_testsuite/testcase.py b/otisweb_testsuite/testcase.py deleted file mode 100644 index 15328a06..00000000 --- a/otisweb_testsuite/testcase.py +++ /dev/null @@ -1,280 +0,0 @@ -import json -import logging -import pprint -from datetime import datetime -from pathlib import Path -from typing import TYPE_CHECKING, Any, Type, Union - -import factory -import factory.random -from django.conf import settings -from django.contrib.auth.models import User -from django.db import models -from django.test import RequestFactory, TestCase -from django.test.client import Client -from django.urls.base import resolve, reverse -from factory.faker import Faker - -factory.random.reseed_random("otisweb") - -# OKAY TIME TO MONKEY PATCH THE MONKEY PATCH -if TYPE_CHECKING: # pragma: no cover - from django.test.client import _MonkeyPatchedWSGIResponse # type: ignore # NOQA - - MonkeyResponseType = _MonkeyPatchedWSGIResponse # type: ignore -else: - MonkeyResponseType = Any - - -# waiting on https://github.com/FactoryBoy/factory_boy/pull/820 ... -class UniqueFaker(Faker): - # based on factory.faker.Faker.generate - def generate(self, **params: Any) -> Any: - locale = params.pop("locale") - subfaker = self._get_faker(locale) - return subfaker.unique.format(self.provider, **params) - - -class EvanTestCase(TestCase): - def setUp(self): - self.client = Client() - - def debug_short(self, response: MonkeyResponseType) -> str: - d: dict[str, Any] = {} - for key in ("headers", "json", "redirect_chain", "request", "wsgi_request"): - d[key] = getattr(response, key, None) - return "\n" + pprint.pformat(d, compact=False, depth=3) + "\n" - - def debug_dump(self, response: MonkeyResponseType) -> None: - timestamp = datetime.now().strftime("%d_%b_%Y_%H%M%S") - html_path = Path(f"/tmp/{settings.WSGI_APPLICATION}.tests/{timestamp}.html") - txt_path = Path(f"/tmp/{settings.WSGI_APPLICATION}.tests/{timestamp}.txt") - - try: - html_path.parent.mkdir(exist_ok=True) - except PermissionError: - pass - else: - if len(response.content) > 0: - logging.info(f"Wrote to {html_path}") - html_path.write_bytes(response.content) - txt_path.write_text(pprint.pformat(response.__dict__, depth=3)) - - def assertHas( - self, response: MonkeyResponseType, text: Union[bytes, int, str], **kwargs: Any - ): - try: - self.assertContains( - response, text, **kwargs, msg_prefix=self.debug_short(response) - ) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertNotHas( - self, response: MonkeyResponseType, text: Union[bytes, str], **kwargs: Any - ): - try: - self.assertNotContains( - response, text, **kwargs, msg_prefix=self.debug_short(response) - ) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertMessage( - self, response: MonkeyResponseType, text: Union[bytes, int, str], **kwargs: Any - ): - self.assertIn(text, [m.message for m in response.context["messages"] or []]) - - def assertNoMessages(self, response: MonkeyResponseType): - self.assertEqual(len(response.context["messages"]), 0) - - def assertResponse20X(self, response: MonkeyResponseType): - try: - self.assertGreaterEqual( - response.status_code, 200, self.debug_short(response) - ) - self.assertLess(response.status_code, 300, self.debug_short(response)) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertResponse30X(self, response: MonkeyResponseType): - try: - self.assertGreaterEqual( - response.status_code, 300, self.debug_short(response) - ) - self.assertLess(response.status_code, 400, self.debug_short(response)) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertResponseOK(self, response: MonkeyResponseType): - try: - self.assertLess(response.status_code, 400, self.debug_short(response)) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertResponse40X(self, response: MonkeyResponseType): - try: - self.assertGreaterEqual( - response.status_code, 400, self.debug_short(response) - ) - self.assertLess(response.status_code, 500, self.debug_short(response)) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertResponseDenied(self, response: MonkeyResponseType): - try: - if response.status_code != 400: - self.assertEqual(response.status_code, 403, self.debug_short(response)) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def assertResponseNotFound(self, response: MonkeyResponseType): - try: - self.assertEqual(response.status_code, 404, self.debug_short(response)) - except AssertionError as e: - self.debug_dump(response) - raise e - else: - return response - - def setupViewGet( - self, viewClass: Type[object], name: str, *args: Any, **kwargs: Any - ): - url = reverse(name, args=args) - request = RequestFactory().get(url, **kwargs) - resolver_match = resolve(url) - view = viewClass() - view.setup(request, *resolver_match.args, **resolver_match.kwargs) # type: ignore - return view - - def get(self, name: str, *args: Any, **kwargs: Any): - if (json_data := kwargs.pop("json", None)) is not None: - kwargs["content_type"] = "application/json" - kwargs["data"] = json.dumps(json_data) - return self.client.get(reverse(name, args=args), **kwargs) - - def post(self, name: str, *args: Any, **kwargs: Any): - if (json_data := kwargs.pop("json", None)) is not None: - kwargs["content_type"] = "application/json" - kwargs["data"] = json.dumps(json_data) - return self.client.post(reverse(name, args=args), **kwargs) - - def url(self, name: str, *args: Any): - return reverse(name, args=args) - - def assertGetOK(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponseOK(self.get(name, *args, **kwargs)) - - def assertGet20X(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponse20X(self.get(name, *args, **kwargs)) - - def assertGet30X(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponse30X(self.get(name, *args, **kwargs)) - - def assertGet40X(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponse40X(self.get(name, *args, **kwargs)) - - def assertGetDenied(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponseDenied(self.get(name, *args, **kwargs)) - - def assertGetNotFound(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponseNotFound(self.get(name, *args, **kwargs)) - - def assertGetRedirects(self, target: str, name: str, *args: Any, **kwargs: Any): - resp = self.get(name, *args, **kwargs) - self.assertRedirects( - resp, - expected_url=target, - target_status_code=200, - msg_prefix=self.debug_short(resp), - ) - return resp - - def assertPostOK(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponseOK(self.post(name, *args, **kwargs)) - - def assertPost20X(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponse20X(self.post(name, *args, **kwargs)) - - def assertPost(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponse30X(self.post(name, *args, **kwargs)) - - def assertPost40X(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponse40X(self.post(name, *args, **kwargs)) - - def assertPostDenied(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponseDenied(self.post(name, *args, **kwargs)) - - def assertPostNotFound(self, name: str, *args: Any, **kwargs: Any): - return self.assertResponseNotFound(self.post(name, *args, **kwargs)) - - def assertPostRedirects(self, target: str, name: str, *args: Any, **kwargs: Any): - resp = self.post(name, *args, **kwargs) - self.assertRedirects( - resp, - expected_url=target, - target_status_code=200, - msg_prefix=self.debug_short(resp), - ) - return resp - - def login_name(self, username: str): - user = User.objects.get(username=username) - self.client.force_login(user) - return user - - def login(self, obj: Union[str, models.Model]): - if isinstance(obj, str): - return self.login_name(obj) - elif isinstance(obj, User): - self.client.force_login(obj) - return obj - elif hasattr(obj, "user"): - user = getattr(obj, "user") - self.client.force_login(user) - return user - - def assertGetBecomesLoginRedirect(self, name: str, *args: Any, **kwargs: Any): - redirectURL = "/accounts/login/?next=" + reverse(name, args=args) - resp = self.get(name, *args, **kwargs) - self.assertRedirects(resp, redirectURL) - return resp - - def assertPostBecomesLoginRedirect(self, name: str, *args: Any, **kwargs: Any): - redirectURL = "/accounts/login/?next=" + reverse(name, args=args) - resp = self.post(name, *args, **kwargs) - self.assertRedirects(resp, redirectURL) - return resp - - def assertGetBecomesStaffRedirect(self, name: str, *args: Any, **kwargs: Any): - redirectURL = "/admin/login/?next=" + reverse(name, args=args) - resp = self.get(name, *args, **kwargs) - self.assertRedirects(resp, redirectURL) - return resp - - def assertPostBecomesStaffRedirect(self, name: str, *args: Any, **kwargs: Any): - redirectURL = "/admin/login/?next=" + reverse(name, args=args) - resp = self.post(name, *args, **kwargs) - self.assertRedirects(resp, redirectURL) - return resp