django-webhook-subscriber 0.4.0__py3-none-any.whl → 2.0.0__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.
- django_webhook_subscriber/__init__.py +7 -1
- django_webhook_subscriber/admin.py +831 -182
- django_webhook_subscriber/apps.py +3 -20
- django_webhook_subscriber/conf.py +11 -24
- django_webhook_subscriber/delivery.py +414 -159
- django_webhook_subscriber/http.py +51 -0
- django_webhook_subscriber/management/commands/webhook.py +169 -0
- django_webhook_subscriber/management/commands/webhook_cache.py +173 -0
- django_webhook_subscriber/management/commands/webhook_logs.py +226 -0
- django_webhook_subscriber/management/commands/webhook_performance_test.py +469 -0
- django_webhook_subscriber/management/commands/webhook_send.py +96 -0
- django_webhook_subscriber/management/commands/webhook_status.py +139 -0
- django_webhook_subscriber/managers.py +36 -14
- django_webhook_subscriber/migrations/0002_remove_webhookregistry_content_type_and_more.py +192 -0
- django_webhook_subscriber/models.py +291 -114
- django_webhook_subscriber/serializers.py +16 -50
- django_webhook_subscriber/tasks.py +434 -56
- django_webhook_subscriber/tests/factories.py +40 -0
- django_webhook_subscriber/tests/settings.py +27 -8
- django_webhook_subscriber/tests/test_delivery.py +453 -190
- django_webhook_subscriber/tests/test_http.py +32 -0
- django_webhook_subscriber/tests/test_managers.py +26 -37
- django_webhook_subscriber/tests/test_models.py +341 -251
- django_webhook_subscriber/tests/test_serializers.py +22 -56
- django_webhook_subscriber/tests/test_tasks.py +477 -189
- django_webhook_subscriber/tests/test_utils.py +98 -94
- django_webhook_subscriber/utils.py +87 -69
- django_webhook_subscriber/validators.py +53 -0
- django_webhook_subscriber-2.0.0.dist-info/METADATA +774 -0
- django_webhook_subscriber-2.0.0.dist-info/RECORD +38 -0
- django_webhook_subscriber/management/commands/check_webhook_tasks.py +0 -113
- django_webhook_subscriber/management/commands/clean_webhook_logs.py +0 -65
- django_webhook_subscriber/management/commands/test_webhook.py +0 -96
- django_webhook_subscriber/signals.py +0 -152
- django_webhook_subscriber/testing.py +0 -14
- django_webhook_subscriber/tests/test_signals.py +0 -268
- django_webhook_subscriber-0.4.0.dist-info/METADATA +0 -448
- django_webhook_subscriber-0.4.0.dist-info/RECORD +0 -33
- {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/WHEEL +0 -0
- {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,219 +1,507 @@
|
|
|
1
|
-
from unittest.mock import
|
|
1
|
+
from unittest.mock import Mock, patch
|
|
2
2
|
|
|
3
|
+
import requests
|
|
3
4
|
from django.test import TestCase
|
|
4
|
-
from django.
|
|
5
|
-
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
from django.utils import timezone
|
|
6
6
|
|
|
7
|
-
from django_webhook_subscriber
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
from django_webhook_subscriber import models, tasks
|
|
8
|
+
|
|
9
|
+
from .factories import (
|
|
10
|
+
ContentTypeFactory,
|
|
11
|
+
WebhookDeliveryLogFactory,
|
|
12
|
+
WebhookSubscriberFactory,
|
|
13
|
+
WebhookSubscriptionFactory,
|
|
11
14
|
)
|
|
12
15
|
|
|
13
16
|
|
|
14
|
-
class
|
|
17
|
+
class DeliverWebhookTasksTests(TestCase):
|
|
15
18
|
def setUp(self):
|
|
16
|
-
|
|
17
|
-
self.example_content_type = ContentType.objects.get_for_model(User)
|
|
18
|
-
|
|
19
|
-
# Create test webhooks
|
|
20
|
-
self.webhook = WebhookRegistry.objects.create(
|
|
21
|
-
name='Test Webhook',
|
|
22
|
-
content_type=self.example_content_type,
|
|
23
|
-
event_signals=['CREATE', 'UPDATE'],
|
|
24
|
-
endpoint='https://example.com/webhook',
|
|
25
|
-
secret='test-secret-key',
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
# Create test payload
|
|
29
|
-
self.payload = {
|
|
30
|
-
'pk': 1,
|
|
31
|
-
'model': 'auth.user',
|
|
32
|
-
'event_signal': 'created',
|
|
33
|
-
'fields': {'username': 'testuser', 'email': 'test@example.com'},
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
self.event_signal = 'created'
|
|
19
|
+
self.subscription = WebhookSubscriptionFactory()
|
|
37
20
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# Call the task function
|
|
45
|
-
result = async_deliver_webhook(
|
|
46
|
-
self.webhook.id, self.payload, self.event_signal
|
|
21
|
+
def mock_response(self, status_code=200, json_data=None):
|
|
22
|
+
mock_resp = requests.Response()
|
|
23
|
+
mock_resp.status_code = status_code
|
|
24
|
+
mock_resp._content = (
|
|
25
|
+
b'{"key": "value"}' if json_data is None else json_data
|
|
47
26
|
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
return mock_resp
|
|
28
|
+
|
|
29
|
+
def test_deliver_webhook_on_subscription_does_not_exist_error(self):
|
|
30
|
+
result = tasks.deliver_webhook("", {}, subscription_id=9999)
|
|
31
|
+
self.assertIn("error", result)
|
|
32
|
+
self.assertIn("Subscription not found", result["error"])
|
|
33
|
+
|
|
34
|
+
def test_deliver_webhook_on_no_active_subscription_error(self):
|
|
35
|
+
# deactivate subscription
|
|
36
|
+
self.subscription.is_active = False
|
|
37
|
+
self.subscription.save()
|
|
38
|
+
result = tasks.deliver_webhook(
|
|
39
|
+
url="",
|
|
40
|
+
payload={},
|
|
41
|
+
subscription_id=self.subscription.id,
|
|
54
42
|
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
result =
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
self.
|
|
43
|
+
self.assertIn("skipped", result)
|
|
44
|
+
self.assertIn("Subscription inactive", result["skipped"])
|
|
45
|
+
|
|
46
|
+
@patch("django_webhook_subscriber.tasks.webhook_session")
|
|
47
|
+
def test_deliver_webhook_on_successful_delivery(
|
|
48
|
+
self, mock_session_context
|
|
49
|
+
):
|
|
50
|
+
# Mocking the session.post request to return a successful response
|
|
51
|
+
mock_session = mock_session_context.return_value.__enter__.return_value
|
|
52
|
+
mock_session.post.return_value = self.mock_response(200)
|
|
53
|
+
|
|
54
|
+
result = tasks.deliver_webhook(
|
|
55
|
+
url="http://example.com/webhook",
|
|
56
|
+
payload={"key": "value"},
|
|
57
|
+
subscription_id=self.subscription.id,
|
|
70
58
|
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@patch('django_webhook_subscriber.delivery.deliver_webhook')
|
|
79
|
-
def test_async_deliver_nonexistent_webhook(self, mock_deliver):
|
|
80
|
-
# Use a non-existent webhook ID
|
|
81
|
-
non_existent_id = 999
|
|
82
|
-
|
|
83
|
-
# Call the task function
|
|
84
|
-
result = async_deliver_webhook(
|
|
85
|
-
non_existent_id,
|
|
86
|
-
self.payload,
|
|
87
|
-
self.event_signal,
|
|
59
|
+
# Asserting result indicates success
|
|
60
|
+
mock_session.post.assert_called_once_with(
|
|
61
|
+
"http://example.com/webhook",
|
|
62
|
+
json={"key": "value"},
|
|
63
|
+
headers=tasks.generate_headers(self.subscription.subscriber),
|
|
64
|
+
timeout=30,
|
|
88
65
|
)
|
|
66
|
+
# Asserting result indicates success
|
|
67
|
+
self.assertTrue(result["success"])
|
|
68
|
+
self.assertEqual(result["status_code"], 200)
|
|
69
|
+
self.assertIn("duration_ms", result)
|
|
70
|
+
self.assertEqual(result["attempt"], 1)
|
|
71
|
+
|
|
72
|
+
# Asserting DeliveryLog was created
|
|
73
|
+
log = models.WebhookDeliveryLog.objects.first()
|
|
74
|
+
self.assertIsNotNone(log)
|
|
75
|
+
self.assertEqual(log.subscription, self.subscription)
|
|
76
|
+
self.assertEqual(log.response_status, 200)
|
|
77
|
+
self.assertEqual(log.response_body, '{"key": "value"}')
|
|
78
|
+
|
|
79
|
+
@patch("django_webhook_subscriber.tasks.webhook_session")
|
|
80
|
+
def test_deliver_webhook_on_failed_delivery(self, mock_session_context):
|
|
81
|
+
# Mocking the session.post request to return a successful response
|
|
82
|
+
mock_session = mock_session_context.return_value.__enter__.return_value
|
|
83
|
+
mock_session.post.return_value = self.mock_response(400)
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
with patch(
|
|
86
|
+
"django_webhook_subscriber.tasks._should_retry_delivery"
|
|
87
|
+
) as mock_should_retry:
|
|
88
|
+
mock_should_retry.return_value = True
|
|
89
|
+
result = tasks.deliver_webhook(
|
|
90
|
+
url="http://example.com/webhook",
|
|
91
|
+
payload={"key": "value"},
|
|
92
|
+
subscription_id=self.subscription.id,
|
|
93
|
+
)
|
|
94
|
+
# Asserting result indicates success
|
|
95
|
+
mock_session.post.assert_called_once_with(
|
|
96
|
+
"http://example.com/webhook",
|
|
97
|
+
json={"key": "value"},
|
|
98
|
+
headers=tasks.generate_headers(self.subscription.subscriber),
|
|
99
|
+
timeout=30,
|
|
100
|
+
)
|
|
101
|
+
# Asserting result indicates success
|
|
102
|
+
self.assertFalse(result["success"])
|
|
103
|
+
self.assertEqual(result["status_code"], 400)
|
|
104
|
+
self.assertIn("duration_ms", result)
|
|
105
|
+
self.assertEqual(result["attempt"], 1)
|
|
106
|
+
self.assertTrue(result["will_retry"])
|
|
107
|
+
|
|
108
|
+
# Asserting DeliveryLog was created
|
|
109
|
+
log = models.WebhookDeliveryLog.objects.first()
|
|
110
|
+
self.assertIsNotNone(log)
|
|
111
|
+
self.assertEqual(log.subscription, self.subscription)
|
|
112
|
+
self.assertEqual(log.response_status, 400)
|
|
113
|
+
self.assertEqual(log.response_body, '{"key": "value"}')
|
|
114
|
+
|
|
115
|
+
@patch("django_webhook_subscriber.tasks.webhook_session")
|
|
116
|
+
def test_deliver_webhook_on_timeout_error_during_delivery(
|
|
117
|
+
self, mock_session_context
|
|
118
|
+
):
|
|
119
|
+
# Mocking the session.post request to return a successful response
|
|
120
|
+
mock_session = mock_session_context.return_value.__enter__.return_value
|
|
121
|
+
mock_session.post.side_effect = requests.exceptions.Timeout("Timeout!")
|
|
122
|
+
|
|
123
|
+
result = tasks.deliver_webhook(
|
|
124
|
+
url="http://example.com/webhook",
|
|
125
|
+
payload={"key": "value"},
|
|
126
|
+
subscription_id=self.subscription.id,
|
|
127
|
+
)
|
|
128
|
+
# Asserting result indicates success
|
|
129
|
+
self.assertIn("error", result)
|
|
130
|
+
self.assertIn("Timeout error", result["error"])
|
|
131
|
+
self.assertEqual(result["attempt"], 1)
|
|
132
|
+
# Asserting DeliveryLog was created
|
|
133
|
+
log = models.WebhookDeliveryLog.objects.first()
|
|
134
|
+
self.assertIsNotNone(log)
|
|
135
|
+
self.assertEqual(log.subscription, self.subscription)
|
|
136
|
+
self.assertEqual(log.error_message, "Timeout after 30s")
|
|
137
|
+
|
|
138
|
+
@patch("django_webhook_subscriber.tasks.webhook_session")
|
|
139
|
+
def test_deliver_webhook_on_connection_error_during_delivery(
|
|
140
|
+
self, mock_session_context
|
|
141
|
+
):
|
|
142
|
+
# Mocking the session.post request to return a successful response
|
|
143
|
+
mock_session = mock_session_context.return_value.__enter__.return_value
|
|
144
|
+
mock_session.post.side_effect = requests.exceptions.ConnectionError(
|
|
145
|
+
"Connection error!"
|
|
146
|
+
)
|
|
92
147
|
|
|
93
|
-
|
|
94
|
-
|
|
148
|
+
result = tasks.deliver_webhook(
|
|
149
|
+
url="http://example.com/webhook",
|
|
150
|
+
payload={"key": "value"},
|
|
151
|
+
subscription_id=self.subscription.id,
|
|
152
|
+
)
|
|
153
|
+
# Asserting result indicates success
|
|
154
|
+
self.assertIn("error", result)
|
|
155
|
+
self.assertIn("Connection error", result["error"])
|
|
156
|
+
self.assertEqual(result["attempt"], 1)
|
|
157
|
+
# Asserting DeliveryLog was created
|
|
158
|
+
log = models.WebhookDeliveryLog.objects.first()
|
|
159
|
+
self.assertIsNotNone(log)
|
|
160
|
+
self.assertEqual(log.subscription, self.subscription)
|
|
161
|
+
self.assertIn("Connection error", log.error_message)
|
|
162
|
+
|
|
163
|
+
@patch("django_webhook_subscriber.tasks.webhook_session")
|
|
164
|
+
def test_deliver_webhook_on_exception_during_delivery(
|
|
165
|
+
self, mock_session_context
|
|
166
|
+
):
|
|
167
|
+
# Mocking the session.post request to return a successful response
|
|
168
|
+
mock_session = mock_session_context.return_value.__enter__.return_value
|
|
169
|
+
mock_session.post.side_effect = Exception("Some error!")
|
|
170
|
+
|
|
171
|
+
result = tasks.deliver_webhook(
|
|
172
|
+
url="http://example.com/webhook",
|
|
173
|
+
payload={"key": "value"},
|
|
174
|
+
subscription_id=self.subscription.id,
|
|
175
|
+
)
|
|
176
|
+
# Asserting result indicates success
|
|
177
|
+
self.assertIn("error", result)
|
|
178
|
+
self.assertIn("Unexpected error", result["error"])
|
|
179
|
+
self.assertEqual(result["attempt"], 1)
|
|
180
|
+
# Asserting DeliveryLog was created
|
|
181
|
+
log = models.WebhookDeliveryLog.objects.first()
|
|
182
|
+
self.assertIsNotNone(log)
|
|
183
|
+
self.assertEqual(log.subscription, self.subscription)
|
|
184
|
+
self.assertIn("Unexpected error", log.error_message)
|
|
185
|
+
|
|
186
|
+
def test_should_retry_delivery_on_max_retries_exceeded(self):
|
|
187
|
+
should_retry = tasks._should_retry_delivery(
|
|
188
|
+
self.subscription,
|
|
189
|
+
attempt=self.subscription.subscriber.max_retries + 1,
|
|
190
|
+
)
|
|
191
|
+
self.assertFalse(should_retry)
|
|
95
192
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
second_webhook = WebhookRegistry.objects.create(
|
|
100
|
-
name='Second Webhook',
|
|
101
|
-
content_type=self.example_content_type,
|
|
102
|
-
event_signals=['CREATE'],
|
|
103
|
-
endpoint='https://example2.com/webhook',
|
|
104
|
-
secret='test-secret-key-2',
|
|
193
|
+
def test_should_retry_delivery_on_status_code_retryable(self):
|
|
194
|
+
should_retry = tasks._should_retry_delivery(
|
|
195
|
+
self.subscription, attempt=1, status_code=500
|
|
105
196
|
)
|
|
197
|
+
self.assertTrue(should_retry)
|
|
106
198
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
199
|
+
def test_should_retry_delivery_on_status_code_not_retryable(self):
|
|
200
|
+
should_retry = tasks._should_retry_delivery(
|
|
201
|
+
self.subscription, attempt=1, status_code=429
|
|
202
|
+
)
|
|
203
|
+
self.assertFalse(should_retry)
|
|
204
|
+
|
|
205
|
+
@patch("django_webhook_subscriber.tasks.deliver_webhook")
|
|
206
|
+
def test_schedule_retry_on_successful_scheduling(self, mock_deliver):
|
|
207
|
+
tasks._schedule_retry(
|
|
208
|
+
url="http://example.com/webhook",
|
|
209
|
+
payload={"key": "value"},
|
|
210
|
+
subscription_id=self.subscription.id,
|
|
211
|
+
current_attempt=1,
|
|
212
|
+
)
|
|
213
|
+
mock_deliver.apply_async.assert_called_once_with(
|
|
214
|
+
args=(
|
|
215
|
+
"http://example.com/webhook",
|
|
216
|
+
{"key": "value"},
|
|
217
|
+
self.subscription.id,
|
|
218
|
+
),
|
|
219
|
+
kwargs={"attempt": 2},
|
|
220
|
+
countdown=60, # default
|
|
221
|
+
)
|
|
110
222
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
223
|
+
@patch("django_webhook_subscriber.tasks.deliver_webhook")
|
|
224
|
+
def test_schedule_retry_on_failure(self, mock_deliver):
|
|
225
|
+
mock_deliver.apply_async.side_effect = Exception("Some error!")
|
|
226
|
+
# Asserting no raise
|
|
227
|
+
tasks._schedule_retry(
|
|
228
|
+
url="http://example.com/webhook",
|
|
229
|
+
payload={"key": "value"},
|
|
230
|
+
subscription_id=self.subscription.id,
|
|
231
|
+
current_attempt=1,
|
|
232
|
+
)
|
|
233
|
+
mock_deliver.apply_async.assert_called_once()
|
|
114
234
|
|
|
115
|
-
|
|
235
|
+
@patch("django_webhook_subscriber.tasks._schedule_retry")
|
|
236
|
+
def test_handle_delivery_exception_on_retry(self, mock_schedule_retry):
|
|
237
|
+
log = WebhookDeliveryLogFactory(subscription=self.subscription)
|
|
116
238
|
with patch(
|
|
117
|
-
|
|
118
|
-
) as
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
239
|
+
"django_webhook_subscriber.tasks._should_retry_delivery"
|
|
240
|
+
) as mock_should_retry:
|
|
241
|
+
mock_should_retry.return_value = True
|
|
242
|
+
tasks._handle_delivery_exception(
|
|
243
|
+
log,
|
|
244
|
+
self.subscription,
|
|
245
|
+
"Some error!",
|
|
246
|
+
url="http://example.com/webhook",
|
|
247
|
+
payload={"key": "value"},
|
|
248
|
+
attempt=1,
|
|
249
|
+
)
|
|
250
|
+
mock_should_retry.assert_called_once_with(self.subscription, 1)
|
|
251
|
+
mock_schedule_retry.assert_called_once_with(
|
|
252
|
+
"http://example.com/webhook",
|
|
253
|
+
{"key": "value"},
|
|
254
|
+
self.subscription.id,
|
|
255
|
+
1,
|
|
125
256
|
)
|
|
126
257
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
endpoint='https://example2.com/webhook',
|
|
169
|
-
secret='test-secret-key-2',
|
|
170
|
-
),
|
|
171
|
-
WebhookRegistry.objects.create(
|
|
172
|
-
name='Third Webhook',
|
|
173
|
-
content_type=self.example_content_type,
|
|
174
|
-
event_signals=['UPDATE'], # Doesn't match our test event
|
|
175
|
-
endpoint='https://example3.com/webhook',
|
|
176
|
-
secret='test-secret-key-3',
|
|
177
|
-
),
|
|
178
|
-
WebhookRegistry.objects.create(
|
|
179
|
-
name='Inactive Webhook',
|
|
180
|
-
content_type=self.example_content_type,
|
|
181
|
-
event_signals=['CREATE'],
|
|
182
|
-
endpoint='https://example4.com/webhook',
|
|
183
|
-
secret='test-secret-key-4',
|
|
184
|
-
is_active=False,
|
|
185
|
-
),
|
|
258
|
+
|
|
259
|
+
class ProcessWebhookDeliveryBatchTasksTests(TestCase):
|
|
260
|
+
def setUp(self):
|
|
261
|
+
content_type = ContentTypeFactory()
|
|
262
|
+
subscriber = WebhookSubscriberFactory(content_type=content_type)
|
|
263
|
+
self.subscriptions = WebhookSubscriptionFactory.create_batch(
|
|
264
|
+
3,
|
|
265
|
+
subscriber=subscriber,
|
|
266
|
+
event_name="created",
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def test_process_webhook_delivery_batch_on_no_subscriptions(self):
|
|
270
|
+
results = tasks.process_webhook_delivery_batch([])
|
|
271
|
+
self.assertIn("processed", results)
|
|
272
|
+
self.assertEqual(results["processed"], 0)
|
|
273
|
+
self.assertIn("error", results)
|
|
274
|
+
self.assertEqual(results["error"], "Empty batch")
|
|
275
|
+
|
|
276
|
+
@patch("django_webhook_subscriber.tasks._validate_subscription_data")
|
|
277
|
+
def test_process_webhook_delivery_batch_on_invalid_subscriptions(
|
|
278
|
+
self, mock_validate
|
|
279
|
+
):
|
|
280
|
+
mock_validate.return_value = False
|
|
281
|
+
results = tasks.process_webhook_delivery_batch([{"id": 9999}])
|
|
282
|
+
self.assertIn("processed", results)
|
|
283
|
+
self.assertEqual(results["processed"], 0)
|
|
284
|
+
self.assertIn("error", results)
|
|
285
|
+
self.assertEqual(results["error"], "No valid subscriptions")
|
|
286
|
+
|
|
287
|
+
@patch("django_webhook_subscriber.tasks._validate_subscription_data")
|
|
288
|
+
@patch("django_webhook_subscriber.tasks.group")
|
|
289
|
+
@patch("django_webhook_subscriber.tasks.deliver_webhook")
|
|
290
|
+
def test_process_webhook_delivery_batch_on_successful_processing(
|
|
291
|
+
self, mock_deliver, mock_group, mock_validate
|
|
292
|
+
):
|
|
293
|
+
mock_validate.return_value = True
|
|
294
|
+
|
|
295
|
+
# Mock the group execution to return successful results
|
|
296
|
+
mock_job = mock_group.return_value
|
|
297
|
+
mock_job.apply_async.return_value.get.return_value = [
|
|
298
|
+
{"success": True, "status_code": 200} for _ in self.subscriptions
|
|
186
299
|
]
|
|
187
300
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
301
|
+
results = tasks.process_webhook_delivery_batch(
|
|
302
|
+
[
|
|
303
|
+
{
|
|
304
|
+
"id": sub.id,
|
|
305
|
+
"url": "https://example.com/webhooks",
|
|
306
|
+
"payload": {"key": "value"},
|
|
307
|
+
}
|
|
308
|
+
for sub in self.subscriptions
|
|
309
|
+
]
|
|
310
|
+
)
|
|
191
311
|
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
312
|
+
# Verify signatures were created
|
|
313
|
+
self.assertEqual(mock_deliver.s.call_count, 3)
|
|
314
|
+
|
|
315
|
+
# Verify group was executed
|
|
316
|
+
mock_group.assert_called_once()
|
|
317
|
+
mock_job.apply_async.assert_called_once()
|
|
318
|
+
|
|
319
|
+
# Assert results
|
|
320
|
+
self.assertIn("batch_id", results)
|
|
321
|
+
self.assertIn("task_ids", results)
|
|
322
|
+
self.assertEqual(results["processed"], 3)
|
|
323
|
+
self.assertEqual(results["total_requested"], 3)
|
|
324
|
+
|
|
325
|
+
@patch("django_webhook_subscriber.tasks._validate_subscription_data")
|
|
326
|
+
@patch("django_webhook_subscriber.tasks.group")
|
|
327
|
+
@patch("django_webhook_subscriber.tasks.deliver_webhook")
|
|
328
|
+
def test_process_webhook_delivery_batch_on_exception_during_processing(
|
|
329
|
+
self, mock_deliver, mock_group, mock_validate
|
|
330
|
+
):
|
|
331
|
+
mock_validate.return_value = True
|
|
332
|
+
|
|
333
|
+
# Mock the group execution to raise an exception
|
|
334
|
+
mock_job = mock_group.return_value
|
|
335
|
+
mock_job.apply_async.side_effect = Exception("Some error!")
|
|
336
|
+
|
|
337
|
+
results = tasks.process_webhook_delivery_batch(
|
|
338
|
+
[
|
|
339
|
+
{
|
|
340
|
+
"id": sub.id,
|
|
341
|
+
"url": "https://example.com/webhooks",
|
|
342
|
+
"payload": {"key": "value"},
|
|
343
|
+
}
|
|
344
|
+
for sub in self.subscriptions
|
|
345
|
+
]
|
|
346
|
+
)
|
|
196
347
|
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
# Get IDs of all webhooks
|
|
200
|
-
webhook_ids = [webhook.id for webhook in webhooks]
|
|
348
|
+
# Verify signatures were created
|
|
349
|
+
self.assertEqual(mock_deliver.s.call_count, 3)
|
|
201
350
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
351
|
+
# Verify group was executed
|
|
352
|
+
mock_group.assert_called_once()
|
|
353
|
+
mock_job.apply_async.assert_called_once()
|
|
206
354
|
|
|
207
|
-
|
|
208
|
-
|
|
355
|
+
# Assert results
|
|
356
|
+
self.assertEqual(results["error"], "e=Exception('Some error!')")
|
|
357
|
+
self.assertEqual(results["processed"], 0)
|
|
358
|
+
|
|
359
|
+
def test_validate_subscription_data_on_valid_data(self):
|
|
360
|
+
results = tasks._validate_subscription_data(
|
|
361
|
+
{
|
|
362
|
+
"id": self.subscriptions[0].id,
|
|
363
|
+
"url": "https://example.com",
|
|
364
|
+
"payload": {},
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
self.assertTrue(results)
|
|
368
|
+
|
|
369
|
+
def test_validate_subscription_data_on_invalid_data(self):
|
|
370
|
+
results = tasks._validate_subscription_data(
|
|
371
|
+
{
|
|
372
|
+
"id": 9999,
|
|
373
|
+
"payload": {},
|
|
374
|
+
}
|
|
375
|
+
)
|
|
376
|
+
self.assertFalse(results)
|
|
209
377
|
|
|
210
|
-
# Verify signatures were created for each webhook
|
|
211
|
-
self.assertEqual(mock_async_task.s.call_count, len(webhook_ids))
|
|
212
378
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
379
|
+
class TaskMaintenanceTasksTests(TestCase):
|
|
380
|
+
def setUp(self):
|
|
381
|
+
# Default 30 days retention
|
|
382
|
+
self.timestamp_created = timezone.now() - timezone.timedelta(days=30)
|
|
383
|
+
self.log = WebhookDeliveryLogFactory()
|
|
384
|
+
self.subscription = self.log.subscription
|
|
385
|
+
self.subscriber = self.subscription.subscriber
|
|
386
|
+
|
|
387
|
+
def mock_response(self, status_code=200, json_data=None):
|
|
388
|
+
mock_resp = requests.Response()
|
|
389
|
+
mock_resp.status_code = status_code
|
|
390
|
+
mock_resp._content = (
|
|
391
|
+
b'{"key": "value"}' if json_data is None else json_data
|
|
392
|
+
)
|
|
393
|
+
return mock_resp
|
|
394
|
+
|
|
395
|
+
def test_cleanup_webhook_logs_on_empty_query(self):
|
|
396
|
+
results = tasks.cleanup_webhook_logs(30)
|
|
397
|
+
self.assertIn("deleted", results)
|
|
398
|
+
self.assertEqual(results["deleted"], 0)
|
|
399
|
+
self.assertIn("cutoff_date", results)
|
|
400
|
+
|
|
401
|
+
def test_cleanup_webhook_logs_on_successful_deletion(self):
|
|
402
|
+
# Changing created timestamp to older than 30 days
|
|
403
|
+
self.log.created_at = self.timestamp_created
|
|
404
|
+
self.log.save()
|
|
405
|
+
results = tasks.cleanup_webhook_logs(days=1)
|
|
406
|
+
self.assertIn("deleted", results)
|
|
407
|
+
self.assertEqual(results["deleted"], 1)
|
|
408
|
+
self.assertIn("cutoff_date", results)
|
|
409
|
+
# Assert logs are deleted
|
|
410
|
+
remaining_logs = models.WebhookDeliveryLog.objects.count()
|
|
411
|
+
self.assertEqual(remaining_logs, 0)
|
|
412
|
+
|
|
413
|
+
def test_cleanup_webhook_logs_passing_subscription(self):
|
|
414
|
+
# Changing created timestamp to older than 30 days
|
|
415
|
+
self.log.created_at = self.timestamp_created
|
|
416
|
+
self.log.save()
|
|
417
|
+
results = tasks.cleanup_webhook_logs(
|
|
418
|
+
days=1, subscription_id=self.subscription.id
|
|
419
|
+
)
|
|
420
|
+
self.assertIn("deleted", results)
|
|
421
|
+
self.assertEqual(results["deleted"], 1)
|
|
422
|
+
self.assertIn("cutoff_date", results)
|
|
423
|
+
# Assert logs are deleted
|
|
424
|
+
remaining_logs = models.WebhookDeliveryLog.objects.count()
|
|
425
|
+
self.assertEqual(remaining_logs, 0)
|
|
426
|
+
|
|
427
|
+
def test_cleanup_webhook_logs_on_exception_during_cleanup(self):
|
|
428
|
+
# Setting exception raise
|
|
429
|
+
with patch(
|
|
430
|
+
"django_webhook_subscriber."
|
|
431
|
+
"models.WebhookDeliveryLog.objects.filter"
|
|
432
|
+
) as mock_filter:
|
|
433
|
+
mock_filter.side_effect = Exception("Some error!")
|
|
434
|
+
results = tasks.cleanup_webhook_logs(30)
|
|
435
|
+
self.assertIn("error", results)
|
|
436
|
+
self.assertIn("Some error!", results["error"])
|
|
437
|
+
|
|
438
|
+
def test_webhook_connectivity_check_on_no_subscribers(self):
|
|
439
|
+
results = tasks.test_webhook_connectivity([9999])
|
|
440
|
+
self.assertEqual(results["tested"], 0)
|
|
441
|
+
self.assertEqual(results["results"], [])
|
|
442
|
+
self.assertEqual(results["error"], "No active subscribers found")
|
|
443
|
+
|
|
444
|
+
@patch("django_webhook_subscriber.tasks._test_single_endpoint")
|
|
445
|
+
def test_webhook_connectivity_check_on_successful_check(self, mock_test):
|
|
446
|
+
# Mocking the session.post request to return a successful response
|
|
447
|
+
mock_test.return_value = {
|
|
448
|
+
"subscriber_id": self.subscriber.id,
|
|
449
|
+
"subscriber_name": self.subscriber.name,
|
|
450
|
+
"url": self.subscriber.target_url,
|
|
451
|
+
"success": True,
|
|
452
|
+
"status_code": 400,
|
|
453
|
+
"duration_ms": 123,
|
|
454
|
+
"error": None,
|
|
455
|
+
}
|
|
456
|
+
results = tasks.test_webhook_connectivity([self.subscriber.id])
|
|
457
|
+
|
|
458
|
+
self.assertEqual(results["tested"], 1)
|
|
459
|
+
self.assertEqual(results["successful_responses"], 0)
|
|
460
|
+
self.assertEqual(results["successful"], 1)
|
|
461
|
+
self.assertEqual(results["failed"], 0)
|
|
462
|
+
self.assertIn("results", results)
|
|
463
|
+
|
|
464
|
+
@patch("django_webhook_subscriber.tasks._test_single_endpoint")
|
|
465
|
+
def test_webhook_connectivity_check_on_exception_raise(self, mock_test):
|
|
466
|
+
# Mocking the session.post request to return a successful response
|
|
467
|
+
mock_test.side_effect = Exception("Some error!")
|
|
468
|
+
results = tasks.test_webhook_connectivity([self.subscriber.id])
|
|
469
|
+
|
|
470
|
+
self.assertIn("error", results)
|
|
471
|
+
self.assertIn("Some error!", results["error"])
|
|
472
|
+
|
|
473
|
+
def test_single_endpoint_check_on_successful_check(self):
|
|
474
|
+
session = Mock()
|
|
475
|
+
session.head.return_value = self.mock_response(200)
|
|
476
|
+
result = tasks._test_single_endpoint(session, self.subscriber)
|
|
477
|
+
self.assertEqual(result["subscriber_id"], self.subscriber.id)
|
|
478
|
+
self.assertEqual(result["subscriber_name"], self.subscriber.name)
|
|
479
|
+
self.assertEqual(result["url"], self.subscriber.target_url)
|
|
480
|
+
self.assertTrue(result["success"])
|
|
481
|
+
self.assertEqual(result["status_code"], 200)
|
|
482
|
+
self.assertIn("duration_ms", result)
|
|
483
|
+
self.assertIsNone(result["error"])
|
|
484
|
+
|
|
485
|
+
def test_single_endpoint_check_on_failed_check(self):
|
|
486
|
+
session = Mock()
|
|
487
|
+
session.head.return_value = self.mock_response(500)
|
|
488
|
+
result = tasks._test_single_endpoint(session, self.subscriber)
|
|
489
|
+
self.assertEqual(result["subscriber_id"], self.subscriber.id)
|
|
490
|
+
self.assertEqual(result["subscriber_name"], self.subscriber.name)
|
|
491
|
+
self.assertEqual(result["url"], self.subscriber.target_url)
|
|
492
|
+
self.assertTrue(result["success"])
|
|
493
|
+
self.assertEqual(result["status_code"], 500)
|
|
494
|
+
self.assertIn("duration_ms", result)
|
|
495
|
+
self.assertIsNone(result["error"])
|
|
496
|
+
|
|
497
|
+
def test_single_endpoint_check_on_exception_check(self):
|
|
498
|
+
session = Mock()
|
|
499
|
+
session.head.side_effect = Exception("Some error!")
|
|
500
|
+
result = tasks._test_single_endpoint(session, self.subscriber)
|
|
501
|
+
self.assertEqual(result["subscriber_id"], self.subscriber.id)
|
|
502
|
+
self.assertEqual(result["subscriber_name"], self.subscriber.name)
|
|
503
|
+
self.assertEqual(result["url"], self.subscriber.target_url)
|
|
504
|
+
self.assertFalse(result["success"])
|
|
505
|
+
self.assertIsNone(result["status_code"])
|
|
506
|
+
self.assertIn("duration_ms", result)
|
|
507
|
+
self.assertIn("Some error!", result["error"])
|