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,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))
@@ -1,331 +0,0 @@
1
- import os
2
- from django.test import TestCase
3
- from django.contrib.auth import get_user_model
4
- from django_cfg.apps.accounts.serializers.profile import (
5
- UserProfileUpdateSerializer,
6
- RegistrationSourceSerializer,
7
- UserRegistrationSourceSerializer,
8
- UserWithSourcesSerializer,
9
- )
10
- from django_cfg.apps.accounts.serializers.otp import OTPRequestSerializer, OTPVerifySerializer
11
- from django_cfg.apps.accounts.models import RegistrationSource, UserRegistrationSource
12
-
13
- User = get_user_model()
14
-
15
- # Disable Telegram notifications in tests
16
- os.environ["TELEGRAM_DISABLED"] = "true"
17
-
18
-
19
- class UserProfileUpdateSerializerTest(TestCase):
20
- def setUp(self):
21
- self.user = User.objects.create(
22
- username="testuser",
23
- email="test@example.com",
24
- first_name="John",
25
- last_name="Doe",
26
- )
27
-
28
- def test_valid_first_name_update(self):
29
- """Test valid first name update."""
30
- data = {"first_name": "Jane"}
31
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
32
- self.assertTrue(serializer.is_valid())
33
- self.assertEqual(serializer.validated_data["first_name"], "Jane")
34
-
35
- def test_first_name_too_short(self):
36
- """Test first name validation - too short."""
37
- data = {"first_name": "J"}
38
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
39
- self.assertFalse(serializer.is_valid())
40
- self.assertIn("first_name", serializer.errors)
41
-
42
- def test_valid_last_name_update(self):
43
- """Test valid last name update."""
44
- data = {"last_name": "Smith"}
45
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
46
- self.assertTrue(serializer.is_valid())
47
- self.assertEqual(serializer.validated_data["last_name"], "Smith")
48
-
49
- def test_last_name_too_short(self):
50
- """Test last name validation - too short."""
51
- data = {"last_name": "S"}
52
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
53
- self.assertFalse(serializer.is_valid())
54
- self.assertIn("last_name", serializer.errors)
55
-
56
- def test_valid_phone_update(self):
57
- """Test valid phone update."""
58
- data = {"phone": "+1 (555) 123-4567"}
59
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
60
- self.assertTrue(serializer.is_valid())
61
- self.assertEqual(serializer.validated_data["phone"], "+1 (555) 123-4567")
62
-
63
- def test_invalid_phone(self):
64
- """Test phone validation - invalid format."""
65
- data = {"phone": "invalid-phone"}
66
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
67
- self.assertFalse(serializer.is_valid())
68
- self.assertIn("phone", serializer.errors)
69
-
70
- def test_multiple_fields_update(self):
71
- """Test updating multiple fields at once."""
72
- data = {
73
- "first_name": "Jane",
74
- "last_name": "Smith",
75
- "company": "Tech Corp",
76
- "position": "Developer",
77
- }
78
- serializer = UserProfileUpdateSerializer(self.user, data=data, partial=True)
79
- self.assertTrue(serializer.is_valid())
80
- self.assertEqual(serializer.validated_data["first_name"], "Jane")
81
- self.assertEqual(serializer.validated_data["last_name"], "Smith")
82
- self.assertEqual(serializer.validated_data["company"], "Tech Corp")
83
- self.assertEqual(serializer.validated_data["position"], "Developer")
84
-
85
-
86
- class RegistrationSourceSerializerTest(TestCase):
87
- def setUp(self):
88
- self.source = RegistrationSource.objects.create(
89
- url="https://reforms.ai",
90
- name="Unreal Dashboard",
91
- description="Main dashboard for Unreal project",
92
- is_active=True,
93
- )
94
-
95
- def test_source_serializer_fields(self):
96
- """Test RegistrationSource serializer includes all required fields."""
97
- serializer = RegistrationSourceSerializer(self.source)
98
- data = serializer.data
99
-
100
- self.assertIn("id", data)
101
- self.assertIn("url", data)
102
- self.assertIn("name", data)
103
- self.assertIn("description", data)
104
- self.assertIn("is_active", data)
105
- self.assertIn("created_at", data)
106
- self.assertIn("updated_at", data)
107
-
108
- self.assertEqual(data["url"], "https://reforms.ai")
109
- self.assertEqual(data["name"], "Unreal Dashboard")
110
- self.assertEqual(data["description"], "Main dashboard for Unreal project")
111
- self.assertTrue(data["is_active"])
112
-
113
- def test_source_serializer_validation(self):
114
- """Test RegistrationSource serializer validation."""
115
- data = {
116
- "url": "https://test.example.com",
117
- "name": "Test Source",
118
- "description": "Test description",
119
- "is_active": True,
120
- }
121
- serializer = RegistrationSourceSerializer(data=data)
122
- self.assertTrue(serializer.is_valid())
123
-
124
-
125
- class UserRegistrationSourceSerializerTest(TestCase):
126
- def setUp(self):
127
- self.user = User.objects.create(username="testuser", email="test@example.com")
128
- self.source = RegistrationSource.objects.create(
129
- url="https://reforms.ai", name="Unreal Dashboard"
130
- )
131
- self.user_source = UserRegistrationSource.objects.create(
132
- user=self.user, source=self.source, first_registration=True
133
- )
134
-
135
- def test_user_source_serializer_fields(self):
136
- """Test UserRegistrationSource serializer includes all required fields."""
137
- serializer = UserRegistrationSourceSerializer(self.user_source)
138
- data = serializer.data
139
-
140
- self.assertIn("id", data)
141
- self.assertIn("user", data)
142
- self.assertIn("source", data)
143
- self.assertIn("first_registration", data)
144
- self.assertIn("registration_date", data)
145
-
146
- self.assertEqual(data["user"], self.user.id)
147
- self.assertTrue(data["first_registration"])
148
-
149
- # Check nested source data
150
- source_data = data["source"]
151
- self.assertEqual(source_data["url"], "https://reforms.ai")
152
- self.assertEqual(source_data["name"], "Unreal Dashboard")
153
-
154
- def test_user_source_serializer_validation(self):
155
- """Test UserRegistrationSource serializer validation."""
156
- data = {
157
- "user": self.user.id,
158
- "source": self.source.id,
159
- "first_registration": False,
160
- }
161
- serializer = UserRegistrationSourceSerializer(data=data)
162
- self.assertTrue(serializer.is_valid())
163
-
164
-
165
- class UserWithSourcesSerializerTest(TestCase):
166
- def setUp(self):
167
- self.user = User.objects.create(
168
- username="testuser",
169
- email="test@example.com",
170
- first_name="John",
171
- last_name="Doe",
172
- )
173
- self.source1 = RegistrationSource.objects.create(
174
- url="https://reforms.ai", name="Unreal Dashboard"
175
- )
176
- self.source2 = RegistrationSource.objects.create(
177
- url="https://app.example.com", name="Example App"
178
- )
179
- self.user_source1 = UserRegistrationSource.objects.create(
180
- user=self.user, source=self.source1, first_registration=True
181
- )
182
- self.user_source2 = UserRegistrationSource.objects.create(
183
- user=self.user, source=self.source2, first_registration=False
184
- )
185
-
186
- def test_user_with_sources_serializer_fields(self):
187
- """Test UserWithSources serializer includes sources information."""
188
- serializer = UserWithSourcesSerializer(self.user)
189
- data = serializer.data
190
-
191
- # Check basic user fields
192
- self.assertIn("id", data)
193
- self.assertIn("email", data)
194
- self.assertIn("first_name", data)
195
- self.assertIn("last_name", data)
196
-
197
- # Check sources fields
198
- self.assertIn("sources", data)
199
- self.assertIn("primary_source", data)
200
-
201
- # Check sources data
202
- sources = data["sources"]
203
- self.assertEqual(len(sources), 2)
204
-
205
- # Check primary source
206
- primary_source = data["primary_source"]
207
- self.assertIsNotNone(primary_source)
208
- self.assertEqual(primary_source["url"], "https://reforms.ai")
209
- self.assertEqual(primary_source["name"], "Unreal Dashboard")
210
-
211
- def test_user_with_sources_no_sources(self):
212
- """Test UserWithSources serializer for user without sources."""
213
- user_without_sources = User.objects.create(
214
- username="nosources", email="nosources@example.com"
215
- )
216
- serializer = UserWithSourcesSerializer(user_without_sources)
217
- data = serializer.data
218
-
219
- self.assertEqual(len(data["sources"]), 0)
220
- self.assertIsNone(data["primary_source"])
221
-
222
-
223
- class OTPRequestSerializerTest(TestCase):
224
- def test_valid_otp_request(self):
225
- """Test valid OTP request with source_url."""
226
- data = {
227
- "email": "test@example.com",
228
- "source_url": "https://reforms.ai",
229
- }
230
- serializer = OTPRequestSerializer(data=data)
231
- self.assertTrue(serializer.is_valid())
232
- self.assertEqual(serializer.validated_data["email"], "test@example.com")
233
- self.assertEqual(
234
- serializer.validated_data["source_url"], "https://reforms.ai"
235
- )
236
-
237
- def test_valid_otp_request_without_source_url(self):
238
- """Test valid OTP request without source_url."""
239
- data = {"email": "test@example.com"}
240
- serializer = OTPRequestSerializer(data=data)
241
- self.assertTrue(serializer.is_valid())
242
- self.assertEqual(serializer.validated_data["email"], "test@example.com")
243
- self.assertNotIn("source_url", serializer.validated_data)
244
-
245
- def test_valid_otp_request_empty_source_url(self):
246
- """Test valid OTP request with empty source_url."""
247
- data = {"email": "test@example.com", "source_url": ""}
248
- serializer = OTPRequestSerializer(data=data)
249
- self.assertTrue(serializer.is_valid())
250
- self.assertEqual(serializer.validated_data["email"], "test@example.com")
251
- self.assertIsNone(serializer.validated_data.get("source_url"))
252
-
253
- def test_invalid_email(self):
254
- """Test invalid email in OTP request."""
255
- data = {
256
- "email": "invalid-email",
257
- "source_url": "https://reforms.ai",
258
- }
259
- serializer = OTPRequestSerializer(data=data)
260
- self.assertFalse(serializer.is_valid())
261
- self.assertIn("email", serializer.errors)
262
-
263
- def test_missing_email(self):
264
- """Test missing email in OTP request."""
265
- data = {"source_url": "https://reforms.ai"}
266
- serializer = OTPRequestSerializer(data=data)
267
- self.assertFalse(serializer.is_valid())
268
- self.assertIn("email", serializer.errors)
269
-
270
- def test_invalid_source_url(self):
271
- """Test invalid source_url in OTP request."""
272
- data = {"email": "test@example.com", "source_url": "not-a-url"}
273
- serializer = OTPRequestSerializer(data=data)
274
- self.assertFalse(serializer.is_valid())
275
- self.assertIn("source_url", serializer.errors)
276
-
277
-
278
- class OTPVerifySerializerTest(TestCase):
279
- def test_valid_otp_verify(self):
280
- """Test valid OTP verification with source_url."""
281
- data = {
282
- "email": "test@example.com",
283
- "otp": "123456",
284
- "source_url": "https://reforms.ai",
285
- }
286
- serializer = OTPVerifySerializer(data=data)
287
- self.assertTrue(serializer.is_valid())
288
- self.assertEqual(serializer.validated_data["email"], "test@example.com")
289
- self.assertEqual(serializer.validated_data["otp"], "123456")
290
- self.assertEqual(
291
- serializer.validated_data["source_url"], "https://reforms.ai"
292
- )
293
-
294
- def test_valid_otp_verify_without_source_url(self):
295
- """Test valid OTP verification without source_url."""
296
- data = {"email": "test@example.com", "otp": "123456"}
297
- serializer = OTPVerifySerializer(data=data)
298
- self.assertTrue(serializer.is_valid())
299
- self.assertEqual(serializer.validated_data["email"], "test@example.com")
300
- self.assertEqual(serializer.validated_data["otp"], "123456")
301
- self.assertNotIn("source_url", serializer.validated_data)
302
-
303
- def test_invalid_otp_format(self):
304
- """Test invalid OTP format."""
305
- data = {
306
- "email": "test@example.com",
307
- "otp": "12345", # Too short
308
- "source_url": "https://reforms.ai",
309
- }
310
- serializer = OTPVerifySerializer(data=data)
311
- self.assertFalse(serializer.is_valid())
312
- self.assertIn("otp", serializer.errors)
313
-
314
- def test_invalid_otp_characters(self):
315
- """Test invalid OTP characters."""
316
- data = {
317
- "email": "test@example.com",
318
- "otp": "12345a", # Contains letter
319
- "source_url": "https://reforms.ai",
320
- }
321
- serializer = OTPVerifySerializer(data=data)
322
- self.assertFalse(serializer.is_valid())
323
- self.assertIn("otp", serializer.errors)
324
-
325
- def test_missing_required_fields(self):
326
- """Test missing required fields."""
327
- data = {"source_url": "https://reforms.ai"}
328
- serializer = OTPVerifySerializer(data=data)
329
- self.assertFalse(serializer.is_valid())
330
- self.assertIn("email", serializer.errors)
331
- self.assertIn("otp", serializer.errors)