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,255 +0,0 @@
1
- from django.urls import reverse
2
- from django.contrib.auth import get_user_model
3
- from rest_framework.test import APITestCase
4
- from rest_framework import status
5
- from rest_framework_simplejwt.tokens import RefreshToken
6
- from unittest.mock import patch
7
-
8
- from ..models import OTPSecret
9
-
10
- User = get_user_model()
11
-
12
-
13
- class OTPViewsTest(APITestCase):
14
- """Test OTP authentication views."""
15
-
16
- def setUp(self):
17
- self.email = "test@example.com"
18
- self.source_url = "https://reforms.ai"
19
- self.otp_request_url = reverse("otp-request-otp")
20
- self.otp_verify_url = reverse("otp-verify-otp")
21
-
22
- @patch("apps.accounts.services.otp_service.OTPService.request_otp")
23
- def test_otp_request_success(self, mock_request_otp):
24
- """Test successful OTP request."""
25
- mock_request_otp.return_value = (True, "success")
26
-
27
- data = {"email": self.email}
28
- response = self.client.post(self.otp_request_url, data)
29
-
30
- self.assertEqual(response.status_code, status.HTTP_200_OK)
31
- self.assertIn("message", response.data)
32
- mock_request_otp.assert_called_once_with(self.email, None)
33
-
34
- @patch("apps.accounts.services.otp_service.OTPService.request_otp")
35
- def test_otp_request_with_source_url(self, mock_request_otp):
36
- """Test successful OTP request with source_url."""
37
- mock_request_otp.return_value = (True, "success")
38
-
39
- data = {"email": self.email, "source_url": self.source_url}
40
- response = self.client.post(self.otp_request_url, data)
41
-
42
- self.assertEqual(response.status_code, status.HTTP_200_OK)
43
- self.assertIn("message", response.data)
44
- mock_request_otp.assert_called_once_with(self.email, self.source_url)
45
-
46
- @patch("apps.accounts.services.otp_service.OTPService.request_otp")
47
- def test_otp_request_failure(self, mock_request_otp):
48
- """Test OTP request failure."""
49
- mock_request_otp.return_value = (False, "email_send_failed")
50
-
51
- data = {"email": self.email}
52
- response = self.client.post(self.otp_request_url, data)
53
-
54
- self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
55
- self.assertIn("error", response.data)
56
-
57
- def test_otp_request_invalid_email(self):
58
- """Test OTP request with invalid email."""
59
- data = {"email": "invalid-email"}
60
- response = self.client.post(self.otp_request_url, data)
61
-
62
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
63
-
64
- def test_otp_request_missing_email(self):
65
- """Test OTP request without email."""
66
- response = self.client.post(self.otp_request_url, {})
67
-
68
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
69
-
70
- def test_otp_request_invalid_source_url(self):
71
- """Test OTP request with invalid source_url."""
72
- data = {"email": self.email, "source_url": "not-a-url"}
73
- response = self.client.post(self.otp_request_url, data)
74
-
75
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
76
-
77
- @patch("apps.accounts.services.otp_service.OTPService.verify_otp")
78
- def test_otp_verify_success(self, mock_verify_otp):
79
- """Test successful OTP verification."""
80
- user = User.objects.create_user(email=self.email, username="testuser")
81
- mock_verify_otp.return_value = user
82
-
83
- data = {"email": self.email, "otp": "123456"}
84
- response = self.client.post(self.otp_verify_url, data)
85
-
86
- self.assertEqual(response.status_code, status.HTTP_200_OK)
87
- self.assertIn("access", response.data)
88
- self.assertIn("refresh", response.data)
89
- self.assertIn("user", response.data)
90
- self.assertEqual(response.data["user"]["email"], self.email)
91
-
92
- @patch("apps.accounts.services.otp_service.OTPService.verify_otp")
93
- def test_otp_verify_with_source_url(self, mock_verify_otp):
94
- """Test successful OTP verification with source_url."""
95
- user = User.objects.create_user(email=self.email, username="testuser")
96
- mock_verify_otp.return_value = user
97
-
98
- data = {"email": self.email, "otp": "123456", "source_url": self.source_url}
99
- response = self.client.post(self.otp_verify_url, data)
100
-
101
- self.assertEqual(response.status_code, status.HTTP_200_OK)
102
- self.assertIn("access", response.data)
103
- self.assertIn("refresh", response.data)
104
- self.assertIn("user", response.data)
105
- mock_verify_otp.assert_called_once_with(self.email, "123456", self.source_url)
106
-
107
- @patch("apps.accounts.services.otp_service.OTPService.verify_otp")
108
- def test_otp_verify_failure(self, mock_verify_otp):
109
- """Test OTP verification failure."""
110
- mock_verify_otp.return_value = None
111
-
112
- data = {"email": self.email, "otp": "123456"}
113
- response = self.client.post(self.otp_verify_url, data)
114
-
115
- self.assertEqual(response.status_code, status.HTTP_410_GONE)
116
- self.assertIn("error", response.data)
117
-
118
- def test_otp_verify_invalid_data(self):
119
- """Test OTP verification with invalid data."""
120
- # Invalid OTP length
121
- data = {"email": self.email, "otp": "12345"} # Too short
122
- response = self.client.post(self.otp_verify_url, data)
123
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
124
-
125
- # Invalid OTP characters
126
- data = {"email": self.email, "otp": "12345a"} # Contains letter
127
- response = self.client.post(self.otp_verify_url, data)
128
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
129
-
130
- # Missing required fields
131
- data = {"source_url": self.source_url}
132
- response = self.client.post(self.otp_verify_url, data)
133
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
134
-
135
-
136
- class UserViewsTest(APITestCase):
137
- """Test user profile views."""
138
-
139
- def setUp(self):
140
- self.user = User.objects.create_user(
141
- email="test@example.com", username="testuser", password="testpass123"
142
- )
143
- self.client.force_authenticate(user=self.user)
144
-
145
- def test_user_detail_authenticated(self):
146
- """Test getting user detail when authenticated."""
147
- url = reverse("profile_detail")
148
- response = self.client.get(url)
149
-
150
- self.assertEqual(response.status_code, status.HTTP_200_OK)
151
- self.assertEqual(response.data["email"], self.user.email)
152
- # username field removed from serializer
153
-
154
- def test_user_detail_unauthenticated(self):
155
- """Test getting user detail when not authenticated."""
156
- self.client.force_authenticate(user=None)
157
- url = reverse("profile_detail")
158
- response = self.client.get(url)
159
-
160
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
161
-
162
-
163
- class TokenRefreshViewTest(APITestCase):
164
- """Test token refresh view."""
165
-
166
-
167
-
168
- def setUp(self):
169
- self.user = User.objects.create_user(
170
- email="test@example.com", username="testuser"
171
- )
172
- self.refresh = RefreshToken.for_user(self.user)
173
-
174
- def test_token_refresh_success(self):
175
- """Test successful token refresh."""
176
- url = reverse("token_refresh")
177
- data = {"refresh": str(self.refresh)}
178
- response = self.client.post(url, data)
179
-
180
- self.assertEqual(response.status_code, status.HTTP_200_OK)
181
- self.assertIn("access", response.data)
182
-
183
- def test_token_refresh_invalid_token(self):
184
- """Test token refresh with invalid token."""
185
- url = reverse("token_refresh")
186
- data = {"refresh": "invalid-token"}
187
- response = self.client.post(url, data)
188
-
189
- self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
190
-
191
- def test_token_refresh_missing_token(self):
192
- """Test token refresh without token."""
193
- url = reverse("token_refresh")
194
- response = self.client.post(url, {})
195
-
196
- self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
197
-
198
-
199
- class IntegrationTest(APITestCase):
200
- """Integration tests for OTP flow."""
201
-
202
-
203
-
204
- def setUp(self):
205
- self.email = "test@example.com"
206
- self.otp_request_url = reverse("otp-request-otp")
207
- self.otp_verify_url = reverse("otp-verify-otp")
208
-
209
- @patch("apps.mailer.services.email_service.EmailService.send_templated_email")
210
- def test_full_otp_flow(self, mock_email):
211
- """Test complete OTP flow."""
212
- mock_email.return_value = True
213
-
214
- # Step 1: Request OTP
215
- data = {"email": self.email}
216
- response = self.client.post(self.otp_request_url, data)
217
- self.assertEqual(response.status_code, status.HTTP_200_OK)
218
-
219
- # Step 2: Get OTP from database
220
- otp = OTPSecret.objects.get(email=self.email)
221
- self.assertIsNotNone(otp)
222
-
223
- # Step 3: Verify OTP
224
- data = {"email": self.email, "otp": otp.secret}
225
- response = self.client.post(self.otp_verify_url, data)
226
- self.assertEqual(response.status_code, status.HTTP_200_OK)
227
- self.assertIn("access", response.data)
228
- self.assertIn("refresh", response.data)
229
- self.assertIn("user", response.data)
230
-
231
- @patch("apps.mailer.services.email_service.EmailService.send_templated_email")
232
- def test_full_otp_flow_with_source_url(self, mock_email):
233
- """Test complete OTP flow with source_url."""
234
- mock_email.return_value = True
235
-
236
- # Step 1: Request OTP with source_url
237
- data = {"email": self.email, "source_url": "https://reforms.ai"}
238
- response = self.client.post(self.otp_request_url, data)
239
- self.assertEqual(response.status_code, status.HTTP_200_OK)
240
-
241
- # Step 2: Get OTP from database
242
- otp = OTPSecret.objects.get(email=self.email)
243
- self.assertIsNotNone(otp)
244
-
245
- # Step 3: Verify OTP with source_url
246
- data = {
247
- "email": self.email,
248
- "otp": otp.secret,
249
- "source_url": "https://reforms.ai",
250
- }
251
- response = self.client.post(self.otp_verify_url, data)
252
- self.assertEqual(response.status_code, status.HTTP_200_OK)
253
- self.assertIn("access", response.data)
254
- self.assertIn("refresh", response.data)
255
- self.assertIn("user", response.data)
@@ -1 +0,0 @@
1
- # Tests package
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Script to run newsletter manager tests
4
- """
5
- import os
6
- import sys
7
- import django
8
- from django.conf import settings
9
-
10
- # Add the project root to Python path
11
- project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
12
- sys.path.insert(0, project_root)
13
-
14
- # Setup Django
15
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')
16
- django.setup()
17
-
18
- # Import and run tests
19
- from django.test.utils import get_runner
20
- from django.conf import settings
21
-
22
- def run_tests():
23
- """Run the newsletter tests"""
24
- TestRunner = get_runner(settings)
25
- test_runner = TestRunner()
26
-
27
- # Test modules to run
28
- test_modules = [
29
- 'mailer.tests.test_newsletter_manager',
30
- 'mailer.tests.test_newsletter_models',
31
- ]
32
-
33
- print("Running Newsletter Manager Tests...")
34
- print("=" * 50)
35
-
36
- failures = test_runner.run_tests(test_modules)
37
-
38
- if failures:
39
- print(f"\n❌ {failures} test(s) failed!")
40
- return False
41
- else:
42
- print("\n✅ All tests passed!")
43
- return True
44
-
45
- if __name__ == '__main__':
46
- success = run_tests()
47
- sys.exit(0 if success else 1)
@@ -1,256 +0,0 @@
1
- """
2
- Integration tests for email tracking functionality.
3
- """
4
-
5
- from django.test import TestCase, Client, override_settings
6
- from django.contrib.auth import get_user_model
7
- from django.urls import reverse
8
- from django.core import mail
9
- from unittest.mock import patch
10
-
11
- from ..models import Newsletter, EmailLog, NewsletterSubscription
12
- from ..services.email_service import NewsletterEmailService
13
-
14
- User = get_user_model()
15
-
16
-
17
- @override_settings(EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend')
18
- class EmailTrackingIntegrationTestCase(TestCase):
19
- """Integration tests for email tracking with real email sending"""
20
-
21
- def setUp(self):
22
- """Set up test data"""
23
- self.client = Client()
24
-
25
- # Create test user
26
- self.user = User.objects.create_user(
27
- username='testuser',
28
- email='test@example.com',
29
- password='testpass123',
30
- is_active=True
31
- )
32
-
33
- # Create test newsletter
34
- self.newsletter = Newsletter.objects.create(
35
- title='Integration Test Newsletter',
36
- description='Test newsletter for integration testing',
37
- is_active=True
38
- )
39
-
40
- # Create newsletter subscription
41
- NewsletterSubscription.objects.create(
42
- user=self.user,
43
- newsletter=self.newsletter,
44
- email=self.user.email, # Add email field
45
- is_active=True
46
- )
47
-
48
- self.email_service = NewsletterEmailService()
49
-
50
- def test_send_email_with_tracking_creates_log(self):
51
- """Test that sending email with tracking creates EmailLog"""
52
- # Clear any existing mail
53
- mail.outbox = []
54
-
55
- # Send email with tracking
56
- result = self.email_service.send_bulk_email(
57
- recipients=['test@example.com'],
58
- subject='Test Email with Tracking',
59
- email_title='Test Title',
60
- main_text='This is a test email with tracking enabled.',
61
- button_text='Click Me',
62
- button_url='https://example.com/test',
63
- enable_tracking=True,
64
- newsletter=self.newsletter
65
- )
66
-
67
- # Check result
68
- self.assertEqual(result['sent_count'], 1)
69
- self.assertEqual(result['failed_count'], 0)
70
-
71
- # Check that email was sent
72
- self.assertEqual(len(mail.outbox), 1)
73
- sent_email = mail.outbox[0]
74
- self.assertEqual(sent_email.to, ['test@example.com'])
75
- self.assertEqual(sent_email.subject, 'Test Email with Tracking')
76
-
77
- # Check that EmailLog was created
78
- email_logs = EmailLog.objects.filter(newsletter=self.newsletter)
79
- self.assertEqual(email_logs.count(), 1)
80
-
81
- email_log = email_logs.first()
82
- self.assertEqual(email_log.recipient, 'test@example.com')
83
- self.assertEqual(email_log.status, EmailLog.EmailLogStatus.SENT)
84
- self.assertIsNotNone(email_log.sent_at)
85
-
86
- # Check HTML version for tracking (tracking is only in HTML version)
87
- self.assertTrue(hasattr(sent_email, 'alternatives') and sent_email.alternatives,
88
- "Email should have HTML alternative for tracking")
89
-
90
- html_body = sent_email.alternatives[0][0]
91
- self.assertIn('track/open/', html_body) # Tracking pixel URL
92
- self.assertIn('track/click/', html_body) # Click tracking URL
93
- self.assertIn(str(email_log.id), html_body) # Tracking ID should be in HTML
94
-
95
- def test_tracking_pixel_marks_email_as_opened(self):
96
- """Test that accessing tracking pixel marks email as opened"""
97
- # First send an email to create EmailLog
98
- result = self.email_service.send_bulk_email(
99
- recipients=['test@example.com'],
100
- subject='Test Email',
101
- email_title='Test',
102
- main_text='Test content',
103
- enable_tracking=True,
104
- newsletter=self.newsletter
105
- )
106
-
107
- # Get the created EmailLog
108
- email_log = EmailLog.objects.filter(newsletter=self.newsletter).first()
109
- self.assertIsNotNone(email_log)
110
- self.assertFalse(email_log.is_opened)
111
-
112
- # Access tracking pixel
113
- tracking_url = reverse('cfg_newsletter:track-open', kwargs={'email_log_id': email_log.id})
114
- response = self.client.get(tracking_url)
115
-
116
- # Check response is a valid image
117
- self.assertEqual(response.status_code, 200)
118
- self.assertEqual(response['Content-Type'], 'image/gif')
119
-
120
- # Check that email is marked as opened
121
- email_log.refresh_from_db()
122
- self.assertTrue(email_log.is_opened)
123
- self.assertIsNotNone(email_log.opened_at)
124
-
125
- def test_click_tracking_marks_email_as_clicked(self):
126
- """Test that click tracking marks email as clicked and redirects"""
127
- # First send an email to create EmailLog
128
- result = self.email_service.send_bulk_email(
129
- recipients=['test@example.com'],
130
- subject='Test Email',
131
- email_title='Test',
132
- main_text='Test content',
133
- button_text='Click Here',
134
- button_url='https://example.com/destination',
135
- enable_tracking=True,
136
- newsletter=self.newsletter
137
- )
138
-
139
- # Get the created EmailLog
140
- email_log = EmailLog.objects.filter(newsletter=self.newsletter).first()
141
- self.assertIsNotNone(email_log)
142
- self.assertFalse(email_log.is_clicked)
143
-
144
- # Access click tracking URL
145
- tracking_url = reverse('cfg_newsletter:track-click', kwargs={'email_log_id': email_log.id})
146
- tracking_url += '?url=https://example.com/destination'
147
-
148
- response = self.client.get(tracking_url)
149
-
150
- # Check redirect response
151
- self.assertEqual(response.status_code, 302)
152
- self.assertEqual(response.url, 'https://example.com/destination')
153
-
154
- # Check that email is marked as clicked
155
- email_log.refresh_from_db()
156
- self.assertTrue(email_log.is_clicked)
157
- self.assertIsNotNone(email_log.clicked_at)
158
-
159
- def test_newsletter_email_sending_with_tracking(self):
160
- """Test complete newsletter sending workflow with tracking"""
161
- # Clear any existing mail from signals
162
- mail.outbox = []
163
-
164
- # Send newsletter email
165
- result = self.email_service.send_newsletter_email(
166
- newsletter=self.newsletter,
167
- subject='Newsletter Test',
168
- email_title='Monthly Newsletter',
169
- main_text='This is our monthly newsletter content.',
170
- button_text='Read More',
171
- button_url='https://example.com/newsletter',
172
- send_to_all=True # Send to all subscribers
173
- )
174
-
175
- # Check result
176
- self.assertEqual(result['sent_count'], 1) # One subscriber
177
- self.assertEqual(result['failed_count'], 0)
178
-
179
- # Check email was sent
180
- self.assertEqual(len(mail.outbox), 1)
181
-
182
- # Check EmailLog was created with tracking
183
- email_logs = EmailLog.objects.filter(newsletter=self.newsletter)
184
- self.assertEqual(email_logs.count(), 1)
185
-
186
- email_log = email_logs.first()
187
- self.assertEqual(email_log.status, EmailLog.EmailLogStatus.SENT)
188
-
189
- # Test tracking functionality
190
- # 1. Test open tracking
191
- open_url = reverse('cfg_newsletter:track-open', kwargs={'email_log_id': email_log.id})
192
- open_response = self.client.get(open_url)
193
- self.assertEqual(open_response.status_code, 200)
194
-
195
- # 2. Test click tracking
196
- click_url = reverse('cfg_newsletter:track-click', kwargs={'email_log_id': email_log.id})
197
- click_url += '?url=https://example.com/newsletter'
198
- click_response = self.client.get(click_url)
199
- self.assertEqual(click_response.status_code, 302)
200
-
201
- # Verify tracking was recorded
202
- email_log.refresh_from_db()
203
- self.assertTrue(email_log.is_opened)
204
- self.assertTrue(email_log.is_clicked)
205
- self.assertIsNotNone(email_log.opened_at)
206
- self.assertIsNotNone(email_log.clicked_at)
207
-
208
- def test_multiple_opens_only_record_first_time(self):
209
- """Test that multiple opens only record the first timestamp"""
210
- # Send email
211
- result = self.email_service.send_bulk_email(
212
- recipients=['test@example.com'],
213
- subject='Test Email',
214
- email_title='Test',
215
- main_text='Test content',
216
- enable_tracking=True,
217
- newsletter=self.newsletter
218
- )
219
-
220
- email_log = EmailLog.objects.filter(newsletter=self.newsletter).first()
221
-
222
- # First open
223
- open_url = reverse('cfg_newsletter:track-open', kwargs={'email_log_id': email_log.id})
224
- self.client.get(open_url)
225
-
226
- email_log.refresh_from_db()
227
- first_opened_at = email_log.opened_at
228
- self.assertIsNotNone(first_opened_at)
229
-
230
- # Second open (should not change timestamp)
231
- self.client.get(open_url)
232
-
233
- email_log.refresh_from_db()
234
- self.assertEqual(email_log.opened_at, first_opened_at)
235
-
236
- def test_tracking_with_invalid_email_log_id(self):
237
- """Test tracking with invalid email log ID handles gracefully"""
238
- import uuid
239
-
240
- # Test open tracking with invalid ID
241
- invalid_id = uuid.uuid4()
242
- open_url = reverse('cfg_newsletter:track-open', kwargs={'email_log_id': invalid_id})
243
- open_response = self.client.get(open_url)
244
-
245
- # Should still return tracking pixel
246
- self.assertEqual(open_response.status_code, 200)
247
- self.assertEqual(open_response['Content-Type'], 'image/gif')
248
-
249
- # Test click tracking with invalid ID
250
- click_url = reverse('cfg_newsletter:track-click', kwargs={'email_log_id': invalid_id})
251
- click_url += '?url=https://example.com/test'
252
- click_response = self.client.get(click_url)
253
-
254
- # Should still redirect
255
- self.assertEqual(click_response.status_code, 302)
256
- self.assertEqual(click_response.url, 'https://example.com/test')