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!")