django-cfg 1.1.61__py3-none-any.whl → 1.1.62__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/management/commands/rundramatiq.py +174 -202
  3. django_cfg/modules/django_tasks.py +54 -428
  4. django_cfg/modules/dramatiq_setup.py +16 -0
  5. {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/METADATA +1 -1
  6. {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/RECORD +9 -27
  7. django_cfg/apps/accounts/tests/__init__.py +0 -1
  8. django_cfg/apps/accounts/tests/test_models.py +0 -412
  9. django_cfg/apps/accounts/tests/test_otp_views.py +0 -143
  10. django_cfg/apps/accounts/tests/test_serializers.py +0 -331
  11. django_cfg/apps/accounts/tests/test_services.py +0 -401
  12. django_cfg/apps/accounts/tests/test_signals.py +0 -110
  13. django_cfg/apps/accounts/tests/test_views.py +0 -255
  14. django_cfg/apps/newsletter/tests/__init__.py +0 -1
  15. django_cfg/apps/newsletter/tests/run_tests.py +0 -47
  16. django_cfg/apps/newsletter/tests/test_email_integration.py +0 -256
  17. django_cfg/apps/newsletter/tests/test_email_tracking.py +0 -332
  18. django_cfg/apps/newsletter/tests/test_newsletter_manager.py +0 -83
  19. django_cfg/apps/newsletter/tests/test_newsletter_models.py +0 -157
  20. django_cfg/apps/support/tests/__init__.py +0 -0
  21. django_cfg/apps/support/tests/test_models.py +0 -106
  22. django_cfg/apps/tasks/@docs/CONFIGURATION.md +0 -663
  23. django_cfg/apps/tasks/@docs/README.md +0 -195
  24. django_cfg/apps/tasks/@docs/TASKS_QUEUES.md +0 -423
  25. django_cfg/apps/tasks/@docs/TROUBLESHOOTING.md +0 -506
  26. {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/WHEEL +0 -0
  27. {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/entry_points.txt +0 -0
  28. {django_cfg-1.1.61.dist-info → django_cfg-1.1.62.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- django_cfg/__init__.py,sha256=LLAKMB4NP27tQ_Kza-viPCeZf8gKcWhpQZIH8AcQOJo,14288
1
+ django_cfg/__init__.py,sha256=hKbPbaVNg7IaLQ4vE3n3Y7U-mkn0685XzcnlerGqy-s,14288
2
2
  django_cfg/apps.py,sha256=k84brkeXJI7EgKZLEpTkM9YFZofKI4PzhFOn1cl9Msc,1656
3
3
  django_cfg/exceptions.py,sha256=RTQEoU3PfR8lqqNNv5ayd_HY2yJLs3eioqUy8VM6AG4,10378
4
4
  django_cfg/integration.py,sha256=-7hvd-4ohLdzH4eufCZTOe3yTzPoQyB_HCfvsSm9AAw,5218
@@ -42,13 +42,6 @@ django_cfg/apps/accounts/templates/emails/otp_email.html,sha256=HmpPNGBxE6JnwqrQ
42
42
  django_cfg/apps/accounts/templates/emails/otp_email.txt,sha256=5IzRhLyGuspK5H1WYtyPPV1-gF4T9fT-JZIkR8-uP0U,502
43
43
  django_cfg/apps/accounts/templates/emails/welcome_email.html,sha256=X4pcPUgnDHZoWramfLjqgHHvs5RXDffueL9N8KjYLsQ,4067
44
44
  django_cfg/apps/accounts/templates/emails/welcome_email.txt,sha256=jVIr4O9_jOOSU8pEZI_1UOCeJRsDOh9iAfi0qN7wR-I,748
45
- django_cfg/apps/accounts/tests/__init__.py,sha256=U4MgTYmvZrIqeRj-RnIWQPferwuz107-38kHhNgwDdk,25
46
- django_cfg/apps/accounts/tests/test_models.py,sha256=9rpYMWExZ3budfnkDIZRW2rkKsR_VYlJdFYooesuXig,14596
47
- django_cfg/apps/accounts/tests/test_otp_views.py,sha256=diRf7phOtijBygg5sF0MIe9Ynf29haW6rUU7qqTge2Y,5972
48
- django_cfg/apps/accounts/tests/test_serializers.py,sha256=kKhqzEDNkup9pfFEyl_QrM-cbJBrwj19nbTGpJiux5o,13259
49
- django_cfg/apps/accounts/tests/test_services.py,sha256=T3tKXUAEVZaIjq3tK9Z-SRBoyiirckNzHAmEiTahKUc,15329
50
- django_cfg/apps/accounts/tests/test_signals.py,sha256=JmpeGdRt64irGrUBocOWKlM5-ZQSeaRx1HXPHq15d_o,4398
51
- django_cfg/apps/accounts/tests/test_views.py,sha256=J18245zx2F-L2Q7tvFjces8S_ypjUTKcH8Qj8eDaYhM,10113
52
45
  django_cfg/apps/accounts/utils/notifications.py,sha256=G_EOQe6JFqYvPVooFXJUcF5YvLx8Lnd_GMLfFwKTLiI,24914
53
46
  django_cfg/apps/accounts/views/__init__.py,sha256=mQRa06_tfIkemXL2MMa05Yj2w1G3pbu2VjC7h4ZE1TI,239
54
47
  django_cfg/apps/accounts/views/otp.py,sha256=wPRu4wMwf5qqtTPJLsLK-QGb34ZD67ZRFJpiKjj0TY4,6618
@@ -90,12 +83,6 @@ django_cfg/apps/newsletter/managers/__init__.py,sha256=rJc4KSQbe-Tvp2CX3j8-5n-FB
90
83
  django_cfg/apps/newsletter/migrations/0001_initial.py,sha256=hXv5zMz3bu6RUyS8ToFGjoDc7dIpM9q1KthzwzA8iRY,6423
91
84
  django_cfg/apps/newsletter/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
85
  django_cfg/apps/newsletter/services/email_service.py,sha256=GbGae6bnuBrX0KFW_2gTCdnVrf254hyNvrUvkHdrQvY,15241
93
- django_cfg/apps/newsletter/tests/__init__.py,sha256=sEKmEHtTUiORyZGhxPTy-h2NfuKib58vwn7IWn7yVzA,16
94
- django_cfg/apps/newsletter/tests/run_tests.py,sha256=jh8u9IG0x-VKda8_jZEKF00UODw77GufVlqG68O4yMg,1171
95
- django_cfg/apps/newsletter/tests/test_email_integration.py,sha256=UOx9WJydzbOX7ZjYZCCRyfhO_BVrWNt64oc7LQoclEg,10199
96
- django_cfg/apps/newsletter/tests/test_email_tracking.py,sha256=dvXmHJh5ldEXkt-Rwxe3vX_kseZ9hq3-7m8W4ORr7pI,12518
97
- django_cfg/apps/newsletter/tests/test_newsletter_manager.py,sha256=8CYtpM_g98p-OqDQQPwTz_XzC2Oc-hqAyg3eHTvWlMQ,2893
98
- django_cfg/apps/newsletter/tests/test_newsletter_models.py,sha256=-k8wzk7-c0efGZZXX2SQpC2Xy1A5cyyAAP2ap0gq9t4,5601
99
86
  django_cfg/apps/newsletter/utils/__init__.py,sha256=rrMmQU1Jp3xLDX7EOOLb4nX8I83UzDifRNxDRlIyqfA,27
100
87
  django_cfg/apps/newsletter/views/__init__.py,sha256=zsM1d9dL1RFtPZUGg6n7dQywGwbhT1R9MSwHOQFJEwo,979
101
88
  django_cfg/apps/newsletter/views/campaigns.py,sha256=Z7b0Sjjx80vGcyjbY2ByFRSA0VezofK1_WYkX2MCbZ0,4673
@@ -118,8 +105,6 @@ django_cfg/apps/support/migrations/0002_alter_message_ticket.py,sha256=MxdjTW7hJ
118
105
  django_cfg/apps/support/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
106
  django_cfg/apps/support/templates/support/chat/access_denied.html,sha256=QdVXR-61aUHmh9Vlag_zXGcS_BG7a807LesIbduqnag,1094
120
107
  django_cfg/apps/support/templates/support/chat/ticket_chat.html,sha256=b2UvCQp122-CRl567J_sLCvcp_zZi21VG4DOy7dmjM8,14471
121
- django_cfg/apps/support/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
122
- django_cfg/apps/support/tests/test_models.py,sha256=sX4_ArzKEZRDVrmCbtGgk28RDnmWAm6KihLBbnBIchY,4620
123
108
  django_cfg/apps/support/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
109
  django_cfg/apps/support/utils/support_email_service.py,sha256=0l4dXqAG6oN2UR7W3JzuRhlomaWa41IT7ceVcanluKs,5570
125
110
  django_cfg/apps/support/views/__init__.py,sha256=E-Iqiy888SncfBTiSyXyGCjaNrMa5Klvapzb3ItYPXc,448
@@ -132,10 +117,6 @@ django_cfg/apps/tasks/apps.py,sha256=WXQvQjFVBuiO7s1ikd4Sf15YKRTvruVzMTo82uNXY08
132
117
  django_cfg/apps/tasks/serializers.py,sha256=o323j6bmeNAVCu-FA3Jrve2tLuFOHMgkfXgZUdsTuS8,2751
133
118
  django_cfg/apps/tasks/urls.py,sha256=NwEk6Vjq_ETEzzbx6R-9Qv3EbCxmX31xfzLICrlQ6cw,587
134
119
  django_cfg/apps/tasks/views.py,sha256=HO4fSHyYALOKmLiLsm2Ir-G2DgCsZGHuoncq8FVRp5s,18048
135
- django_cfg/apps/tasks/@docs/CONFIGURATION.md,sha256=erW3GJG5WBoiZn8dzcj6yH9o6zNrO8_kkX7srleXjeY,16357
136
- django_cfg/apps/tasks/@docs/README.md,sha256=A1A4e56ed1Dh6R5XHEgInOkHv25fUQfJGVIs9f_G1ZM,6363
137
- django_cfg/apps/tasks/@docs/TASKS_QUEUES.md,sha256=5AzkyDFZg3YJ9UVfRNNdO9_z6_7t6c6CScwz0yCWsTc,10233
138
- django_cfg/apps/tasks/@docs/TROUBLESHOOTING.md,sha256=B81bP8RVSc4U3j9hZp-Aq40rgzHYQQvnXU1_DnueKtQ,11473
139
120
  django_cfg/apps/tasks/static/tasks/css/dashboard.css,sha256=mDtAwFWzNrWEJkbkQFewmzFSYRXgTfGIXVkfWkbcUW0,4765
140
121
  django_cfg/apps/tasks/static/tasks/js/api.js,sha256=01OL5P0AO4fg9vCjVNx5s1mGgh6Ate69rucfA27CS3c,4048
141
122
  django_cfg/apps/tasks/static/tasks/js/dashboard.js,sha256=iBizGudFpzkX29cJbYv7Zim2xHx94uoBtmPCJ7t6TzE,23496
@@ -175,7 +156,7 @@ django_cfg/management/commands/create_token.py,sha256=beHtUTuyFZhG97F9vSkaX-u7ti
175
156
  django_cfg/management/commands/generate.py,sha256=w0BF7IMftxNjxTxFuY8cw5pNKGW-LmTScJ8kZpxHu_8,4248
176
157
  django_cfg/management/commands/list_urls.py,sha256=D8ikInA3uE1LbQGLWmfdLnEqPg7wqrI3caQA6iTe_-0,11009
177
158
  django_cfg/management/commands/migrator.py,sha256=mhMM63uv_Jp9zHVVM7TMwCB90uv3iFZn1vOG-rXyi3s,16191
178
- django_cfg/management/commands/rundramatiq.py,sha256=EUSLLaE2XZ7HCghwN3E2P6jrtziz8HpETN0uydKvXT8,10049
159
+ django_cfg/management/commands/rundramatiq.py,sha256=1e07PWrtSnxTPZ8RCpgY_yxwV2y-EAduVZDZ34DRqoI,8896
179
160
  django_cfg/management/commands/runserver_ngrok.py,sha256=mcTioDIzHgha6sGo5eazlJhdKr8y5-uEQIc3qG3AvCI,5237
180
161
  django_cfg/management/commands/script.py,sha256=I6zOEQEGaED0HoLxl2EqKz39HwbKg9HhdxnGKybfH5s,16974
181
162
  django_cfg/management/commands/show_config.py,sha256=0YJ99P1XvymT3fWritaNmn_HJ-PVb0I-yBy757M_bn8,8337
@@ -208,8 +189,9 @@ django_cfg/modules/base.py,sha256=X90X-0iBfnaUSaC7S8_ULa_rdT41tqTVJnT05_RuyK4,47
208
189
  django_cfg/modules/django_email.py,sha256=uBvvqRVe1DG73Qq2d2IBYTjhFRdvHgsIbkVw3ge9OW8,16586
209
190
  django_cfg/modules/django_logger.py,sha256=VfcPCurTdU3iI593EJNs3wUoWQowu7-ykJGuHNkE79M,6325
210
191
  django_cfg/modules/django_ngrok.py,sha256=OAvir2pBFHfko-XaVgZTjeJwyZw-NSEILaKNlqQziqA,10476
211
- django_cfg/modules/django_tasks.py,sha256=SOqwphvg_jlUjYmZG7o5mfePJkcwLW0m9RKO4ed7lBs,27322
192
+ django_cfg/modules/django_tasks.py,sha256=Vo45h4x8UHieAYdjp3g79efMns0cotkwp5ko4Fbt_pI,12868
212
193
  django_cfg/modules/django_telegram.py,sha256=Mun2tAm0P2cUyQlAs8FaPe-FVgcrv7L_-FPTXQQEUT0,16356
194
+ django_cfg/modules/dramatiq_setup.py,sha256=Jke4aO_L1t6F3OAc4pl12zppKzL0gb1p6ilfQ3zUIZ8,454
213
195
  django_cfg/modules/logger.py,sha256=4_zeasNehr8LGz8r_ckv15-fQS63zCodiqD4CYIEyFI,10546
214
196
  django_cfg/modules/django_currency/README.md,sha256=Ox3jgRtsbOIaMuYDkIhrs9ijLGLbn-2R7mD9n2tjAVE,8512
215
197
  django_cfg/modules/django_currency/__init__.py,sha256=SLzzYkkqoz9EsspkzEK0yZ4_Q3JKmb3e_c1GfdYF3GY,1294
@@ -270,8 +252,8 @@ django_cfg/templates/emails/base_email.html,sha256=TWcvYa2IHShlF_E8jf1bWZStRO0v8
270
252
  django_cfg/utils/__init__.py,sha256=64wwXJuXytvwt8Ze_erSR2HmV07nGWJ6DV5wloRBvYE,435
271
253
  django_cfg/utils/path_resolution.py,sha256=eML-6-RIGTs5TePktIQN8nxfDUEFJ3JA0AzWBcihAbs,13894
272
254
  django_cfg/utils/smart_defaults.py,sha256=iL6_n3jGDW5812whylWAwXik0xBSYihdLp4IJ26T5eA,20547
273
- django_cfg-1.1.61.dist-info/METADATA,sha256=9MfvaMizPU_nhP68eF52gJt0P9alC1aKyjwb8nwwU6o,38953
274
- django_cfg-1.1.61.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
275
- django_cfg-1.1.61.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
276
- django_cfg-1.1.61.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
277
- django_cfg-1.1.61.dist-info/RECORD,,
255
+ django_cfg-1.1.62.dist-info/METADATA,sha256=kN7aAQBvL6bjI5WJrhev0tknRjeiRBoNfFhtQscS74o,38953
256
+ django_cfg-1.1.62.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
257
+ django_cfg-1.1.62.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
258
+ django_cfg-1.1.62.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
259
+ django_cfg-1.1.62.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- # Tests for accounts app
@@ -1,412 +0,0 @@
1
- from django.test import TestCase
2
- from django.contrib.auth import get_user_model
3
- from django.utils import timezone
4
- from datetime import timedelta
5
-
6
- from ..models import OTPSecret, RegistrationSource, UserRegistrationSource
7
-
8
- User = get_user_model()
9
-
10
-
11
- class CustomUserModelTest(TestCase):
12
- """Test CustomUser model."""
13
-
14
- def setUp(self):
15
- self.user_data = {
16
- 'email': 'test@example.com',
17
- 'username': 'testuser',
18
- 'password': 'testpass123'
19
- }
20
-
21
- def test_create_user(self):
22
- """Test creating a user."""
23
- user = User.objects.create_user(**self.user_data)
24
-
25
- self.assertEqual(user.email, 'test@example.com')
26
- self.assertEqual(user.username, 'testuser')
27
- self.assertTrue(user.check_password('testpass123'))
28
- self.assertTrue(user.is_active)
29
- self.assertFalse(user.is_staff)
30
-
31
- def test_create_superuser(self):
32
- """Test creating a superuser."""
33
- user = User.objects.create_superuser(**self.user_data)
34
-
35
- self.assertTrue(user.is_staff)
36
- self.assertTrue(user.is_superuser)
37
- self.assertTrue(user.is_admin)
38
-
39
- def test_user_string_representation(self):
40
- """Test user string representation."""
41
- user = User.objects.create_user(**self.user_data)
42
- self.assertEqual(str(user), 'test@example.com')
43
-
44
- def test_email_unique(self):
45
- """Test email uniqueness."""
46
- User.objects.create_user(**self.user_data)
47
-
48
- with self.assertRaises(Exception):
49
- User.objects.create_user(**self.user_data)
50
-
51
- def test_user_creation(self):
52
- """Test user creation."""
53
- user_data = {
54
- 'email': 'test@example.com',
55
- 'first_name': 'Test',
56
- 'last_name': 'User',
57
- }
58
- user = User.objects.create_user(**user_data)
59
-
60
- self.assertEqual(user.email, 'test@example.com')
61
- self.assertEqual(user.first_name, 'Test')
62
- self.assertEqual(user.last_name, 'User')
63
- self.assertTrue(user.is_active)
64
- self.assertFalse(user.is_staff)
65
- self.assertFalse(user.is_superuser)
66
- self.assertFalse(user.phone_verified) # New field default
67
-
68
- def test_user_phone_verification(self):
69
- """Test user phone verification field."""
70
- user = User.objects.create_user(
71
- email='test@example.com',
72
- phone='+1234567890'
73
- )
74
-
75
- # Initially phone not verified
76
- self.assertFalse(user.phone_verified)
77
-
78
- # Mark phone as verified
79
- user.phone_verified = True
80
- user.save()
81
-
82
- user.refresh_from_db()
83
- self.assertTrue(user.phone_verified)
84
-
85
- def test_user_get_identifier_for_otp(self):
86
- """Test getting identifier for OTP based on channel."""
87
- user = User.objects.create_user(
88
- email='test@example.com',
89
- phone='+1234567890'
90
- )
91
-
92
- # Test email channel
93
- self.assertEqual(user.get_identifier_for_otp('email'), 'test@example.com')
94
-
95
- # Test phone channel
96
- self.assertEqual(user.get_identifier_for_otp('phone'), '+1234567890')
97
-
98
- # Test default (email)
99
- self.assertEqual(user.get_identifier_for_otp(), 'test@example.com')
100
-
101
-
102
- class OTPSecretModelTest(TestCase):
103
- """Test OTPSecret model."""
104
-
105
- def setUp(self):
106
- self.email = 'test@example.com'
107
-
108
- def test_otp_generation(self):
109
- """Test OTP generation."""
110
- otp = OTPSecret.generate_otp()
111
- self.assertEqual(len(otp), 6)
112
- self.assertTrue(otp.isdigit())
113
-
114
- def test_otp_creation(self):
115
- """Test OTP creation."""
116
- otp = OTPSecret.objects.create(
117
- email=self.email,
118
- secret='123456'
119
- )
120
- self.assertEqual(otp.email, self.email)
121
- self.assertEqual(otp.secret, '123456')
122
- self.assertFalse(otp.is_used)
123
- self.assertIsNotNone(otp.expires_at)
124
-
125
- def test_otp_expiration(self):
126
- """Test OTP expiration."""
127
- # Create OTP with past expiration
128
- past_time = timezone.now() - timedelta(minutes=1)
129
- otp = OTPSecret.objects.create(
130
- email=self.email,
131
- secret='123456',
132
- expires_at=past_time
133
- )
134
- self.assertFalse(otp.is_valid)
135
-
136
- def test_otp_mark_used(self):
137
- """Test marking OTP as used."""
138
- otp = OTPSecret.objects.create(
139
- email=self.email,
140
- secret='123456'
141
- )
142
- self.assertFalse(otp.is_used)
143
- otp.mark_used()
144
- self.assertTrue(otp.is_used)
145
-
146
- def test_otp_str_representation(self):
147
- """Test OTP string representation."""
148
- otp = OTPSecret.objects.create(
149
- email=self.email,
150
- secret='123456'
151
- )
152
- self.assertEqual(str(otp), f"OTP for {self.email} (email)")
153
-
154
- def test_otp_create_for_email(self):
155
- """Test creating OTP for email channel."""
156
- otp = OTPSecret.create_for_email(self.email)
157
-
158
- self.assertEqual(otp.channel_type, 'email')
159
- self.assertEqual(otp.recipient, self.email)
160
- self.assertEqual(otp.email, self.email) # Backward compatibility
161
- self.assertEqual(len(otp.secret), 6)
162
- self.assertTrue(otp.secret.isdigit())
163
- self.assertFalse(otp.is_used)
164
-
165
- def test_otp_create_for_phone(self):
166
- """Test creating OTP for phone channel."""
167
- phone = '+1234567890'
168
- otp = OTPSecret.create_for_phone(phone)
169
-
170
- self.assertEqual(otp.channel_type, 'phone')
171
- self.assertEqual(otp.recipient, phone)
172
- self.assertIsNone(otp.email) # No email for phone OTP
173
- self.assertEqual(len(otp.secret), 6)
174
- self.assertTrue(otp.secret.isdigit())
175
- self.assertFalse(otp.is_used)
176
-
177
- def test_otp_channel_types(self):
178
- """Test different channel types."""
179
- email_otp = OTPSecret.create_for_email('test@example.com')
180
- phone_otp = OTPSecret.create_for_phone('+1234567890')
181
-
182
- self.assertEqual(email_otp.channel_type, 'email')
183
- self.assertEqual(phone_otp.channel_type, 'phone')
184
-
185
- # Test string representations
186
- self.assertEqual(str(email_otp), "OTP for test@example.com (email)")
187
- self.assertEqual(str(phone_otp), "OTP for +1234567890 (phone)")
188
-
189
-
190
- class RegistrationSourceModelTest(TestCase):
191
- """Test RegistrationSource model."""
192
-
193
- def setUp(self):
194
- self.source = RegistrationSource.objects.create(
195
- url='https://reforms.ai',
196
- name='Unreal Dashboard',
197
- description='Main dashboard for Unreal project',
198
- is_active=True
199
- )
200
-
201
- def test_source_creation(self):
202
- """Test source creation."""
203
- self.assertEqual(self.source.url, 'https://reforms.ai')
204
- self.assertEqual(self.source.name, 'Unreal Dashboard')
205
- self.assertEqual(self.source.description, 'Main dashboard for Unreal project')
206
- self.assertTrue(self.source.is_active)
207
- self.assertIsNotNone(self.source.created_at)
208
- self.assertIsNotNone(self.source.updated_at)
209
-
210
- def test_source_get_domain(self):
211
- """Test domain extraction from URL."""
212
- test_cases = [
213
- ('https://test1.unrealon.com', 'test1.unrealon.com'),
214
- ('https://www.example.com', 'www.example.com'),
215
- ('http://app.test.com', 'app.test.com'),
216
- ('https://sub.domain.co.uk', 'sub.domain.co.uk'),
217
- ]
218
-
219
- for url, expected_domain in test_cases:
220
- source = RegistrationSource.objects.create(url=url)
221
- self.assertEqual(source.get_domain(), expected_domain)
222
-
223
- def test_source_get_display_name(self):
224
- """Test display name generation."""
225
- # Source with custom name
226
- source_with_name = RegistrationSource.objects.create(
227
- url='https://example.com',
228
- name='Custom Name'
229
- )
230
- self.assertEqual(source_with_name.get_display_name(), 'Custom Name')
231
-
232
- # Source without name (uses domain)
233
- source_without_name = RegistrationSource.objects.create(
234
- url='https://app.example.com'
235
- )
236
- self.assertEqual(source_without_name.get_display_name(), 'app.example.com')
237
-
238
- def test_source_str_representation(self):
239
- """Test source string representation."""
240
- # Source with custom name
241
- source_with_name = RegistrationSource.objects.create(
242
- url='https://example.com',
243
- name='Custom Name'
244
- )
245
- self.assertEqual(str(source_with_name), 'Custom Name')
246
-
247
- # Source without name (uses domain)
248
- source_without_name = RegistrationSource.objects.create(
249
- url='https://app.example.com'
250
- )
251
- self.assertEqual(str(source_without_name), 'app.example.com')
252
-
253
- def test_source_unique_url(self):
254
- """Test that source URL must be unique."""
255
- RegistrationSource.objects.create(url='https://example.com')
256
-
257
- # Try to create another source with same URL
258
- with self.assertRaises(Exception): # Should raise IntegrityError
259
- RegistrationSource.objects.create(url='https://example.com')
260
-
261
- def test_source_ordering(self):
262
- """Test source ordering by created_at descending."""
263
- source1 = RegistrationSource.objects.create(url='https://example1.com')
264
- source2 = RegistrationSource.objects.create(url='https://example2.com')
265
-
266
- sources = RegistrationSource.objects.all()
267
- self.assertEqual(sources[0], source2) # Most recent first
268
- self.assertEqual(sources[1], source1)
269
-
270
-
271
- class UserRegistrationSourceModelTest(TestCase):
272
- """Test UserRegistrationSource model."""
273
-
274
- def setUp(self):
275
- self.user = User.objects.create(
276
- email='test@example.com',
277
- username='testuser'
278
- )
279
- self.source = RegistrationSource.objects.create(
280
- url='https://reforms.ai',
281
- name='Unreal Dashboard'
282
- )
283
-
284
- def test_user_source_creation(self):
285
- """Test user-source relationship creation."""
286
- user_source = UserRegistrationSource.objects.create(
287
- user=self.user,
288
- source=self.source,
289
- first_registration=True
290
- )
291
-
292
- self.assertEqual(user_source.user, self.user)
293
- self.assertEqual(user_source.source, self.source)
294
- self.assertTrue(user_source.first_registration)
295
- self.assertIsNotNone(user_source.registration_date)
296
-
297
- def test_user_source_unique_constraint(self):
298
- """Test that user-source relationship is unique."""
299
- UserRegistrationSource.objects.create(
300
- user=self.user,
301
- source=self.source,
302
- first_registration=True
303
- )
304
-
305
- # Try to create duplicate relationship
306
- with self.assertRaises(Exception): # Should raise IntegrityError
307
- UserRegistrationSource.objects.create(
308
- user=self.user,
309
- source=self.source,
310
- first_registration=False
311
- )
312
-
313
- def test_user_source_ordering(self):
314
- """Test user-source ordering by registration_date descending."""
315
- user_source1 = UserRegistrationSource.objects.create(
316
- user=self.user,
317
- source=self.source,
318
- first_registration=True
319
- )
320
-
321
- # Create another source and relationship
322
- source2 = RegistrationSource.objects.create(url='https://example.com')
323
- user_source2 = UserRegistrationSource.objects.create(
324
- user=self.user,
325
- source=source2,
326
- first_registration=False
327
- )
328
-
329
- user_sources = UserRegistrationSource.objects.all()
330
- self.assertEqual(user_sources[0], user_source2) # Most recent first
331
- self.assertEqual(user_sources[1], user_source1)
332
-
333
-
334
- class CustomUserSourceMethodsTest(TestCase):
335
- """Test CustomUser source-related methods."""
336
-
337
- def setUp(self):
338
- self.user = User.objects.create(
339
- email='test@example.com',
340
- username='testuser'
341
- )
342
- self.source1 = RegistrationSource.objects.create(
343
- url='https://reforms.ai',
344
- name='Unreal Dashboard'
345
- )
346
- self.source2 = RegistrationSource.objects.create(
347
- url='https://app.example.com',
348
- name='Example App'
349
- )
350
-
351
- def test_user_get_sources(self):
352
- """Test user get_sources method."""
353
- # Create user-source relationships
354
- UserRegistrationSource.objects.create(
355
- user=self.user,
356
- source=self.source1,
357
- first_registration=True
358
- )
359
- UserRegistrationSource.objects.create(
360
- user=self.user,
361
- source=self.source2,
362
- first_registration=False
363
- )
364
-
365
- sources = self.user.get_sources()
366
- self.assertEqual(sources.count(), 2)
367
- self.assertIn(self.source1, sources)
368
- self.assertIn(self.source2, sources)
369
-
370
- def test_user_get_primary_source(self):
371
- """Test user get_primary_source method."""
372
- # Create user-source relationships
373
- UserRegistrationSource.objects.create(
374
- user=self.user,
375
- source=self.source1,
376
- first_registration=True
377
- )
378
- UserRegistrationSource.objects.create(
379
- user=self.user,
380
- source=self.source2,
381
- first_registration=False
382
- )
383
-
384
- primary_source = self.user.get_primary_source()
385
- self.assertEqual(primary_source, self.source1)
386
-
387
- def test_user_no_sources(self):
388
- """Test user methods when no sources exist."""
389
- sources = self.user.get_sources()
390
- self.assertEqual(sources.count(), 0)
391
-
392
- primary_source = self.user.get_primary_source()
393
- self.assertIsNone(primary_source)
394
-
395
- def test_user_multiple_first_registrations(self):
396
- """Test behavior when multiple sources have first_registration=True."""
397
- # Create multiple sources with first_registration=True
398
- UserRegistrationSource.objects.create(
399
- user=self.user,
400
- source=self.source1,
401
- first_registration=True
402
- )
403
- UserRegistrationSource.objects.create(
404
- user=self.user,
405
- source=self.source2,
406
- first_registration=True
407
- )
408
-
409
- # Should return the first one (by registration_date)
410
- primary_source = self.user.get_primary_source()
411
- self.assertIsNotNone(primary_source)
412
- self.assertIn(primary_source, [self.source1, self.source2])
@@ -1,143 +0,0 @@
1
- """
2
- Tests for OTP views with controlled welcome email notifications.
3
- """
4
-
5
- from django.test import TestCase
6
- from django.contrib.auth import get_user_model
7
- from django.urls import reverse
8
- from django.utils import timezone
9
- from unittest.mock import patch, MagicMock
10
- from datetime import timedelta
11
- from rest_framework.test import APIClient
12
- from rest_framework import status
13
-
14
- from ..models import OTPSecret
15
- from ..utils.notifications import AccountNotifications
16
-
17
- User = get_user_model()
18
-
19
-
20
- class OTPViewWelcomeEmailTestCase(TestCase):
21
- """Test cases for controlled welcome email in OTP views."""
22
-
23
- def setUp(self):
24
- """Set up test data."""
25
- self.client = APIClient()
26
- self.email = "test@example.com"
27
- self.otp_code = "123456"
28
-
29
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_welcome_email")
30
- def test_welcome_email_sent_for_new_user(self, mock_welcome_email):
31
- """Test that welcome email is sent only for new users during OTP verification."""
32
- # Create a new user (simulating recent registration)
33
- user = User.objects.create_user(email=self.email, username="newuser")
34
-
35
- # Create OTP for the user
36
- otp = OTPSecret.create_for_email(self.email)
37
- otp.secret = self.otp_code
38
- otp.save()
39
-
40
- # Mock the OTP verification to return the new user
41
- with patch("django_cfg.apps.accounts.services.otp_service.OTPService.verify_email_otp") as mock_verify:
42
- mock_verify.return_value = user
43
-
44
- # Make OTP verification request
45
- response = self.client.post(
46
- reverse("cfg_accounts:otp-verify-otp"),
47
- data={
48
- "identifier": self.email,
49
- "otp": self.otp_code,
50
- },
51
- format="json"
52
- )
53
-
54
- self.assertEqual(response.status_code, status.HTTP_200_OK)
55
-
56
- # Welcome email should be sent for new user
57
- mock_welcome_email.assert_called_once_with(
58
- user, send_email=True, send_telegram=False
59
- )
60
-
61
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_welcome_email")
62
- def test_no_welcome_email_for_existing_user(self, mock_welcome_email):
63
- """Test that welcome email is NOT sent for existing users during OTP verification."""
64
- # Create an existing user (older than 5 minutes)
65
- old_time = timezone.now() - timedelta(minutes=10)
66
- user = User.objects.create_user(email=self.email, username="olduser")
67
- user.date_joined = old_time
68
- user.save()
69
-
70
- # Create OTP for the user
71
- otp = OTPSecret.create_for_email(self.email)
72
- otp.secret = self.otp_code
73
- otp.save()
74
-
75
- # Mock the OTP verification to return the existing user
76
- with patch("django_cfg.apps.accounts.services.otp_service.OTPService.verify_email_otp") as mock_verify:
77
- mock_verify.return_value = user
78
-
79
- # Make OTP verification request
80
- response = self.client.post(
81
- reverse("cfg_accounts:otp-verify-otp"),
82
- data={
83
- "identifier": self.email,
84
- "otp": self.otp_code,
85
- },
86
- format="json"
87
- )
88
-
89
- self.assertEqual(response.status_code, status.HTTP_200_OK)
90
-
91
- # Welcome email should NOT be sent for existing user
92
- mock_welcome_email.assert_not_called()
93
-
94
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_welcome_email")
95
- def test_welcome_email_error_handling(self, mock_welcome_email):
96
- """Test that OTP verification succeeds even if welcome email fails."""
97
- # Create a new user
98
- user = User.objects.create_user(email=self.email, username="newuser")
99
-
100
- # Create OTP for the user
101
- otp = OTPSecret.create_for_email(self.email)
102
- otp.secret = self.otp_code
103
- otp.save()
104
-
105
- # Mock welcome email to raise an exception
106
- mock_welcome_email.side_effect = Exception("Email service error")
107
-
108
- # Mock the OTP verification to return the new user
109
- with patch("django_cfg.apps.accounts.services.otp_service.OTPService.verify_email_otp") as mock_verify:
110
- mock_verify.return_value = user
111
-
112
- # Make OTP verification request
113
- response = self.client.post(
114
- reverse("cfg_accounts:otp-verify-otp"),
115
- data={
116
- "identifier": self.email,
117
- "otp": self.otp_code,
118
- },
119
- format="json"
120
- )
121
-
122
- # OTP verification should still succeed
123
- self.assertEqual(response.status_code, status.HTTP_200_OK)
124
- self.assertIn("access", response.data)
125
- self.assertIn("refresh", response.data)
126
-
127
- # Welcome email should have been attempted
128
- mock_welcome_email.assert_called_once()
129
-
130
- def test_integration_with_notifications_class(self):
131
- """Test integration with AccountNotifications class methods."""
132
- # Create a user
133
- user = User.objects.create_user(email=self.email, username="testuser")
134
-
135
- # Test that AccountNotifications methods exist and are callable
136
- self.assertTrue(hasattr(AccountNotifications, "send_welcome_email"))
137
- self.assertTrue(hasattr(AccountNotifications, "send_otp_notification"))
138
- self.assertTrue(hasattr(AccountNotifications, "send_otp_verification_success"))
139
-
140
- # Test that methods are static
141
- self.assertTrue(callable(AccountNotifications.send_welcome_email))
142
- self.assertTrue(callable(AccountNotifications.send_otp_notification))
143
- self.assertTrue(callable(AccountNotifications.send_otp_verification_success))