diff --git a/apps/users/api/serializers.py b/apps/users/api/serializers.py index 9a97de8..0c678b0 100644 --- a/apps/users/api/serializers.py +++ b/apps/users/api/serializers.py @@ -6,6 +6,11 @@ from core.serializers.base import BaseModelSerializer User = get_user_model() +INVALID_MOBILE_FORMAT_MESSAGE = "فرمت شماره موبایل نادرست است." +INVALID_MOBILE_NUMBER_MESSAGE = "شماره موبایل معتبر نیست." +PASSWORD_MISMATCH_MESSAGE = "رمز عبور مطابقت ندارد." +NEW_PASSWORD_MISMATCH_MESSAGE = "رمز عبور جدید و تکرار آن مطابقت ندارند." + class UserProfilePictureSerializer(BaseModelSerializer): class Meta: @@ -39,10 +44,10 @@ class RegisterSerializer(serializers.Serializer): re_password = data.get("re_password", "") if not (mobile.isdigit() and len(mobile) == 11): - raise serializers.ValidationError({"mobile": "فرمت شماره موبایل نادرست است."}) + raise serializers.ValidationError({"mobile": INVALID_MOBILE_FORMAT_MESSAGE}) if password != re_password: - raise serializers.ValidationError({"password": "رمز عبور مطابقت ندارد."}) + raise serializers.ValidationError({"password": PASSWORD_MISMATCH_MESSAGE}) return data @@ -54,7 +59,7 @@ class SendOTPSerializer(serializers.Serializer): def validate_mobile(self, value): if not value.isdigit() or len(value) != 11 or not value.startswith("09"): - raise serializers.ValidationError("شماره موبایل معتبر نیست.") + raise serializers.ValidationError(INVALID_MOBILE_NUMBER_MESSAGE) return value @@ -65,7 +70,7 @@ class LoginOtpSerializer(serializers.Serializer): def validate_mobile(self, value): if not (value.isdigit() and len(value) == 11): - raise serializers.ValidationError("فرمت شماره موبایل نادرست است.") + raise serializers.ValidationError(INVALID_MOBILE_FORMAT_MESSAGE) return value @@ -75,7 +80,7 @@ class LoginSerializer(serializers.Serializer): def validate_mobile(self, value): if not (value.isdigit() and len(value) == 11): - raise serializers.ValidationError("فرمت شماره موبایل نادرست است.") + raise serializers.ValidationError(INVALID_MOBILE_FORMAT_MESSAGE) return value @@ -90,7 +95,7 @@ class GoogleOAuthCompleteSerializer(serializers.Serializer): def validate_mobile(self, value): normalized = "".join(ch for ch in value if ch.isdigit()) if len(normalized) != 11 or not normalized.startswith("09"): - raise serializers.ValidationError("فرمت شماره موبایل نادرست است.") + raise serializers.ValidationError(INVALID_MOBILE_FORMAT_MESSAGE) return normalized @@ -105,9 +110,14 @@ class ResetPasswordSerializer(serializers.Serializer): password = serializers.CharField(write_only=True) re_password = serializers.CharField(write_only=True) + def validate_mobile(self, value): + if not value.isdigit() or len(value) != 11 or not value.startswith("09"): + raise serializers.ValidationError(INVALID_MOBILE_NUMBER_MESSAGE) + return value + def validate(self, data): if data.get("password") != data.get("re_password"): - raise serializers.ValidationError({"password": "رمز عبور مطابقت ندارد."}) + raise serializers.ValidationError({"password": PASSWORD_MISMATCH_MESSAGE}) return data @@ -118,7 +128,8 @@ class ChangePasswordSerializer(serializers.Serializer): def validate(self, data): if data.get("new_password") != data.get("re_password"): - raise serializers.ValidationError({"new_password": "رمز عبور جدید Ùˆ تکرار آن مطابقت ندارند."}) + raise serializers.ValidationError({"new_password": NEW_PASSWORD_MISMATCH_MESSAGE}) + return data diff --git a/apps/users/tests/test_api_views.py b/apps/users/tests/test_api_views.py index caa558a..a34df04 100644 --- a/apps/users/tests/test_api_views.py +++ b/apps/users/tests/test_api_views.py @@ -65,6 +65,20 @@ class UserApiViewTests(APITestCase): mode="login", ) + @patch("apps.users.api.views.generate_and_send_otp") + def test_send_otp_view_supports_forget_password_mode(self, generate_and_send_otp): + response = self.client.post( + "/api/users/otp/send/", + {"mobile": "09123330001", "mode": "forget_password"}, + format="json", + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + generate_and_send_otp.assert_called_once_with( + mobile="09123330001", + mode="forget_password", + ) + @patch("apps.users.api.views.login_with_password") def test_login_view_returns_tokens(self, login_with_password): login_with_password.return_value = {"access": "a", "refresh": "r"} @@ -112,6 +126,21 @@ class UserApiViewTests(APITestCase): password="new-secret123", ) + def test_reset_password_view_rejects_invalid_mobile_format(self): + response = self.client.post( + "/api/users/password/reset/", + { + "mobile": "9123330001", + "code": "123456", + "password": "new-secret123", + "re_password": "new-secret123", + }, + format="json", + ) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertIn("error", response.data) + @patch("apps.users.api.views.change_password") def test_change_password_view_requires_auth_and_calls_service(self, change_password): self.client.force_authenticate(user=self.user)