agateblue / django-dynamic-preferences

Dynamic global and instance settings for your django project
https://django-dynamic-preferences.readthedocs.org/
BSD 3-Clause "New" or "Revised" License
350 stars 87 forks source link

Mocking global preferences when testing #296

Closed uktamjon-komilov closed 1 year ago

uktamjon-komilov commented 1 year ago

dynamic_preferences_registry.py:

from dynamic_preferences.types import BooleanPreference
from dynamic_preferences.preferences import Section
from dynamic_preferences.registries import global_preferences_registry

authentication = Section("authentication")

@global_preferences_registry.register
class RegistrationOTPEnabledMode(BooleanPreference):
    section = authentication
    name = "registration_otp_enabled"
    default = False

global_preferences = global_preferences_registry.manager()

serializers.py:

from django.contrib.auth.models import User
from rest_framework import serializers

from .utils import generate_otp

class UserRegistrationLoginSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(required=False)
    password = serializers.CharField(required=False)

    def validate(self, attrs):
        attrs = super().validate(attrs)
        user = User.objects.filter(email=attrs["email"]).first()
        attrs["user"] = user
        return attrs

    def create(self, validated_data):
        user = validated_data.get("user", None)
        if user is not None:
            return user

        password = validated_data.get("password", None)
        if password is None:
            password = generate_otp(8)

        username = validated_data.get("username", None)
        if username is None:
            username = generate_otp(8)

        user = User.objects.create_user(
            email=validated_data["email"],
            username=username,
            password=password,
        )
        return user

views.py:

from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework_simplejwt.tokens import RefreshToken

from .serializers import UserRegistrationLoginSerializer
from .dynamic_preferences_registry import global_preferences
from .tasks import send_otp_task

class UserRegistrationLoginView(generics.CreateAPIView):
    serializer_class = UserRegistrationLoginSerializer

    def create(self, request, *args, **kwargs):
        otp_enabled = global_preferences["authentication__registration_otp_enabled"]

        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        user = serializer.save()

        if otp_enabled:
            send_otp_task.delay(user.id)
            data = {"message": "An OTP has been sent to your email."}
            return Response(data, status=status.HTTP_200_OK)

        token = RefreshToken.for_user(user)
        data = {
            "message": "Login succeeded",
            "access": str(token.access_token),
            "refresh": str(token),
        }
        return Response(data, status=status.HTTP_201_CREATED)

tests.py:

from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User

class UserRegistrationLoginViewTestCase(APITestCase):
    def setUp(self):
        self.url = reverse("user_registration_login")
        self.valid_payload = {"email": "test@test.com"}
        self.invalid_payload = {"email": "invalidemail"}

    ...

    def test_create_user_with_otp_enabled(self):
        with (...):
            response = self.client.post(self.url, data=self.valid_payload)
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            self.assertTrue("message" in response.data)

    def test_create_user_with_otp_disabled(self):
        with (...):
            response = self.client.post(self.url, data=self.valid_payload)
            self.assertEqual(response.status_code, status.HTTP_201_CREATED)
            self.assertTrue("access" in response.data)

How to mock global preferences when testing OTP enabled and disabled situations in the last tests?

agateblue commented 1 year ago

In tests.py you should be able to simply do something like

from dynamic_preferences_registry import global_preferences

class UserRegistrationLoginViewTestCase(APITestCase):

    def test_create_user_with_otp_enabled(self):
        global_preferences['authentication__registration_otp_enabled'] = True
        response = self.client.post(self.url, data=self.valid_payload)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertTrue("message" in response.data)

    def test_create_user_with_otp_disabled(self):
        global_preferences['authentication__registration_otp_enabled'] = False
        response = self.client.post(self.url, data=self.valid_payload)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertTrue("access" in response.data)

Can you please try that and let me know if it works for you?

agateblue commented 1 year ago

Closing because of inactivity, feel free to reopen if my suggestion doesn't work

jwaschkau commented 3 days ago

@agateblue Maybe it would be helpful to have annotations like django provides. In tests you can just write with self.modify_settings(..): to alter settings for the context or @modify_settings(MYSETTING="xyz") to overwrite for a function.

See here for current documentation: https://docs.djangoproject.com/en/5.1/topics/testing/tools/#overriding-settings

What are your thoughts?