From acbaa00f0792925a400d0a455d36380d0ae7edf6 Mon Sep 17 00:00:00 2001 From: Tom Wojcik Date: Mon, 28 Jul 2025 13:43:09 +0200 Subject: [PATCH 1/4] refactor tests into pytest --- poetry.lock | 52 ++- pyproject.toml | 8 +- testproject/conftest.py | 146 +++++++- testproject/testapp/factories.py | 95 +++++ testproject/testapp/tests/common.py | 60 --- .../tests/social/test_provider_auth.py | 73 ++-- .../testapp/tests/social/test_token_jwt.py | 14 +- testproject/testapp/tests/test_activation.py | 108 +++--- testproject/testapp/tests/test_email.py | 42 +-- .../testapp/tests/test_password_reset.py | 175 +++++---- .../tests/test_password_reset_confirm.py | 350 +++++++++--------- .../testapp/tests/test_resend_activation.py | 151 ++++---- .../testapp/tests/test_reset_username.py | 173 +++++---- .../tests/test_reset_username_confirm.py | 338 ++++++++--------- .../testapp/tests/test_set_password.py | 182 ++++----- .../testapp/tests/test_set_username.py | 305 +++++++-------- testproject/testapp/tests/test_settings.py | 62 ++-- .../testapp/tests/test_token_create.py | 97 +++-- ...ken_create_custom_username_login_fields.py | 113 ++++-- .../testapp/tests/test_token_destroy.py | 59 ++- testproject/testapp/tests/test_user_create.py | 203 +++++----- testproject/testapp/tests/test_user_delete.py | 138 +++---- testproject/testapp/tests/test_user_detail.py | 235 ++++++------ testproject/testapp/tests/test_user_list.py | 58 ++- testproject/testapp/tests/test_user_me.py | 160 ++++---- testproject/testapp/tests/test_user_view.py | 188 +++++----- .../testapp/tests/test_webauthn/test_login.py | 66 ++-- .../tests/test_webauthn/test_login_request.py | 41 +- .../tests/test_webauthn/test_signup.py | 82 ++-- .../test_webauthn/test_signup_request.py | 33 +- .../testapp/tests/test_webauthn/utils.py | 5 +- 31 files changed, 2070 insertions(+), 1742 deletions(-) create mode 100644 testproject/testapp/factories.py delete mode 100644 testproject/testapp/tests/common.py diff --git a/poetry.lock b/poetry.lock index aa26c9b2..93e2bce2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -686,6 +686,38 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "factory-boy" +version = "3.3.3" +description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby." +optional = false +python-versions = ">=3.8" +files = [ + {file = "factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc"}, + {file = "factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03"}, +] + +[package.dependencies] +Faker = ">=0.7.0" + +[package.extras] +dev = ["Django", "Pillow", "SQLAlchemy", "coverage", "flake8", "isort", "mongoengine", "mongomock", "mypy", "tox", "wheel (>=0.32.0)", "zest.releaser[recommended]"] +doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] + +[[package]] +name = "faker" +version = "37.4.2" +description = "Faker is a Python package that generates fake data for you." +optional = false +python-versions = ">=3.9" +files = [ + {file = "faker-37.4.2-py3-none-any.whl", hash = "sha256:b70ed1af57bfe988cbcd0afd95f4768c51eaf4e1ce8a30962e127ac5c139c93f"}, + {file = "faker-37.4.2.tar.gz", hash = "sha256:8e281bbaea30e5658895b8bea21cc50d27aaf3a43db3f2694409ca5701c56b0a"}, +] + +[package.dependencies] +tzdata = "*" + [[package]] name = "filelock" version = "3.18.0" @@ -1153,6 +1185,24 @@ pytest = ">=7.0.0" docs = ["sphinx", "sphinx_rtd_theme"] testing = ["Django", "django-configurations (>=2.0)"] +[[package]] +name = "pytest-env" +version = "1.1.3" +description = "pytest plugin that allows you to add environment variables." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_env-1.1.3-py3-none-any.whl", hash = "sha256:aada77e6d09fcfb04540a6e462c58533c37df35fa853da78707b17ec04d17dfc"}, + {file = "pytest_env-1.1.3.tar.gz", hash = "sha256:fcd7dc23bb71efd3d35632bde1bbe5ee8c8dc4489d6617fb010674880d96216b"}, +] + +[package.dependencies] +pytest = ">=7.4.3" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +test = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "pytest-mock (>=3.12)"] + [[package]] name = "pytest-mock" version = "3.14.1" @@ -1848,4 +1898,4 @@ webauthn = ["webauthn"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "729ee879e81144bc67b34a9949fca0b6a2bd61f3b06c184b90ef7a28896c9746" +content-hash = "6a1988560b3887c413c309e80d5207771bdcc2217bc8dcaec32557090297cd9c" diff --git a/pyproject.toml b/pyproject.toml index aa879658..eea217d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,8 @@ tox = "^4.4.8" babel = "^2.12.1" pytest-mock = "^3.14.0" deepdiff = "^8.0.1" +pytest-env = "^1.1.3" +factory-boy = "^3.3.0" [tool.poetry.group.code-quality.dependencies] black = ">=23.1,<26.0" @@ -143,7 +145,11 @@ in-place = true [tool.pytest.ini_options] minversion = "7.0" DJANGO_SETTINGS_MODULE = "testproject.settings" -python_paths = "testproject" +python_paths = ["testproject"] +addopts = "--tb=short --strict-markers" +markers = [ + "django_db: mark test to use django database" +] [tool.coverage.run] source = ["djoser"] diff --git a/testproject/conftest.py b/testproject/conftest.py index d72289fc..67415ddd 100644 --- a/testproject/conftest.py +++ b/testproject/conftest.py @@ -1,8 +1,148 @@ import pytest +from rest_framework.test import APIClient +from djoser.conf import settings as djoser_settings + +from testapp.factories import ( + UserFactory, + TokenFactory, +) + +Token = djoser_settings.TOKEN_MODEL + + +@pytest.fixture(autouse=True) +def allow_db_access(db): + yield + + +@pytest.fixture +def api_client(): + """DRF API client fixture.""" + return APIClient() + + +@pytest.fixture +def user(db): + """Create a basic user for testing.""" + return UserFactory() + + +@pytest.fixture +def create_superuser(db): + """Create a superuser for testing.""" + return UserFactory.create( + username="admin", + email="admin@example.com", + is_superuser=True, + is_staff=True, + ) + + +@pytest.fixture +def inactive_user(db): + """Create an inactive user for testing.""" + return UserFactory.create(is_active=False) + + +@pytest.fixture +def authenticated_client(api_client, user): + """API client authenticated with a token.""" + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION=f"Token {token.key}") + return api_client + + +@pytest.fixture +def signal_tracker(): + """Track Django signals for testing.""" + + class SignalTracker: + def __init__(self): + self.signal_sent = False + self.signals_received = [] + + def receiver(self, sender=None, **kwargs): + self.signal_sent = True + self.signals_received.append({"sender": sender, "kwargs": kwargs}) + + def reset(self): + self.signal_sent = False + self.signals_received = [] + + return SignalTracker() @pytest.fixture -def user(): - from testapp.tests.common import create_user +def djoser_settings(settings): + """ + Fixture to easily modify DJOSER settings in tests. + + Usage: + def test_something(djoser_settings): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + djoser_settings.update(WEBAUTHN={"RP_NAME": "test"}) + # Settings are automatically applied + """ + + class DjoserSettingsProxy: + def __init__(self, settings_dict): + # Make a copy to avoid modifying the original + self._settings = dict(settings_dict) + self._original_settings = dict(settings_dict) + + def __getitem__(self, key): + return self._settings[key] + + def __setitem__(self, key, value): + self._settings[key] = value + self._reload() + + def __getattr__(self, name): + # Support attribute access like djoser_settings.EMAIL + if name.startswith("_"): + raise AttributeError( + f"'{self.__class__.__name__}' object has no attribute '{name}'" + ) + # First try to get from our custom settings + if name in self._settings: + return self._settings[name] + # If not found, check the actual djoser settings object + from djoser.conf import settings as djoser_conf_settings + + return getattr(djoser_conf_settings, name, None) + + def __setattr__(self, name, value): + if name.startswith("_"): + super().__setattr__(name, value) + else: + self._settings[name] = value + self._reload() + + def update(self, **kwargs): + self._settings.update(kwargs) + self._reload() + + def get(self, key, default=None): + return self._settings.get(key, default) + + def clear(self): + # Reset to only default settings (not Django project settings) + + self._settings = {} + self._reload() + + def _reload(self): + # Update Django settings + settings.DJOSER = self._settings + # Force reload of djoser settings + from djoser.conf import reload_djoser_settings + + reload_djoser_settings(setting="DJOSER", value=self._settings) + + prx = DjoserSettingsProxy(settings.DJOSER) + yield prx + # Restore original settings + settings.DJOSER = prx._original_settings + from djoser.conf import reload_djoser_settings - return create_user() + reload_djoser_settings(setting="DJOSER", value=prx._original_settings) diff --git a/testproject/testapp/factories.py b/testproject/testapp/factories.py new file mode 100644 index 00000000..1ed493f4 --- /dev/null +++ b/testproject/testapp/factories.py @@ -0,0 +1,95 @@ +import factory +from factory import Faker +from django.contrib.auth import get_user_model + +from djoser.conf import settings as djoser_settings + + +User = get_user_model() + + +class UserFactory(factory.django.DjangoModelFactory): + class Meta: + model = User + skip_postgeneration_save = True + + username = factory.Sequence(lambda n: f"user{n}") + email = Faker("email") + + @factory.post_generation + def password(self, create, extracted, **kwargs): + if create: + # Check if password was explicitly passed as a parameter + password_value = extracted if extracted is not None else "secret" + if password_value is None: + self.set_unusable_password() + self.raw_password = None + else: + self.set_password(password_value) + self.raw_password = password_value + self.save() + + +class CustomUserFactory(factory.django.DjangoModelFactory): + class Meta: + model = "testapp.CustomUser" + skip_postgeneration_save = True + + custom_username = factory.Sequence(lambda n: f"user{n}") + custom_email = Faker("email") + custom_required_field = "42" + + @factory.post_generation + def password(self, create, extracted, **kwargs): + if create: + # Check if password was explicitly passed as a parameter + password_value = extracted if extracted is not None else "secret" + if password_value is None: + self.set_unusable_password() + self.raw_password = None + else: + self.set_password(password_value) + self.raw_password = password_value + self.save() + + +class ExampleUserFactory(factory.django.DjangoModelFactory): + class Meta: + model = "testapp.ExampleUser" + skip_postgeneration_save = True + + email = Faker("email") + + @factory.post_generation + def password(self, create, extracted, **kwargs): + if create: + # Check if password was explicitly passed as a parameter + password_value = extracted if extracted is not None else "secret" + if password_value is None: + self.set_unusable_password() + self.raw_password = None + else: + self.set_password(password_value) + self.raw_password = password_value + self.save() + + +class TokenFactory(factory.django.DjangoModelFactory): + class Meta: + model = djoser_settings.TOKEN_MODEL + + user = factory.SubFactory(UserFactory) + + +class CredentialOptionsFactory(factory.django.DjangoModelFactory): + class Meta: + model = "webauthn.CredentialOptions" + + challenge = Faker("sha256") + username = factory.Sequence(lambda n: f"user{n}") + display_name = Faker("name") + ukey = Faker("uuid4") + user = factory.SubFactory(UserFactory) + credential_id = Faker("sha256") + sign_count = 0 + public_key = Faker("sha256") diff --git a/testproject/testapp/tests/common.py b/testproject/testapp/tests/common.py deleted file mode 100644 index a31d19e6..00000000 --- a/testproject/testapp/tests/common.py +++ /dev/null @@ -1,60 +0,0 @@ -from django.contrib.auth import get_user_model -from django.db import IntegrityError - -from djoser.conf import settings as djoser_settings - -from unittest import mock - -__all__ = [ - "get_user_model", - "IntegrityError", - "mock", - "RunCheck", - "PermCheckClass", - "SerializerCheckClass", -] - -Token = djoser_settings.TOKEN_MODEL - - -def create_user(use_custom_data=False, **kwargs): - data = ( - {"username": "john", "password": "secret", "email": "john@beatles.com"} - if not use_custom_data - else { - "custom_username": "john", - "password": "secret", - "custom_email": "john@beatles.com", - "custom_required_field": "42", - } - ) - data.update(kwargs) - user = get_user_model().objects.create_user(**data) - user.raw_password = data["password"] - return user - - -def login_user(client, user): - token = Token.objects.create(user=user) - client.credentials(HTTP_AUTHORIZATION="Token " + token.key) - - -def perform_create_mock(x): - raise IntegrityError - - -class RunCheck(Exception): - pass - - -class PermCheckClass: - def has_permission(self, *args, **kwargs): - raise RunCheck("working") - - def has_object_permission(self, *args, **kwargs): - raise RunCheck("working") - - -class SerializerCheckClass: - def __init__(self, *args, **kwargs): - raise RunCheck("working") diff --git a/testproject/testapp/tests/social/test_provider_auth.py b/testproject/testapp/tests/social/test_provider_auth.py index 9279907c..1c81884e 100644 --- a/testproject/testapp/tests/social/test_provider_auth.py +++ b/testproject/testapp/tests/social/test_provider_auth.py @@ -1,55 +1,72 @@ +import pytest +from testapp.factories import UserFactory from django.contrib.sessions.middleware import SessionMiddleware -from djet import assertions, restframework from rest_framework import status +from rest_framework.test import APIRequestFactory from social_core.exceptions import AuthException import djoser.social.views -from ..common import create_user, mock +from unittest import mock -class ProviderAuthViewTestCase( - restframework.APIViewTestCase, assertions.StatusCodeAssertionsMixin -): - view_class = djoser.social.views.ProviderAuthView - middleware = [SessionMiddleware] +@pytest.mark.django_db +class TestProviderAuthView: + + @pytest.fixture(autouse=True) + def setup(self): + self.factory = APIRequestFactory() + self.view_class = djoser.social.views.ProviderAuthView + + def _get_view_response(self, request, **kwargs): + """Helper to get view response with middleware applied""" + view = self.view_class.as_view() + # Apply middleware + middleware = SessionMiddleware(lambda req: None) + middleware.process_request(request) + request.session.save() + return view(request, **kwargs) def test_get_facebook_provider_fails_if_no_redirect_uri(self): - request = self.factory.get() - response = self.view(request, provider="facebook") + request = self.factory.get("/auth/facebook/") + response = self._get_view_response(request, provider="facebook") - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST def test_get_facebook_provider_fails_if_wrong_redirect_uri(self): - request = self.factory.get(data={"redirect_uri": "http://yolo.com/"}) - response = self.view(request, provider="facebook") + request = self.factory.get( + "/auth/facebook/", data={"redirect_uri": "http://yolo.com/"} + ) + response = self._get_view_response(request, provider="facebook") - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST def test_get_facebook_provider_provides_valid_authorization_url(self): - request = self.factory.get(data={"redirect_uri": "http://test.localhost/"}) - response = self.view(request, provider="facebook") + request = self.factory.get( + "/auth/facebook/", data={"redirect_uri": "http://test.localhost/"} + ) + response = self._get_view_response(request, provider="facebook") - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertIn("authorization_url", response.data) + assert response.status_code == status.HTTP_200_OK + assert "authorization_url" in response.data def test_post_facebook_provider_success_returns_token(self): data = {"code": "XYZ", "state": "ABC"} mock.patch( "social_core.backends.facebook.FacebookOAuth2.auth_complete", - return_value=create_user(), + return_value=UserFactory.create(), ).start() mock.patch( "social_core.backends.oauth.OAuthAuth.get_session_state", return_value=data["state"], ).start() - request = self.factory.post() + request = self.factory.post("/auth/facebook/") request.GET = {k: v for k, v in data.items()} - response = self.view(request, provider="facebook") - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assertEqual(set(response.data.keys()), {"access", "refresh", "user"}) + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_201_CREATED + assert set(response.data.keys()) == {"access", "refresh", "user"} def test_post_facebook_provider_code_validation_fails(self): data = {"code": "XYZ", "state": "ABC"} @@ -63,10 +80,10 @@ def test_post_facebook_provider_code_validation_fails(self): return_value=data["state"], ).start() - request = self.factory.post() + request = self.factory.post("/auth/facebook/") request.GET = {k: v for k, v in data.items()} - response = self.view(request, provider="facebook") - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST def test_post_facebook_provider_validation_fails_if_invalid_state(self): data = {"code": "XYZ", "state": "ABC"} @@ -76,7 +93,7 @@ def test_post_facebook_provider_validation_fails_if_invalid_state(self): return_value=data["state"][::-1], ).start() - request = self.factory.post() + request = self.factory.post("/auth/facebook/") request.GET = {k: v for k, v in data.items()} - response = self.view(request, provider="facebook") - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST diff --git a/testproject/testapp/tests/social/test_token_jwt.py b/testproject/testapp/tests/social/test_token_jwt.py index 5cb5452f..faa890e0 100644 --- a/testproject/testapp/tests/social/test_token_jwt.py +++ b/testproject/testapp/tests/social/test_token_jwt.py @@ -1,17 +1,17 @@ -from django.test import TestCase +import pytest +from testapp.factories import UserFactory from rest_framework_simplejwt.serializers import TokenVerifySerializer from djoser.social.token.jwt import TokenStrategy -from ..common import create_user - -class JWTStrategyTestCase(TestCase): +@pytest.mark.django_db +class TestJWTStrategy: def test_obtain_provides_valid_token_for_given_user(self): - user = create_user() + user = UserFactory.create() res = TokenStrategy.obtain(user) - self.assertEqual(res["user"], user) + assert res["user"] == user data = {"token": res["access"]} serializer = TokenVerifySerializer(data=data) - self.assertTrue(serializer.is_valid()) + assert serializer.is_valid() diff --git a/testproject/testapp/tests/test_activation.py b/testproject/testapp/tests/test_activation.py index 8dfac3a8..9ccc4cb5 100644 --- a/testproject/testapp/tests/test_activation.py +++ b/testproject/testapp/tests/test_activation.py @@ -1,11 +1,7 @@ -from django.conf import settings +import pytest from django.contrib.auth.tokens import default_token_generator -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user import djoser.signals import djoser.utils @@ -13,18 +9,14 @@ from djoser.conf import settings as default_settings -class ActivationViewTest( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin -): - def setUp(self): - self.base_url = reverse("user-activation") - self.signal_sent = False +@pytest.mark.django_db +class TestActivationView: - def signal_receiver(self, *args, **kwargs): - self.signal_sent = True + @pytest.fixture(autouse=True) + def setup(self): + self.base_url = reverse("user-activation") - def test_post_activate_user_and_not_login(self): - user = create_user() + def test_post_activate_user_and_not_login(self, api_client, user): user.is_active = False user.save() data = { @@ -32,74 +24,72 @@ def test_post_activate_user_and_not_login(self): "token": default_token_generator.make_token(user), } - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) user.refresh_from_db() - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assertTrue(user.is_active) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert user.is_active - def test_post_respond_with_bad_request_when_wrong_uid(self): - user = create_user() + def test_post_respond_with_bad_request_when_wrong_uid(self, api_client, user): data = {"uid": "wrong-uid", "token": default_token_generator.make_token(user)} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual(list(response.data.keys()), ["uid"]) - self.assertEqual( - response.data["uid"], - [default_settings.CONSTANTS.messages.INVALID_UID_ERROR], - ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert list(response.data.keys()) == ["uid"] + assert response.data["uid"] == [ + default_settings.CONSTANTS.messages.INVALID_UID_ERROR + ] - def test_post_respond_with_bad_request_when_stale_token(self): - user = create_user() - djoser.signals.user_activated.connect(self.signal_receiver) + def test_post_respond_with_bad_request_when_stale_token( + self, api_client, user, signal_tracker + ): + djoser.signals.user_activated.connect(signal_tracker.receiver) data = { "uid": djoser.utils.encode_uid(user.pk), "token": default_token_generator.make_token(user), } - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_403_FORBIDDEN) - self.assertEqual(list(response.data.keys()), ["detail"]) - self.assertEqual( - response.data["detail"], - default_settings.CONSTANTS.messages.STALE_TOKEN_ERROR, + assert response.status_code == status.HTTP_403_FORBIDDEN + assert list(response.data.keys()) == ["detail"] + assert ( + response.data["detail"] + == default_settings.CONSTANTS.messages.STALE_TOKEN_ERROR ) - self.assertFalse(self.signal_sent) + assert not signal_tracker.signal_sent - def test_post_respond_with_bad_request_when_wrong_token(self): - user = create_user() - djoser.signals.user_activated.connect(self.signal_receiver) + def test_post_respond_with_bad_request_when_wrong_token( + self, api_client, user, signal_tracker + ): + djoser.signals.user_activated.connect(signal_tracker.receiver) data = {"uid": djoser.utils.encode_uid(user.pk), "token": "wrong-token"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual(list(response.data.keys()), ["token"]) - self.assertEqual( - response.data["token"], - [default_settings.CONSTANTS.messages.INVALID_TOKEN_ERROR], - ) - self.assertFalse(self.signal_sent) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert list(response.data.keys()) == ["token"] + assert response.data["token"] == [ + default_settings.CONSTANTS.messages.INVALID_TOKEN_ERROR + ] + assert not signal_tracker.signal_sent - @override_settings( - DJOSER=dict(settings.DJOSER, **{"SEND_CONFIRMATION_EMAIL": True}) - ) - def test_post_sent_confirmation_email(self): - user = create_user() + def test_post_sent_confirmation_email( + self, djoser_settings, api_client, user, signal_tracker, mailoutbox + ): + djoser_settings["SEND_CONFIRMATION_EMAIL"] = True user.is_active = False user.save() - djoser.signals.user_activated.connect(self.signal_receiver) + djoser.signals.user_activated.connect(signal_tracker.receiver) data = { "uid": djoser.utils.encode_uid(user.pk), "token": default_token_generator.make_token(user), } - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) - self.assertTrue(self.signal_sent) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [user.email] + assert signal_tracker.signal_sent diff --git a/testproject/testapp/tests/test_email.py b/testproject/testapp/tests/test_email.py index 3a87b487..c13e200d 100644 --- a/testproject/testapp/tests/test_email.py +++ b/testproject/testapp/tests/test_email.py @@ -4,8 +4,6 @@ from unittest import mock from unittest.mock import patch -from django.conf import settings -from django.test.utils import override_settings from djoser.email import BaseDjoserEmail from djoser.conf import settings as djoser_settings import pytest @@ -13,42 +11,34 @@ @pytest.mark.django_db class TestDjoserEmail: - @override_settings(DJOSER=dict()) - def test_base_djoser_email_get_context_data_with_no_settings_uses_defaults(self): + def test_base_djoser_email_get_context_data_with_no_settings_uses_defaults( + self, djoser_settings + ): + djoser_settings.clear() base_djoser_email = BaseDjoserEmail() context_produced_without_settings = base_djoser_email.get_context_data() default_context = super(BaseDjoserEmail, base_djoser_email).get_context_data() default_context.pop("view") assert context_produced_without_settings == default_context - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{ - "EMAIL_FRONTEND_DOMAIN": "my_domain", - "EMAIL_FRONTEND_SITE_NAME": "my_site_name", - "EMAIL_FRONTEND_PROTOCOL": "https", - }, - ) - ) - def test_base_djoser_email_get_context_data_overrides_defaults_correctly(self): + def test_base_djoser_email_get_context_data_overrides_defaults_correctly( + self, djoser_settings + ): + djoser_settings["EMAIL_FRONTEND_DOMAIN"] = "my_domain" + djoser_settings["EMAIL_FRONTEND_SITE_NAME"] = "my_site_name" + djoser_settings["EMAIL_FRONTEND_PROTOCOL"] = "https" + base_djoser_email = BaseDjoserEmail() context_produced_using_settings = base_djoser_email.get_context_data() assert context_produced_using_settings.get("domain") == "my_domain" assert context_produced_using_settings.get("site_name") == "my_site_name" assert context_produced_using_settings.get("protocol") == "https" - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{ - "EMAIL_FRONTEND_DOMAIN": "my_domain", - "EMAIL_FRONTEND_SITE_NAME": "my_site_name", - "EMAIL_FRONTEND_PROTOCOL": "https", - }, - ) - ) - def test_all_emails_can_be_pickled(self, user): + def test_all_emails_can_be_pickled(self, djoser_settings, user): + djoser_settings["EMAIL_FRONTEND_DOMAIN"] = "my_domain" + djoser_settings["EMAIL_FRONTEND_SITE_NAME"] = "my_site_name" + djoser_settings["EMAIL_FRONTEND_PROTOCOL"] = "https" + email_cls_keys = list(djoser_settings.EMAIL.keys()) request = mock.MagicMock() for email_cls_key in email_cls_keys: diff --git a/testproject/testapp/tests/test_password_reset.py b/testproject/testapp/tests/test_password_reset.py index ccf6817f..ab5dc4d8 100644 --- a/testproject/testapp/tests/test_password_reset.py +++ b/testproject/testapp/tests/test_password_reset.py @@ -1,106 +1,119 @@ -from django.conf import settings +import pytest +from testapp.factories import UserFactory, CustomUserFactory from django.contrib.sites.shortcuts import get_current_site from django.core import mail -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.models import CustomUser -from testapp.tests.common import create_user, mock +from unittest import mock +from testapp.models import CustomUser from djoser.compat import get_user_email from djoser.conf import settings as default_settings -class PasswordResetViewTest( - APITestCase, assertions.StatusCodeAssertionsMixin, assertions.EmailAssertionsMixin +@pytest.mark.django_db +def test_post_should_send_email_to_user_with_password_reset_link( + api_client, mailoutbox ): - def setUp(self): - self.base_url = reverse("user-reset-password") + base_url = reverse("user-reset-password") + user = UserFactory.create() + data = {"email": user.email} - def test_post_should_send_email_to_user_with_password_reset_link(self): - user = create_user() - data = {"email": user.email} + response = api_client.post(base_url, data) + request = response.wsgi_request - response = self.client.post(self.base_url, data) - request = response.wsgi_request + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 1 + assert user.email in [recipient for email in mailoutbox for recipient in email.to] + site = get_current_site(request) + assert site.domain in mail.outbox[0].body + assert site.name in mail.outbox[0].body - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) - site = get_current_site(request) - self.assertIn(site.domain, mail.outbox[0].body) - self.assertIn(site.name, mail.outbox[0].body) - def test_post_send_email_to_user_with_request_domain_and_site_name(self): - user = create_user() - data = {"email": user.email} +@pytest.mark.django_db +def test_post_send_email_to_user_with_request_domain_and_site_name(api_client): + base_url = reverse("user-reset-password") + user = UserFactory.create() + data = {"email": user.email} - response = self.client.post(self.base_url, data) - request = response.wsgi_request + response = api_client.post(base_url, data) + request = response.wsgi_request - self.assertIn(request.get_host(), mail.outbox[0].body) + assert request.get_host() in mail.outbox[0].body - def test_post_should_not_send_email_to_user_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(0) +@pytest.mark.django_db +def test_post_should_not_send_email_to_user_if_user_does_not_exist( + api_client, mailoutbox +): + base_url = reverse("user-reset-password") + data = {"email": "john@beatles.com"} - def test_post_should_return_no_content_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 0 - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) +@pytest.mark.django_db +def test_post_should_return_no_content_if_user_does_not_exist(api_client): + base_url = reverse("user-reset-password") + data = {"email": "john@beatles.com"} - @override_settings( - DJOSER=dict(settings.DJOSER, **{"PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND": True}) - ) - def test_post_should_return_bad_request_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} - - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()[0], default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND - ) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.views.User", CustomUser) - @override_settings(AUTH_USER_MODEL="testapp.CustomUser") - def test_post_should_send_email_to_custom_user_with_password_reset_link( - self, - ): # noqa - user = create_user(use_custom_data=True) - data = {"custom_email": get_user_email(user)} - - response = self.client.post(self.base_url, data) - request = response.wsgi_request - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[get_user_email(user)]) - site = get_current_site(request) - self.assertIn(site.domain, mail.outbox[0].body) - self.assertIn(site.name, mail.outbox[0].body) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.views.User", CustomUser) - @override_settings( - AUTH_USER_MODEL="testapp.CustomUser", - DJOSER=dict(settings.DJOSER, **{"PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND": True}), + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + + +@pytest.mark.django_db +def test_post_should_return_bad_request_if_user_does_not_exist( + api_client, djoser_settings +): + djoser_settings.update(PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND=True) + base_url = reverse("user-reset-password") + data = {"email": "john@beatles.com"} + + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()[0] == default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND + + +@pytest.mark.django_db +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.views.User", CustomUser) +def test_post_should_send_email_to_custom_user_with_password_reset_link( + api_client, djoser_settings, mailoutbox +): + djoser_settings.update(AUTH_USER_MODEL="testapp.CustomUser") + base_url = reverse("user-reset-password") + user = CustomUserFactory.create(custom_required_field="42") + data = {"custom_email": get_user_email(user)} + + response = api_client.post(base_url, data) + request = response.wsgi_request + + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 1 + assert get_user_email(user) in [ + recipient for email in mailoutbox for recipient in email.to + ] + site = get_current_site(request) + assert site.domain in mail.outbox[0].body + assert site.name in mail.outbox[0].body + + +@pytest.mark.django_db +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.views.User", CustomUser) +def test_post_should_return_bad_request_with_custom_email_field_if_user_does_not_exist( + api_client, djoser_settings +): + djoser_settings.update( + AUTH_USER_MODEL="testapp.CustomUser", PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND=True ) - def test_post_should_return_bad_request_with_custom_email_field_if_user_does_not_exist( # NOQA: E501 - self, - ): - data = {"custom_email": "john@beatles.com"} + base_url = reverse("user-reset-password") + data = {"custom_email": "john@beatles.com"} - response = self.client.post(self.base_url, data) + response = api_client.post(base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()[0], default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND - ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()[0] == default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND diff --git a/testproject/testapp/tests/test_password_reset_confirm.py b/testproject/testapp/tests/test_password_reset_confirm.py index 47ce2303..924bb2ac 100644 --- a/testproject/testapp/tests/test_password_reset_confirm.py +++ b/testproject/testapp/tests/test_password_reset_confirm.py @@ -1,182 +1,188 @@ -from django.conf import settings +import pytest +from testapp.factories import UserFactory from django.contrib.auth.tokens import default_token_generator -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user import djoser.utils import djoser.views from djoser.conf import settings as default_settings -class PasswordResetConfirmViewTest( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin -): - def setUp(self): - self.base_url = reverse("user-reset-password-confirm") - - def test_post_set_new_password(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_password": "new password", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - user.refresh_from_db() - self.assertTrue(user.check_password(data["new_password"])) - self.assert_emails_in_mailbox(0) - - def test_post_not_set_new_password_if_broken_uid(self): - user = create_user() - data = { - "uid": "x", - "token": default_token_generator.make_token(user), - "new_password": "new password", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertIn("uid", response.data) - user.refresh_from_db() - self.assertFalse(user.check_password(data["new_password"])) - - def test_post_readable_error_message_when_uid_is_broken(self): - """Regression test for - https://github.com/sunscrapers/djoser/issues/122. - - When uid was not correct unicode string, error message was a - standard Python error messsage. Now we provide human readable - message. - """ - user = create_user() - data = { - "uid": b"\xd3\x10\xb4", - "token": default_token_generator.make_token(user), - "new_password": "new password", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertIn("uid", response.data) - self.assertEqual(len(response.data["uid"]), 1) - self.assertEqual( - response.data["uid"][0], - default_settings.CONSTANTS.messages.INVALID_UID_ERROR, - ) - - def test_post_not_set_new_password_if_user_does_not_exist(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk + 1), - "token": default_token_generator.make_token(user), - "new_password": "new password", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertIn("uid", response.data) - user.refresh_from_db() - self.assertFalse(user.check_password(data["new_password"])) - - def test_post_not_set_new_password_if_wrong_token(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": "wrong-token", - "new_password": "new password", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["token"], - [default_settings.CONSTANTS.messages.INVALID_TOKEN_ERROR], - ) - user.refresh_from_db() - self.assertFalse(user.check_password(data["new_password"])) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"PASSWORD_RESET_CONFIRM_RETYPE": True}) - ) - def test_post_not_set_new_password_if_password_mismatch(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_password": "new password", - "re_new_password": "wrong", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["non_field_errors"], - [default_settings.CONSTANTS.messages.PASSWORD_MISMATCH_ERROR], - ) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"PASSWORD_RESET_CONFIRM_RETYPE": True}) - ) - def test_post_not_set_new_password_if_mismatch(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_password": "new password", - "re_new_password": "wrong", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertFalse(user.check_password(data["new_password"])) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"PASSWORD_RESET_CONFIRM_RETYPE": True}) - ) - def test_post_not_reset_if_fails_password_validation(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_password": "666", - "re_new_password": "isokpassword", - } - - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data, {"new_password": ["Password 666 is not allowed."]} - ) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"PASSWORD_CHANGED_EMAIL_CONFIRMATION": True}) +@pytest.mark.django_db +def test_post_set_new_password(api_client, mailoutbox): + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_password": "new password", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + user.refresh_from_db() + assert user.check_password(data["new_password"]) + assert len(mailoutbox) == 0 + + +@pytest.mark.django_db +def test_post_not_set_new_password_if_broken_uid(api_client): + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": "x", + "token": default_token_generator.make_token(user), + "new_password": "new password", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "uid" in response.data + user.refresh_from_db() + assert not user.check_password(data["new_password"]) + + +@pytest.mark.django_db +def test_post_readable_error_message_when_uid_is_broken(api_client): + """Regression test for + https://github.com/sunscrapers/djoser/issues/122. + + When uid was not correct unicode string, error message was a + standard Python error messsage. Now we provide human readable + message. + """ + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": b"\xd3\x10\xb4", + "token": default_token_generator.make_token(user), + "new_password": "new password", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "uid" in response.data + assert len(response.data["uid"]) == 1 + assert ( + response.data["uid"][0] == default_settings.CONSTANTS.messages.INVALID_UID_ERROR ) - def test_post_password_changed_confirmation_email(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_password": "new password", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - user.refresh_from_db() - self.assertTrue(user.check_password(data["new_password"])) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) + + +@pytest.mark.django_db +def test_post_not_set_new_password_if_user_does_not_exist(api_client): + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk + 1), + "token": default_token_generator.make_token(user), + "new_password": "new password", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "uid" in response.data + user.refresh_from_db() + assert not user.check_password(data["new_password"]) + + +@pytest.mark.django_db +def test_post_not_set_new_password_if_wrong_token(api_client): + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": "wrong-token", + "new_password": "new password", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["token"] == [ + default_settings.CONSTANTS.messages.INVALID_TOKEN_ERROR + ] + user.refresh_from_db() + assert not user.check_password(data["new_password"]) + + +@pytest.mark.django_db +def test_post_not_set_new_password_if_password_mismatch(api_client, djoser_settings): + djoser_settings.update(PASSWORD_RESET_CONFIRM_RETYPE=True) + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_password": "new password", + "re_new_password": "wrong", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["non_field_errors"] == [ + default_settings.CONSTANTS.messages.PASSWORD_MISMATCH_ERROR + ] + + +@pytest.mark.django_db +def test_post_not_set_new_password_if_mismatch(api_client, djoser_settings): + djoser_settings.update(PASSWORD_RESET_CONFIRM_RETYPE=True) + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_password": "new password", + "re_new_password": "wrong", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert not user.check_password(data["new_password"]) + + +@pytest.mark.django_db +def test_post_not_reset_if_fails_password_validation(api_client, djoser_settings): + djoser_settings.update(PASSWORD_RESET_CONFIRM_RETYPE=True) + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_password": "666", + "re_new_password": "isokpassword", + } + + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data == {"new_password": ["Password 666 is not allowed."]} + + +@pytest.mark.django_db +def test_post_password_changed_confirmation_email( + api_client, djoser_settings, mailoutbox +): + djoser_settings.update(PASSWORD_CHANGED_EMAIL_CONFIRMATION=True) + base_url = reverse("user-reset-password-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_password": "new password", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + user.refresh_from_db() + assert user.check_password(data["new_password"]) + assert len(mailoutbox) == 1 + assert user.email in [recipient for email in mailoutbox for recipient in email.to] diff --git a/testproject/testapp/tests/test_resend_activation.py b/testproject/testapp/tests/test_resend_activation.py index c451b916..f1ca133f 100644 --- a/testproject/testapp/tests/test_resend_activation.py +++ b/testproject/testapp/tests/test_resend_activation.py @@ -1,76 +1,91 @@ -from django.conf import settings -from django.test.utils import override_settings -from djet import assertions +import pytest +from testapp.factories import UserFactory, CustomUserFactory from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase from testapp.models import CustomUser -from testapp.tests.common import create_user, mock +from unittest import mock from djoser.compat import get_user_email -class TestResendActivationEmail( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin +@pytest.fixture +def base_url(): + return reverse("user-resend-activation") + + +def test_resend_activation_view(djoser_settings, client, base_url, mailoutbox): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + user = UserFactory.create(is_active=False) + data = {"email": user.email} + response = client.post(base_url, data) + + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [user.email] + assert response.status_code == status.HTTP_204_NO_CONTENT + + +def test_dont_resend_activation_when_disabled( + djoser_settings, client, base_url, mailoutbox +): + djoser_settings["SEND_ACTIVATION_EMAIL"] = False + user = UserFactory.create(is_active=False) + data = {"email": user.email} + response = client.post(base_url, data) + + assert len(mailoutbox) == 0 + assert response.status_code == status.HTTP_400_BAD_REQUEST + + +def test_dont_resend_activation_when_active( + djoser_settings, client, base_url, mailoutbox +): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + user = UserFactory.create(is_active=True) + data = {"email": user.email} + response = client.post(base_url, data) + + assert len(mailoutbox) == 0 + assert response.status_code == status.HTTP_204_NO_CONTENT + + +def test_dont_resend_activation_when_no_password( + djoser_settings, client, base_url, mailoutbox +): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + user = UserFactory.create(is_active=False) + user.set_unusable_password() + user.raw_password = None + user.save() + data = {"email": user.email} + response = client.post(base_url, data) + + assert len(mailoutbox) == 0 + assert response.status_code == status.HTTP_204_NO_CONTENT + + +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.views.User", CustomUser) +@pytest.mark.django_db(transaction=True) +def test_resend_activation_view_custom_user( + djoser_settings, client, base_url, mailoutbox, settings +): + settings.AUTH_USER_MODEL = "testapp.CustomUser" + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + user = CustomUserFactory.create(custom_required_field="42", is_active=False) + data = {"custom_email": get_user_email(user)} + response = client.post(base_url, data) + + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [get_user_email(user)] + assert response.status_code == status.HTTP_204_NO_CONTENT + + +def test_post_should_return_no_content_if_user_does_not_exist( + djoser_settings, client, base_url ): - def setUp(self): - self.base_url = reverse("user-resend-activation") - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_resend_activation_view(self): - user = create_user(is_active=False) - data = {"email": user.email} - response = self.client.post(self.base_url, data) - - self.assert_email_exists(to=[user.email]) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": False})) - def test_dont_resend_activation_when_disabled(self): - user = create_user(is_active=False) - data = {"email": user.email} - response = self.client.post(self.base_url, data) - - self.assert_emails_in_mailbox(0) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_dont_resend_activation_when_active(self): - user = create_user(is_active=True) - data = {"email": user.email} - response = self.client.post(self.base_url, data) - - self.assert_emails_in_mailbox(0) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_dont_resend_activation_when_no_password(self): - user = create_user(is_active=False, password=None) - data = {"email": user.email} - response = self.client.post(self.base_url, data) - - self.assert_emails_in_mailbox(0) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.views.User", CustomUser) - @override_settings( - AUTH_USER_MODEL="testapp.CustomUser", - DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True}), - ) - def test_resend_activation_view_custom_user(self): - user = create_user(use_custom_data=True, is_active=False) - data = {"custom_email": get_user_email(user)} - response = self.client.post(self.base_url, data) - - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[get_user_email(user)]) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_post_should_return_no_content_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + data = {"email": "john@beatles.com"} + + response = client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT diff --git a/testproject/testapp/tests/test_reset_username.py b/testproject/testapp/tests/test_reset_username.py index 5245a898..c376f415 100644 --- a/testproject/testapp/tests/test_reset_username.py +++ b/testproject/testapp/tests/test_reset_username.py @@ -1,14 +1,12 @@ -from django.conf import settings +import pytest +from testapp.factories import UserFactory, CustomUserFactory from django.contrib.auth import get_user_model from django.contrib.sites.shortcuts import get_current_site from django.core import mail -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase from testapp.models import CustomUser -from testapp.tests.common import create_user, mock +from unittest import mock from djoser.compat import get_user_email from djoser.conf import settings as default_settings @@ -16,94 +14,109 @@ User = get_user_model() -class UsernameResetViewTest( - APITestCase, assertions.StatusCodeAssertionsMixin, assertions.EmailAssertionsMixin +@pytest.mark.django_db +def test_post_should_send_email_to_user_with_username_reset_link( + api_client, mailoutbox ): - def setUp(self): - self.base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + user = UserFactory.create() + data = {"email": user.email} - def test_post_should_send_email_to_user_with_username_reset_link(self): - user = create_user() - data = {"email": user.email} + response = api_client.post(base_url, data) + request = response.wsgi_request - response = self.client.post(self.base_url, data) - request = response.wsgi_request + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 1 + assert user.email in [recipient for email in mailoutbox for recipient in email.to] + site = get_current_site(request) + assert site.domain in mail.outbox[0].body + assert site.name in mail.outbox[0].body - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) - site = get_current_site(request) - self.assertIn(site.domain, mail.outbox[0].body) - self.assertIn(site.name, mail.outbox[0].body) - def test_post_send_email_to_user_with_request_domain_and_site_name(self): - user = create_user() - data = {"email": user.email} +@pytest.mark.django_db +def test_post_send_email_to_user_with_request_domain_and_site_name(api_client): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + user = UserFactory.create() + data = {"email": user.email} - response = self.client.post(self.base_url, data) - request = response.wsgi_request + response = api_client.post(base_url, data) + request = response.wsgi_request - self.assertIn(request.get_host(), mail.outbox[0].body) + assert request.get_host() in mail.outbox[0].body - def test_post_should_not_send_email_to_user_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(0) +@pytest.mark.django_db +def test_post_should_not_send_email_to_user_if_user_does_not_exist( + api_client, mailoutbox +): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + data = {"email": "john@beatles.com"} - def test_post_should_return_no_content_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 0 - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) +@pytest.mark.django_db +def test_post_should_return_no_content_if_user_does_not_exist(api_client): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + data = {"email": "john@beatles.com"} - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USERNAME_RESET_SHOW_EMAIL_NOT_FOUND": True}) - ) - def test_post_should_return_bad_request_if_user_does_not_exist(self): - data = {"email": "john@beatles.com"} - - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()[0], default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND - ) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.views.User", CustomUser) - @override_settings(AUTH_USER_MODEL="testapp.CustomUser") - def test_post_should_send_email_to_custom_user_with_username_reset_link( - self, - ): # noqa - user = create_user(use_custom_data=True) - data = {"custom_email": get_user_email(user)} - - response = self.client.post(self.base_url, data) - request = response.wsgi_request - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[get_user_email(user)]) - site = get_current_site(request) - self.assertIn(site.domain, mail.outbox[0].body) - self.assertIn(site.name, mail.outbox[0].body) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.views.User", CustomUser) - @override_settings( - AUTH_USER_MODEL="testapp.CustomUser", - DJOSER=dict(settings.DJOSER, **{"USERNAME_RESET_SHOW_EMAIL_NOT_FOUND": True}), + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + + +@pytest.mark.django_db +def test_post_should_return_bad_request_if_user_does_not_exist( + api_client, djoser_settings +): + djoser_settings.update(USERNAME_RESET_SHOW_EMAIL_NOT_FOUND=True) + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + data = {"email": "john@beatles.com"} + + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()[0] == default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND + + +@pytest.mark.django_db +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.views.User", CustomUser) +def test_post_should_send_email_to_custom_user_with_username_reset_link( + api_client, djoser_settings, mailoutbox +): + djoser_settings.update(AUTH_USER_MODEL="testapp.CustomUser") + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + user = CustomUserFactory.create(custom_required_field="42") + data = {"custom_email": get_user_email(user)} + + response = api_client.post(base_url, data) + request = response.wsgi_request + + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 1 + assert get_user_email(user) in [ + recipient for email in mailoutbox for recipient in email.to + ] + site = get_current_site(request) + assert site.domain in mail.outbox[0].body + assert site.name in mail.outbox[0].body + + +@pytest.mark.django_db +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.views.User", CustomUser) +def test_post_should_return_bad_request_with_custom_email_field_if_user_does_not_exist( + api_client, djoser_settings +): + djoser_settings.update( + AUTH_USER_MODEL="testapp.CustomUser", USERNAME_RESET_SHOW_EMAIL_NOT_FOUND=True ) - def test_post_should_return_bad_request_with_custom_email_field_if_user_does_not_exist( # NOQA: E501 - self, - ): - data = {"custom_email": "john@beatles.com"} + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}") + data = {"custom_email": "john@beatles.com"} - response = self.client.post(self.base_url, data) + response = api_client.post(base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()[0], default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND - ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.json()[0] == default_settings.CONSTANTS.messages.EMAIL_NOT_FOUND diff --git a/testproject/testapp/tests/test_reset_username_confirm.py b/testproject/testapp/tests/test_reset_username_confirm.py index e9272617..a3399a98 100644 --- a/testproject/testapp/tests/test_reset_username_confirm.py +++ b/testproject/testapp/tests/test_reset_username_confirm.py @@ -1,12 +1,9 @@ -from django.conf import settings +import pytest +from testapp.factories import UserFactory from django.contrib.auth import get_user_model from django.contrib.auth.tokens import default_token_generator -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user import djoser.utils import djoser.views @@ -15,167 +12,176 @@ User = get_user_model() -class UsernameResetConfirmViewTest( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin -): - def setUp(self): - self.base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") - - def test_post_set_new_username(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - user.refresh_from_db() - - self.assertEqual(data["new_username"], user.username) - self.assert_emails_in_mailbox(0) - - def test_post_not_set_new_username_if_broken_uid(self): - user = create_user() - data = { - "uid": "x", - "token": default_token_generator.make_token(user), - "new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertIn("uid", response.data) - user.refresh_from_db() - self.assertNotEqual(user.username, data["new_username"]) - - def test_post_readable_error_message_when_uid_is_broken(self): - user = create_user() - data = { - "uid": b"\xd3\x10\xb4", - "token": default_token_generator.make_token(user), - "new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertIn("uid", response.data) - self.assertEqual(len(response.data["uid"]), 1) - self.assertEqual( - response.data["uid"][0], - default_settings.CONSTANTS.messages.INVALID_UID_ERROR, - ) +@pytest.mark.django_db +def test_post_set_new_username(api_client, mailoutbox): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_username": "new_username", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + user.refresh_from_db() + + assert data["new_username"] == user.username + assert len(mailoutbox) == 0 + + +@pytest.mark.django_db +def test_post_not_set_new_username_if_broken_uid(api_client): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": "x", + "token": default_token_generator.make_token(user), + "new_username": "new_username", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "uid" in response.data + user.refresh_from_db() + assert user.username != data["new_username"] + + +@pytest.mark.django_db +def test_post_readable_error_message_when_uid_is_broken(api_client): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": b"\xd3\x10\xb4", + "token": default_token_generator.make_token(user), + "new_username": "new_username", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "uid" in response.data + assert len(response.data["uid"]) == 1 + assert ( + response.data["uid"][0] == default_settings.CONSTANTS.messages.INVALID_UID_ERROR + ) - def test_post_not_set_new_username_if_user_does_not_exist(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk + 1), - "token": default_token_generator.make_token(user), - "new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertIn("uid", response.data) - user.refresh_from_db() - self.assertNotEqual(user.username, data["new_username"]) - - def test_post_not_set_new_username_if_wrong_token(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": "wrong-token", - "new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["token"], - [default_settings.CONSTANTS.messages.INVALID_TOKEN_ERROR], - ) - user.refresh_from_db() - self.assertNotEqual(user.username, data["new_username"]) - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USERNAME_RESET_CONFIRM_RETYPE": True}) - ) - def test_post_not_set_new_username_if_username_mismatch(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_username": "new_username", - "re_new_username": "wrong", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["non_field_errors"], - [ - default_settings.CONSTANTS.messages.USERNAME_MISMATCH_ERROR.format( - User.USERNAME_FIELD - ) - ], # noqa +@pytest.mark.django_db +def test_post_not_set_new_username_if_user_does_not_exist(api_client): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk + 1), + "token": default_token_generator.make_token(user), + "new_username": "new_username", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "uid" in response.data + user.refresh_from_db() + assert user.username != data["new_username"] + + +@pytest.mark.django_db +def test_post_not_set_new_username_if_wrong_token(api_client): + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": "wrong-token", + "new_username": "new_username", + } + + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["token"] == [ + default_settings.CONSTANTS.messages.INVALID_TOKEN_ERROR + ] + user.refresh_from_db() + assert user.username != data["new_username"] + + +@pytest.mark.django_db +def test_post_not_set_new_username_if_username_mismatch(api_client, djoser_settings): + djoser_settings.update(USERNAME_RESET_CONFIRM_RETYPE=True) + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_username": "new_username", + "re_new_username": "wrong", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["non_field_errors"] == [ + default_settings.CONSTANTS.messages.USERNAME_MISMATCH_ERROR.format( + User.USERNAME_FIELD ) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USERNAME_RESET_CONFIRM_RETYPE": True}) - ) - def test_post_not_set_new_username_if_mismatch(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_username": "new_username", - "re_new_username": "wrong", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertNotEqual(user.username, data["new_username"]) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USERNAME_RESET_CONFIRM_RETYPE": True}) - ) - def test_post_not_reset_if_fails_username_validation(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_username": "new username", - "re_new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertNotEqual(user.username, data["new_username"]) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USERNAME_CHANGED_EMAIL_CONFIRMATION": True}) - ) - def test_post_username_changed_confirmation_email(self): - user = create_user() - data = { - "uid": djoser.utils.encode_uid(user.pk), - "token": default_token_generator.make_token(user), - "new_username": "new_username", - } - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - user.refresh_from_db() - self.assertEqual(user.username, data["new_username"]) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) + ] + + +@pytest.mark.django_db +def test_post_not_set_new_username_if_mismatch(api_client, djoser_settings): + djoser_settings.update(USERNAME_RESET_CONFIRM_RETYPE=True) + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_username": "new_username", + "re_new_username": "wrong", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert user.username != data["new_username"] + + +@pytest.mark.django_db +def test_post_not_reset_if_fails_username_validation(api_client, djoser_settings): + djoser_settings.update(USERNAME_RESET_CONFIRM_RETYPE=True) + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_username": "new username", + "re_new_username": "new_username", + } + + response = api_client.post(base_url, data) + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert user.username != data["new_username"] + + +@pytest.mark.django_db +def test_post_username_changed_confirmation_email( + api_client, djoser_settings, mailoutbox +): + djoser_settings.update(USERNAME_CHANGED_EMAIL_CONFIRMATION=True) + base_url = reverse(f"user-reset-{User.USERNAME_FIELD}-confirm") + user = UserFactory.create() + data = { + "uid": djoser.utils.encode_uid(user.pk), + "token": default_token_generator.make_token(user), + "new_username": "new_username", + } + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + user.refresh_from_db() + assert user.username == data["new_username"] + assert len(mailoutbox) == 1 + assert user.email in [recipient for email in mailoutbox for recipient in email.to] diff --git a/testproject/testapp/tests/test_set_password.py b/testproject/testapp/tests/test_set_password.py index 1368cf82..4e2519c7 100644 --- a/testproject/testapp/tests/test_set_password.py +++ b/testproject/testapp/tests/test_set_password.py @@ -1,177 +1,157 @@ +import pytest import importlib from django.conf import settings from django.contrib.auth import HASH_SESSION_KEY -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user, login_user from djoser.conf import settings as djoser_settings Token = djoser_settings.TOKEN_MODEL -class SetPasswordViewTest( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin -): - def setUp(self): +class TestSetPasswordView: + @pytest.fixture(autouse=True) + def setup(self): self.base_url = reverse("user-set-password") - def test_post_set_new_password(self): - user = create_user() + def test_post_set_new_password(self, authenticated_client, user, mailoutbox): data = {"new_password": "new password", "current_password": "secret"} - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + assert response.status_code == status.HTTP_204_NO_CONTENT user.refresh_from_db() - self.assertTrue(user.check_password(data["new_password"])) - self.assert_emails_in_mailbox(0) + assert user.check_password(data["new_password"]) + assert len(mailoutbox) == 0 - @override_settings(DJOSER=dict(settings.DJOSER, **{"SET_PASSWORD_RETYPE": True})) - def test_post_set_new_password_with_retype(self): - user = create_user() + def test_post_set_new_password_with_retype( + self, authenticated_client, user, mailoutbox, djoser_settings + ): + djoser_settings["SET_PASSWORD_RETYPE"] = True data = { "new_password": "new password", "re_new_password": "new password", "current_password": "secret", } - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + assert response.status_code == status.HTTP_204_NO_CONTENT user.refresh_from_db() - self.assertTrue(user.check_password(data["new_password"])) - self.assert_emails_in_mailbox(0) + assert user.check_password(data["new_password"]) + assert len(mailoutbox) == 0 - def test_post_not_set_new_password_if_wrong_current_password(self): - user = create_user() + def test_post_not_set_new_password_if_wrong_current_password( + self, authenticated_client, user + ): data = {"new_password": "new password", "current_password": "wrong"} - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST - @override_settings(DJOSER=dict(settings.DJOSER, **{"SET_PASSWORD_RETYPE": True})) - def test_post_not_set_new_password_if_mismatch(self): - user = create_user() + def test_post_not_set_new_password_if_mismatch( + self, authenticated_client, user, djoser_settings + ): + djoser_settings["SET_PASSWORD_RETYPE"] = True data = { "new_password": "new password", "re_new_password": "wrong", "current_password": "secret", } - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST user.refresh_from_db() - self.assertTrue(user.check_password(data["current_password"])) + assert user.check_password(data["current_password"]) - def test_post_not_set_new_password_if_fails_validation(self): - user = create_user() + def test_post_not_set_new_password_if_fails_validation( + self, authenticated_client, user + ): data = { "new_password": "666", "re_new_password": "666", "current_password": "secret", } - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data, {"new_password": ["Password 666 is not allowed."]} - ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data == {"new_password": ["Password 666 is not allowed."]} - @override_settings( - DJOSER=dict(settings.DJOSER, **{"LOGOUT_ON_PASSWORD_CHANGE": True}) - ) - def test_post_logout_after_password_change(self): - user = create_user() + def test_post_logout_after_password_change( + self, djoser_settings, authenticated_client, user + ): + djoser_settings["LOGOUT_ON_PASSWORD_CHANGE"] = True data = {"new_password": "new password", "current_password": "secret"} - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + assert response.status_code == status.HTTP_204_NO_CONTENT is_logged = Token.objects.filter(user=user).exists() - self.assertFalse(is_logged) + assert not is_logged - def test_post_not_logout_after_password_change_if_setting_is_false(self): - user = create_user() + def test_post_not_logout_after_password_change_if_setting_is_false( + self, djoser_settings, authenticated_client, user + ): + djoser_settings["LOGOUT_ON_PASSWORD_CHANGE"] = False data = {"new_password": "new password", "current_password": "secret"} - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + assert response.status_code == status.HTTP_204_NO_CONTENT is_logged = Token.objects.filter(user=user).exists() - self.assertTrue(is_logged) + assert is_logged - @override_settings( - DJOSER=dict(settings.DJOSER, **{"PASSWORD_CHANGED_EMAIL_CONFIRMATION": True}) - ) - def test_post_password_changed_confirmation_email(self): - user = create_user() + def test_post_password_changed_confirmation_email( + self, djoser_settings, authenticated_client, user, mailoutbox + ): + djoser_settings["PASSWORD_CHANGED_EMAIL_CONFIRMATION"] = True data = {"new_password": "new password", "current_password": "secret"} - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + assert response.status_code == status.HTTP_204_NO_CONTENT user.refresh_from_db() - self.assertTrue(user.check_password(data["new_password"])) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) - - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{ - "PASSWORD_CHANGED_EMAIL_CONFIRMATION": True, - "LOGOUT_ON_PASSWORD_CHANGE": True, - "CREATE_SESSION_ON_LOGIN": True, - }, - ) - ) - def test_post_logout_with_confirmation_email_if_session_created(self): - user = create_user() + assert user.check_password(data["new_password"]) + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [user.email] + + def test_post_logout_with_confirmation_email_if_session_created( + self, djoser_settings, authenticated_client, user + ): + djoser_settings["PASSWORD_CHANGED_EMAIL_CONFIRMATION"] = True + djoser_settings["LOGOUT_ON_PASSWORD_CHANGE"] = True + djoser_settings["CREATE_SESSION_ON_LOGIN"] = True data = {"new_password": "new password", "current_password": "secret"} - login_user(self.client, user) - response = self.client.post(self.base_url, data) + response = authenticated_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + assert response.status_code == status.HTTP_204_NO_CONTENT is_logged = Token.objects.filter(user=user).exists() - self.assertFalse(is_logged) - - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{"LOGOUT_ON_PASSWORD_CHANGE": False, "CREATE_SESSION_ON_LOGIN": True}, - ) - ) - def test_post_logout_cycle_session(self): - user = create_user() + assert not is_logged + + def test_post_logout_cycle_session(self, djoser_settings, api_client, db): + djoser_settings["LOGOUT_ON_PASSWORD_CHANGE"] = False + djoser_settings["CREATE_SESSION_ON_LOGIN"] = True + from testapp.factories import UserFactory + + user = UserFactory.create() data = {"new_password": "new password", "current_password": "secret"} - login_user(self.client, user) - self.client.force_login(user) + api_client.force_authenticate(user=user) + api_client.force_login(user) - response = self.client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) + response = api_client.post(self.base_url, data) + assert response.status_code == status.HTTP_204_NO_CONTENT user.refresh_from_db() - session_id = self.client.cookies["sessionid"].coded_value + session_id = api_client.cookies["sessionid"].coded_value engine = importlib.import_module(settings.SESSION_ENGINE) session = engine.SessionStore(session_id) session_key = session[HASH_SESSION_KEY] - self.assertEqual(session_key, user.get_session_auth_hash()) + assert session_key == user.get_session_auth_hash() diff --git a/testproject/testapp/tests/test_set_username.py b/testproject/testapp/tests/test_set_username.py index d2165e98..6bf2e443 100644 --- a/testproject/testapp/tests/test_set_username.py +++ b/testproject/testapp/tests/test_set_username.py @@ -1,156 +1,167 @@ -from django.conf import settings +import pytest +from testapp.factories import TokenFactory +from testapp.factories import UserFactory, CustomUserFactory from django.contrib.auth import get_user_model -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase from testapp.models import CustomUser -from testapp.tests.common import create_user, login_user, mock +from unittest import mock User = get_user_model() -class SetUsernameViewTest( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin +@pytest.fixture +def base_url(): + return reverse(f"user-set-{User.USERNAME_FIELD}") + + +def test_post_set_new_username(api_client, base_url): + user = UserFactory.create() + data = {"new_username": "ringo", "current_password": "secret"} + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + user.refresh_from_db() + assert data["new_username"] == user.username + + +def test_post_not_set_new_username_if_wrong_current_password(api_client, base_url): + user = UserFactory.create() + orig_username = user.get_username() + data = {"new_username": "ringo", "current_password": "wrong"} + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert orig_username == user.username + + +def test_post_not_set_new_username_if_mismatch(djoser_settings, api_client, base_url): + djoser_settings["SET_USERNAME_RETYPE"] = True + user = UserFactory.create() + data = { + "new_username": "ringo", + "re_new_username": "wrong", + "current_password": "secret", + } + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert data["new_username"] != user.username + + +def test_post_not_set_new_username_if_exists(api_client, base_url): + username = "tom" + UserFactory.create(username=username) + user = UserFactory.create(username="john") + data = {"new_username": username, "current_password": "secret"} + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert user.username != username + + +def test_post_not_set_new_username_if_invalid(api_client, base_url): + user = UserFactory.create() + data = {"new_username": "$ wrong username #", "current_password": "secret"} + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert user.username != data["new_username"] + + +def test_post_update_username_and_send_activation_email( + djoser_settings, api_client, base_url, mailoutbox +): + djoser_settings["USERNAME_CHANGED_EMAIL_CONFIRMATION"] = True + user = UserFactory.create() + data = {"new_username": "dango", "current_password": "secret"} + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [user.email] + + +def test_post_not_set_new_username_if_same(api_client, base_url): + user = UserFactory.create() + data = {"new_username": user.username, "current_password": "secret"} + token = TokenFactory.create(user=user) + api_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert user.is_active + + +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.serializers.SetUsernameSerializer.Meta.model", CustomUser) +@mock.patch( + "djoser.serializers.SetUsernameSerializer.Meta.fields", + (CustomUser.USERNAME_FIELD, "custom_username"), +) +@mock.patch("djoser.views.User", CustomUser) +@pytest.mark.django_db(transaction=True) +def test_post_set_new_custom_username(djoser_settings, api_client, base_url, settings): + settings.AUTH_USER_MODEL = "testapp.CustomUser" + djoser_settings["LOGIN_FIELD"] = CustomUser.USERNAME_FIELD + user = CustomUserFactory.create(custom_required_field="42") + data = {"new_custom_username": "ringo", "current_password": "secret"} + api_client.force_authenticate(user) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_204_NO_CONTENT + user.refresh_from_db() + assert data["new_custom_username"] == user.get_username() + + +@mock.patch("djoser.serializers.User", CustomUser) +@mock.patch("djoser.serializers.SetUsernameSerializer.Meta.model", CustomUser) +@mock.patch( + "djoser.serializers.SetUsernameSerializer.Meta.fields", + (CustomUser.USERNAME_FIELD, "custom_username"), +) +@mock.patch("djoser.views.User", CustomUser) +@pytest.mark.django_db(transaction=True) +def test_post_not_set_new_custom_username_if_mismatch( + djoser_settings, api_client, base_url, settings ): - def setUp(self): - self.base_url = reverse(f"user-set-{User.USERNAME_FIELD}") - - def test_post_set_new_username(self): - user = create_user() - data = {"new_username": "ringo", "current_password": "secret"} - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - user.refresh_from_db() - self.assertEqual(data["new_username"], user.username) - - def test_post_not_set_new_username_if_wrong_current_password(self): - user = create_user() - orig_username = user.get_username() - data = {"new_username": "ringo", "current_password": "wrong"} - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertEqual(orig_username, user.username) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SET_USERNAME_RETYPE": True})) - def test_post_not_set_new_username_if_mismatch(self): - user = create_user() - data = { - "new_username": "ringo", - "re_new_username": "wrong", - "current_password": "secret", - } - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertNotEqual(data["new_username"], user.username) - - def test_post_not_set_new_username_if_exists(self): - username = "tom" - create_user(username=username) - user = create_user(username="john") - data = {"new_username": username, "current_password": "secret"} - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertNotEqual(user.username, username) - - def test_post_not_set_new_username_if_invalid(self): - user = create_user() - data = {"new_username": "$ wrong username #", "current_password": "secret"} - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertNotEqual(user.username, data["new_username"]) - - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USERNAME_CHANGED_EMAIL_CONFIRMATION": True}) - ) - def test_post_update_username_and_send_activation_email(self): - user = create_user() - data = {"new_username": "dango", "current_password": "secret"} - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[user.email]) - - def test_post_not_set_new_username_if_same(self): - user = create_user() - data = {"new_username": "john", "current_password": "secret"} - login_user(self.client, user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertTrue(user.is_active) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.serializers.SetUsernameSerializer.Meta.model", CustomUser) - @mock.patch( - "djoser.serializers.SetUsernameSerializer.Meta.fields", - (CustomUser.USERNAME_FIELD, "custom_username"), - ) - @mock.patch("djoser.views.User", CustomUser) - @override_settings( - AUTH_USER_MODEL="testapp.CustomUser", - DJOSER=dict(settings.DJOSER, **{"LOGIN_FIELD": CustomUser.USERNAME_FIELD}), - ) - def test_post_set_new_custom_username(self): - user = create_user(use_custom_data=True) - data = {"new_custom_username": "ringo", "current_password": "secret"} - self.client.force_authenticate(user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - user.refresh_from_db() - self.assertEqual(data["new_custom_username"], user.get_username()) - - @mock.patch("djoser.serializers.User", CustomUser) - @mock.patch("djoser.serializers.SetUsernameSerializer.Meta.model", CustomUser) - @mock.patch( - "djoser.serializers.SetUsernameSerializer.Meta.fields", - (CustomUser.USERNAME_FIELD, "custom_username"), - ) - @mock.patch("djoser.views.User", CustomUser) - @override_settings( - AUTH_USER_MODEL="testapp.CustomUser", - DJOSER=dict( - settings.DJOSER, - **{"SET_USERNAME_RETYPE": True, "LOGIN_FIELD": CustomUser.USERNAME_FIELD}, - ), - ) - def test_post_not_set_new_custom_username_if_mismatch(self): - user = create_user(use_custom_data=True) - data = { - "new_custom_username": "ringo", - "re_new_custom_username": "wrong", - "current_password": "secret", - } - self.client.force_authenticate(user) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - user.refresh_from_db() - self.assertNotEqual(data["new_custom_username"], user.get_username()) + settings.AUTH_USER_MODEL = "testapp.CustomUser" + djoser_settings["SET_USERNAME_RETYPE"] = True + djoser_settings["LOGIN_FIELD"] = CustomUser.USERNAME_FIELD + user = CustomUserFactory.create(custom_required_field="42") + data = { + "new_custom_username": "ringo", + "re_new_custom_username": "wrong", + "current_password": "secret", + } + api_client.force_authenticate(user) + + response = api_client.post(base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + assert data["new_custom_username"] != user.get_username() diff --git a/testproject/testapp/tests/test_settings.py b/testproject/testapp/tests/test_settings.py index 3f63d33d..2f88f7a7 100644 --- a/testproject/testapp/tests/test_settings.py +++ b/testproject/testapp/tests/test_settings.py @@ -1,36 +1,32 @@ -from django.conf import settings -from django.test.testcases import SimpleTestCase -from django.test.utils import override_settings from django.utils.module_loading import import_string -class SettingsTestCase(SimpleTestCase): - @override_settings(DJOSER=dict()) - def test_settings_should_be_default_if_djoser_not_in_django_settings(self): - from djoser.conf import default_settings - from djoser.conf import settings as djoser_settings - - for setting_name, setting_value in default_settings.items(): - overridden_value = getattr(djoser_settings, setting_name) - try: - self.assertEqual(setting_value, overridden_value) - except AssertionError: - setting_value = import_string(setting_value) - self.assertEqual(setting_value, overridden_value) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SET_USERNAME_RETYPE": True})) - def test_djoser_simple_setting_overriden(self): - from djoser.conf import settings as djoser_settings - - self.assertTrue(djoser_settings.SET_USERNAME_RETYPE) - - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{"SERIALIZERS": {"user": "djoser.serializers.TokenSerializer"}}, - ) - ) - def test_djoser_serializer_setting_overriden(self): - from djoser.conf import settings as djoser_settings - - self.assertEqual(djoser_settings.SERIALIZERS.user.__name__, "TokenSerializer") +def test_settings_should_be_default_if_djoser_not_in_django_settings(djoser_settings): + djoser_settings.clear() + + from djoser.conf import default_settings + from djoser.conf import settings as djoser_settings_module + + for setting_name, setting_value in default_settings.items(): + overridden_value = getattr(djoser_settings_module, setting_name) + try: + assert setting_value == overridden_value + except AssertionError: + setting_value = import_string(setting_value) + assert setting_value == overridden_value + + +def test_djoser_simple_setting_overriden(djoser_settings): + djoser_settings["SET_USERNAME_RETYPE"] = True + + from djoser.conf import settings as djoser_settings_module + + assert djoser_settings_module.SET_USERNAME_RETYPE + + +def test_djoser_serializer_setting_overriden(djoser_settings): + djoser_settings["SERIALIZERS"] = {"user": "djoser.serializers.TokenSerializer"} + + from djoser.conf import settings as djoser_settings_module + + assert djoser_settings_module.SERIALIZERS.user.__name__ == "TokenSerializer" diff --git a/testproject/testapp/tests/test_token_create.py b/testproject/testapp/tests/test_token_create.py index cc01fde5..de40bf10 100644 --- a/testproject/testapp/tests/test_token_create.py +++ b/testproject/testapp/tests/test_token_create.py @@ -1,78 +1,71 @@ +import pytest from django.contrib.auth import user_logged_in, user_login_failed -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user from djoser.conf import settings -class TokenCreateViewTest( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.InstanceAssertionsMixin, -): - def setUp(self): - self.signal_sent = False - self.base_url = reverse("login") +@pytest.mark.django_db +class TestTokenCreateView: - def signal_receiver(self, *args, **kwargs): - self.signal_sent = True + @pytest.fixture(autouse=True) + def setup(self): + self.base_url = reverse("login") - def test_post_should_login_user(self): - user = create_user() + def test_post_should_login_user(self, api_client, user, signal_tracker): previous_last_login = user.last_login data = {"username": user.username, "password": user.raw_password} - user_logged_in.connect(self.signal_receiver) + user_logged_in.connect(signal_tracker.receiver) - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) user.refresh_from_db() - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertEqual(response.data["auth_token"], user.auth_token.key) - self.assertNotEqual(user.last_login, previous_last_login) - self.assertTrue(self.signal_sent) + assert response.status_code == status.HTTP_200_OK + assert response.data["auth_token"] == user.auth_token.key + assert user.last_login != previous_last_login + assert signal_tracker.signal_sent - def test_post_should_not_login_if_user_is_not_active(self): + def test_post_should_not_login_if_user_is_not_active( + self, api_client, inactive_user, signal_tracker + ): """In Django >= 1.10 authenticate() returns None if user is inactive, while in Django < 1.10 authenticate() succeeds if user is inactive.""" - user = create_user() - data = {"username": user.username, "password": user.raw_password} - user.is_active = False - user.save() - user_logged_in.connect(self.signal_receiver) - - response = self.client.post(self.base_url, data) - - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["non_field_errors"][0], - settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR, + data = { + "username": inactive_user.username, + "password": inactive_user.raw_password, + } + user_logged_in.connect(signal_tracker.receiver) + + response = api_client.post(self.base_url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert ( + response.data["non_field_errors"][0] + == settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR ) - self.assertFalse(self.signal_sent) + assert not signal_tracker.signal_sent - def test_post_should_not_login_if_invalid_credentials(self): - user = create_user() + def test_post_should_not_login_if_invalid_credentials( + self, api_client, user, signal_tracker + ): data = {"username": user.username, "password": "wrong"} - user_login_failed.connect(self.signal_receiver) + user_login_failed.connect(signal_tracker.receiver) - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["non_field_errors"], - [settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR], - ) - self.assertTrue(self.signal_sent) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["non_field_errors"] == [ + settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR + ] + assert signal_tracker.signal_sent - def test_post_should_not_login_if_empty_request(self): + def test_post_should_not_login_if_empty_request(self, api_client): data = {} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data["non_field_errors"], - [settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR], - ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["non_field_errors"] == [ + settings.CONSTANTS.messages.INVALID_CREDENTIALS_ERROR + ] diff --git a/testproject/testapp/tests/test_token_create_custom_username_login_fields.py b/testproject/testapp/tests/test_token_create_custom_username_login_fields.py index cf877dcc..07e27cca 100644 --- a/testproject/testapp/tests/test_token_create_custom_username_login_fields.py +++ b/testproject/testapp/tests/test_token_create_custom_username_login_fields.py @@ -8,19 +8,14 @@ @pytest.mark.django_db -class BaseTestUsernameLoginFields: +class TestModelBackendLoginFields: url = reverse("login") @pytest.fixture(autouse=True) def add_authentication_backend(self, settings): - raise NotImplementedError - - @pytest.fixture(autouse=True) - def settings(self, settings): settings.AUTHENTICATION_BACKENDS = [ "django.contrib.auth.backends.ModelBackend", ] - return settings @pytest.fixture def signal_user_logged_in_patched(self): @@ -111,17 +106,6 @@ def _test_failing_login( assert user.last_login == previous_last_login signal_user_login_failed_patched.assert_called_once() - -@pytest.mark.django_db -class TestModelBackendLoginFields(BaseTestUsernameLoginFields): - url = reverse("login") - - @pytest.fixture(autouse=True) - def add_authentication_backend(self, settings): - settings.AUTHENTICATION_BACKENDS = [ - "django.contrib.auth.backends.ModelBackend", - ] - @pytest.mark.parametrize( "login_field, username_field, send_field", [ @@ -192,7 +176,7 @@ def test_failing_login( @pytest.mark.django_db -class TestLoginFieldBackend(BaseTestUsernameLoginFields): +class TestLoginFieldBackend: url = reverse("login") @pytest.fixture(autouse=True) @@ -201,6 +185,95 @@ def add_authentication_backend(self, settings): "djoser.auth_backends.LoginFieldBackend", ] + @pytest.fixture + def signal_user_logged_in_patched(self): + signal_handler = mock.MagicMock() + user_logged_in.connect(signal_handler) + return signal_handler + + @pytest.fixture + def signal_user_login_failed_patched(self): + signal_handler = mock.MagicMock() + user_login_failed.connect(signal_handler) + return signal_handler + + def configure_djoser_settings( + self, settings, mocker, login_field, username_field, user_can_authenticate + ): + settings.DJOSER["LOGIN_FIELD"] = login_field + mocker.patch("djoser.serializers.settings.LOGIN_FIELD", login_field) + mocker.patch("djoser.serializers.User.USERNAME_FIELD", username_field) + mocker.patch.object( + ModelBackend, "user_can_authenticate", return_value=user_can_authenticate + ) + + def _test_successful_login( + self, + user, + client, + settings, + mocker, + signal_user_logged_in_patched, + login_field, + username_field, + send_field, + ): + self.configure_djoser_settings( + settings=settings, + mocker=mocker, + login_field=login_field, + username_field=username_field, + user_can_authenticate=True, + ) + + if send_field == "username": + data = {"username": user.username, "password": user.raw_password} + else: + data = {"email": user.email, "password": user.raw_password} + + previous_last_login = user.last_login + response = client.post(self.url, data) + + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + + assert response.data["auth_token"] == user.auth_token.key + assert user.last_login != previous_last_login + signal_user_logged_in_patched.assert_called_once() + + def _test_failing_login( + self, + user, + client, + settings, + mocker, + signal_user_login_failed_patched, + login_field, + username_field, + send_field, + user_can_authenticate, + ): + self.configure_djoser_settings( + settings=settings, + mocker=mocker, + login_field=login_field, + username_field=username_field, + user_can_authenticate=user_can_authenticate, + ) + if send_field == "username": + data = {"username": user.username, "password": user.raw_password} + else: + data = {"email": user.email, "password": user.raw_password} + + previous_last_login = user.last_login + response = client.post(self.url, data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + user.refresh_from_db() + + assert user.last_login == previous_last_login + signal_user_login_failed_patched.assert_called_once() + @pytest.mark.parametrize( "login_field, username_field, send_field", [ @@ -239,10 +312,6 @@ def test_successful_login( ("username", "email", False, "username"), ("email", "username", False, "username"), ("email", "email", False, "username"), - ("username", "username", True, "email"), - ("email", "email", True, "username"), - ("username", "email", True, "email"), - ("email", "username", True, "username"), ], ) def test_failing_login( diff --git a/testproject/testapp/tests/test_token_destroy.py b/testproject/testapp/tests/test_token_destroy.py index 2a42c2ed..d95911fd 100644 --- a/testproject/testapp/tests/test_token_destroy.py +++ b/testproject/testapp/tests/test_token_destroy.py @@ -1,54 +1,47 @@ -from django.conf import settings +import pytest from django.contrib.auth import user_logged_out -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user, login_user -class TokenDestroyViewTest(APITestCase, assertions.StatusCodeAssertionsMixin): - def setUp(self): +class TestTokenDestroyView: + @pytest.fixture(autouse=True) + def setup(self): self.signal_sent = False self.base_url = reverse("logout") def signal_receiver(self, *args, **kwargs): self.signal_sent = True - def test_post_should_logout_logged_in_user(self): - user = create_user() - user_logged_out.connect(self.signal_receiver) - - login_user(self.client, user) - response = self.client.post(self.base_url) + def test_post_should_logout_logged_in_user( + self, authenticated_client, signal_tracker + ): + user_logged_out.connect(signal_tracker.receiver) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assertEqual(response.data, None) - self.assertTrue(self.signal_sent) + response = authenticated_client.post(self.base_url) - def test_post_should_deny_logging_out_when_user_not_logged_in(self): - create_user() - response = self.client.post(self.base_url) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.data is None + assert signal_tracker.signal_sent - self.assert_status_equal(response, status.HTTP_401_UNAUTHORIZED) + def test_post_should_deny_logging_out_when_user_not_logged_in(self, api_client): + response = api_client.post(self.base_url) - def test_options(self): - user = create_user() + assert response.status_code == status.HTTP_401_UNAUTHORIZED - login_user(self.client, user) - response = self.client.options(self.base_url) + def test_options(self, authenticated_client): + response = authenticated_client.options(self.base_url) - self.assert_status_equal(response, status.HTTP_200_OK) + assert response.status_code == status.HTTP_200_OK - @override_settings(DJOSER=dict(settings.DJOSER, **{"TOKEN_MODEL": None})) - def test_none_token_model_results_in_no_operation(self): - user = create_user() + def test_none_token_model_results_in_no_operation( + self, authenticated_client, djoser_settings + ): + djoser_settings["TOKEN_MODEL"] = None user_logged_out.connect(self.signal_receiver) - login_user(self.client, user) - response = self.client.post(self.base_url) + response = authenticated_client.post(self.base_url) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assertEqual(response.data, None) - self.assertFalse(self.signal_sent) + assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.data is None + assert not self.signal_sent diff --git a/testproject/testapp/tests/test_user_create.py b/testproject/testapp/tests/test_user_create.py index 3bebc943..26dba182 100644 --- a/testproject/testapp/tests/test_user_create.py +++ b/testproject/testapp/tests/test_user_create.py @@ -1,120 +1,110 @@ -from django.conf import settings +import pytest from django.contrib.auth import get_user_model -from django.test.utils import override_settings -from djet import assertions +from django.db import IntegrityError from importlib.metadata import version from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase from testapp.models import CustomUser, ExampleUser -from testapp.tests.common import create_user, mock, perform_create_mock +from unittest import mock from djoser.conf import settings as default_settings User = get_user_model() -class UserCreateViewTest( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.EmailAssertionsMixin, - assertions.InstanceAssertionsMixin, -): - def setUp(self): +@pytest.mark.django_db +class TestUserCreateView: + + @pytest.fixture(autouse=True) + def setup(self): self.base_url = reverse("user-list") # /auth/users/ - def test_post_create_user_without_login(self): + def test_post_create_user_without_login(self, api_client): data = {"username": "john", "password": "secret", "csrftoken": "asdf"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assertTrue("password" not in response.data) - self.assert_instance_exists(User, username=data["username"]) + assert response.status_code == status.HTTP_201_CREATED + assert "password" not in response.data + assert User.objects.filter(username=data["username"]).exists() user = User.objects.get(username=data["username"]) - self.assertTrue(user.check_password(data["password"])) + assert user.check_password(data["password"]) - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_post_create_user_with_login_and_send_activation_email(self): + def test_post_create_user_with_login_and_send_activation_email( + self, api_client, mailoutbox, djoser_settings + ): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True data = {"username": "john", "email": "john@beatles.com", "password": "secret"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assert_instance_exists(User, username=data["username"]) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[data["email"]]) + assert response.status_code == status.HTTP_201_CREATED + assert User.objects.filter(username=data["username"]).exists() + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [data["email"]] user = User.objects.get(username="john") - self.assertFalse(user.is_active) + assert not user.is_active - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{"SEND_ACTIVATION_EMAIL": False, "SEND_CONFIRMATION_EMAIL": True}, - ) - ) - def test_post_create_user_with_login_and_send_confirmation_email(self): + def test_post_create_user_with_login_and_send_confirmation_email( + self, djoser_settings, api_client, mailoutbox + ): + djoser_settings["SEND_ACTIVATION_EMAIL"] = False + djoser_settings["SEND_CONFIRMATION_EMAIL"] = True data = {"username": "john", "email": "john@beatles.com", "password": "secret"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assert_instance_exists(User, username=data["username"]) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[data["email"]]) + assert response.status_code == status.HTTP_201_CREATED + assert User.objects.filter(username=data["username"]).exists() + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [data["email"]] user = User.objects.get(username="john") - self.assertTrue(user.is_active) + assert user.is_active - def test_post_not_create_new_user_if_username_exists(self): - create_user(username="john") - data = {"username": "john", "password": "secret", "csrftoken": "asdf"} - response = self.client.post(self.base_url, data) + def test_post_not_create_new_user_if_username_exists(self, api_client, user): + data = {"username": user.username, "password": "secret", "csrftoken": "asdf"} + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST - def test_post_not_register_if_fails_password_validation(self): + def test_post_not_register_if_fails_password_validation(self, api_client): data = {"username": "john", "password": "666", "csrftoken": "asdf"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST response.render() - self.assertEqual( - str(response.data["password"][0]), "Password 666 is not allowed." - ) + assert str(response.data["password"][0]) == "Password 666 is not allowed." if version("djangorestframework") >= "3.9.0": - self.assertEqual(response.data["password"][0].code, "no666") + assert response.data["password"][0].code == "no666" - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USER_CREATE_PASSWORD_RETYPE": True}) - ) - def test_post_not_register_if_password_mismatch(self): + def test_post_not_register_if_password_mismatch(self, djoser_settings, api_client): + djoser_settings["USER_CREATE_PASSWORD_RETYPE"] = True data = { "username": "john", "password": "secret", "re_password": "wrong", "csrftoken": "asdf", } - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST response.render() - self.assertEqual( - str(response.data["non_field_errors"][0]), - default_settings.CONSTANTS.messages.PASSWORD_MISMATCH_ERROR, + assert ( + str(response.data["non_field_errors"][0]) + == default_settings.CONSTANTS.messages.PASSWORD_MISMATCH_ERROR ) @mock.patch( "djoser.serializers.UserCreateSerializer.perform_create", - side_effect=perform_create_mock, + side_effect=IntegrityError(), ) - def test_post_return_400_for_integrity_error(self, perform_create): + def test_post_return_400_for_integrity_error(self, perform_create_mock, api_client): data = {"username": "john", "email": "john@beatles.com", "password": "secret"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.data, - [default_settings.CONSTANTS.messages.CANNOT_CREATE_USER_ERROR], - ) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data == [ + default_settings.CONSTANTS.messages.CANNOT_CREATE_USER_ERROR + ] @mock.patch("djoser.serializers.User", CustomUser) @mock.patch("djoser.serializers.UserCreateSerializer.Meta.model", CustomUser) @@ -124,25 +114,28 @@ def test_post_return_400_for_integrity_error(self, perform_create): + (CustomUser.USERNAME_FIELD, CustomUser._meta.pk.name, "password"), ) @mock.patch("djoser.views.User", CustomUser) - @override_settings(AUTH_USER_MODEL="testapp.CustomUser") - def test_post_create_custom_user_with_all_required_fields(self): + @pytest.mark.django_db(transaction=True) + def test_post_create_custom_user_with_all_required_fields( + self, api_client, settings + ): + settings.AUTH_USER_MODEL = "testapp.CustomUser" data = { "custom_username": "john", "password": "secret", "custom_required_field": "42", } - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assertTrue("password" not in response.data) + assert response.status_code == status.HTTP_201_CREATED + assert "password" not in response.data custom_user_model = get_user_model() - self.assert_instance_exists( - custom_user_model, custom_username=data[custom_user_model.USERNAME_FIELD] - ) + assert custom_user_model.objects.filter( + custom_username=data[custom_user_model.USERNAME_FIELD] + ).exists() user = custom_user_model.objects.get( custom_username=data[custom_user_model.USERNAME_FIELD] ) - self.assertTrue(user.check_password(data["password"])) + assert user.check_password(data["password"]) @mock.patch("djoser.serializers.User", CustomUser) @mock.patch("djoser.serializers.UserCreateSerializer.Meta.model", CustomUser) @@ -152,14 +145,17 @@ def test_post_create_custom_user_with_all_required_fields(self): + (CustomUser.USERNAME_FIELD, CustomUser._meta.pk.name, "password"), ) @mock.patch("djoser.views.User", CustomUser) - @override_settings(AUTH_USER_MODEL="testapp.CustomUser") - def test_post_not_create_custom_user_with_missing_required_fields(self): + @pytest.mark.django_db(transaction=True) + def test_post_not_create_custom_user_with_missing_required_fields( + self, api_client, settings + ): + settings.AUTH_USER_MODEL = "testapp.CustomUser" data = {"custom_username": "john", "password": "secret"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST response.render() - self.assertEqual(response.data["custom_required_field"][0].code, "required") + assert response.data["custom_required_field"][0].code == "required" @mock.patch("djoser.serializers.User", ExampleUser) @mock.patch("djoser.serializers.UserCreateSerializer.Meta.model", ExampleUser) @@ -169,16 +165,17 @@ def test_post_not_create_custom_user_with_missing_required_fields(self): + (ExampleUser.USERNAME_FIELD, ExampleUser._meta.pk.name, "password"), ) @mock.patch("djoser.views.User", ExampleUser) - @override_settings(AUTH_USER_MODEL="testapp.ExampleUser") - def test_post_create_custom_user_without_username(self): + @pytest.mark.django_db(transaction=True) + def test_post_create_custom_user_without_username(self, api_client, settings): + settings.AUTH_USER_MODEL = "testapp.ExampleUser" data = {"password": "secret", "email": "test@user1.com"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assertTrue("password" not in response.data) - self.assert_instance_exists(ExampleUser, email=data["email"]) + assert response.status_code == status.HTTP_201_CREATED + assert "password" not in response.data + assert ExampleUser.objects.filter(email=data["email"]).exists() user = ExampleUser.objects.get(email=data["email"]) - self.assertTrue(user.check_password(data["password"])) + assert user.check_password(data["password"]) @mock.patch("djoser.serializers.User", ExampleUser) @mock.patch("djoser.serializers.UserCreateSerializer.Meta.model", ExampleUser) @@ -188,22 +185,20 @@ def test_post_create_custom_user_without_username(self): + (ExampleUser.USERNAME_FIELD, ExampleUser._meta.pk.name, "password"), ) @mock.patch("djoser.views.User", ExampleUser) - @override_settings(AUTH_USER_MODEL="testapp.ExampleUser") - def test_post_create_custom_user_missing_required_fields(self): + @pytest.mark.django_db(transaction=True) + def test_post_create_custom_user_missing_required_fields( + self, api_client, settings + ): + settings.AUTH_USER_MODEL = "testapp.ExampleUser" data = {"password": "secret"} - response = self.client.post(self.base_url, data) + response = api_client.post(self.base_url, data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["email"][0].code, "required") + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data["email"][0].code == "required" - @override_settings( - DJOSER=dict(settings.DJOSER, **{"USER_CREATE_PASSWORD_RETYPE": True}) - ) - def test_user_create_with_retype_password(self): - # GIVEN user is required to retype password - # (see decorator) - # WHEN sent correctly retyped password + def test_user_create_with_retype_password(self, djoser_settings, api_client): + djoser_settings["USER_CREATE_PASSWORD_RETYPE"] = True data = {"username": "john", "password": "secret", "re_password": "secret"} - response = self.client.post(self.base_url, data) - # THEN I get correct response - self.assert_status_equal(response, status.HTTP_201_CREATED) + response = api_client.post(self.base_url, data) + + assert response.status_code == status.HTTP_201_CREATED diff --git a/testproject/testapp/tests/test_user_delete.py b/testproject/testapp/tests/test_user_delete.py index 5240a2cd..33b48e7c 100644 --- a/testproject/testapp/tests/test_user_delete.py +++ b/testproject/testapp/tests/test_user_delete.py @@ -2,50 +2,67 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.test import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase import djoser.views from djoser.conf import settings as djoser_settings -from .common import PermCheckClass, RunCheck, SerializerCheckClass, create_user +from testapp.factories import UserFactory User = get_user_model() -class UserMeDeleteViewTest( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.EmailAssertionsMixin, - assertions.InstanceAssertionsMixin, -): +# Test-specific classes +class RunCheck(Exception): + """Custom exception for testing.""" + + pass + + +class PermCheckClass: + """Mock permission class for testing.""" + + def has_permission(self, *args, **kwargs): + raise RunCheck("working") + + def has_object_permission(self, *args, **kwargs): + raise RunCheck("working") + + +class SerializerCheckClass: + """Mock serializer class for testing.""" + + def __init__(self, *args, **kwargs): + raise RunCheck("working") + + +class TestUserMeDeleteView: viewset = djoser.views.UserViewSet - def test_delete_user_if_logged_in(self): - user = create_user() - self.assert_instance_exists(User, username="john") + def test_delete_user_if_logged_in(self, api_client, db): + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "secret"} - self.client.force_authenticate(user=user) - response = self.client.delete(reverse("user-me"), data=data) + api_client.force_authenticate(user=user) + response = api_client.delete(reverse("user-me"), data=data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_instance_does_not_exist(User, username="john") + assert response.status_code == status.HTTP_204_NO_CONTENT + assert not User.objects.filter(username=user.username).exists() - def test_not_delete_if_fails_password_validation(self): - user = create_user() - self.assert_instance_exists(User, username="john") + def test_not_delete_if_fails_password_validation(self, api_client, db): + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - self.client.force_authenticate(user=user) - response = self.client.delete(reverse("user-me"), data=data) + api_client.force_authenticate(user=user) + response = api_client.delete(reverse("user-me"), data=data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {"current_password": ["Invalid password."]}) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data == {"current_password": ["Invalid password."]} - def test_permission_class(self): + def test_permission_class(self, api_client, db): old_value = djoser_settings.PERMISSIONS["user_delete"] with ( override_settings( @@ -56,17 +73,17 @@ def test_permission_class(self): ), pytest.raises(RunCheck), ): - user = create_user() - self.assert_instance_exists(User, username="john") + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - self.client.force_authenticate(user=user) - self.client.delete(reverse("user-me"), data=data) + api_client.force_authenticate(user=user) + api_client.delete(reverse("user-me"), data=data) override_settings( DJOSER=dict(settings.DJOSER, **{"PERMISSIONS": {"user_delete": old_value}}) ).enable() - def test_serializer_class(self): + def test_serializer_class(self, api_client, db): old_value = djoser_settings.SERIALIZERS["user_delete"] with ( override_settings( @@ -77,53 +94,48 @@ def test_serializer_class(self): ), pytest.raises(RunCheck), ): - user = create_user() - self.assert_instance_exists(User, username="john") + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - self.client.force_authenticate(user=user) - self.client.delete(reverse("user-me"), data=data) + api_client.force_authenticate(user=user) + api_client.delete(reverse("user-me"), data=data) override_settings( DJOSER=dict(settings.DJOSER, **{"SERIALIZERS": {"user_delete": old_value}}) ).enable() -class UserViewSetDeletionTest( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.EmailAssertionsMixin, - assertions.InstanceAssertionsMixin, -): - def test_delete_user_if_logged_in(self): - user = create_user() - self.assert_instance_exists(User, username="john") +class TestUserViewSetDeletion: + def test_delete_user_if_logged_in(self, api_client, db): + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "secret"} - self.client.force_authenticate(user=user) + api_client.force_authenticate(user=user) - response = self.client.delete( + response = api_client.delete( reverse("user-detail", kwargs={User._meta.pk.name: user.pk}), data=data, ) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_instance_does_not_exist(User, username="john") + assert response.status_code == status.HTTP_204_NO_CONTENT + assert not User.objects.filter(username=user.username).exists() - def test_not_delete_if_fails_password_validation(self): - user = create_user() - self.assert_instance_exists(User, username="john") + def test_not_delete_if_fails_password_validation(self, api_client, db): + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - self.client.force_authenticate(user=user) + api_client.force_authenticate(user=user) - response = self.client.delete( + response = api_client.delete( reverse("user-detail", kwargs={User._meta.pk.name: user.pk}), data=data, ) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {"current_password": ["Invalid password."]}) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data == {"current_password": ["Invalid password."]} - def test_permission_class(self): + def test_permission_class(self, api_client, db): old_value = djoser_settings.PERMISSIONS["user_delete"] with ( override_settings( @@ -134,12 +146,12 @@ def test_permission_class(self): ), pytest.raises(RunCheck), ): - user = create_user() - self.assert_instance_exists(User, username="john") + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - self.client.force_authenticate(user=user) - self.client.delete( + api_client.force_authenticate(user=user) + api_client.delete( reverse("user-detail", kwargs={User._meta.pk.name: user.pk}), data=data, ) @@ -147,7 +159,7 @@ def test_permission_class(self): DJOSER=dict(settings.DJOSER, **{"PERMISSIONS": {"user_delete": old_value}}) ).enable() - def test_serializer_class(self): + def test_serializer_class(self, api_client, db): old_value = djoser_settings.SERIALIZERS["user_delete"] with ( override_settings( @@ -158,12 +170,12 @@ def test_serializer_class(self): ), pytest.raises(RunCheck), ): - user = create_user() - self.assert_instance_exists(User, username="john") + user = UserFactory.create() + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - self.client.force_authenticate(user=user) - self.client.delete( + api_client.force_authenticate(user=user) + api_client.delete( reverse("user-detail", kwargs={User._meta.pk.name: user.pk}), data=data, ) diff --git a/testproject/testapp/tests/test_user_detail.py b/testproject/testapp/tests/test_user_detail.py index 837f3644..fec07b74 100644 --- a/testproject/testapp/tests/test_user_detail.py +++ b/testproject/testapp/tests/test_user_detail.py @@ -1,224 +1,227 @@ -from djet import assertions +from testapp.factories import UserFactory +import pytest from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user, login_user import djoser.permissions import djoser.views +import djoser.signals -class BaseUserViewSetListTest(APITestCase, assertions.StatusCodeAssertionsMixin): - def setUp(self): - super().setUp() - self.user = create_user(username="user", email="user@example.com") - self.superuser = create_user( - username="superuser", - email="superuser@example.com", - is_superuser=True, - is_staff=True, - ) - - -class ModifiedPermissionsTest(APITestCase): +@pytest.fixture +def setup_modified_permissions(): """Test case that overrides user-detail permission to CurrentUserOrAdminOrReadOnly.""" + previous_permissions = djoser.views.UserViewSet.permission_classes + djoser.views.UserViewSet.permission_classes = [ + djoser.permissions.CurrentUserOrAdminOrReadOnly + ] + yield + djoser.views.UserViewSet.permission_classes = previous_permissions - def setUp(self): - super().setUp() - self.previous_permissions = djoser.views.UserViewSet.permission_classes - djoser.views.UserViewSet.permission_classes = [ - djoser.permissions.CurrentUserOrAdminOrReadOnly - ] - def tearDown(self): - super().tearDown() - djoser.views.UserViewSet.permission_classes = self.previous_permissions +@pytest.mark.django_db +class TestUserViewSetList: + @pytest.fixture(autouse=True) + def setup(self, user, create_superuser): + self.user = user + self.superuser = create_superuser + def test_unauthenticated_user_cannot_get_user_detail(self, api_client): + response = api_client.get(reverse("user-detail", args=[self.user.pk])) -class UserViewSetListTest(BaseUserViewSetListTest): - def test_unauthenticated_user_cannot_get_user_detail(self): - response = self.client.get(reverse("user-detail", args=[self.user.pk])) + assert response.status_code == status.HTTP_401_UNAUTHORIZED - self.assert_status_equal(response, status.HTTP_401_UNAUTHORIZED) + def test_user_can_get_own_details(self, authenticated_client): + response = authenticated_client.get(reverse("user-detail", args=[self.user.pk])) - def test_user_can_get_own_details(self): - login_user(self.client, self.user) - response = self.client.get(reverse("user-detail", args=[self.user.pk])) + assert response.status_code == status.HTTP_200_OK - self.assert_status_equal(response, status.HTTP_200_OK) + def test_user_cannot_get_other_user_detail(self, authenticated_client): + response = authenticated_client.get( + reverse("user-detail", args=[self.superuser.pk]) + ) - def test_user_cannot_get_other_user_detail(self): - login_user(self.client, self.user) - response = self.client.get(reverse("user-detail", args=[self.superuser.pk])) + assert response.status_code == status.HTTP_404_NOT_FOUND - self.assert_status_equal(response, status.HTTP_404_NOT_FOUND) + def test_superuser_can_get_other_user_detail(self, api_client): + # Login as superuser + api_client.force_authenticate(user=self.superuser) + response = api_client.get(reverse("user-detail", args=[self.user.pk])) - def test_superuser_can_get_other_user_detail(self): - login_user(self.client, self.superuser) - response = self.client.get(reverse("user-detail", args=[self.user.pk])) + assert response.status_code == status.HTTP_200_OK - self.assert_status_equal(response, status.HTTP_200_OK) +@pytest.mark.django_db +class TestModifiedPermissionsUserViewSetList: + @pytest.fixture(autouse=True) + def setup(self, user, create_superuser): + self.user = user + self.superuser = create_superuser -class ModifiedPermissionsUserViewSetListTest( - BaseUserViewSetListTest, - ModifiedPermissionsTest, -): - def test_user_can_get_other_user_detail(self): - login_user(self.client, self.user) - response = self.client.get(reverse("user-detail", args=[self.superuser.pk])) - self.assert_status_equal(response, status.HTTP_200_OK) + def test_user_can_get_other_user_detail( + self, api_client, setup_modified_permissions + ): + api_client.force_authenticate(user=self.user) + response = api_client.get(reverse("user-detail", args=[self.superuser.pk])) + assert response.status_code == status.HTTP_200_OK - def test_user_cant_set_other_user_detail(self): - login_user(self.client, self.user) - response = self.client.get(reverse("user-detail", args=[self.superuser.pk])) - self.assert_status_equal(response, status.HTTP_200_OK) + def test_user_cant_set_other_user_detail( + self, api_client, setup_modified_permissions + ): + api_client.force_authenticate(user=self.user) + response = api_client.get(reverse("user-detail", args=[self.superuser.pk])) + assert response.status_code == status.HTTP_200_OK -class UserViewSetEditTest(APITestCase, assertions.StatusCodeAssertionsMixin): - def setUp(self): +@pytest.mark.django_db +class TestUserViewSetEdit: + @pytest.fixture(autouse=True) + def setup(self, user): + self.user = user self.signal_sent = False def signal_receiver(self, *args, **kwargs): self.signal_sent = True - def test_patch_edits_user_attribute(self): - user = create_user() + def test_patch_edits_user_attribute(self, api_client): djoser.signals.user_updated.connect(self.signal_receiver) - login_user(self.client, user) - response = self.client.patch( - path=reverse("user-detail", args=(user.pk,)), + api_client.force_authenticate(user=self.user) + response = api_client.patch( + path=reverse("user-detail", args=(self.user.pk,)), data={"email": "new@gmail.com"}, ) - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertTrue("email" in response.data) + assert response.status_code == status.HTTP_200_OK + assert "email" in response.data - user.refresh_from_db() - self.assertTrue(user.email == "new@gmail.com") - self.assertTrue(self.signal_sent) + self.user.refresh_from_db() + assert self.user.email == "new@gmail.com" + assert self.signal_sent + + def test_patch_cant_edit_others_attribute(self, api_client, db): - def test_patch_cant_edit_others_attribute(self): - user = create_user() - another_user = create_user( + another_user = UserFactory.create( **{"username": "paul", "password": "secret", "email": "paul@beatles.com"} ) - login_user(self.client, user) - response = self.client.patch( + api_client.force_authenticate(user=self.user) + response = api_client.patch( path=reverse("user-detail", args=(another_user.pk,)), data={"email": "new@gmail.com"}, ) - self.assert_status_equal(response, status.HTTP_404_NOT_FOUND) + assert response.status_code == status.HTTP_404_NOT_FOUND another_user.refresh_from_db() - self.assertTrue(another_user.email == "paul@beatles.com") + assert another_user.email == "paul@beatles.com" - def test_put_edits_user_attribute(self): + def test_put_edits_user_attribute(self, api_client): user_data = { - "username": "paul", - "password": "secret", - "email": "paul@beatles.com", + "username": self.user.username, + "password": "changed_secret", + "email": self.user.email, } - user = create_user(**user_data) - user_data["password"] = "changed_secret" - login_user(self.client, user) + api_client.force_authenticate(user=self.user) - response = self.client.patch( - path=reverse("user-detail", args=(user.pk,)), data=user_data + response = api_client.patch( + path=reverse("user-detail", args=(self.user.pk,)), data=user_data ) - self.assert_status_equal(response, status.HTTP_200_OK) + assert response.status_code == status.HTTP_200_OK - user.refresh_from_db() - self.assertTrue(user.email == "paul@beatles.com") + self.user.refresh_from_db() + assert self.user.email == user_data["email"] + + def test_put_cant_edit_others_attribute(self, api_client, db): - def test_put_cant_edit_others_attribute(self): - user = create_user() another_user_data = { "username": "paul", "password": "secret", "email": "paul@beatles.com", } - another_user = create_user(**another_user_data) + another_user = UserFactory.create(**another_user_data) another_user_data["password"] = "changed_secret" - login_user(self.client, user) + api_client.force_authenticate(user=self.user) - response = self.client.patch( + response = api_client.patch( path=reverse("user-detail", args=(another_user.pk,)), data=another_user_data ) - self.assert_status_equal(response, status.HTTP_404_NOT_FOUND) + assert response.status_code == status.HTTP_404_NOT_FOUND another_user.refresh_from_db() - self.assertTrue(another_user.email == "paul@beatles.com") + assert another_user.email == "paul@beatles.com" + +@pytest.mark.django_db +class TestModifiedPermissionsViewSetEdit: + @pytest.fixture(autouse=True) + def setup(self, user, create_superuser): -class ModifiedPermissionsViewSetEditTest( - ModifiedPermissionsTest, assertions.StatusCodeAssertionsMixin -): - def test_put_cant_edit_others_attribute(self): - user = create_user() + self.user = user + self.create_user = UserFactory.create + self.superuser = create_superuser + + def test_put_cant_edit_others_attribute( + self, api_client, setup_modified_permissions + ): another_user_data = { "username": "paul", "password": "secret", "email": "paul@beatles.com", } - another_user = create_user(**another_user_data) + another_user = self.create_user(**another_user_data) another_user_data["password"] = "changed_secret" another_user_data["email"] = "paulmc@beatles.com" - login_user(self.client, user) + api_client.force_authenticate(user=self.user) - response = self.client.patch( + response = api_client.patch( path=reverse("user-detail", args=(another_user.pk,)), data=another_user_data ) - self.assert_status_equal(response, status.HTTP_404_NOT_FOUND) + assert response.status_code == status.HTTP_404_NOT_FOUND another_user.refresh_from_db() - assert another_user.email, "paul@beatles.com" + assert another_user.email == "paul@beatles.com" - def test_put_cant_edit_own_attribute(self): + def test_put_cant_edit_own_attribute(self, api_client, setup_modified_permissions): user_data = { "username": "paul", "password": "secret", "email": "paul@beatles.com", } - user = create_user(**user_data) + user = self.create_user(**user_data) user_data["password"] = "changed_secret" user_data["email"] = "paulmc@beatles.com" - login_user(self.client, user) + api_client.force_authenticate(user=user) - response = self.client.patch( + response = api_client.patch( path=reverse("user-detail", args=(user.pk,)), data=user_data ) - self.assert_status_equal(response, status.HTTP_200_OK) + assert response.status_code == status.HTTP_200_OK user.refresh_from_db() - assert user.email, "paulmc@beatles.com" + assert user.email == "paulmc@beatles.com" - def test_superuser_put_can_edit_others_attribute(self): - superuser = create_user( - is_superuser=True, - is_staff=True, - ) + def test_superuser_put_can_edit_others_attribute( + self, api_client, setup_modified_permissions + ): another_user_data = { "username": "paul", "password": "secret", "email": "paul@beatles.com", } - another_user = create_user(**another_user_data) + another_user = self.create_user(**another_user_data) another_user_data["password"] = "changed_secret" another_user_data["email"] = "paulmc@beatles.com" - login_user(self.client, superuser) + api_client.force_authenticate(user=self.superuser) - response = self.client.patch( + response = api_client.patch( path=reverse("user-detail", args=(another_user.pk,)), data=another_user_data ) - self.assert_status_equal(response, status.HTTP_200_OK) + assert response.status_code == status.HTTP_200_OK another_user.refresh_from_db() assert another_user.email == "paulmc@beatles.com" diff --git a/testproject/testapp/tests/test_user_list.py b/testproject/testapp/tests/test_user_list.py index 51308314..d508a3eb 100644 --- a/testproject/testapp/tests/test_user_list.py +++ b/testproject/testapp/tests/test_user_list.py @@ -1,47 +1,37 @@ -from django.conf import settings -from django.test import override_settings -from djet import assertions +import pytest from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user, login_user -class UserViewSetListTest(APITestCase, assertions.StatusCodeAssertionsMixin): - def setUp(self): +class TestUserViewSetList: + @pytest.fixture(autouse=True) + def setup(self, user, create_superuser): self.base_url = reverse("user-list") - self.user = create_user(username="user", email="user@example.com") - self.superuser = create_user( - username="superuser", - email="superuser@example.com", - is_superuser=True, - is_staff=True, - ) + self.user = user + self.superuser = create_superuser - def test_unauthenticated_user_cannot_list_users(self): - response = self.client.get(self.base_url) + def test_unauthenticated_user_cannot_list_users(self, api_client): + response = api_client.get(self.base_url) - self.assert_status_equal(response, status.HTTP_401_UNAUTHORIZED) + assert response.status_code == status.HTTP_401_UNAUTHORIZED - @override_settings(DJOSER=dict(settings.DJOSER, **{"HIDE_USERS": True})) - def test_user_cannot_list_other_users(self): - login_user(self.client, self.user) - response = self.client.get(self.base_url) + def test_user_cannot_list_other_users(self, authenticated_client, djoser_settings): + djoser_settings["HIDE_USERS"] = True + response = authenticated_client.get(self.base_url) - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertEqual(len(response.json()), 1) + assert response.status_code == status.HTTP_200_OK + assert len(response.json()) == 1 - @override_settings(DJOSER=dict(settings.DJOSER, **{"HIDE_USERS": False})) - def test_user_can_list_other_users(self): - login_user(self.client, self.user) - response = self.client.get(self.base_url) + def test_user_can_list_other_users(self, authenticated_client, djoser_settings): + djoser_settings["HIDE_USERS"] = False + response = authenticated_client.get(self.base_url) - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertEqual(len(response.json()), 2) + assert response.status_code == status.HTTP_200_OK + assert len(response.json()) == 2 - def test_superuser_can_list_all_users(self): - login_user(self.client, self.superuser) - response = self.client.get(self.base_url) + def test_superuser_can_list_all_users(self, api_client): + api_client.force_authenticate(user=self.superuser) + response = api_client.get(self.base_url) - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertEqual(len(response.json()), 2) + assert response.status_code == status.HTTP_200_OK + assert len(response.json()) == 2 diff --git a/testproject/testapp/tests/test_user_me.py b/testproject/testapp/tests/test_user_me.py index 942d0198..f4c19b2c 100644 --- a/testproject/testapp/tests/test_user_me.py +++ b/testproject/testapp/tests/test_user_me.py @@ -1,127 +1,119 @@ -from django.conf import settings +import pytest from django.contrib.auth import get_user_model -from django.test.utils import override_settings -from djet import assertions from rest_framework import serializers, status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user, login_user User = get_user_model() -class UserViewSetMeTest( - APITestCase, - assertions.EmailAssertionsMixin, - assertions.InstanceAssertionsMixin, - assertions.StatusCodeAssertionsMixin, -): +@pytest.mark.django_db +class TestUserViewSetMe: + class DummyCurrentUserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ("is_staff",) - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.base_url = reverse("user-me") - self.user = create_user() - login_user(self.client, self.user) - def test_get_return_user(self): - response = self.client.get(self.base_url) + def test_get_return_user(self, authenticated_client): + response = authenticated_client.get(self.base_url) - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertEqual( - set(response.data.keys()), - set([User.USERNAME_FIELD, User._meta.pk.name] + User.REQUIRED_FIELDS), + assert response.status_code == status.HTTP_200_OK + assert set(response.data.keys()) == set( + [User.USERNAME_FIELD, User._meta.pk.name] + User.REQUIRED_FIELDS ) - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": False})) - def test_put_email_change_with_send_activation_email_false(self): + def test_put_email_change_with_send_activation_email_false( + self, authenticated_client, user, djoser_settings + ): + djoser_settings["SEND_ACTIVATION_EMAIL"] = False data = {"email": "ringo@beatles.com"} - response = self.client.put(self.base_url, data=data) + response = authenticated_client.put(self.base_url, data=data) - self.assert_status_equal(response, status.HTTP_200_OK) - self.user.refresh_from_db() - self.assertEqual(data["email"], self.user.email) - self.assertTrue(self.user.is_active) + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + assert data["email"] == user.email + assert user.is_active - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_put_email_change_with_send_activation_email_true(self): + def test_put_email_change_with_send_activation_email_true( + self, authenticated_client, user, mailoutbox, djoser_settings + ): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + data = {"email": "ringo@beatles.com"} + response = authenticated_client.put(self.base_url, data=data) + + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + assert data["email"] == user.email + assert not user.is_active + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [data["email"]] + + def test_patch_email_change_with_send_activation_email_false( + self, authenticated_client, user, djoser_settings + ): + djoser_settings["SEND_ACTIVATION_EMAIL"] = False data = {"email": "ringo@beatles.com"} - response = self.client.put(self.base_url, data=data) + response = authenticated_client.patch(self.base_url, data=data) - self.assert_status_equal(response, status.HTTP_200_OK) - self.user.refresh_from_db() - self.assertEqual(data["email"], self.user.email) - self.assertFalse(self.user.is_active) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[data["email"]]) + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + assert data["email"] == user.email + assert user.is_active - def test_patch_email_change_with_send_activation_email_false(self): + def test_patch_email_change_with_send_activation_email_true( + self, authenticated_client, user, mailoutbox, djoser_settings + ): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True data = {"email": "ringo@beatles.com"} - response = self.client.patch(self.base_url, data=data) + response = authenticated_client.patch(self.base_url, data=data) - self.assert_status_equal(response, status.HTTP_200_OK) - self.user.refresh_from_db() - self.assertEqual(data["email"], self.user.email) - self.assertTrue(self.user.is_active) + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + assert data["email"] == user.email + assert not user.is_active + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [data["email"]] - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_patch_email_change_with_send_activation_email_true(self): - data = {"email": "ringo@beatles.com"} - response = self.client.patch(self.base_url, data=data) - - self.assert_status_equal(response, status.HTTP_200_OK) - self.user.refresh_from_db() - self.assertEqual(data["email"], self.user.email) - self.assertFalse(self.user.is_active) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[data["email"]]) - - @override_settings( - DJOSER=dict( - settings.DJOSER, - **{"SERIALIZERS": {"current_user": DummyCurrentUserSerializer}}, - ) - ) - def test_serializer(self): + def test_serializer(self, djoser_settings, authenticated_client, user): """Test that the endpoints use the proper serializer. How it works: it adds an additional field to the current_user serializer and then checks that the field shows in the response. """ - response = self.client.get(self.base_url) + djoser_settings["SERIALIZERS"] = { + "current_user": self.DummyCurrentUserSerializer + } + response = authenticated_client.get(self.base_url) + + user.refresh_from_db() + assert response.data["is_staff"] == user.is_staff - self.user.refresh_from_db() - self.assertEqual(response.data["is_staff"], self.user.is_staff) +@pytest.mark.django_db +class TestUserViewSetMeDelete: -class UserViewSetMeDeleteTest( - APITestCase, - assertions.InstanceAssertionsMixin, - assertions.StatusCodeAssertionsMixin, -): - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.base_url = reverse("user-me") - def test_delete_user_if_logged_in(self): - user = create_user() - self.assert_instance_exists(User, username="john") + def test_delete_user_if_logged_in(self, authenticated_client, user): + assert User.objects.filter(username=user.username).exists() data = {"current_password": "secret"} - login_user(self.client, user) - response = self.client.delete(self.base_url, data=data) + response = authenticated_client.delete(self.base_url, data=data) - self.assert_status_equal(response, status.HTTP_204_NO_CONTENT) - self.assert_instance_does_not_exist(User, username="john") + assert response.status_code == status.HTTP_204_NO_CONTENT + assert not User.objects.filter(username=user.username).exists() - def test_not_delete_if_fails_password_validation(self): - user = create_user() - self.assert_instance_exists(User, username="john") + def test_not_delete_if_fails_password_validation(self, authenticated_client, user): + assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} - login_user(self.client, user) - response = self.client.delete(self.base_url, data=data) + response = authenticated_client.delete(self.base_url, data=data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {"current_password": ["Invalid password."]}) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.data == {"current_password": ["Invalid password."]} diff --git a/testproject/testapp/tests/test_user_view.py b/testproject/testapp/tests/test_user_view.py index 7bf84b04..a1c0be38 100644 --- a/testproject/testapp/tests/test_user_view.py +++ b/testproject/testapp/tests/test_user_view.py @@ -1,92 +1,112 @@ -from django.conf import settings +import pytest +from testapp.factories import UserFactory, TokenFactory from django.contrib.auth import get_user_model -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase - -from .common import create_user, login_user User = get_user_model() -class UserViewTest( - APITestCase, assertions.EmailAssertionsMixin, assertions.StatusCodeAssertionsMixin +@pytest.fixture +def user(db): + return UserFactory.create() + + +@pytest.fixture +def authenticated_client(api_client, user): + api_client.force_authenticate(user=user) + return api_client + + +@pytest.fixture +def user_url(user): + return reverse("user-detail", kwargs={User._meta.pk.name: user.pk}) + + +def test_get_return_user(authenticated_client, user, user_url): + token = TokenFactory.create(user=user) + authenticated_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + response = authenticated_client.get(user_url) + + assert response.status_code == status.HTTP_200_OK + assert set(response.data.keys()) == set( + [User.USERNAME_FIELD, User._meta.pk.name] + User.REQUIRED_FIELDS + ) + + +def test_email_change_with_send_activation_email_false( + djoser_settings, authenticated_client, user, user_url +): + djoser_settings["SEND_ACTIVATION_EMAIL"] = False + data = {"email": "ringo@beatles.com"} + + token = TokenFactory.create(user=user) + authenticated_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + response = authenticated_client.put(user_url, data=data) + + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + assert data["email"] == user.email + assert user.is_active + + +def test_email_change_with_send_activation_email_true( + djoser_settings, authenticated_client, user, user_url, mailoutbox +): + djoser_settings["SEND_ACTIVATION_EMAIL"] = True + data = {"email": "ringo@beatles.com"} + + token = TokenFactory.create(user=user) + authenticated_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + response = authenticated_client.put(user_url, data=data) + + assert response.status_code == status.HTTP_200_OK + user.refresh_from_db() + assert data["email"] == user.email + assert not user.is_active + assert len(mailoutbox) == 1 + assert mailoutbox[0].to == [data["email"]] + + +def test_fail_403_without_permission( + djoser_settings, authenticated_client, user, user_url +): + djoser_settings["HIDE_USERS"] = False + other_user = UserFactory.create( + **{ + "username": "paul", + "password": "verysecret", + "email": "paul@beatles.com", + } + ) + data = {"email": "ringo@beatles.com"} + url = reverse("user-detail", kwargs={User._meta.pk.name: other_user.pk}) + + token = TokenFactory.create(user=user) + authenticated_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + response1 = authenticated_client.put(url, data=data) + assert response1.status_code == status.HTTP_403_FORBIDDEN + response2 = authenticated_client.get(user_url) + assert response2.status_code == status.HTTP_200_OK + + +def test_fail_404_without_permission( + djoser_settings, authenticated_client, user, user_url ): - def setUp(self): - self.user = create_user() - self.client.force_authenticate(user=self.user) - self.url = reverse("user-detail", kwargs={User._meta.pk.name: self.user.pk}) - - def test_get_return_user(self): - login_user(self.client, self.user) - response = self.client.get(self.url) - - self.assert_status_equal(response, status.HTTP_200_OK) - self.assertEqual( - set(response.data.keys()), - set([User.USERNAME_FIELD, User._meta.pk.name] + User.REQUIRED_FIELDS), - ) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": False})) - def test_email_change_with_send_activation_email_false(self): - data = {"email": "ringo@beatles.com"} - - login_user(self.client, self.user) - response = self.client.put(self.url, data=data) - - self.assert_status_equal(response, status.HTTP_200_OK) - self.user.refresh_from_db() - self.assertEqual(data["email"], self.user.email) - self.assertTrue(self.user.is_active) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_email_change_with_send_activation_email_true(self): - data = {"email": "ringo@beatles.com"} - - login_user(self.client, self.user) - response = self.client.put(self.url, data=data) - - self.assert_status_equal(response, status.HTTP_200_OK) - self.user.refresh_from_db() - self.assertEqual(data["email"], self.user.email) - self.assertFalse(self.user.is_active) - self.assert_emails_in_mailbox(1) - self.assert_email_exists(to=[data["email"]]) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"HIDE_USERS": False})) - def test_fail_403_without_permission(self): - other_user = create_user( - **{ - "username": "paul", - "password": "verysecret", - "email": "paul@beatles.com", - } - ) - data = {"email": "ringo@beatles.com"} - url = reverse("user-detail", kwargs={User._meta.pk.name: other_user.pk}) - - login_user(self.client, self.user) - response1 = self.client.put(url, data=data) - self.assert_status_equal(response1, status.HTTP_403_FORBIDDEN) - response2 = self.client.get(self.url) - self.assert_status_equal(response2, status.HTTP_200_OK) - - @override_settings(DJOSER=dict(settings.DJOSER, **{"HIDE_USERS": True})) - def test_fail_404_without_permission(self): - other_user = create_user( - **{ - "username": "paul", - "password": "verysecret", - "email": "paul@beatles.com", - } - ) - data = {"email": "ringo@beatles.com"} - url = reverse("user-detail", kwargs={User._meta.pk.name: other_user.pk}) - - login_user(self.client, self.user) - response1 = self.client.put(url, data=data) - self.assert_status_equal(response1, status.HTTP_404_NOT_FOUND) - response2 = self.client.get(self.url) - self.assert_status_equal(response2, status.HTTP_200_OK) + djoser_settings["HIDE_USERS"] = True + other_user = UserFactory.create( + **{ + "username": "paul", + "password": "verysecret", + "email": "paul@beatles.com", + } + ) + data = {"email": "ringo@beatles.com"} + url = reverse("user-detail", kwargs={User._meta.pk.name: other_user.pk}) + + token = TokenFactory.create(user=user) + authenticated_client.credentials(HTTP_AUTHORIZATION="Token " + token.key) + response1 = authenticated_client.put(url, data=data) + assert response1.status_code == status.HTTP_404_NOT_FOUND + response2 = authenticated_client.get(user_url) + assert response2.status_code == status.HTTP_200_OK diff --git a/testproject/testapp/tests/test_webauthn/test_login.py b/testproject/testapp/tests/test_webauthn/test_login.py index 99fe39b7..c84c5b1f 100644 --- a/testproject/testapp/tests/test_webauthn/test_login.py +++ b/testproject/testapp/tests/test_webauthn/test_login.py @@ -1,12 +1,9 @@ +import pytest from copy import deepcopy -from django.conf import settings from django.contrib.auth import get_user_model -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase from djoser.conf import settings as djoser_settings @@ -31,21 +28,16 @@ PUBLIC_KEY = "pAEDAzkBACBZAQDKjp4zmIBGEe97svBO9uHFPf2oe-IeMLW3Nq5jkEWeoCpiyPqbkeXo13IZAQMj40uub2QYqXEYugNRkuhCVRUvRbaiW2ws9i2AoukCgR_pB9DHnWPzo1mJEKU0RFUqxD4K1x5CX-JzvO8rsdMxBAz_Ja1piMaj23YgM9WCuRWehLO7P8373KUKCMKbbf6yWVvpeGC-lePjhMOmJkU6EkOFhCSy6pOMDWzTx5es8PC9zUFyrk3yUkhzd69rTu95y5kHnfLBjShlFZnnxpXTfmOOM934rGnkkaLcfTcSu-cckJXG_rh36eDHta-erCwS8ZUbedq5p14bNha4aSCzF115IUMBAAE" # noqa -@override_settings( - DJOSER={ - **settings.DJOSER, - **{"WEBAUTHN": {"RP_NAME": RP_NAME, "RP_ID": RP_ID, "ORIGIN": ORIGIN}}, - } -) -class TestLoginView( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.InstanceAssertionsMixin, -): - url = reverse("webauthn_login") +@pytest.mark.django_db +class TestLoginView: - def setUp(self): - self.co = co = create_credential_options( + @pytest.fixture(autouse=True) + def credential_options(self, djoser_settings): + djoser_settings.update( + WEBAUTHN={"RP_NAME": RP_NAME, "RP_ID": RP_ID, "ORIGIN": ORIGIN} + ) + self.url = reverse("webauthn_login") + co = create_credential_options( challenge=ASSERTION_CHALLENGE, username=USERNAME, display_name=USER_DISPLAY_NAME, @@ -55,27 +47,33 @@ def setUp(self): co.public_key = PUBLIC_KEY co.sign_count = 0 co.save() + yield co - def test_post_with_invalid_login_response_should_return_400(self): - for invalid_field in LOGIN_DATA.keys(): - with self.subTest(invalid_field=invalid_field): - data = deepcopy(LOGIN_DATA) - data[invalid_field] = "invalid_data" - response = self.client.post(self.url, data=data) + @pytest.mark.parametrize("invalid_field", list(LOGIN_DATA.keys())) + def test_post_with_invalid_login_response_should_return_400( + self, api_client, invalid_field + ): + data = deepcopy(LOGIN_DATA) + data[invalid_field] = "invalid_data" + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST - def test_post_with_valid_login_response_should_create_and_return_auth_token(self): + def test_post_with_valid_login_response_should_create_and_return_auth_token( + self, api_client + ): data = deepcopy(LOGIN_DATA) - response = self.client.post(self.url, data=data) + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assert_instance_exists(djoser_settings.TOKEN_MODEL) - self.assertTrue("auth_token" in response.json()) + assert response.status_code == status.HTTP_201_CREATED + assert djoser_settings.TOKEN_MODEL.objects.exists() + assert "auth_token" in response.json() - def test_challenge_should_not_be_stored_after_successful_login(self): + def test_challenge_should_not_be_stored_after_successful_login( + self, credential_options, api_client + ): data = deepcopy(LOGIN_DATA) - self.client.post(self.url, data=data) + api_client.post(self.url, data=data) - self.co.refresh_from_db() - self.assertEqual(self.co.challenge, "") + credential_options.refresh_from_db() + assert credential_options.challenge == "" diff --git a/testproject/testapp/tests/test_webauthn/test_login_request.py b/testproject/testapp/tests/test_webauthn/test_login_request.py index 29a4b315..ec5770ec 100644 --- a/testproject/testapp/tests/test_webauthn/test_login_request.py +++ b/testproject/testapp/tests/test_webauthn/test_login_request.py @@ -1,39 +1,44 @@ +import pytest from django.contrib.auth import get_user_model -from django.test import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user -from django.conf import settings from .utils import create_credential_options User = get_user_model() -@override_settings(DJOSER=dict(settings.DJOSER, **{"LOGIN_FIELD": "username"})) -class TestLoginRequestView(APITestCase, assertions.StatusCodeAssertionsMixin): - url = reverse("webauthn_login_request") +@pytest.mark.django_db +class TestLoginRequestView: - def test_post_with_non_existing_username_should_return_400(self): + @pytest.fixture(autouse=True) + def setup(self): + self.url = reverse("webauthn_login_request") + + def test_post_with_non_existing_username_should_return_400( + self, api_client, djoser_settings + ): + djoser_settings["LOGIN_FIELD"] = "username" data = {"username": "john"} - response = self.client.post(self.url, data=data) + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST - def test_post_with_username_not_registered_with_webauthn_should_return_400(self): - user = create_user() + def test_post_with_username_not_registered_with_webauthn_should_return_400( + self, api_client, user, djoser_settings + ): + djoser_settings["LOGIN_FIELD"] = "username" data = {"username": user.username} - response = self.client.post(self.url, data=data) + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) + assert response.status_code == status.HTTP_400_BAD_REQUEST def test_post_with_username_registered_with_webauthn_should_return_login_assertion( - self, + self, api_client, djoser_settings ): + djoser_settings["LOGIN_FIELD"] = "username" co = create_credential_options(with_user=True) data = {"username": co.username} - response = self.client.post(self.url, data=data) + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_200_OK) + assert response.status_code == status.HTTP_200_OK diff --git a/testproject/testapp/tests/test_webauthn/test_signup.py b/testproject/testapp/tests/test_webauthn/test_signup.py index ba65e4c2..c8083f02 100644 --- a/testproject/testapp/tests/test_webauthn/test_signup.py +++ b/testproject/testapp/tests/test_webauthn/test_signup.py @@ -1,18 +1,11 @@ +import pytest from copy import deepcopy - -from django.conf import settings from django.contrib.auth import get_user_model -from django.test.utils import override_settings -from djet import assertions from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase - -from .utils import create_credential_options User = get_user_model() - REGISTRATION_CHALLENGE = "BG7Th4n4iNUmNuRqMjI8NUhFgcNPWmqP" RP_NAME = "Web Authentication" RP_ID = "3fadfd13.ngrok.io" @@ -28,58 +21,59 @@ } -@override_settings( - DJOSER={ - **settings.DJOSER, - **{"WEBAUTHN": {"RP_NAME": RP_NAME, "RP_ID": RP_ID, "ORIGIN": ORIGIN}}, - } -) -class TestSignupView( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.InstanceAssertionsMixin, - assertions.EmailAssertionsMixin, -): - url = reverse("webauthn_signup", args=[USER_ID]) +@pytest.mark.django_db +class TestSignupView: + + @pytest.fixture(autouse=True) + def setup(self, djoser_settings): + from djoser.webauthn.models import CredentialOptions - def setUp(self): - self.co = create_credential_options( + djoser_settings.update( + WEBAUTHN={"RP_NAME": RP_NAME, "RP_ID": RP_ID, "ORIGIN": ORIGIN} + ) + + self.url = reverse("webauthn_signup", args=[USER_ID]) + self.co = CredentialOptions.objects.create( challenge=REGISTRATION_CHALLENGE, username=USERNAME, display_name=USER_DISPLAY_NAME, ukey=USER_ID, + credential_id="f00", ) - def test_post_with_invalid_registration_response_should_return_400(self): - for invalid_field in ("clientData", "attObj"): - with self.subTest(invalid_field=invalid_field): - data = deepcopy(SIGNUP_DATA) - data[invalid_field] = "invalid_data" - response = self.client.post(self.url, data=data) + @pytest.mark.parametrize("invalid_field", ["clientData", "attObj"]) + def test_post_with_invalid_registration_response_should_return_400( + self, api_client, invalid_field + ): + data = deepcopy(SIGNUP_DATA) + data[invalid_field] = "invalid_data" + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - self.assert_instance_does_not_exist(User) + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert not User.objects.exists() - def test_post_with_valid_registration_response_should_create_user(self): + def test_post_with_valid_registration_response_should_create_user(self, api_client): data = deepcopy(SIGNUP_DATA) - response = self.client.post(self.url, data=data) + response = api_client.post(self.url, data=data) - self.assert_status_equal(response, status.HTTP_201_CREATED) - self.assert_instance_exists(User, username=USERNAME) + assert response.status_code == status.HTTP_201_CREATED + assert User.objects.filter(username=USERNAME).exists() - def test_challenge_should_not_be_stored_after_successfull_signup(self): + def test_challenge_should_not_be_stored_after_successfull_signup(self, api_client): data = deepcopy(SIGNUP_DATA) - self.client.post(self.url, data=data) + api_client.post(self.url, data=data) self.co.refresh_from_db() - self.assertEqual(self.co.challenge, "") + assert self.co.challenge == "" - @override_settings(DJOSER=dict(settings.DJOSER, **{"SEND_ACTIVATION_EMAIL": True})) - def test_register_user_when_email_confirmation_is_required(self): + def test_register_user_when_email_confirmation_is_required( + self, api_client, djoser_settings, mailoutbox + ): + djoser_settings.update(SEND_ACTIVATION_EMAIL=True) data = deepcopy(SIGNUP_DATA) - self.client.post(self.url, data=data) + api_client.post(self.url, data=data) - self.assert_instance_exists(User, username=USERNAME) + assert User.objects.filter(username=USERNAME).exists() user = User.objects.get(username=USERNAME) - self.assertFalse(user.is_active) - self.assert_emails_in_mailbox(1) + assert not user.is_active + assert len(mailoutbox) == 1 diff --git a/testproject/testapp/tests/test_webauthn/test_signup_request.py b/testproject/testapp/tests/test_webauthn/test_signup_request.py index 5431261d..4b9034db 100644 --- a/testproject/testapp/tests/test_webauthn/test_signup_request.py +++ b/testproject/testapp/tests/test_webauthn/test_signup_request.py @@ -1,29 +1,26 @@ -from djet import assertions +import pytest +from testapp.factories import UserFactory from rest_framework import status from rest_framework.reverse import reverse -from rest_framework.test import APITestCase -from testapp.tests.common import create_user from djoser.webauthn.models import CredentialOptions -class SignupRequestViewTest( - APITestCase, - assertions.StatusCodeAssertionsMixin, - assertions.InstanceAssertionsMixin, -): +@pytest.mark.django_db +def test_post_with_duplicate_username_should_fail(api_client): url = reverse("webauthn_signup_request") + user = UserFactory.create() + data = {"username": user.username, "display_name": user.username} + response = api_client.post(url, data=data) - def test_post_with_duplicate_username_should_fail(self): - user = create_user() - data = {"username": user.username, "display_name": user.username} - response = self.client.post(self.url, data=data) + assert response.status_code == status.HTTP_400_BAD_REQUEST - self.assert_status_equal(response, status.HTTP_400_BAD_REQUEST) - def test_post_should_create_credential_options(self): - data = {"username": "john", "display_name": "John Doe"} - response = self.client.post(self.url, data=data) +@pytest.mark.django_db +def test_post_should_create_credential_options(api_client): + url = reverse("webauthn_signup_request") + data = {"username": "john", "display_name": "John Doe"} + response = api_client.post(url, data=data) - self.assert_status_equal(response, status.HTTP_200_OK) - self.assert_instance_exists(CredentialOptions, username=data["username"]) + assert response.status_code == status.HTTP_200_OK + assert CredentialOptions.objects.filter(username=data["username"]).exists() diff --git a/testproject/testapp/tests/test_webauthn/utils.py b/testproject/testapp/tests/test_webauthn/utils.py index c1907b39..419263e8 100644 --- a/testproject/testapp/tests/test_webauthn/utils.py +++ b/testproject/testapp/tests/test_webauthn/utils.py @@ -1,5 +1,4 @@ -from testapp.tests.common import create_user - +from testapp.factories import UserFactory from djoser.webauthn.models import CredentialOptions @@ -15,6 +14,6 @@ def create_credential_options( username=username, display_name=display_name, ukey=ukey, - user=None if not with_user else create_user(username=username), + user=None if not with_user else UserFactory.create(username=username), credential_id="f00", ) From 6ed6c4e62e9727c452a472409230ed0493c8db92 Mon Sep 17 00:00:00 2001 From: Tom Wojcik Date: Mon, 28 Jul 2025 14:16:26 +0200 Subject: [PATCH 2/4] revert some TestModelBackendLoginFields changes --- ...ken_create_custom_username_login_fields.py | 151 +++++------------- 1 file changed, 43 insertions(+), 108 deletions(-) diff --git a/testproject/testapp/tests/test_token_create_custom_username_login_fields.py b/testproject/testapp/tests/test_token_create_custom_username_login_fields.py index 07e27cca..8587f528 100644 --- a/testproject/testapp/tests/test_token_create_custom_username_login_fields.py +++ b/testproject/testapp/tests/test_token_create_custom_username_login_fields.py @@ -8,14 +8,19 @@ @pytest.mark.django_db -class TestModelBackendLoginFields: +class BaseTestUsernameLoginFields: url = reverse("login") @pytest.fixture(autouse=True) def add_authentication_backend(self, settings): + raise NotImplementedError + + @pytest.fixture(autouse=True) + def settings(self, settings): settings.AUTHENTICATION_BACKENDS = [ "django.contrib.auth.backends.ModelBackend", ] + return settings @pytest.fixture def signal_user_logged_in_patched(self): @@ -30,10 +35,14 @@ def signal_user_login_failed_patched(self): return signal_handler def configure_djoser_settings( - self, settings, mocker, login_field, username_field, user_can_authenticate + self, + djoser_settings, + mocker, + login_field, + username_field, + user_can_authenticate, ): - settings.DJOSER["LOGIN_FIELD"] = login_field - mocker.patch("djoser.serializers.settings.LOGIN_FIELD", login_field) + djoser_settings["LOGIN_FIELD"] = login_field mocker.patch("djoser.serializers.User.USERNAME_FIELD", username_field) mocker.patch.object( ModelBackend, "user_can_authenticate", return_value=user_can_authenticate @@ -43,7 +52,7 @@ def _test_successful_login( self, user, client, - settings, + djoser_settings, mocker, signal_user_logged_in_patched, login_field, @@ -51,7 +60,7 @@ def _test_successful_login( send_field, ): self.configure_djoser_settings( - settings=settings, + djoser_settings=djoser_settings, mocker=mocker, login_field=login_field, username_field=username_field, @@ -77,7 +86,7 @@ def _test_failing_login( self, user, client, - settings, + djoser_settings, mocker, signal_user_login_failed_patched, login_field, @@ -86,7 +95,7 @@ def _test_failing_login( user_can_authenticate, ): self.configure_djoser_settings( - settings=settings, + djoser_settings=djoser_settings, mocker=mocker, login_field=login_field, username_field=username_field, @@ -106,6 +115,17 @@ def _test_failing_login( assert user.last_login == previous_last_login signal_user_login_failed_patched.assert_called_once() + +@pytest.mark.django_db +class TestModelBackendLoginFields(BaseTestUsernameLoginFields): + url = reverse("login") + + @pytest.fixture(autouse=True) + def add_authentication_backend(self, settings): + settings.AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + ] + @pytest.mark.parametrize( "login_field, username_field, send_field", [ @@ -117,7 +137,7 @@ def test_successful_login( self, user, client, - settings, + djoser_settings, mocker, signal_user_logged_in_patched, login_field, @@ -127,7 +147,7 @@ def test_successful_login( self._test_successful_login( user, client, - settings, + djoser_settings, mocker, signal_user_logged_in_patched, login_field, @@ -154,7 +174,7 @@ def test_failing_login( self, user, client, - settings, + djoser_settings, mocker, signal_user_login_failed_patched, login_field, @@ -165,7 +185,7 @@ def test_failing_login( self._test_failing_login( user, client, - settings, + djoser_settings, mocker, signal_user_login_failed_patched, login_field, @@ -176,7 +196,7 @@ def test_failing_login( @pytest.mark.django_db -class TestLoginFieldBackend: +class TestLoginFieldBackend(BaseTestUsernameLoginFields): url = reverse("login") @pytest.fixture(autouse=True) @@ -185,95 +205,6 @@ def add_authentication_backend(self, settings): "djoser.auth_backends.LoginFieldBackend", ] - @pytest.fixture - def signal_user_logged_in_patched(self): - signal_handler = mock.MagicMock() - user_logged_in.connect(signal_handler) - return signal_handler - - @pytest.fixture - def signal_user_login_failed_patched(self): - signal_handler = mock.MagicMock() - user_login_failed.connect(signal_handler) - return signal_handler - - def configure_djoser_settings( - self, settings, mocker, login_field, username_field, user_can_authenticate - ): - settings.DJOSER["LOGIN_FIELD"] = login_field - mocker.patch("djoser.serializers.settings.LOGIN_FIELD", login_field) - mocker.patch("djoser.serializers.User.USERNAME_FIELD", username_field) - mocker.patch.object( - ModelBackend, "user_can_authenticate", return_value=user_can_authenticate - ) - - def _test_successful_login( - self, - user, - client, - settings, - mocker, - signal_user_logged_in_patched, - login_field, - username_field, - send_field, - ): - self.configure_djoser_settings( - settings=settings, - mocker=mocker, - login_field=login_field, - username_field=username_field, - user_can_authenticate=True, - ) - - if send_field == "username": - data = {"username": user.username, "password": user.raw_password} - else: - data = {"email": user.email, "password": user.raw_password} - - previous_last_login = user.last_login - response = client.post(self.url, data) - - assert response.status_code == status.HTTP_200_OK - user.refresh_from_db() - - assert response.data["auth_token"] == user.auth_token.key - assert user.last_login != previous_last_login - signal_user_logged_in_patched.assert_called_once() - - def _test_failing_login( - self, - user, - client, - settings, - mocker, - signal_user_login_failed_patched, - login_field, - username_field, - send_field, - user_can_authenticate, - ): - self.configure_djoser_settings( - settings=settings, - mocker=mocker, - login_field=login_field, - username_field=username_field, - user_can_authenticate=user_can_authenticate, - ) - if send_field == "username": - data = {"username": user.username, "password": user.raw_password} - else: - data = {"email": user.email, "password": user.raw_password} - - previous_last_login = user.last_login - response = client.post(self.url, data) - - assert response.status_code == status.HTTP_400_BAD_REQUEST - user.refresh_from_db() - - assert user.last_login == previous_last_login - signal_user_login_failed_patched.assert_called_once() - @pytest.mark.parametrize( "login_field, username_field, send_field", [ @@ -287,7 +218,7 @@ def test_successful_login( self, user, client, - settings, + djoser_settings, mocker, signal_user_logged_in_patched, login_field, @@ -297,7 +228,7 @@ def test_successful_login( self._test_successful_login( user, client, - settings, + djoser_settings, mocker, signal_user_logged_in_patched, login_field, @@ -312,13 +243,17 @@ def test_successful_login( ("username", "email", False, "username"), ("email", "username", False, "username"), ("email", "email", False, "username"), + ("username", "username", True, "email"), + ("email", "email", True, "username"), + ("username", "email", True, "email"), + ("email", "username", True, "username"), ], ) def test_failing_login( self, user, client, - settings, + djoser_settings, mocker, signal_user_login_failed_patched, login_field, @@ -329,7 +264,7 @@ def test_failing_login( self._test_failing_login( user, client, - settings, + djoser_settings, mocker, signal_user_login_failed_patched, login_field, @@ -338,9 +273,9 @@ def test_failing_login( user_can_authenticate, ) - def test_user_does_not_exist(self, client, settings, mocker): + def test_user_does_not_exist(self, client, djoser_settings, mocker): self.configure_djoser_settings( - settings=settings, + djoser_settings=djoser_settings, mocker=mocker, login_field="username", username_field="username", From 28c30e2bb8dab48d8e47b010111d5125a639fea9 Mon Sep 17 00:00:00 2001 From: Tom Wojcik Date: Mon, 28 Jul 2025 15:31:06 +0200 Subject: [PATCH 3/4] add tests, minor cleanup --- testproject/testapp/factories.py | 50 ++----- .../tests/social/test_provider_auth.py | 127 +++++++++++++++++- .../testapp/tests/test_translations.py | 4 +- testproject/testapp/tests/test_user_me.py | 22 +-- .../tests/test_webauthn/test_signup.py | 100 +++++++++----- .../testapp/tests/test_webauthn/utils.py | 46 +++++++ 6 files changed, 272 insertions(+), 77 deletions(-) diff --git a/testproject/testapp/factories.py b/testproject/testapp/factories.py index 1ed493f4..2c1ca845 100644 --- a/testproject/testapp/factories.py +++ b/testproject/testapp/factories.py @@ -8,18 +8,16 @@ User = get_user_model() -class UserFactory(factory.django.DjangoModelFactory): +class BaseUserFactory(factory.django.DjangoModelFactory): + """Base factory with common password handling logic.""" + class Meta: - model = User + abstract = True skip_postgeneration_save = True - username = factory.Sequence(lambda n: f"user{n}") - email = Faker("email") - @factory.post_generation def password(self, create, extracted, **kwargs): if create: - # Check if password was explicitly passed as a parameter password_value = extracted if extracted is not None else "secret" if password_value is None: self.set_unusable_password() @@ -30,49 +28,29 @@ def password(self, create, extracted, **kwargs): self.save() -class CustomUserFactory(factory.django.DjangoModelFactory): +class UserFactory(BaseUserFactory): + class Meta: + model = User + + username = factory.Sequence(lambda n: f"user{n}") + email = Faker("email") + + +class CustomUserFactory(BaseUserFactory): class Meta: model = "testapp.CustomUser" - skip_postgeneration_save = True custom_username = factory.Sequence(lambda n: f"user{n}") custom_email = Faker("email") custom_required_field = "42" - @factory.post_generation - def password(self, create, extracted, **kwargs): - if create: - # Check if password was explicitly passed as a parameter - password_value = extracted if extracted is not None else "secret" - if password_value is None: - self.set_unusable_password() - self.raw_password = None - else: - self.set_password(password_value) - self.raw_password = password_value - self.save() - -class ExampleUserFactory(factory.django.DjangoModelFactory): +class ExampleUserFactory(BaseUserFactory): class Meta: model = "testapp.ExampleUser" - skip_postgeneration_save = True email = Faker("email") - @factory.post_generation - def password(self, create, extracted, **kwargs): - if create: - # Check if password was explicitly passed as a parameter - password_value = extracted if extracted is not None else "secret" - if password_value is None: - self.set_unusable_password() - self.raw_password = None - else: - self.set_password(password_value) - self.raw_password = password_value - self.save() - class TokenFactory(factory.django.DjangoModelFactory): class Meta: diff --git a/testproject/testapp/tests/social/test_provider_auth.py b/testproject/testapp/tests/social/test_provider_auth.py index 1c81884e..b0d057a4 100644 --- a/testproject/testapp/tests/social/test_provider_auth.py +++ b/testproject/testapp/tests/social/test_provider_auth.py @@ -3,7 +3,12 @@ from django.contrib.sessions.middleware import SessionMiddleware from rest_framework import status from rest_framework.test import APIRequestFactory -from social_core.exceptions import AuthException +from social_core.exceptions import ( + AuthException, + AuthForbidden, + AuthCanceled, + AuthUnknownError, +) import djoser.social.views @@ -97,3 +102,123 @@ def test_post_facebook_provider_validation_fails_if_invalid_state(self): request.GET = {k: v for k, v in data.items()} response = self._get_view_response(request, provider="facebook") assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_post_facebook_provider_auth_forbidden_error(self): + """Test handling of AuthForbidden exception.""" + data = {"code": "XYZ", "state": "ABC"} + + mock.patch( + "social_core.backends.facebook.FacebookOAuth2.auth_complete", + side_effect=AuthForbidden(backend=None), + ).start() + mock.patch( + "social_core.backends.oauth.OAuthAuth.get_session_state", + return_value=data["state"], + ).start() + + request = self.factory.post("/auth/facebook/") + request.GET = {k: v for k, v in data.items()} + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_post_facebook_provider_auth_canceled_error(self): + """Test handling of AuthCanceled exception.""" + data = {"code": "XYZ", "state": "ABC"} + + mock.patch( + "social_core.backends.facebook.FacebookOAuth2.auth_complete", + side_effect=AuthCanceled(backend=None), + ).start() + mock.patch( + "social_core.backends.oauth.OAuthAuth.get_session_state", + return_value=data["state"], + ).start() + + request = self.factory.post("/auth/facebook/") + request.GET = {k: v for k, v in data.items()} + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_post_facebook_provider_auth_unknown_error(self): + """Test handling of AuthUnknownError exception.""" + data = {"code": "XYZ", "state": "ABC"} + + mock.patch( + "social_core.backends.facebook.FacebookOAuth2.auth_complete", + side_effect=AuthUnknownError(backend=None), + ).start() + mock.patch( + "social_core.backends.oauth.OAuthAuth.get_session_state", + return_value=data["state"], + ).start() + + request = self.factory.post("/auth/facebook/") + request.GET = {k: v for k, v in data.items()} + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_post_facebook_provider_missing_code_parameter(self): + """Test handling of missing code parameter.""" + data = {"state": "ABC"} # Missing code + + request = self.factory.post("/auth/facebook/") + request.GET = data + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_post_facebook_provider_missing_state_parameter(self): + """Test handling of missing state parameter.""" + data = {"code": "XYZ"} # Missing state + + request = self.factory.post("/auth/facebook/") + request.GET = data + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_get_unsupported_provider_returns_404(self): + """Test that unsupported providers return 404.""" + request = self.factory.get( + "/auth/unsupported/", data={"redirect_uri": "http://test.localhost/"} + ) + response = self._get_view_response(request, provider="unsupported") + + assert response.status_code == status.HTTP_404_NOT_FOUND + + def test_post_with_expired_authorization_code(self): + """Test handling of expired authorization code.""" + data = {"code": "EXPIRED_CODE", "state": "ABC"} + + # Create a mock backend + mock_backend = mock.Mock() + + mock.patch( + "social_core.backends.facebook.FacebookOAuth2.auth_complete", + side_effect=AuthException(mock_backend), + ).start() + mock.patch( + "social_core.backends.oauth.OAuthAuth.get_session_state", + return_value=data["state"], + ).start() + + request = self.factory.post("/auth/facebook/") + request.GET = data + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + def test_post_with_network_error_during_auth(self): + """Test handling of network errors during authentication.""" + data = {"code": "XYZ", "state": "ABC"} + + mock.patch( + "social_core.backends.facebook.FacebookOAuth2.auth_complete", + side_effect=ConnectionError("Network error"), + ).start() + mock.patch( + "social_core.backends.oauth.OAuthAuth.get_session_state", + return_value=data["state"], + ).start() + + request = self.factory.post("/auth/facebook/") + request.GET = data + response = self._get_view_response(request, provider="facebook") + assert response.status_code == status.HTTP_400_BAD_REQUEST diff --git a/testproject/testapp/tests/test_translations.py b/testproject/testapp/tests/test_translations.py index 4dc79046..72393feb 100644 --- a/testproject/testapp/tests/test_translations.py +++ b/testproject/testapp/tests/test_translations.py @@ -9,7 +9,7 @@ def test_constants_translations_are_up_to_date(): messages = {force_str(v) for k, v in vars(Messages).items() if k.isupper()} - ERROR_TEMPALTE = ( + ERROR_TEMPLATE = ( "Error message '{message}' was found in " "locale {locale} but can't be found in the messages class" ) @@ -30,7 +30,7 @@ def test_constants_translations_are_up_to_date(): continue if message.id not in messages: raise ValueError( - ERROR_TEMPALTE.format( + ERROR_TEMPLATE.format( message=message.id, locale=specific_locale.name ) ) diff --git a/testproject/testapp/tests/test_user_me.py b/testproject/testapp/tests/test_user_me.py index f4c19b2c..6a07c87f 100644 --- a/testproject/testapp/tests/test_user_me.py +++ b/testproject/testapp/tests/test_user_me.py @@ -18,7 +18,7 @@ class Meta: def setup(self): self.base_url = reverse("user-me") - def test_get_return_user(self, authenticated_client): + def test_get_current_user_returns_user_data(self, authenticated_client): response = authenticated_client.get(self.base_url) assert response.status_code == status.HTTP_200_OK @@ -26,7 +26,7 @@ def test_get_return_user(self, authenticated_client): [User.USERNAME_FIELD, User._meta.pk.name] + User.REQUIRED_FIELDS ) - def test_put_email_change_with_send_activation_email_false( + def test_put_email_change_without_activation_email_requirement( self, authenticated_client, user, djoser_settings ): djoser_settings["SEND_ACTIVATION_EMAIL"] = False @@ -38,7 +38,7 @@ def test_put_email_change_with_send_activation_email_false( assert data["email"] == user.email assert user.is_active - def test_put_email_change_with_send_activation_email_true( + def test_put_email_change_with_activation_email_requirement( self, authenticated_client, user, mailoutbox, djoser_settings ): djoser_settings["SEND_ACTIVATION_EMAIL"] = True @@ -52,7 +52,7 @@ def test_put_email_change_with_send_activation_email_true( assert len(mailoutbox) == 1 assert mailoutbox[0].to == [data["email"]] - def test_patch_email_change_with_send_activation_email_false( + def test_patch_email_change_without_activation_email_requirement( self, authenticated_client, user, djoser_settings ): djoser_settings["SEND_ACTIVATION_EMAIL"] = False @@ -64,7 +64,7 @@ def test_patch_email_change_with_send_activation_email_false( assert data["email"] == user.email assert user.is_active - def test_patch_email_change_with_send_activation_email_true( + def test_patch_email_change_with_activation_email_requirement( self, authenticated_client, user, mailoutbox, djoser_settings ): djoser_settings["SEND_ACTIVATION_EMAIL"] = True @@ -78,7 +78,9 @@ def test_patch_email_change_with_send_activation_email_true( assert len(mailoutbox) == 1 assert mailoutbox[0].to == [data["email"]] - def test_serializer(self, djoser_settings, authenticated_client, user): + def test_current_user_serializer_configuration( + self, djoser_settings, authenticated_client, user + ): """Test that the endpoints use the proper serializer. How it works: it adds an additional field to the current_user @@ -100,7 +102,9 @@ class TestUserViewSetMeDelete: def setup(self): self.base_url = reverse("user-me") - def test_delete_user_if_logged_in(self, authenticated_client, user): + def test_delete_authenticated_user_with_correct_password( + self, authenticated_client, user + ): assert User.objects.filter(username=user.username).exists() data = {"current_password": "secret"} @@ -109,7 +113,9 @@ def test_delete_user_if_logged_in(self, authenticated_client, user): assert response.status_code == status.HTTP_204_NO_CONTENT assert not User.objects.filter(username=user.username).exists() - def test_not_delete_if_fails_password_validation(self, authenticated_client, user): + def test_delete_user_fails_with_incorrect_password( + self, authenticated_client, user + ): assert User.objects.filter(username=user.username).exists() data = {"current_password": "incorrect"} diff --git a/testproject/testapp/tests/test_webauthn/test_signup.py b/testproject/testapp/tests/test_webauthn/test_signup.py index c8083f02..9a8b3e01 100644 --- a/testproject/testapp/tests/test_webauthn/test_signup.py +++ b/testproject/testapp/tests/test_webauthn/test_signup.py @@ -1,24 +1,11 @@ import pytest -from copy import deepcopy from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.reverse import reverse -User = get_user_model() +from .utils import get_webauthn_signup_data, get_webauthn_settings, WEBAUTHN_TEST_DATA -REGISTRATION_CHALLENGE = "BG7Th4n4iNUmNuRqMjI8NUhFgcNPWmqP" -RP_NAME = "Web Authentication" -RP_ID = "3fadfd13.ngrok.io" -ORIGIN = "https://3fadfd13.ngrok.io" -USERNAME = "testuser" -USER_DISPLAY_NAME = "A Test User" -USER_ID = "\x80\xf1\xdc\xec\xb5\x18\xb1\xc8b\x05\x886\xbc\xdfJ\xdf" -SIGNUP_DATA = { - "clientData": "ew0KCSJ0eXBlIiA6ICJ3ZWJhdXRobi5jcmVhdGUiLA0KCSJjaGFsbGVuZ2UiIDogIkJHN1RoNG40aU5VbU51UnFNakk4TlVoRmdjTlBXbXFQIiwNCgkib3JpZ2luIiA6ICJodHRwczovLzNmYWRmZDEzLm5ncm9rLmlvIiwNCgkidG9rZW5CaW5kaW5nIiA6IA0KCXsNCgkJInN0YXR1cyIgOiAic3VwcG9ydGVkIg0KCX0NCn0", # noqa - "attObj": "o2NmbXRkbm9uZWhhdXRoRGF0YVkBZ8-CnWXgcASczJuZcxGxAUOJ7xA1fHeCSAxHxXqSqlMsRQAAAABgKLAXsdRMArSzr82vyWuyACCgTbLFqUdf_NegYeOYWcLCYBXlUddoptLz2eQO5DHa4qQBAwM5AQAgWQEAyo6eM5iARhHve7LwTvbhxT39qHviHjC1tzauY5BFnqAqYsj6m5Hl6NdyGQEDI-NLrm9kGKlxGLoDUZLoQlUVL0W2oltsLPYtgKLpAoEf6QfQx51j86NZiRClNERVKsQ-CtceQl_ic7zvK7HTMQQM_yWtaYjGo9t2IDPVgrkVnoSzuz_N-9ylCgjCm23-sllb6XhgvpXj44TDpiZFOhJDhYQksuqTjA1s08eXrPDwvc1Bcq5N8lJIc3eva07vecuZB53ywY0oZRWZ58aV035jjjPd-Kxp5JGi3H03ErvnHJCVxv64d-ngx7WvnqwsEvGVG3nauadeGzYWuGkgsxddeSFDAQABZ2F0dFN0bXSg", # noqa - "username": USERNAME, - "email": "john.doe@example.com", -} +User = get_user_model() @pytest.mark.django_db @@ -28,16 +15,14 @@ class TestSignupView: def setup(self, djoser_settings): from djoser.webauthn.models import CredentialOptions - djoser_settings.update( - WEBAUTHN={"RP_NAME": RP_NAME, "RP_ID": RP_ID, "ORIGIN": ORIGIN} - ) + djoser_settings.update(WEBAUTHN=get_webauthn_settings()) - self.url = reverse("webauthn_signup", args=[USER_ID]) + self.url = reverse("webauthn_signup", args=[WEBAUTHN_TEST_DATA["USER_ID"]]) self.co = CredentialOptions.objects.create( - challenge=REGISTRATION_CHALLENGE, - username=USERNAME, - display_name=USER_DISPLAY_NAME, - ukey=USER_ID, + challenge=WEBAUTHN_TEST_DATA["REGISTRATION_CHALLENGE"], + username=WEBAUTHN_TEST_DATA["USERNAME"], + display_name=WEBAUTHN_TEST_DATA["USER_DISPLAY_NAME"], + ukey=WEBAUTHN_TEST_DATA["USER_ID"], credential_id="f00", ) @@ -45,7 +30,7 @@ def setup(self, djoser_settings): def test_post_with_invalid_registration_response_should_return_400( self, api_client, invalid_field ): - data = deepcopy(SIGNUP_DATA) + data = get_webauthn_signup_data() data[invalid_field] = "invalid_data" response = api_client.post(self.url, data=data) @@ -53,14 +38,14 @@ def test_post_with_invalid_registration_response_should_return_400( assert not User.objects.exists() def test_post_with_valid_registration_response_should_create_user(self, api_client): - data = deepcopy(SIGNUP_DATA) + data = get_webauthn_signup_data() response = api_client.post(self.url, data=data) assert response.status_code == status.HTTP_201_CREATED - assert User.objects.filter(username=USERNAME).exists() + assert User.objects.filter(username=WEBAUTHN_TEST_DATA["USERNAME"]).exists() def test_challenge_should_not_be_stored_after_successfull_signup(self, api_client): - data = deepcopy(SIGNUP_DATA) + data = get_webauthn_signup_data() api_client.post(self.url, data=data) self.co.refresh_from_db() @@ -70,10 +55,65 @@ def test_register_user_when_email_confirmation_is_required( self, api_client, djoser_settings, mailoutbox ): djoser_settings.update(SEND_ACTIVATION_EMAIL=True) - data = deepcopy(SIGNUP_DATA) + data = get_webauthn_signup_data() api_client.post(self.url, data=data) - assert User.objects.filter(username=USERNAME).exists() - user = User.objects.get(username=USERNAME) + assert User.objects.filter(username=WEBAUTHN_TEST_DATA["USERNAME"]).exists() + user = User.objects.get(username=WEBAUTHN_TEST_DATA["USERNAME"]) assert not user.is_active assert len(mailoutbox) == 1 + + def test_post_with_empty_client_data_should_return_400(self, api_client): + """Test error handling for empty client data.""" + data = get_webauthn_signup_data() + data["clientData"] = "" + response = api_client.post(self.url, data=data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert not User.objects.exists() + + def test_post_with_empty_attestation_object_should_return_400(self, api_client): + """Test error handling for empty attestation object.""" + data = get_webauthn_signup_data() + data["attObj"] = "" + response = api_client.post(self.url, data=data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert not User.objects.exists() + + def test_post_with_malformed_base64_client_data_should_return_400(self, api_client): + """Test error handling for malformed base64 client data.""" + data = get_webauthn_signup_data() + data["clientData"] = "not-valid-base64-data!!!" + response = api_client.post(self.url, data=data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert not User.objects.exists() + + def test_post_with_malformed_base64_attestation_object_should_return_400( + self, api_client + ): + """Test error handling for malformed base64 attestation object.""" + data = get_webauthn_signup_data() + data["attObj"] = "not-valid-base64-data!!!" + response = api_client.post(self.url, data=data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert not User.objects.exists() + + def test_post_with_missing_required_fields_should_return_400(self, api_client): + """Test error handling for missing required fields.""" + incomplete_data = {"username": WEBAUTHN_TEST_DATA["USERNAME"]} + response = api_client.post(self.url, data=incomplete_data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert not User.objects.exists() + + def test_post_with_invalid_user_id_should_return_404(self, api_client): + """Test error handling for invalid user ID in URL.""" + invalid_url = reverse("webauthn_signup", args=["invalid-user-id"]) + data = get_webauthn_signup_data() + response = api_client.post(invalid_url, data=data) + + assert response.status_code == status.HTTP_404_NOT_FOUND + assert not User.objects.exists() diff --git a/testproject/testapp/tests/test_webauthn/utils.py b/testproject/testapp/tests/test_webauthn/utils.py index 419263e8..9a3b6727 100644 --- a/testproject/testapp/tests/test_webauthn/utils.py +++ b/testproject/testapp/tests/test_webauthn/utils.py @@ -2,6 +2,52 @@ from djoser.webauthn.models import CredentialOptions +# WebAuthn test constants moved from individual test files +WEBAUTHN_TEST_DATA = { + "REGISTRATION_CHALLENGE": "BG7Th4n4iNUmNuRqMjI8NUhFgcNPWmqP", + "RP_NAME": "Web Authentication", + "RP_ID": "3fadfd13.example.com", + "ORIGIN": "https://3fadfd13.example.com", + "USERNAME": "testuser", + "USER_DISPLAY_NAME": "A Test User", + "USER_ID": "\x80\xf1\xdc\xec\xb5\x18\xb1\xc8b\x05\x886\xbc\xdfJ\xdf", + "CLIENT_DATA": ( + "ew0KCSJ0eXBlIiA6ICJ3ZWJhdXRobi5jcmVhdGUiLA0KCSJjaGFsbGVuZ2UiIDogIkJHN1RoNG40" + "aU5VbU51UnFNakk4TlVoRmdjTlBXbXFQIiwNCgkib3JpZ2luIiA6ICJodHRwczovLzNmYWRmZDEz" + "Lm5ncm9rLmlvIiwNCgkidG9rZW5CaW5kaW5nIiA6IA0KCXsNCgkJInN0YXR1cyIgOiAic3VwcG9y" + "dGVkIg0KCX0NCn0" + ), + "ATT_OBJ": ( + "o2NmbXRkbm9uZWhhdXRoRGF0YVkBZ8-CnWXgcASczJuZcxGxAUOJ7xA1fHeCSAxHxXqSqlMsRQAA" + "AABgKLAXsdRMArSzr82vyWuyACCgTbLFqUdf_NegYeOYWcLCYBXlUddoptLz2eQO5DHa4qQBAwM5" + "AQAgWQEAyo6eM5iARhHve7LwTvbhxT39qHviHjC1tzauY5BFnqAqYsj6m5Hl6NdyGQEDI-NLrm9k" + "GKlxGLoDUZLoQlUVL0W2oltsLPYtgKLpAoEf6QfQx51j86NZiRClNERVKsQ-CtceQl_ic7zvK7HT" + "MQQM_yWtaYjGo9t2IDPVgrkVnoSzuz_N-9ylCgjCm23-sllb6XhgvpXj44TDpiZFOhJDhYQksuqT" + "jA1s08eXrPDwvc1Bcq5N8lJIc3eva07vecuZB53ywY0oZRWZ58aV035jjjPd-Kxp5JGi3H03Ervn" + "HJCVxv64d-ngx7WvnqwsEvGVG3nauadeGzYWuGkgsxddeSFDAQABZ2F0dFN0bXSg" + ), +} + + +def get_webauthn_signup_data(username=None, email=None): + """Get standard WebAuthn signup data.""" + return { + "clientData": WEBAUTHN_TEST_DATA["CLIENT_DATA"], + "attObj": WEBAUTHN_TEST_DATA["ATT_OBJ"], + "username": username or WEBAUTHN_TEST_DATA["USERNAME"], + "email": email or "john.doe@example.com", + } + + +def get_webauthn_settings(): + """Get standard WebAuthn settings for tests.""" + return { + "RP_NAME": WEBAUTHN_TEST_DATA["RP_NAME"], + "RP_ID": WEBAUTHN_TEST_DATA["RP_ID"], + "ORIGIN": WEBAUTHN_TEST_DATA["ORIGIN"], + } + + def create_credential_options( challenge="f00", username="john", From 1d4bdd08b967720b1097f1ab2f665b108d7fdf14 Mon Sep 17 00:00:00 2001 From: Tom Wojcik Date: Mon, 28 Jul 2025 15:39:23 +0200 Subject: [PATCH 4/4] fix TestSignupView, handle conn error in social --- djoser/social/serializers.py | 4 ++ djoser/social/views.py | 9 +++- .../tests/test_webauthn/test_signup.py | 50 ++++++++++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/djoser/social/serializers.py b/djoser/social/serializers.py index d0f3513b..7ba9319d 100644 --- a/djoser/social/serializers.py +++ b/djoser/social/serializers.py @@ -30,6 +30,10 @@ def validate(self, attrs): user = backend.auth_complete() except exceptions.AuthException as e: raise serializers.ValidationError(str(e)) + except (ConnectionError, OSError) as e: + raise serializers.ValidationError( + f"Network error during authentication: {str(e)}" + ) return {"user": user} def _validate_state(self, value): diff --git a/djoser/social/views.py b/djoser/social/views.py index 0f062f1e..6704fd4b 100644 --- a/djoser/social/views.py +++ b/djoser/social/views.py @@ -1,6 +1,7 @@ from rest_framework import generics, permissions, status from rest_framework.response import Response from social_django.utils import load_backend, load_strategy +from social_core.exceptions import MissingBackend from djoser.conf import settings @@ -20,7 +21,13 @@ def get(self, request, *args, **kwargs): strategy.session_set("redirect_uri", redirect_uri) backend_name = self.kwargs["provider"] - backend = load_backend(strategy, backend_name, redirect_uri=redirect_uri) + try: + backend = load_backend(strategy, backend_name, redirect_uri=redirect_uri) + except MissingBackend: + return Response( + {"detail": f"Provider '{backend_name}' not supported"}, + status=status.HTTP_404_NOT_FOUND, + ) authorization_url = backend.auth_url() return Response(data={"authorization_url": authorization_url}) diff --git a/testproject/testapp/tests/test_webauthn/test_signup.py b/testproject/testapp/tests/test_webauthn/test_signup.py index 9a8b3e01..be16c03a 100644 --- a/testproject/testapp/tests/test_webauthn/test_signup.py +++ b/testproject/testapp/tests/test_webauthn/test_signup.py @@ -2,6 +2,7 @@ from django.contrib.auth import get_user_model from rest_framework import status from rest_framework.reverse import reverse +from unittest import mock from .utils import get_webauthn_signup_data, get_webauthn_settings, WEBAUTHN_TEST_DATA @@ -38,15 +39,39 @@ def test_post_with_invalid_registration_response_should_return_400( assert not User.objects.exists() def test_post_with_valid_registration_response_should_create_user(self, api_client): - data = get_webauthn_signup_data() - response = api_client.post(self.url, data=data) + # Mock the WebAuthn verification to return a valid credential + mock_credential = mock.Mock() + mock_credential.credential_id.decode.return_value = "test_credential_id" + mock_credential.public_key.decode.return_value = "test_public_key" + mock_credential.sign_count = 0 + + with mock.patch( + "djoser.webauthn.views.WebAuthnRegistrationResponse" + ) as mock_response_class: + mock_response = mock_response_class.return_value + mock_response.verify.return_value = mock_credential + + data = get_webauthn_signup_data() + response = api_client.post(self.url, data=data) assert response.status_code == status.HTTP_201_CREATED assert User.objects.filter(username=WEBAUTHN_TEST_DATA["USERNAME"]).exists() def test_challenge_should_not_be_stored_after_successfull_signup(self, api_client): - data = get_webauthn_signup_data() - api_client.post(self.url, data=data) + # Mock the WebAuthn verification to return a valid credential + mock_credential = mock.Mock() + mock_credential.credential_id.decode.return_value = "test_credential_id" + mock_credential.public_key.decode.return_value = "test_public_key" + mock_credential.sign_count = 0 + + with mock.patch( + "djoser.webauthn.views.WebAuthnRegistrationResponse" + ) as mock_response_class: + mock_response = mock_response_class.return_value + mock_response.verify.return_value = mock_credential + + data = get_webauthn_signup_data() + api_client.post(self.url, data=data) self.co.refresh_from_db() assert self.co.challenge == "" @@ -55,8 +80,21 @@ def test_register_user_when_email_confirmation_is_required( self, api_client, djoser_settings, mailoutbox ): djoser_settings.update(SEND_ACTIVATION_EMAIL=True) - data = get_webauthn_signup_data() - api_client.post(self.url, data=data) + + # Mock the WebAuthn verification to return a valid credential + mock_credential = mock.Mock() + mock_credential.credential_id.decode.return_value = "test_credential_id" + mock_credential.public_key.decode.return_value = "test_public_key" + mock_credential.sign_count = 0 + + with mock.patch( + "djoser.webauthn.views.WebAuthnRegistrationResponse" + ) as mock_response_class: + mock_response = mock_response_class.return_value + mock_response.verify.return_value = mock_credential + + data = get_webauthn_signup_data() + api_client.post(self.url, data=data) assert User.objects.filter(username=WEBAUTHN_TEST_DATA["USERNAME"]).exists() user = User.objects.get(username=WEBAUTHN_TEST_DATA["USERNAME"])