Split backend tests into unit and integration suites, add isolated fixtures and coverage enforcement, and update CI and test documentation. Cover current authentication, content, notification, payment, media, and admin flows; fix behaviors revealed by the new tests. Validate both suites and the per-module gate against local PostgreSQL.
90 lines
4.7 KiB
Python
90 lines
4.7 KiB
Python
import pytest
|
|
|
|
from apps.users.models import Major, University, User
|
|
from apps.users.services import auth
|
|
|
|
|
|
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
|
|
|
|
|
|
def test_otp_send_modes_and_login_otp(user_factory, fake_redis, monkeypatch, mocker):
|
|
monkeypatch.setattr(auth, "_redis", lambda: fake_redis)
|
|
sms = mocker.patch("apps.users.services.auth.send_critical_sms.delay")
|
|
user = user_factory(is_mobile_verified=False)
|
|
with pytest.raises(auth.AuthServiceError):
|
|
auth.generate_and_send_otp("bad", "register")
|
|
with pytest.raises(auth.AuthServiceError):
|
|
auth.generate_and_send_otp(user.mobile, "register")
|
|
with pytest.raises(auth.AuthServiceError):
|
|
auth.generate_and_send_otp("09197770061", "login")
|
|
response = auth.generate_and_send_otp(user.mobile, "login")
|
|
assert response["expires_in_seconds"] == auth.OTP_EXPIRY_SECONDS
|
|
code = fake_redis.get(auth._otp_key("login", user.mobile)).decode()
|
|
sms.assert_called_with(user.mobile, auth.SMS_KIND_LOGIN, code)
|
|
tokens = auth.login_with_otp(user.mobile, code)
|
|
assert tokens["access_token"] and tokens["refresh_token"]
|
|
user.refresh_from_db()
|
|
assert user.is_mobile_verified
|
|
with pytest.raises(auth.AuthServiceError):
|
|
auth.login_with_otp(user.mobile, code)
|
|
|
|
|
|
def test_mobile_claim_conflict_and_verification(user_factory, fake_redis, monkeypatch, mocker):
|
|
monkeypatch.setattr(auth, "_redis", lambda: fake_redis)
|
|
sms = mocker.patch("apps.users.services.auth.send_critical_sms.delay")
|
|
user = user_factory()
|
|
another = user_factory()
|
|
with pytest.raises(auth.AuthServiceError):
|
|
auth.send_authenticated_mobile_otp(user, another.mobile)
|
|
new_mobile = "09197770062"
|
|
auth.send_authenticated_mobile_otp(user, new_mobile)
|
|
code = fake_redis.get(auth._otp_key("verify_mobile", new_mobile)).decode()
|
|
sms.assert_called_with(new_mobile, auth.SMS_KIND_VERIFY_MOBILE, code)
|
|
assert auth.verify_authenticated_mobile_otp(user, new_mobile, code).mobile == new_mobile
|
|
user.refresh_from_db()
|
|
assert user.mobile == new_mobile and user.is_mobile_verified
|
|
assert auth.lookup_mobile_registration_state(new_mobile) == {"exists": True, "has_password": True}
|
|
assert auth.lookup_mobile_registration_state("09197770063") == {"exists": False, "has_password": False}
|
|
|
|
|
|
def test_registration_validates_profile_fields_and_duplicate_email(user_factory, fake_redis, monkeypatch):
|
|
monkeypatch.setattr(auth, "_redis", lambda: fake_redis)
|
|
existing = user_factory(email="used@example.com")
|
|
payload = auth.RegistrationPayload(username="new_profile", mobile="09197770064", password="StrongPass123!", code="12345")
|
|
fake_redis.set(auth._verified_otp_key("register", payload.mobile), "1")
|
|
for changes, field in [({"student_id": "123"}, "student_id"),
|
|
({"email": existing.email}, "email"),
|
|
({"major": "invalid"}, "major"),
|
|
({"university": "invalid"}, "university")]:
|
|
fake_redis.set(auth._verified_otp_key("register", payload.mobile), "1")
|
|
current = auth.RegistrationPayload(**{**payload.__dict__, **changes})
|
|
with pytest.raises(auth.AuthServiceError) as error:
|
|
auth.register_user(current)
|
|
assert error.value.field == field
|
|
major = Major.objects.create(code="TEST_AUTH_MAJOR", name="Auth Major")
|
|
university = University.objects.create(code="TEST_AUTH_UNI", name="Auth University")
|
|
current = auth.RegistrationPayload(**{**payload.__dict__, "major": major.code, "university": university.code,
|
|
"student_id": "1234567890"})
|
|
fake_redis.set(auth._verified_otp_key("register", payload.mobile), "1")
|
|
created = auth.register_user(current)
|
|
assert created.major == major and created.university == university
|
|
assert created.student_id == "1234567890"
|
|
assert User.objects.filter(username=payload.username).count() == 1
|
|
|
|
|
|
def test_password_reset_rejects_reuse_and_inactive_login(user_factory, fake_redis, monkeypatch):
|
|
monkeypatch.setattr(auth, "_redis", lambda: fake_redis)
|
|
user = user_factory(is_active=False)
|
|
with pytest.raises(auth.AuthServiceError) as error:
|
|
auth.login_with_password(user.mobile, "TestPass123!")
|
|
assert error.value.status_code == 401
|
|
user.is_active = True
|
|
user.save(update_fields=["is_active"])
|
|
with pytest.raises(auth.AuthServiceError):
|
|
auth.reset_password_with_otp("09197770065", "12345", "NewStrongPass123!")
|
|
fake_redis.set(auth._otp_key("reset_password", user.mobile), "12345")
|
|
with pytest.raises(auth.AuthServiceError) as error:
|
|
auth.reset_password_with_otp(user.mobile, "12345", "TestPass123!")
|
|
assert error.value.field == "password"
|
|
assert user.check_password("TestPass123!")
|