Files
crmApi/apps/jwt_auth/test_customer_login.py
T
2026-10-09 10:07:31 +03:30

84 lines
5.0 KiB
Python

"""Customer OTP journey, repeat submits, and recoverable failure contracts."""
from datetime import timedelta
from unittest.mock import patch
from django.utils import timezone
from apps.cuser.models import SmsOpt
from apps.jwt_auth.test_regressions import RegressionFixtures
class CustomerLoginTests(RegressionFixtures):
def test_complete_customer_journey_login_refresh_and_logout(self):
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms', return_value=True) as send:
sent = self.post('login/mobile/', {'mobile': '۰۹۱۲۳۴۵۶۷۸۹'})
self.assertEqual(sent.status_code, 200)
self.assertEqual(sent.data['result']['mobile'], self.user.mobile)
self.assertEqual(sent.data['result']['retry_after'], 60)
send.assert_called_once()
code = SmsOpt.objects.get(mobile=self.user.mobile).opt
options = self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': code})
self.assertEqual(options.status_code, 200)
self.assertEqual(len(options.data), 1)
self.assertEqual(options.data[0]['cuser_role']['role_type']['role_name_en'], 'workspace_customer')
tokens = self.post('token/', {'uuid_opt': options.data[0]['uuid']})
self.assertEqual(tokens.status_code, 200)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + tokens.data['access'])
self.assertEqual(self.client.get('/cuser/info/').status_code, 200)
rotated = self.post('token/refresh/', {'refresh': tokens.data['refresh']})
self.assertEqual(rotated.status_code, 200)
self.assertNotEqual(rotated.data['refresh'], tokens.data['refresh'])
self.assertEqual(self.post('logout/', {'refresh': tokens.data['refresh']}).status_code, 204)
self.client.credentials(HTTP_AUTHORIZATION='Bearer ' + rotated.data['access'])
self.assertEqual(self.client.get('/cuser/info/').status_code, 401)
self.assertEqual(self.post('token/refresh/', {'refresh': rotated.data['refresh']}).status_code, 401)
def test_repeat_send_does_not_invalidate_code_or_send_another_sms(self):
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms', return_value=True) as send:
self.assertEqual(self.post('login/mobile/', {'mobile': self.user.mobile}).status_code, 200)
original = SmsOpt.objects.get(mobile=self.user.mobile)
for _ in range(3):
repeated = self.post('login/mobile/', {'mobile': self.user.mobile})
self.assertEqual(repeated.status_code, 429)
self.assertIn('قبلاً ارسال', repeated.data['detail'])
self.assertGreater(int(repeated['Retry-After']), 0)
self.assertEqual(SmsOpt.objects.get().pk, original.pk)
self.assertEqual(SmsOpt.objects.get().opt, original.opt)
send.assert_called_once()
verified = self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': original.opt})
self.assertEqual(verified.status_code, 200)
def test_resend_after_interval_replaces_code_once(self):
original = self.otp()
SmsOpt.objects.filter(pk=original.pk).update(created_at=timezone.now() - timedelta(seconds=61))
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms', return_value=True) as send:
response = self.post('login/mobile/', {'mobile': self.user.mobile})
self.assertEqual(response.status_code, 200)
self.assertFalse(SmsOpt.objects.filter(pk=original.pk).exists())
self.assertEqual(SmsOpt.objects.filter(mobile=self.user.mobile).count(), 1)
send.assert_called_once()
def test_expired_code_does_not_prevent_requesting_a_new_one(self):
self.otp(expired=True)
with patch('apps.jwt_auth.services.DefaultSmsProvider.send_sms', return_value=True):
self.assertEqual(self.post('login/mobile/', {'mobile': self.user.mobile}).status_code, 200)
def test_panel_disabled_between_send_and_verify_has_clear_error(self):
code = self.otp()
self.role.customer_panel = False
self.role.save(update_fields=['customer_panel'])
response = self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': code.opt})
self.assertEqual(response.status_code, 403)
self.assertIn('پنل مشتریان', str(response.data))
def test_double_verify_and_double_token_exchange_cannot_create_multiple_sessions(self):
from apps.jwt_auth.models import JwtToken
code = self.otp()
first = self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': code.opt})
self.assertEqual(first.status_code, 200)
self.assertEqual(self.post('login/mobile/verify/', {'mobile': self.user.mobile, 'opt': code.opt}).status_code, 400)
option = first.data[0]['uuid']
self.assertEqual(self.post('token/', {'uuid_opt': option}).status_code, 200)
self.assertEqual(self.post('token/', {'uuid_opt': option}).status_code, 401)
self.assertEqual(JwtToken.objects.count(), 1)