import pytest from apps.users.services import auth pytestmark = pytest.mark.unit def test_otp_normalizes_persian_digits_and_rejects_wrong_length(): assert auth._normalize_otp_code(" ۱۲۳۴۵ ") == "12345" with pytest.raises(auth.AuthServiceError) as error: auth._normalize_otp_code("1234") assert error.value.field == "code" def test_otp_is_single_use(fake_redis, monkeypatch): monkeypatch.setattr(auth, "_redis", lambda: fake_redis) fake_redis.set(auth._otp_key("login", "09190000001"), "12345") auth.verify_otp_code(mobile="09190000001", code="12345", mode="login") assert fake_redis.get(auth._otp_key("login", "09190000001")) is None with pytest.raises(auth.AuthServiceError): auth.verify_otp_code(mobile="09190000001", code="12345", mode="login") def test_otp_issue_stores_code_and_queues_sms(fake_redis, monkeypatch, mocker): monkeypatch.setattr(auth, "_redis", lambda: fake_redis) send_sms = mocker.patch("apps.users.services.auth.send_critical_sms.delay") result = auth._issue_otp("09190000001", "register") stored = fake_redis.get(auth._otp_key("register", "09190000001")) assert stored and len(stored) == 5 send_sms.assert_called_once_with("09190000001", auth.SMS_KIND_REGISTER, stored.decode()) assert result["expires_in_seconds"] == auth.OTP_EXPIRY_SECONDS def test_verified_otp_is_consumed_once(fake_redis, monkeypatch): monkeypatch.setattr(auth, "_redis", lambda: fake_redis) key = auth._verified_otp_key("register", "09190000001") fake_redis.set(key, "1") assert auth._consume_verified_otp("09190000001", "register") assert not auth._consume_verified_otp("09190000001", "register")