Files
Salon/backend/apps/accounts/tests/test_phone_auth_request_contract.py
T
2026-03-13 23:26:09 +03:00

170 lines
5.3 KiB
Python

from unittest.mock import patch
import pytest
from django.test import override_settings
from django.urls import reverse
from apps.accounts.models import OtpPurpose, PhoneOTP, User
@pytest.mark.django_db
@override_settings(OTP_PROVIDER="console")
def test_phone_auth_request_creates_customer_for_new_phone(client):
with patch("apps.accounts.services.otp.generate_code", return_value="123456"):
response = client.post(
reverse("phone_auth_request"),
{
"phone_number": "0512345678",
"channel": "sms",
"first_name": "Sara",
"last_name": "Ali",
"email": "sara@example.com",
},
content_type="application/json",
)
assert response.status_code == 201
data = response.json()
assert "request_id" in data
assert "expires_at" in data
user = User.objects.get(phone_number="+966512345678")
assert user.role == "customer"
assert user.is_phone_verified is False
otp = PhoneOTP.objects.get(id=data["request_id"])
assert otp.phone_number == "+966512345678"
assert otp.channel == "sms"
assert otp.purpose == OtpPurpose.AUTH
@pytest.mark.django_db
@override_settings(OTP_PROVIDER="console")
def test_phone_auth_request_existing_phone_no_duplicate_user(client):
User.objects.create_user(
phone_number="+966512345678",
email="existing@example.com",
first_name="Existing",
)
before_count = User.objects.filter(phone_number="+966512345678").count()
with patch("apps.accounts.services.otp.generate_code", return_value="123456"):
response = client.post(
reverse("phone_auth_request"),
{"phone_number": "0512345678", "channel": "sms"},
content_type="application/json",
)
assert response.status_code == 201
assert User.objects.filter(phone_number="+966512345678").count() == before_count
assert PhoneOTP.objects.filter(phone_number="+966512345678").count() == 1
@pytest.mark.django_db
@override_settings(OTP_PROVIDER="console")
def test_phone_auth_request_rejects_email_already_used(client):
User.objects.create_user(
phone_number="+966500000001",
email="taken@example.com",
)
before_otp_count = PhoneOTP.objects.count()
response = client.post(
reverse("phone_auth_request"),
{
"phone_number": "0512345678",
"channel": "sms",
"email": "taken@example.com",
},
content_type="application/json",
)
assert response.status_code == 400
assert "detail" in response.json()
assert User.objects.filter(phone_number="+966512345678").count() == 0
assert PhoneOTP.objects.count() == before_otp_count
@pytest.mark.django_db
def test_phone_auth_request_invalid_phone_localized_en(client):
response = client.post(
reverse("phone_auth_request"),
{"phone_number": "123", "channel": "sms"},
content_type="application/json",
HTTP_ACCEPT_LANGUAGE="en",
)
assert response.status_code == 400
assert response.json()["phone_number"][0] == "Phone number must be in E.164 format or a valid Saudi mobile"
@pytest.mark.django_db
def test_phone_auth_request_invalid_phone_localized_ar(client):
response = client.post(
reverse("phone_auth_request"),
{"phone_number": "123", "channel": "sms"},
content_type="application/json",
HTTP_ACCEPT_LANGUAGE="ar-sa",
)
assert response.status_code == 400
assert response.json()["phone_number"][0] == "يجب أن يكون رقم الهاتف بصيغة E.164 أو رقم جوال سعودي صالح"
@pytest.mark.django_db
@override_settings(
OTP_PROVIDER="console",
OTP_MAX_PER_WINDOW=5,
OTP_WINDOW_MINUTES=15,
OTP_RESEND_COOLDOWN_SECONDS=60,
)
def test_phone_auth_request_cooldown_returns_retry_after(client):
with patch("apps.accounts.services.otp.generate_code", return_value="123456"):
first = client.post(
reverse("phone_auth_request"),
{"phone_number": "0512345678", "channel": "sms"},
content_type="application/json",
)
assert first.status_code == 201
second = client.post(
reverse("phone_auth_request"),
{"phone_number": "0512345678", "channel": "sms"},
content_type="application/json",
)
assert second.status_code == 429
data = second.json()
assert "detail" in data
assert data["retry_after_seconds"] > 0
@pytest.mark.django_db
@override_settings(
OTP_PROVIDER="console",
OTP_MAX_PER_WINDOW=1,
OTP_WINDOW_MINUTES=15,
OTP_RESEND_COOLDOWN_SECONDS=0,
)
def test_phone_auth_request_rate_limit_returns_retry_after(client):
with patch("apps.accounts.services.otp.generate_code", return_value="123456"):
first = client.post(
reverse("phone_auth_request"),
{"phone_number": "0512345678", "channel": "sms"},
content_type="application/json",
)
assert first.status_code == 201
second = client.post(
reverse("phone_auth_request"),
{"phone_number": "0512345678", "channel": "sms"},
content_type="application/json",
)
assert second.status_code == 429
data = second.json()
assert "detail" in data
assert data["retry_after_seconds"] > 0