FIX(account): fix duplicated invite code
fix duplicated invite code
This commit is contained in:
parent
f6099af0e4
commit
0973d08637
4 changed files with 66 additions and 12 deletions
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.1.4 on 2026-07-25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0009_inviterequest'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='inviterequest',
|
||||||
|
name='mobile_number',
|
||||||
|
field=models.CharField(db_index=True, max_length=30, unique=True, verbose_name='mobile number'),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -278,7 +278,7 @@ class InviteRequest(BaseModel):
|
||||||
related_name='invite_requests',
|
related_name='invite_requests',
|
||||||
verbose_name=_('inviter'),
|
verbose_name=_('inviter'),
|
||||||
)
|
)
|
||||||
mobile_number = models.CharField(_('mobile number'), max_length=30, db_index=True)
|
mobile_number = models.CharField(_('mobile number'), unique=True, max_length=30, db_index=True)
|
||||||
application = models.ForeignKey(
|
application = models.ForeignKey(
|
||||||
oauth2_settings.APPLICATION_MODEL,
|
oauth2_settings.APPLICATION_MODEL,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
||||||
from utils.exceptions import UnprocessableEntity
|
from utils.exceptions import UnprocessableEntity, Conflict
|
||||||
|
|
||||||
phone_number_validator = RegexValidator(regex=r'^\+98[1-9]\d{9}$', message=_(
|
phone_number_validator = RegexValidator(regex=r'^\+98[1-9]\d{9}$', message=_(
|
||||||
"Phone number must be entered in the format: '+989999999999'."
|
"Phone number must be entered in the format: '+989999999999'."
|
||||||
|
|
@ -150,6 +150,19 @@ class InviteRequestSerializer(serializers.ModelSerializer):
|
||||||
def validate_mobile_number(self, value):
|
def validate_mobile_number(self, value):
|
||||||
value = unidecode(value)
|
value = unidecode(value)
|
||||||
phone_number_validator(value)
|
phone_number_validator(value)
|
||||||
|
|
||||||
|
if User.objects.filter(phone_number=value).exists():
|
||||||
|
raise Conflict(
|
||||||
|
detail=_('This mobile number is already registered as a user.'),
|
||||||
|
code='user_already_exists',
|
||||||
|
)
|
||||||
|
|
||||||
|
if InviteRequest.objects.filter(mobile_number=value).exists():
|
||||||
|
raise Conflict(
|
||||||
|
detail=_('An invite request for this mobile number already exists.'),
|
||||||
|
code='invite_request_already_exists',
|
||||||
|
)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
|
|
||||||
|
|
@ -153,9 +153,11 @@ class InviteRequestTests(APITestCase):
|
||||||
user_uuid = uuid.UUID('c14e8b86-8f4a-44d9-b29d-badceb47005f')
|
user_uuid = uuid.UUID('c14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||||
inviter_uuid = uuid.UUID('d14e8b86-8f4a-44d9-b29d-badceb47005f')
|
inviter_uuid = uuid.UUID('d14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||||
application_uuid = uuid.UUID('e14e8b86-8f4a-44d9-b29d-badceb47005f')
|
application_uuid = uuid.UUID('e14e8b86-8f4a-44d9-b29d-badceb47005f')
|
||||||
|
access_token = 'inviteAccessToken123456789012345678901'
|
||||||
client_id = 'inviteClientId1234567890123456789012'
|
client_id = 'inviteClientId1234567890123456789012'
|
||||||
client_secret = 'inviteClientSecret123456789012345678'
|
client_secret = 'inviteClientSecret123456789012345678'
|
||||||
mobile_number = '+989120000000'
|
mobile_number = '+989120000000'
|
||||||
|
expire_datetime = timezone.now() + timedelta(seconds=3600)
|
||||||
|
|
||||||
client = APIClient()
|
client = APIClient()
|
||||||
|
|
||||||
|
|
@ -177,9 +179,13 @@ class InviteRequestTests(APITestCase):
|
||||||
allowed_scope='accounts.invite:submit',
|
allowed_scope='accounts.invite:submit',
|
||||||
)
|
)
|
||||||
|
|
||||||
def basic_auth_header(self):
|
AccessToken.objects.create(
|
||||||
credentials = f'{self.client_id}:{self.client_secret}'.encode('utf-8')
|
token=self.access_token,
|
||||||
return 'Basic ' + base64.b64encode(credentials).decode('utf-8')
|
application_id=self.application_uuid,
|
||||||
|
scope='accounts.invite:submit',
|
||||||
|
expires=self.expire_datetime,
|
||||||
|
)
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
|
||||||
|
|
||||||
def test_create_invite_request_success(self):
|
def test_create_invite_request_success(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
|
@ -189,7 +195,6 @@ class InviteRequestTests(APITestCase):
|
||||||
'mobile_number': self.mobile_number,
|
'mobile_number': self.mobile_number,
|
||||||
},
|
},
|
||||||
format='json',
|
format='json',
|
||||||
HTTP_AUTHORIZATION=self.basic_auth_header(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 201)
|
self.assertEqual(response.status_code, 201)
|
||||||
|
|
@ -208,13 +213,32 @@ class InviteRequestTests(APITestCase):
|
||||||
'mobile_number': self.mobile_number,
|
'mobile_number': self.mobile_number,
|
||||||
},
|
},
|
||||||
format='json',
|
format='json',
|
||||||
HTTP_AUTHORIZATION=self.basic_auth_header(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
self.assertEqual(InviteRequest.objects.count(), 0)
|
self.assertEqual(InviteRequest.objects.count(), 0)
|
||||||
|
|
||||||
def test_create_invite_request_invalid_client_credentials(self):
|
def test_create_invite_request_existing_user(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse('users:invite_request_api'),
|
||||||
|
data={
|
||||||
|
'invite_code': str(self.inviter_uuid),
|
||||||
|
'mobile_number': self.inviter.phone_number,
|
||||||
|
},
|
||||||
|
format='json',
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 409)
|
||||||
|
self.assertEqual(response.json()['details']['error'], 'user_already_exists')
|
||||||
|
self.assertEqual(InviteRequest.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_create_invite_request_duplicate_mobile_number(self):
|
||||||
|
InviteRequest.objects.create(
|
||||||
|
inviter=self.inviter,
|
||||||
|
mobile_number=self.mobile_number,
|
||||||
|
application=self.application,
|
||||||
|
)
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse('users:invite_request_api'),
|
reverse('users:invite_request_api'),
|
||||||
data={
|
data={
|
||||||
|
|
@ -222,11 +246,11 @@ class InviteRequestTests(APITestCase):
|
||||||
'mobile_number': self.mobile_number,
|
'mobile_number': self.mobile_number,
|
||||||
},
|
},
|
||||||
format='json',
|
format='json',
|
||||||
HTTP_AUTHORIZATION='Basic ' + base64.b64encode(b'wrong:credentials').decode('utf-8'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 409)
|
||||||
self.assertEqual(InviteRequest.objects.count(), 0)
|
self.assertEqual(response.json()['details']['error'], 'invite_request_already_exists')
|
||||||
|
self.assertEqual(InviteRequest.objects.count(), 1)
|
||||||
|
|
||||||
def test_create_invite_request_missing_scope(self):
|
def test_create_invite_request_missing_scope(self):
|
||||||
self.application.allowed_scope = ''
|
self.application.allowed_scope = ''
|
||||||
|
|
@ -239,7 +263,6 @@ class InviteRequestTests(APITestCase):
|
||||||
'mobile_number': self.mobile_number,
|
'mobile_number': self.mobile_number,
|
||||||
},
|
},
|
||||||
format='json',
|
format='json',
|
||||||
HTTP_AUTHORIZATION=self.basic_auth_header(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue