Skip to content
36 changes: 36 additions & 0 deletions apps/roles/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Test factories for role data."""

from __future__ import annotations

import factory

from apps.roles.models import Role


class RoleFactory(factory.django.DjangoModelFactory):
"""Create realistic role records for tests."""

class Meta:
model = Role
django_get_or_create = ("slug",)

class Params:
student = factory.Trait(
slug="student",
display_name="Student",
description="Student role used by automated tests.",
)
tutor = factory.Trait(
slug="tutor",
display_name="Tutor",
description="Tutor role used by automated tests.",
)
admin = factory.Trait(
slug="admin",
display_name="Admin",
description="Admin role used by automated tests.",
)

slug = factory.Sequence(lambda number: f"role-{number}")
display_name = factory.Sequence(lambda number: f"Role {number}")
description = "Role used by automated tests."
1 change: 1 addition & 0 deletions apps/roles/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Role test package."""
44 changes: 44 additions & 0 deletions apps/roles/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Model tests for StudyBuddy roles."""

from __future__ import annotations

import pytest
from django.db import IntegrityError, transaction

from apps.roles.factories import RoleFactory
from apps.roles.models import Role


@pytest.mark.django_db
def test_role_string_uses_display_name():
"""The role string representation uses the product-facing display name."""
role = RoleFactory(display_name="Learner")

assert str(role) == "Learner"


@pytest.mark.django_db
def test_role_slug_must_be_unique():
"""Duplicate role slugs are rejected by the database."""
RoleFactory(slug="learner", display_name="Learner")

with pytest.raises(IntegrityError):
with transaction.atomic():
Role.objects.create(slug="learner", display_name="Learner Duplicate")


@pytest.mark.django_db
@pytest.mark.parametrize(
("trait", "slug", "display_name"),
[
("student", "student", "Student"),
("tutor", "tutor", "Tutor"),
("admin", "admin", "Admin"),
],
)
def test_role_factory_common_role_traits(trait, slug, display_name):
"""Common role traits provide stable slugs for access-control tests."""
role = RoleFactory(**{trait: True})

assert role.slug == slug
assert role.display_name == display_name
38 changes: 38 additions & 0 deletions apps/users/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Test factories for user data."""

from __future__ import annotations

import factory
from django.contrib.auth import get_user_model

from apps.roles.factories import RoleFactory

CustomUser = get_user_model()


class CustomUserFactory(factory.django.DjangoModelFactory):
"""Create realistic custom users for tests."""

class Meta:
model = CustomUser
django_get_or_create = ("email",)
skip_postgeneration_save = True

email = factory.Sequence(lambda number: f"user{number}@example.com")
username = factory.Sequence(lambda number: f"user{number}")
first_name = "Study"
last_name = "Buddy"
password = factory.django.Password("password123")


class UserWithRoleFactory(CustomUserFactory):
"""Create a user and attach a role after persistence."""

@factory.post_generation
def role(self, create: bool, extracted, **kwargs) -> None:
"""Attach an explicit role or create a default test role."""
if not create:
return

role = extracted or RoleFactory(slug="learner", display_name="Learner")
self.studybuddy_roles.add(role)
1 change: 1 addition & 0 deletions apps/users/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""User test package."""
53 changes: 53 additions & 0 deletions apps/users/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Model tests for the custom user model."""

from __future__ import annotations

import pytest
from django.contrib.auth import get_user_model

from apps.roles.factories import RoleFactory
from apps.users.factories import CustomUserFactory, UserWithRoleFactory

CustomUser = get_user_model()


@pytest.mark.django_db
def test_user_uses_email_as_login_identifier():
"""Custom users use email as their natural login identifier."""
user = CustomUser.objects.create_user(
email="learner@EXAMPLE.COM",
password="StrongPassword123!",
)

assert CustomUser.USERNAME_FIELD == "email"
assert user.email == "learner@example.com"
assert user.username == "learner"
assert user.check_password("StrongPassword123!")


@pytest.mark.django_db
def test_user_can_be_assigned_roles():
"""Users can be connected to role records for access control."""
role = RoleFactory(slug="learner", display_name="Learner")
user = CustomUserFactory()

user.studybuddy_roles.add(role)

assert user.studybuddy_roles.filter(slug="learner").exists()


@pytest.mark.django_db
def test_user_with_role_factory_accepts_common_role_traits():
"""User factories can model common role assignments."""
role = RoleFactory(tutor=True)
user = UserWithRoleFactory(role=role)

assert user.studybuddy_roles.filter(slug="tutor").exists()


@pytest.mark.django_db
def test_user_with_role_factory_creates_default_role():
"""User role factories create a learner role when none is provided."""
user = UserWithRoleFactory()

assert user.studybuddy_roles.filter(slug="learner").exists()
Loading
Loading