Search code examples
pythondjangodjango-rest-frameworkdjango-testing

I am new to django and testing and I'm trying to test a registration and login view


The view has IsAdminUser restriction. I am also using token authentication.

This is the view:

class UserRegister(generics.CreateAPIView):
    permission_classes = [IsAdminUser]
    queryset = User.objects.all()
    serializer_class = RegisterSerializer

This are the Model and ModelManager:

class UserManager(BaseUserManager):
    def create_superuser(
        self,
        name: str,
        email: str,
        number: str,
        password: Optional[str] = None,
    ) -> "User":
        return self.create_user(name, email, number, password, True, True)

    def create_staff(
        self,
        name: str,
        email: str,
        number: str,
        password: Optional[str] = None,
    ) -> "User":
        return self.create_user(name, email, number, password, True)

    def create_user(
        self,
        name: str,
        email: str,
        number: str,
        password: Optional[str] = None,
        is_staff: bool = False,
        is_superuser: bool = False,
    ) -> "User":
        user = User(
            name=name,
            email=self.normalize_email(email),
            number=number,
            is_staff=is_staff,
            is_superuser=is_superuser,
        )

        user.set_password(password)
        user.save(using=self._db)

        token = Token.objects.create(user=user)

        return user


class User(AbstractBaseUser, PermissionsMixin):
    name = models.CharField(max_length=50, unique=True)
    email = models.EmailField(max_length=250, unique=True)
    date = models.DateTimeField("Date", auto_now=True)
    number = models.CharField(max_length=100, default="")

    is_superuser = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    objects = UserManager()

    USERNAME_FIELD = "name"
    REQUIRED_FIELDS = ["email", "number", "password"]

    def set_password(self, password: Optional[str] = None) -> Any:
        if password:
            self.password = password
        else:
            self.password = ""
        return self.password

    def __str__(self) -> str:
        return f"{self.name} ({self.email}) {self.number} {self.password}"

This is the serializer I am using:

class RegisterSerializer(serializers.ModelSerializer):
    repeat_password = serializers.CharField(
        write_only=True, style={"input_type": "password"}
    )

    class Meta:
        model = User
        fields = ("name", "email", "number", "password", "repeat_password", "is_staff")
        extra_kwargs = {"password": {"write_only": True}}

    def validate(self, attrs: dict) -> dict:
        password = attrs.get("password")
        repeat_password = attrs.get("repeat_password")

        if password != repeat_password:
            raise serializers.ValidationError("Passwords do not match.")

        return attrs

    def create(self, validated_password: Any) -> Any:
        repeat_password = validated_password.pop("repeat_password")
        return User.objects.create_user(**validated_password)

And this is the tests code:

class TestSetUp(APITestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()
        cls.user_manager = UserManager()

    def setUp(self):
        self.register_url = reverse("user_urls:register")

        self.register_user_data = {
            "name": "TestUser",
            "email": "[email protected]",
            "number": "1213",
            "password": "Test@123",
        }

        self.login_url = reverse("user_urls:login")

        self.login_user_data = {
            "name": "TestUser",
            "password": "Test@123",
        }

        self.register_user_data_with_empty_password = {
            "name": "TestUser",
            "email": "[email protected]",
            "number": "1213",
            "password": "",
        }

        return super().setUp()

    def create_staff(self):
        self.user = self.user_manager.create_staff(**self.register_user_data)
        try:
            self.token = Token.objects.get(user=self.user)
        except Token.DoesNotExist:
            self.token = Token.objects.create(user=self.user)

    def create_superuser(self):
        self.user = self.user_manager.create_superuser(**self.register_user_data)
        try:
            self.token = Token.objects.get(user=self.user)
        except Token.DoesNotExist:
            self.token = Token.objects.create(user=self.user)

This snippet is supposed create a user using the custom built model User and UserManager, with only the fields specified in the code(name, email, number, password)


class TestViews(TestSetUp):

    def test_register_with_no_data(self) -> None:
        res = self.client.post(self.register_url)
        self.assertEqual(res.status_code, 401)

    def test_register_with_data(self) -> None:
        User.objects.create_staff(**self.register_user_data)
        res = self.client.post(self.register_url)
        self.assertEqual(res.status_code, 201)

    def test_user_cannot_login_with_invalid_credentials(self):
        self.client.post(self.register_url,self.register_user_data, format='json')
        res = self.client.post(
            self.login_url, self.login_user_data, format="json")
        self.assertEqual(res.status_code, 401)

    def test_user_can_login_with_valid_credentials(self):

        res = self.client.post(
            self.register_url, self.login_user_data, format='json'
        )
        if res.status_code != 201:
            raise ValueError(f"Registration failed with error: {res.data}")
        name = res.data["name"]
        password = res.data["password"]
        user = User.objects.get(name=name, password=password)
        user.is_verified = True
        user.save()

        response = self.client.post(
            self.login_url, self.login_user_data, format="json")
        self.assertEqual(response.status_code, 201)


Here I need the TestSetUp to be called and for it to create a normal user, this is restricted by the IsAdminUser restriction in the view, meaning only the Authentication Token of a staff or superuser is allowed to create a new normal user.

Every time I run coverage I get a 401 Error with the message
ValueError: Registration failed with error: {'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}

This is the traceback log of the error:

reating test database for alias 'default'...
System check identified no issues (0 silenced).
..F.EF
======================================================================
ERROR: test_user_can_login_with_valid_credentials (user.tests.tests_views.TestViews.test_user_can_login_with_valid_credentials)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Owner\Desktop\myapp\user\tests\tests_views.py", line 23, in test_user_can_login_with_valid_credentials
    raise ValueError(f"Registration failed with error: {res.data}")
ValueError: Registration failed with error: {'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}

======================================================================
FAIL: test_register_with_data (user.tests.tests_views.TestViews.test_register_with_data)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Owner\Desktop\myapp\user\tests\tests_views.py", line 13, in test_register_with_data
    self.assertEqual(res.status_code, 201)
AssertionError: 401 != 201

======================================================================
FAIL: test_user_cannot_login_with_invalid_credentials (user.tests.tests_views.TestViews.test_user_cannot_login_with_invalid_credentials)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\Owner\Desktop\myapp\user\tests\tests_views.py", line 18, in test_user_cannot_login_with_invalid_credentials     
    self.assertEqual(res.status_code, 401)
AssertionError: 400 != 401

----------------------------------------------------------------------
Ran 6 tests in 0.045s

FAILED (failures=2, errors=1)
Destroying test database for alias 'default'...

I haven't tried much because I don't understand what I'm supposed to do.

I have looked through the DRF Test docs and I can't find anything that could help me deal with this, can anybody help?

Also this is my first time asking a question in StackOverflow, so if I am unclear about anything please tell me.

I wasn't sure if the urls are in any way necessary to this, so I decided to add the at the bottom:

app_name = "user_urls"

router = routers.SimpleRouter()
router.register(r"user", UserView)

urlpatterns = [
    path(
        "register/",
        UserRegister.as_view(),
        name="register",
    ),
    path("login/", obtain_auth_token, name="login"),
    path(
        "logout/",
        auth_views.LogoutView.as_view(),
        name="logout",
    ),
]

urlpatterns += router.urls

Solution

  • https://www.django-rest-framework.org/api-guide/testing/ please have a look testing docs of DRF, you normally create a user and it will create easily, but when you create a user and access to some API's you have to authenticate that use like this as DRF testing tells

    self.client.force_authenticate(user=the user you created will come here)
    

    after that, you can get or post on that API