django-cfg 1.1.61__py3-none-any.whl → 1.1.63__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.63.dist-info}/METADATA +145 -4
  6. {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.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.63.dist-info}/WHEEL +0 -0
  27. {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/entry_points.txt +0 -0
  28. {django_cfg-1.1.61.dist-info → django_cfg-1.1.63.dist-info}/licenses/LICENSE +0 -0
@@ -1,401 +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 unittest.mock import patch
5
- from datetime import timedelta
6
-
7
- from ..services import OTPService
8
- from ..models import OTPSecret, RegistrationSource, UserRegistrationSource
9
-
10
- User = get_user_model()
11
-
12
-
13
- class OTPServiceTest(TestCase):
14
- """Test OTPService."""
15
-
16
- def setUp(self):
17
- self.email = "test@example.com"
18
- self.source_url = "https://reforms.ai"
19
-
20
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_otp_notification")
21
- def test_request_otp_new_user(self, mock_email):
22
- """Test OTP request for new user."""
23
- mock_email.return_value = True
24
-
25
- success, error_type = OTPService.request_email_otp(self.email)
26
-
27
- self.assertTrue(success)
28
- self.assertEqual(error_type, "success")
29
-
30
- # User should be created
31
- user = User.objects.get(email=self.email)
32
- self.assertIsNotNone(user)
33
-
34
- # OTP should be created
35
- otp = OTPSecret.objects.get(recipient=self.email, channel_type='email')
36
- self.assertIsNotNone(otp)
37
- self.assertEqual(len(otp.secret), 6)
38
- self.assertTrue(otp.secret.isdigit())
39
-
40
- # OTP notification should be sent
41
- mock_email.assert_called_once()
42
-
43
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_otp_notification")
44
- def test_request_otp_new_user_with_source_url(self, mock_email):
45
- """Test OTP request for new user with source_url."""
46
- mock_email.return_value = True
47
-
48
- success, error_type = OTPService.request_email_otp(self.email, self.source_url)
49
-
50
- self.assertTrue(success)
51
- self.assertEqual(error_type, "success")
52
-
53
- # User should be created
54
- user = User.objects.get(email=self.email)
55
- self.assertIsNotNone(user)
56
-
57
- # Source should be created
58
- source = RegistrationSource.objects.get(url=self.source_url)
59
- self.assertIsNotNone(source)
60
- self.assertEqual(source.name, "reforms.ai")
61
-
62
- # User-source relationship should be created
63
- user_source = UserRegistrationSource.objects.get(user=user, source=source)
64
- self.assertIsNotNone(user_source)
65
- self.assertTrue(user_source.first_registration)
66
-
67
- # OTP should be created
68
- otp = OTPSecret.objects.get(recipient=self.email, channel_type='email')
69
- self.assertIsNotNone(otp)
70
-
71
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_otp_notification")
72
- def test_request_otp_existing_user(self, mock_email):
73
- """Test OTP request for existing user."""
74
- mock_email.return_value = True
75
-
76
- # Create existing user
77
- User.objects.create_user(email=self.email, username="existing_user")
78
-
79
- success, error_type = OTPService.request_email_otp(self.email)
80
-
81
- self.assertTrue(success)
82
- self.assertEqual(error_type, "success")
83
-
84
- # Should not create duplicate user
85
- users = User.objects.filter(email=self.email)
86
- self.assertEqual(users.count(), 1)
87
-
88
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_otp_notification")
89
- def test_request_otp_existing_user_with_source_url(self, mock_email):
90
- """Test OTP request for existing user with source_url."""
91
- mock_email.return_value = True
92
-
93
- # Create existing user
94
- user = User.objects.create_user(email=self.email, username="existing_user")
95
-
96
- success, error_type = OTPService.request_email_otp(self.email, self.source_url)
97
-
98
- self.assertTrue(success)
99
- self.assertEqual(error_type, "success")
100
-
101
- # Source should be created
102
- source = RegistrationSource.objects.get(url=self.source_url)
103
- self.assertIsNotNone(source)
104
-
105
- # User-source relationship should be created
106
- user_source = UserRegistrationSource.objects.get(user=user, source=source)
107
- self.assertIsNotNone(user_source)
108
- self.assertFalse(user_source.first_registration) # Not first registration
109
-
110
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_otp_notification")
111
- def test_request_otp_reuse_active(self, mock_email):
112
- """Test OTP request reuses active OTP."""
113
- mock_email.return_value = True
114
-
115
- # Create existing OTP with known secret
116
- existing_otp = OTPSecret.create_for_email(self.email)
117
- existing_secret = existing_otp.secret
118
-
119
- success, error_type = OTPService.request_email_otp(self.email)
120
-
121
- self.assertTrue(success)
122
- self.assertEqual(error_type, "success")
123
-
124
- # Should reuse existing OTP
125
- otp_count = OTPSecret.objects.filter(recipient=self.email, channel_type='email').count()
126
- self.assertEqual(otp_count, 1)
127
-
128
- # Should use existing secret
129
- otp = OTPSecret.objects.get(recipient=self.email, channel_type='email')
130
- self.assertEqual(otp.secret, existing_secret)
131
-
132
- @patch("django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_otp_notification")
133
- def test_request_otp_email_failure(self, mock_email):
134
- """Test OTP request when email fails."""
135
- mock_email.side_effect = Exception("Email service error")
136
-
137
- success, error_type = OTPService.request_email_otp(self.email)
138
-
139
- self.assertFalse(success)
140
- self.assertEqual(error_type, "email_send_failed")
141
-
142
- def test_request_otp_invalid_email(self):
143
- """Test OTP request with invalid email."""
144
- success, error_type = OTPService.request_email_otp("")
145
-
146
- self.assertFalse(success)
147
- self.assertEqual(error_type, "invalid_email")
148
-
149
- def test_verify_otp_success(self):
150
- """Test successful OTP verification."""
151
- # Create user and OTP
152
- user = User.objects.create_user(email=self.email, username="testuser")
153
- otp = OTPSecret.create_for_email(self.email)
154
- otp.secret = "123456" # Set known secret for test
155
- otp.save()
156
-
157
- # Verify OTP
158
- result_user = OTPService.verify_email_otp(self.email, "123456")
159
-
160
- self.assertIsNotNone(result_user)
161
- self.assertEqual(result_user, user)
162
-
163
- # OTP should be marked as used
164
- otp.refresh_from_db()
165
- self.assertTrue(otp.is_used)
166
-
167
- def test_verify_otp_success_with_source_url(self):
168
- """Test successful OTP verification with source_url."""
169
- # Create user and OTP
170
- user = User.objects.create_user(email=self.email, username="testuser")
171
- otp = OTPSecret.create_for_email(self.email)
172
- otp.secret = "123456" # Set known secret for test
173
- otp.save()
174
-
175
- # Verify OTP with source_url
176
- result_user = OTPService.verify_email_otp(self.email, "123456", self.source_url)
177
-
178
- self.assertIsNotNone(result_user)
179
- self.assertEqual(result_user, user)
180
-
181
- # Source should be created
182
- source = RegistrationSource.objects.get(url=self.source_url)
183
- self.assertIsNotNone(source)
184
-
185
- # User-source relationship should be created
186
- user_source = UserRegistrationSource.objects.get(user=user, source=source)
187
- self.assertIsNotNone(user_source)
188
- self.assertFalse(user_source.first_registration) # Not first registration
189
-
190
- def test_verify_otp_invalid_code(self):
191
- """Test OTP verification with invalid code."""
192
- # Create user and OTP
193
- User.objects.create_user(email=self.email, username="testuser")
194
- otp = OTPSecret.create_for_email(self.email)
195
- otp.secret = "123456" # Set known secret for test
196
- otp.save()
197
-
198
- # Try to verify with wrong code
199
- result_user = OTPService.verify_email_otp(self.email, "654321")
200
-
201
- self.assertIsNone(result_user)
202
-
203
- def test_verify_otp_expired(self):
204
- """Test OTP verification with expired OTP."""
205
- # Create user and expired OTP
206
- User.objects.create_user(email=self.email, username="testuser")
207
- expired_time = timezone.now() - timedelta(minutes=11)
208
- otp = OTPSecret.create_for_email(self.email)
209
- otp.secret = "123456" # Set known secret for test
210
- otp.expires_at = expired_time
211
- otp.save()
212
-
213
- # Try to verify expired OTP
214
- result_user = OTPService.verify_email_otp(self.email, "123456")
215
-
216
- self.assertIsNone(result_user)
217
-
218
- def test_verify_otp_used(self):
219
- """Test OTP verification with used OTP."""
220
- # Create user and used OTP
221
- User.objects.create_user(email=self.email, username="testuser")
222
- otp = OTPSecret.create_for_email(self.email)
223
- otp.secret = "123456" # Set known secret for test
224
- otp.is_used = True
225
- otp.save()
226
-
227
- # Try to verify used OTP
228
- result_user = OTPService.verify_email_otp(self.email, "123456")
229
-
230
- self.assertIsNone(result_user)
231
-
232
- def test_verify_otp_no_user(self):
233
- """Test OTP verification when user doesn't exist."""
234
- # Create OTP but no user
235
- otp = OTPSecret.create_for_email(self.email)
236
- otp.secret = "123456" # Set known secret for test
237
- otp.save()
238
-
239
- # Try to verify OTP
240
- result_user = OTPService.verify_email_otp(self.email, "123456")
241
-
242
- self.assertIsNone(result_user)
243
-
244
- def test_verify_otp_invalid_input(self):
245
- """Test OTP verification with invalid input."""
246
- # Test with empty email
247
- result_user = OTPService.verify_email_otp("", "123456")
248
- self.assertIsNone(result_user)
249
-
250
- # Test with empty OTP
251
- result_user = OTPService.verify_email_otp(self.email, "")
252
- self.assertIsNone(result_user)
253
-
254
- # Test with None values
255
- result_user = OTPService.verify_email_otp(None, "123456")
256
- self.assertIsNone(result_user)
257
-
258
- result_user = OTPService.verify_email_otp(self.email, None)
259
- self.assertIsNone(result_user)
260
-
261
-
262
- class PhoneOTPServiceTest(TestCase):
263
- """Test Phone OTP Service functionality."""
264
-
265
- def setUp(self):
266
- self.phone = "+1234567890"
267
- self.email = "test@example.com"
268
-
269
- @patch("django_cfg.apps.accounts.services.otp_service.send_whatsapp_otp")
270
- def test_request_phone_otp_new_user(self, mock_whatsapp_otp):
271
- """Test phone OTP request for new user."""
272
- mock_whatsapp_otp.return_value = (True, "OTP sent successfully")
273
-
274
- success, error_type = OTPService.request_phone_otp(self.phone)
275
-
276
- self.assertTrue(success)
277
- self.assertEqual(error_type, "success")
278
-
279
- # User should be created with temp email
280
- users = User.objects.filter(phone=self.phone)
281
- self.assertEqual(users.count(), 1)
282
- user = users.first()
283
- self.assertIsNotNone(user)
284
- self.assertTrue(user.email.startswith("phone_"))
285
-
286
- # OTP should be created for phone
287
- otp = OTPSecret.objects.get(recipient=self.phone, channel_type='phone')
288
- self.assertIsNotNone(otp)
289
- self.assertEqual(len(otp.secret), 6)
290
- self.assertTrue(otp.secret.isdigit())
291
- self.assertEqual(otp.channel_type, 'phone') # Phone channel
292
-
293
- # WhatsApp OTP should be sent
294
- mock_whatsapp_otp.assert_called_once()
295
-
296
- @patch("django_cfg.apps.accounts.services.otp_service.send_whatsapp_otp")
297
- def test_request_phone_otp_existing_user(self, mock_whatsapp_otp):
298
- """Test phone OTP request for existing user."""
299
- mock_whatsapp_otp.return_value = (True, "OTP sent successfully")
300
-
301
- # Create existing user with phone
302
- User.objects.create_user(
303
- email=self.email,
304
- username="existing_user",
305
- phone=self.phone
306
- )
307
-
308
- success, error_type = OTPService.request_phone_otp(self.phone)
309
-
310
- self.assertTrue(success)
311
- self.assertEqual(error_type, "success")
312
-
313
- # Should not create duplicate user
314
- users = User.objects.filter(phone=self.phone)
315
- self.assertEqual(users.count(), 1)
316
-
317
- def test_request_phone_otp_invalid_phone(self):
318
- """Test phone OTP request with invalid phone."""
319
- success, error_type = OTPService.request_phone_otp("invalid-phone")
320
-
321
- self.assertFalse(success)
322
- self.assertEqual(error_type, "invalid_phone")
323
-
324
- @patch("django_cfg.apps.accounts.services.otp_service.verify_otp")
325
- def test_verify_phone_otp_success(self, mock_verify_otp):
326
- """Test successful phone OTP verification."""
327
- mock_verify_otp.return_value = (True, "OTP verified successfully")
328
-
329
- # Create user with phone
330
- user = User.objects.create_user(
331
- email=self.email,
332
- username="testuser",
333
- phone=self.phone
334
- )
335
-
336
- # Create phone OTP
337
- otp = OTPSecret.create_for_phone(self.phone)
338
- otp.secret = "123456" # Set known secret for test
339
- otp.save()
340
-
341
- # Verify OTP
342
- result_user = OTPService.verify_phone_otp(self.phone, "123456")
343
-
344
- self.assertIsNotNone(result_user)
345
- self.assertEqual(result_user, user)
346
- self.assertTrue(result_user.phone_verified) # Should mark phone as verified
347
-
348
- # Mock verify_otp was called
349
- mock_verify_otp.assert_called_once_with(self.phone, "123456")
350
-
351
- @patch("django_cfg.apps.accounts.services.otp_service.verify_otp")
352
- def test_verify_phone_otp_invalid_code(self, mock_verify_otp):
353
- """Test phone OTP verification with invalid code."""
354
- mock_verify_otp.return_value = (False, "Invalid OTP code")
355
-
356
- # Create user with phone
357
- User.objects.create_user(
358
- email=self.email,
359
- username="testuser",
360
- phone=self.phone
361
- )
362
-
363
- # Create phone OTP
364
- otp = OTPSecret.create_for_phone(self.phone)
365
- otp.secret = "123456" # Set known secret for test
366
- otp.save()
367
-
368
- # Try to verify with wrong code
369
- result_user = OTPService.verify_phone_otp(self.phone, "654321")
370
-
371
- self.assertIsNone(result_user)
372
-
373
- def test_phone_validation(self):
374
- """Test phone number validation."""
375
- from ..services.otp_service import OTPService
376
-
377
- # Valid phones
378
- self.assertTrue(OTPService._validate_phone("+1234567890"))
379
- self.assertTrue(OTPService._validate_phone("+12345678901234"))
380
-
381
- # Invalid phones
382
- self.assertFalse(OTPService._validate_phone("1234567890")) # No +
383
- self.assertFalse(OTPService._validate_phone("+0234567890")) # Starts with 0
384
- self.assertFalse(OTPService._validate_phone("")) # Empty
385
- self.assertFalse(OTPService._validate_phone("+12")) # Too short
386
-
387
- def test_channel_detection(self):
388
- """Test automatic channel detection."""
389
- from ..services.otp_service import OTPService
390
-
391
- # Email detection
392
- self.assertEqual(OTPService._determine_channel("test@example.com"), "email")
393
- self.assertEqual(OTPService._determine_channel("user+tag@domain.co.uk"), "email")
394
-
395
- # Phone detection
396
- self.assertEqual(OTPService._determine_channel("+1234567890"), "phone")
397
- self.assertEqual(OTPService._determine_channel("1234567890"), "phone")
398
- self.assertEqual(OTPService._determine_channel("(555) 123-4567"), "phone")
399
-
400
- # Default to email for unclear cases
401
- self.assertEqual(OTPService._determine_channel("unclear"), "email")
@@ -1,110 +0,0 @@
1
- """
2
- Tests for account signals and email notifications.
3
- """
4
-
5
- import logging
6
- from unittest.mock import patch, MagicMock
7
- from django.test import TestCase
8
- from django.contrib.auth import get_user_model
9
- from django.utils import timezone
10
-
11
- from django_cfg.apps.accounts.signals import trigger_login_notification
12
- from django_cfg.apps.accounts.utils.notifications import AccountNotifications
13
-
14
- User = get_user_model()
15
-
16
-
17
- class AccountSignalsTestCase(TestCase):
18
- """Test cases for account signals."""
19
-
20
- def setUp(self):
21
- """Set up test data."""
22
- self.user_data = {
23
- "email": "test@example.com",
24
- "username": "testuser",
25
- "first_name": "Test",
26
- "last_name": "User",
27
- }
28
-
29
- def test_user_registration_signal_disabled(self):
30
- """Test that welcome email is NOT sent automatically when new user is created."""
31
- # This test verifies that the signal is disabled by checking that the signal function is commented out
32
-
33
- # Read the signals.py file to verify the signal is disabled
34
- import inspect
35
- from django_cfg.apps.accounts import signals
36
-
37
- # Get the source code of the signals module
38
- source = inspect.getsource(signals)
39
-
40
- # Check that send_user_registration_email is commented out
41
- self.assertIn("# @receiver(post_save, sender=User)", source)
42
- self.assertIn("# def send_user_registration_email", source)
43
-
44
- print("✅ Welcome email signal is properly disabled")
45
-
46
- def test_user_profile_update_signal(self):
47
- """Test that security alert is sent when user profile is updated."""
48
- with patch(
49
- "django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_profile_update_notification"
50
- ) as mock_send_security:
51
- user = User.objects.create(**self.user_data)
52
-
53
- # Change email directly on the user object and save
54
- user.email = "newemail@example.com"
55
- user.save()
56
-
57
- # Check that the signal was triggered
58
- mock_send_security.assert_called()
59
- args, kwargs = mock_send_security.call_args
60
- # The second argument should be the changes list
61
- self.assertIn("email address", args[1])
62
-
63
- def test_signal_error_handling(self):
64
- """Test that signals handle errors gracefully."""
65
- with patch(
66
- "django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_profile_update_notification",
67
- side_effect=Exception("Email service error"),
68
- ):
69
- user = User.objects.create(**self.user_data)
70
- # Change profile to trigger signal
71
- user.email = "newemail@example.com"
72
- user.save()
73
- # Test passes if no exception is raised
74
-
75
- def test_multiple_profile_changes(self):
76
- """Test that security alert is sent for multiple profile changes."""
77
- with patch(
78
- "django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_profile_update_notification"
79
- ) as mock_send:
80
- user = User.objects.create(**self.user_data)
81
-
82
- # Change multiple fields directly on the user object
83
- user.email = "newemail@example.com"
84
- user.username = "newusername"
85
- user.first_name = "New"
86
- user.save()
87
-
88
- mock_send.assert_called()
89
- args, kwargs = mock_send.call_args
90
- # The second argument should be the changes list
91
- changes_list = args[1]
92
- self.assertIn("email address", changes_list)
93
- self.assertIn("username", changes_list)
94
- self.assertIn("name", changes_list)
95
-
96
- def test_login_notification_signal(self):
97
- """Test that login notification is sent when user logs in."""
98
- user = User.objects.create(**self.user_data)
99
-
100
- with patch(
101
- "django_cfg.apps.accounts.utils.notifications.AccountNotifications.send_login_notification"
102
- ) as mock_send_login:
103
- # Simulate login signal
104
- trigger_login_notification(user=user, ip_address="127.0.0.1")
105
-
106
- # Check that the signal was triggered
107
- mock_send_login.assert_called()
108
- args, kwargs = mock_send_login.call_args
109
- # Check that the user is passed as first argument
110
- self.assertEqual(args[0], user)