from unittest.mock import patch import pytest from django.urls import reverse from apps.accounts.models import OtpPurpose, PhoneOTP, User @pytest.mark.django_db def test_create_user_rejects_email_only_identity(): # Phone-first invariant: do not allow creating users without phone_number. with pytest.raises(ValueError): User.objects.create_user(email="email-only@example.com") @pytest.mark.django_db def test_register_requires_phone_number(client): # Public registration must keep phone as required identifier. response = client.post( reverse("register"), {"email": "new@example.com", "password": "StrongPass123"}, content_type="application/json", ) assert response.status_code == 400 assert "phone_number" in response.json() @pytest.mark.django_db def test_phone_auth_verify_rejects_verify_purpose_otp(client): user = User.objects.create_user(phone_number="+966512345678") otp = PhoneOTP.objects.create( phone_number=user.phone_number, channel="sms", purpose=OtpPurpose.VERIFY, provider="console", code_hash="not-used", expires_at=PhoneOTP.expiry_at(), ) # Purpose boundary: /phone/verify must only accept auth OTP requests. with patch("apps.accounts.views.verify_otp", return_value=True): response = client.post( reverse("phone_auth_verify"), {"request_id": str(otp.id), "code": "123456"}, content_type="application/json", ) assert response.status_code == 400 @pytest.mark.django_db def test_otp_verify_rejects_auth_purpose_otp(client): otp = PhoneOTP.objects.create( phone_number="+966512345678", channel="sms", purpose=OtpPurpose.AUTH, provider="console", code_hash="not-used", expires_at=PhoneOTP.expiry_at(), ) # Purpose boundary: /otp/verify must only accept verify OTP requests. with patch("apps.accounts.views.verify_otp", return_value=True): response = client.post( reverse("otp_verify"), {"request_id": str(otp.id), "code": "123456"}, content_type="application/json", ) assert response.status_code == 400