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.
Files changed (41) hide show
  1. django_webhook_subscriber/__init__.py +7 -1
  2. django_webhook_subscriber/admin.py +831 -182
  3. django_webhook_subscriber/apps.py +3 -20
  4. django_webhook_subscriber/conf.py +11 -24
  5. django_webhook_subscriber/delivery.py +414 -159
  6. django_webhook_subscriber/http.py +51 -0
  7. django_webhook_subscriber/management/commands/webhook.py +169 -0
  8. django_webhook_subscriber/management/commands/webhook_cache.py +173 -0
  9. django_webhook_subscriber/management/commands/webhook_logs.py +226 -0
  10. django_webhook_subscriber/management/commands/webhook_performance_test.py +469 -0
  11. django_webhook_subscriber/management/commands/webhook_send.py +96 -0
  12. django_webhook_subscriber/management/commands/webhook_status.py +139 -0
  13. django_webhook_subscriber/managers.py +36 -14
  14. django_webhook_subscriber/migrations/0002_remove_webhookregistry_content_type_and_more.py +192 -0
  15. django_webhook_subscriber/models.py +291 -114
  16. django_webhook_subscriber/serializers.py +16 -50
  17. django_webhook_subscriber/tasks.py +434 -56
  18. django_webhook_subscriber/tests/factories.py +40 -0
  19. django_webhook_subscriber/tests/settings.py +27 -8
  20. django_webhook_subscriber/tests/test_delivery.py +453 -190
  21. django_webhook_subscriber/tests/test_http.py +32 -0
  22. django_webhook_subscriber/tests/test_managers.py +26 -37
  23. django_webhook_subscriber/tests/test_models.py +341 -251
  24. django_webhook_subscriber/tests/test_serializers.py +22 -56
  25. django_webhook_subscriber/tests/test_tasks.py +477 -189
  26. django_webhook_subscriber/tests/test_utils.py +98 -94
  27. django_webhook_subscriber/utils.py +87 -69
  28. django_webhook_subscriber/validators.py +53 -0
  29. django_webhook_subscriber-2.0.0.dist-info/METADATA +774 -0
  30. django_webhook_subscriber-2.0.0.dist-info/RECORD +38 -0
  31. django_webhook_subscriber/management/commands/check_webhook_tasks.py +0 -113
  32. django_webhook_subscriber/management/commands/clean_webhook_logs.py +0 -65
  33. django_webhook_subscriber/management/commands/test_webhook.py +0 -96
  34. django_webhook_subscriber/signals.py +0 -152
  35. django_webhook_subscriber/testing.py +0 -14
  36. django_webhook_subscriber/tests/test_signals.py +0 -268
  37. django_webhook_subscriber-0.4.0.dist-info/METADATA +0 -448
  38. django_webhook_subscriber-0.4.0.dist-info/RECORD +0 -33
  39. {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/WHEEL +0 -0
  40. {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/licenses/LICENSE +0 -0
  41. {django_webhook_subscriber-0.4.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,197 +1,460 @@
1
- from unittest.mock import patch, MagicMock
1
+ from unittest.mock import Mock, patch
2
2
 
3
- from django.test import TestCase, override_settings
4
3
  from django.contrib.contenttypes.models import ContentType
5
- from django.contrib.auth.models import User
4
+ from django.core.cache import cache
5
+ from django.test import TestCase, override_settings
6
6
 
7
- from django_webhook_subscriber.models import (
8
- WebhookDeliveryLog,
9
- WebhookRegistry,
10
- )
11
- from django_webhook_subscriber.delivery import (
12
- get_webhook_for_model,
13
- process_and_deliver_webhook,
14
- prepare_headers,
15
- deliver_webhook,
7
+ from django_webhook_subscriber import delivery, models, utils
8
+
9
+ from .factories import (
10
+ WebhookDeliveryLogFactory,
11
+ WebhookSubscriberFactory,
12
+ WebhookSubscriptionFactory,
16
13
  )
17
14
 
18
15
 
19
- @override_settings(WEBHOOK_SUBSCRIBER={'DEFAULT_USE_ASYNC': False})
20
- class WebhookDeliveryTests(TestCase):
16
+ class WebhookDeliveryProcessorTests(TestCase):
17
+ def setUp(self):
18
+ self.processor = delivery.WebhookDeliveryProcessor()
19
+ # Clear cache before each test
20
+ delivery.clear_webhook_cache()
21
+
22
+ # Create test content type and model instance
23
+ self.content_type = ContentType.objects.get_for_model(
24
+ models.WebhookDeliveryLog
25
+ )
26
+ self.instance = WebhookDeliveryLogFactory()
27
+ self.subscriber = WebhookSubscriberFactory(
28
+ content_type=self.content_type
29
+ )
30
+ self.subscription = WebhookSubscriptionFactory(
31
+ subscriber=self.subscriber, event_name="created"
32
+ )
33
+
34
+ def tearDown(self):
35
+ cache.clear()
36
+ delivery.clear_webhook_cache()
37
+
38
+ def test_delivery_process_on_initialization(self):
39
+ self.assertEqual(self.processor.cache_ttl, 300)
40
+ with override_settings(WEBHOOK_SUBSCRIBER={"WEBHOOK_CACHE_TTL": 600}):
41
+ processor = delivery.WebhookDeliveryProcessor()
42
+ self.assertEqual(processor.cache_ttl, 600)
43
+
44
+ def test_send_webhook_on_webhooks_disabled(self):
45
+ with utils.disable_webhooks():
46
+ result = self.processor.send_webhook(self.instance, "created")
47
+ self.assertEqual(result, {"skipped": "Webhooks disabled"})
48
+
49
+ def test_send_webhook_on_no_subscriptions(self):
50
+ with patch.object(
51
+ self.processor, "_get_subscriptions_cached"
52
+ ) as mock_get_subs:
53
+ mock_get_subs.return_value = []
54
+ result = self.processor.send_webhook(self.instance, "created")
55
+ self.assertEqual(result, {"skipped": "No subscriptions"})
56
+
57
+ def test_send_webhook_on_successful_generation(self):
58
+ # Generating subscription, and subscriber
59
+ subscription = WebhookSubscriptionFactory(
60
+ subscriber=self.subscriber,
61
+ event_name="created",
62
+ )
63
+ with (
64
+ patch.object(
65
+ self.processor, "_get_subscriptions_cached"
66
+ ) as mock_get_subs,
67
+ patch.object(
68
+ self.processor, "_group_subscriptions_by_serializer"
69
+ ) as mock_group,
70
+ patch.object(self.processor, "_generate_payload") as mock_generate,
71
+ patch.object(self.processor, "_deliver_webhooks") as mock_deliver,
72
+ ):
73
+ mock_get_subs.return_value = [{"id": subscription.id}]
74
+ mock_group.return_value = {None: [{"id": subscription.id}]}
75
+ mock_generate.return_value = {"id": self.instance.id}
76
+ mock_deliver.return_value = {"delivered": 1}
77
+
78
+ self.processor.send_webhook(self.instance, "created")
79
+ mock_get_subs.assert_called_once_with(self.instance, "created")
80
+ mock_group.assert_called_once_with([{"id": subscription.id}])
81
+ mock_generate.assert_called_once_with(
82
+ self.instance, "created", None
83
+ )
84
+ mock_deliver.assert_called_once()
85
+ args = mock_deliver.call_args[0][0]
86
+ self.assertEqual(args[0]["id"], subscription.id)
87
+
88
+ def test_send_webhook_on_exception_raised(self):
89
+ # Generating subscription, and subscriber
90
+ with patch.object(
91
+ self.processor, "_get_subscriptions_cached"
92
+ ) as mock_get_subs:
93
+ mock_get_subs.side_effect = Exception("Test exception")
94
+ results = self.processor.send_webhook(self.instance, "created")
95
+ self.assertIn("error", results)
96
+ self.assertIn("Error sending webhook", results["error"])
97
+
98
+ def test_group_subscriptions_by_serializer(self):
99
+ # Generating subscriptions with different serializers
100
+ results = self.processor._group_subscriptions_by_serializer([])
101
+ self.assertEqual(dict(results), {})
102
+ # Creating subscriptions
103
+
104
+ results = self.processor._group_subscriptions_by_serializer(
105
+ [
106
+ {"id": 1, "serializer_class": None},
107
+ {"id": 2, "serializer_class": "some.path.SerializerA"},
108
+ ]
109
+ )
110
+ self.assertEqual(
111
+ dict(results),
112
+ {
113
+ None: [{"id": 1, "serializer_class": None}],
114
+ "some.path.SerializerA": [
115
+ {"id": 2, "serializer_class": "some.path.SerializerA"}
116
+ ],
117
+ },
118
+ )
119
+
120
+ @patch("django_webhook_subscriber.delivery.get_content_type_id")
121
+ def test_get_subscriptions_cached_on_cache_hit(self, mock_get_ct_id):
122
+ mock_get_ct_id.return_value = 1
123
+ cached_data = [{"id": 1, "url": "http://example.com"}]
124
+ cache.set("webhook_subscriptions:1:created", cached_data)
125
+
126
+ result = self.processor._get_subscriptions_cached(
127
+ self.instance, "created"
128
+ )
129
+
130
+ self.assertEqual(result, cached_data)
131
+ mock_get_ct_id.assert_called_once()
132
+
133
+ @patch("django_webhook_subscriber.delivery.get_content_type_id")
134
+ @patch.object(
135
+ delivery.WebhookDeliveryProcessor, "_fetch_subscriptions_from_db"
136
+ )
137
+ def test_get_subscriptions_cached_on_cache_miss(
138
+ self, mock_fetch, mock_get_ct_id
139
+ ):
140
+ mock_get_ct_id.return_value = 1
141
+ db_data = [{"id": 1, "url": "http://example.com"}]
142
+ mock_fetch.return_value = db_data
143
+
144
+ result = self.processor._get_subscriptions_cached(
145
+ self.instance, "created"
146
+ )
147
+
148
+ self.assertEqual(result, db_data)
149
+ mock_fetch.assert_called_once_with(1, "created")
150
+ # Verify it was cached
151
+ cached = cache.get("webhook_subscriptions:1:created")
152
+ self.assertEqual(cached, db_data)
153
+
154
+ @patch("django_webhook_subscriber.delivery.get_content_type_id")
155
+ @patch.object(
156
+ delivery.WebhookDeliveryProcessor, "_fetch_subscriptions_from_db"
157
+ )
158
+ def test_get_subscriptions_cached_on_empty_cache(
159
+ self, mock_fetch, mock_get_ct_id
160
+ ):
161
+ mock_get_ct_id.return_value = 1
162
+ mock_fetch.return_value = []
163
+
164
+ result = self.processor._get_subscriptions_cached(
165
+ self.instance, "created"
166
+ )
167
+
168
+ self.assertEqual(result, [])
169
+ mock_fetch.assert_called_once()
170
+
171
+ def test_fetch_subscriptions_from_db_on_no_subscriptions(self):
172
+ result = self.processor._fetch_subscriptions_from_db(
173
+ 999, "nonexistent"
174
+ )
175
+
176
+ self.assertEqual(result, [])
177
+
178
+ def test_fetch_subscriptions_from_db_on_existing_subscriptions(self):
179
+ # Create test data
180
+ result = self.processor._fetch_subscriptions_from_db(
181
+ self.content_type.id,
182
+ "created",
183
+ )
184
+
185
+ self.assertEqual(len(result), 1)
186
+ self.assertEqual(result[0]["id"], self.subscription.id)
187
+ self.assertEqual(result[0]["subscriber_id"], self.subscriber.id)
188
+ self.assertEqual(result[0]["url"], self.subscriber.target_url)
189
+
190
+ @patch("django_webhook_subscriber.delivery.serialize_webhook_instance")
191
+ def test_generate_payload_on_successful_serialization(
192
+ self, mock_serialize
193
+ ):
194
+ mock_serialize.return_value = {"field1": "value1", "field2": "value2"}
195
+
196
+ result = self.processor._generate_payload(
197
+ self.instance, "created", None
198
+ )
199
+
200
+ self.assertEqual(result["pk"], 1)
201
+ self.assertEqual(result["event_signal"], "created")
202
+ self.assertEqual(
203
+ result["source"], "django_webhook_subscriber.webhookdeliverylog"
204
+ )
205
+ self.assertEqual(
206
+ result["fields"], {"field1": "value1", "field2": "value2"}
207
+ )
208
+ self.assertIn("timestamp", result)
209
+
210
+ @patch("django_webhook_subscriber.delivery.serialize_webhook_instance")
211
+ def test_generate_payload_on_serialization_exception(self, mock_serialize):
212
+ mock_serialize.side_effect = Exception("Serialization failed")
213
+
214
+ result = self.processor._generate_payload(
215
+ self.instance, "created", None
216
+ )
217
+
218
+ self.assertEqual(result["pk"], 1)
219
+ self.assertEqual(result["event_signal"], "created")
220
+ self.assertIn("error", result)
221
+ self.assertEqual(result["fields"], {})
222
+
223
+ def test_deliver_webhooks_on_no_subscriptions(self):
224
+ result = self.processor._deliver_webhooks([])
225
+
226
+ self.assertEqual(result, {"processed": 0})
227
+
228
+ @patch("django_webhook_subscriber.delivery.process_webhook_delivery_batch")
229
+ def test_deliver_webhooks_on_single_batch_processing(self, mock_task):
230
+ mock_result = Mock()
231
+ mock_result.id = "task-123"
232
+ mock_task.delay.return_value = mock_result
233
+
234
+ subscriptions = [{"id": i} for i in range(10)]
235
+ result = self.processor._deliver_webhooks(subscriptions)
236
+
237
+ self.assertEqual(result["processed"], 10)
238
+ self.assertEqual(result["batches"], 1)
239
+ self.assertEqual(result["task_id"], "task-123")
240
+ mock_task.delay.assert_called_once()
241
+
242
+ @patch("django_webhook_subscriber.delivery.process_webhook_delivery_batch")
243
+ @override_settings(WEBHOOK_SUBSCRIBER={"MAX_BATCH_SIZE": 5})
244
+ def test_deliver_webhooks_on_multiple_batch_processing(self, mock_task):
245
+ mock_result1 = Mock()
246
+ mock_result1.id = "task-1"
247
+ mock_result2 = Mock()
248
+ mock_result2.id = "task-2"
249
+ mock_task.delay.side_effect = [mock_result1, mock_result2]
250
+
251
+ # Create processor with new settings
252
+ processor = delivery.WebhookDeliveryProcessor()
253
+ subscriptions = [{"id": i} for i in range(10)]
254
+ result = processor._deliver_webhooks(subscriptions)
255
+
256
+ self.assertEqual(result["processed"], 10)
257
+ self.assertEqual(result["batches"], 2)
258
+ self.assertEqual(len(result["task_ids"]), 2)
259
+ self.assertEqual(mock_task.delay.call_count, 2)
260
+
261
+ @patch("django_webhook_subscriber.delivery.process_webhook_delivery_batch")
262
+ def test_process_single_batch_on_successful_delivery(self, mock_task):
263
+ mock_result = Mock()
264
+ mock_result.id = "task-123"
265
+ mock_task.delay.return_value = mock_result
266
+
267
+ subscriptions = [{"id": 1}]
268
+ result = self.processor._process_single_batch(subscriptions)
269
+
270
+ self.assertEqual(result["processed"], 1)
271
+ self.assertEqual(result["batches"], 1)
272
+ self.assertEqual(result["task_id"], "task-123")
273
+
274
+ @patch("django_webhook_subscriber.delivery.process_webhook_delivery_batch")
275
+ def test_process_single_batch_on_delivery_exception(self, mock_task):
276
+ mock_task.delay.side_effect = Exception("Celery error")
277
+
278
+ subscriptions = [{"id": 1}]
279
+ result = self.processor._process_single_batch(subscriptions)
280
+
281
+ self.assertIn("error", result)
282
+ self.assertEqual(result["processed"], 0)
283
+
284
+ @patch("django_webhook_subscriber.delivery.process_webhook_delivery_batch")
285
+ def test_process_multiple_batches_on_successful_delivery(self, mock_task):
286
+ mock_result1 = Mock()
287
+ mock_result1.id = "task-1"
288
+ mock_result2 = Mock()
289
+ mock_result2.id = "task-2"
290
+ mock_task.delay.side_effect = [mock_result1, mock_result2]
291
+
292
+ subscriptions = [{"id": i} for i in range(10)]
293
+ result = self.processor._process_multiple_batches(subscriptions, 5)
294
+
295
+ self.assertEqual(result["processed"], 10)
296
+ self.assertEqual(result["batches"], 2)
297
+ self.assertEqual(len(result["task_ids"]), 2)
298
+
299
+ @patch("django_webhook_subscriber.delivery.process_webhook_delivery_batch")
300
+ def test_process_multiple_batches_on_delivery_exception(self, mock_task):
301
+ mock_result1 = Mock()
302
+ mock_result1.id = "task-1"
303
+ mock_task.delay.side_effect = [mock_result1, Exception("Celery error")]
304
+
305
+ subscriptions = [{"id": i} for i in range(10)]
306
+ result = self.processor._process_multiple_batches(subscriptions, 5)
307
+
308
+ self.assertEqual(result["processed"], 5)
309
+ self.assertEqual(result["batches"], 2)
310
+ self.assertEqual(len(result["task_ids"]), 1) # Only successful one
311
+ # Check that one batch has error
312
+ error_batch = next(b for b in result["batch_details"] if "error" in b)
313
+ self.assertIn("error", error_batch)
314
+
315
+ def test_clear_webhook_cache_on_no_parameters(self):
316
+ # Set up some cache data
317
+ cache.set("webhook_subscriptions:1:created", [{"id": 1}])
318
+ cache.set("webhook_subscriptions:2:updated", [{"id": 2}])
319
+
320
+ self.processor.clear_webhook_cache()
321
+
322
+ # Verify cache is cleared
323
+ self.assertIsNone(cache.get("webhook_subscriptions:1:created"))
324
+ self.assertIsNone(cache.get("webhook_subscriptions:2:updated"))
325
+
326
+ def test_clear_webhook_cache_on_content_type_only(self):
327
+ # Create subscriptions for testing
328
+ WebhookSubscriptionFactory(
329
+ subscriber=self.subscriber, event_name="updated"
330
+ )
331
+
332
+ # Set up cache
333
+ cache.set(
334
+ f"webhook_subscriptions:{self.content_type.id}:created",
335
+ [{"id": 1}],
336
+ )
337
+ cache.set(
338
+ f"webhook_subscriptions:{self.content_type.id}:updated",
339
+ [{"id": 2}],
340
+ )
341
+
342
+ self.processor.clear_webhook_cache(content_type=self.content_type)
343
+
344
+ # Verify both are cleared
345
+ self.assertIsNone(
346
+ cache.get(f"webhook_subscriptions:{self.content_type.id}:created")
347
+ )
348
+ self.assertIsNone(
349
+ cache.get(f"webhook_subscriptions:{self.content_type.id}:updated")
350
+ )
351
+
352
+ def test_clear_webhook_cache_on_content_type_and_event_name(self):
353
+ cache_key = f"webhook_subscriptions:{self.content_type.id}:created"
354
+ cache.set(cache_key, [{"id": 1}])
355
+
356
+ self.processor.clear_webhook_cache(
357
+ content_type=self.content_type, event_name="created"
358
+ )
359
+
360
+ self.assertIsNone(cache.get(cache_key))
361
+
362
+ def test_get_cache_stats_on_no_cache(self):
363
+ stats = self.processor.get_cache_stats()
364
+
365
+ self.assertEqual(stats["cached_keys"], 0)
366
+ self.assertEqual(stats["total_cached_subscriptions"], 0)
367
+ self.assertEqual(stats["cache_hit_ratio"], 0.0)
368
+ self.assertGreater(stats["total_possible_keys"], 0)
369
+
370
+ def test_get_cache_stats_on_existing_cache(self):
371
+ # Populate cache
372
+ cache_key = f"webhook_subscriptions:{self.content_type.id}:created"
373
+ cached_data = [{"id": 1}, {"id": 2}]
374
+ cache.set(cache_key, cached_data)
375
+
376
+ stats = self.processor.get_cache_stats()
377
+
378
+ self.assertEqual(stats["cached_keys"], 1)
379
+ self.assertEqual(stats["total_cached_subscriptions"], 2)
380
+ self.assertEqual(stats["cache_hit_ratio"], 50.0)
381
+ self.assertEqual(stats["total_possible_keys"], 2)
382
+
383
+
384
+ class WebhookDeliveryProcessorFunctionsTests(TestCase):
21
385
  def setUp(self):
22
- # Create a content type for the WebhookDeliveryLog model
23
- self.example_content_type = ContentType.objects.get_for_model(
24
- WebhookDeliveryLog
25
- )
26
- # Create test webhook registry
27
- self.webhook = WebhookRegistry.objects.create(
28
- name='Test Webhook',
29
- content_type=self.example_content_type,
30
- event_signals=['CREATE', 'UPDATE'],
31
- endpoint='http://example.com/webhook/',
32
- secret='test-secret-key',
33
- )
34
-
35
- # Create test log
36
- self.log = WebhookDeliveryLog.objects.create(
37
- webhook=self.webhook,
38
- event_signal='created',
39
- payload={'key': 'value'},
40
- )
41
-
42
- def test_get_webhook_for_model(self):
43
- # get_webhook for the model instance
44
- webhooks = get_webhook_for_model(self.log)
45
- self.assertEqual(len(webhooks), 1)
46
- self.assertEqual(webhooks[0], self.webhook)
47
- # get webhook for a different model instance
48
- user = User.objects.create(username='testuser')
49
- user_webhooks = get_webhook_for_model(user)
50
- self.assertEqual(len(user_webhooks), 0)
51
-
52
- def test_prepare_headers(self):
53
- headers = prepare_headers(self.webhook)
54
- self.assertEqual(headers['Content-Type'], 'application/json')
55
- self.assertEqual(headers['X-Secret'], 'test-secret-key')
56
-
57
- def test_prepare_headers_with_custom_headers(self):
58
- self.webhook.headers = {
59
- 'X-Custom': 'custom-value',
60
- 'Content-Type': 'text/plain',
61
- }
62
- self.webhook.save()
63
-
64
- headers = prepare_headers(self.webhook)
65
- self.assertEqual(headers['Content-Type'], 'text/plain')
66
- self.assertEqual(headers['X-Secret'], 'test-secret-key')
67
- self.assertEqual(headers['X-Custom'], 'custom-value')
68
-
69
- @patch('django_webhook_subscriber.delivery.deliver_webhook')
70
- def test_process_and_deliver_webhook(self, mock_deliver_webhook):
71
- # Create a second webhook with a non-matching event type
72
- non_matching_webhook = WebhookRegistry.objects.create(
73
- name='Non-Matching Webhook',
74
- content_type=self.example_content_type,
75
- event_signals=['DELETE'],
76
- endpoint='http://example.com/non-matching-webhook/',
77
- secret='non-matching-secret-key',
78
- )
79
-
80
- # Create a third webhook that is inactive
81
- inactive_webhook = WebhookRegistry.objects.create(
82
- name='Inactive Webhook',
83
- content_type=self.example_content_type,
84
- event_signals=['CREATE'],
85
- endpoint='http://example.com/inactive-webhook/',
86
- secret='inactive-secret-key',
87
- is_active=False,
88
- )
89
-
90
- # Call the function to process and deliver webhooks
91
- payload = {'key': 'value'}
92
- event_signal = 'created'
93
- process_and_deliver_webhook(self.log, event_signal, payload)
94
-
95
- # Verify that deliver_webhook was called only for the matching, active
96
- # webhook
97
- mock_deliver_webhook.assert_called_once_with(
98
- self.webhook,
99
- payload,
100
- event_signal,
101
- )
102
-
103
- # Ensure non-matching and inactive webhooks were not processed
104
- self.assertNotIn(
105
- non_matching_webhook, mock_deliver_webhook.call_args_list
106
- )
107
- self.assertNotIn(inactive_webhook, mock_deliver_webhook.call_args_list)
108
-
109
- @patch('requests.post')
110
- def test_deliver_webhook_success(self, mock_post):
111
- # Mock successful response
112
- mock_response = MagicMock()
113
- mock_response.status_code = 200
114
- mock_response.ok = True # TODO check without this
115
- mock_response.content = '{"status": "ok"}'
116
- mock_post.return_value = mock_response
117
-
118
- # Test delivery
119
- payload = {'key': 'value'}
120
- log = deliver_webhook(self.webhook, payload, 'created')
121
-
122
- # Verify the request was made properly
123
- mock_post.assert_called_once()
124
- args, kwargs = mock_post.call_args
125
- self.assertEqual(args[0], self.webhook.endpoint)
126
- self.assertEqual(kwargs['json'], payload)
127
- self.assertEqual(kwargs['headers']['X-Secret'], self.webhook.secret)
128
-
129
- # Verify log was created
130
- self.assertEqual(log.webhook, self.webhook)
131
- self.assertEqual(log.event_signal, 'created')
132
- self.assertEqual(log.response_status, 200)
133
- self.assertEqual(log.response_body, '{"status": "ok"}')
134
-
135
- # Verify the webhook was updated
136
- self.webhook.refresh_from_db()
137
- self.assertEqual(self.webhook.last_response, '{"status": "ok"}')
138
- self.assertIsNotNone(self.webhook.last_success)
139
-
140
- @patch('requests.post')
141
- def test_deliver_webhook_response_error(self, mock_post):
142
- # Mock successful response
143
- mock_response = MagicMock()
144
- mock_response.status_code = 400
145
- mock_response.content = '{"status": "error"}'
146
- mock_post.return_value = mock_response
147
-
148
- # Test delivery
149
- payload = {'key': 'value'}
150
- log = deliver_webhook(self.webhook, payload, 'created')
151
-
152
- # Verify log was created
153
- self.assertEqual(log.webhook, self.webhook)
154
- self.assertEqual(log.event_signal, 'created')
155
- self.assertEqual(log.response_status, 400)
156
- self.assertEqual(log.response_body, '{"status": "error"}')
157
-
158
- # Verify the webhook was updated
159
- self.webhook.refresh_from_db()
160
- self.assertEqual(self.webhook.last_response, '{"status": "error"}')
161
- self.assertIsNotNone(self.webhook.last_failure)
162
-
163
- @patch('requests.post')
164
- def test_deliver_webhook_keep_response_false(self, mock_post):
165
- self.webhook.keep_last_response = False
166
- # Mock successful response
167
- mock_response = MagicMock()
168
- mock_response.status_code = 200
169
- mock_response.ok = True # TODO check without this
170
- mock_response.content = '{"status": "ok"}'
171
- mock_post.return_value = mock_response
172
-
173
- # Test delivery
174
- payload = {'key': 'value'}
175
- deliver_webhook(self.webhook, payload, 'created')
176
-
177
- # Verify the webhook was updated
178
- self.webhook.refresh_from_db()
179
- self.assertIsNone(self.webhook.last_response)
180
- self.assertIsNotNone(self.webhook.last_success)
181
-
182
- @patch('requests.post')
183
- def test_deliver_webhook_failure(self, mock_post):
184
- # Mock successful response
185
- mock_post.side_effect = Exception("Connection error")
186
-
187
- # Test delivery
188
- payload = {'key': 'value'}
189
- log = deliver_webhook(self.webhook, payload, 'created')
190
-
191
- # Verify log was created with error
192
- self.assertEqual(log.error_message, "Connection error")
193
- self.assertIsNone(log.response_status)
194
-
195
- # Verify webhook was updated
196
- self.webhook.refresh_from_db()
197
- self.assertIsNotNone(self.webhook.last_failure)
386
+ self.processor = delivery.webhook_delivery_processor
387
+
388
+ def test_send_webhook_function_on_calling_the_right_method(self):
389
+ with patch.object(self.processor, "send_webhook") as mock_send:
390
+ mock_send.return_value = Mock()
391
+ result = delivery.send_webhooks(
392
+ instance=self, event_name="created", extra_context={}
393
+ )
394
+ mock_send.assert_called_once_with(
395
+ self, "created", context=None, extra_context={}
396
+ )
397
+ self.assertEqual(result, mock_send.return_value)
398
+
399
+ def test_clear_webhook_cache_function_on_calling_the_right_functions(self):
400
+ with (
401
+ patch.object(self.processor, "clear_webhook_cache") as mock_clear,
402
+ patch(
403
+ "django_webhook_subscriber.delivery.clear_content_type_cache"
404
+ ) as mock_clear_content_cache,
405
+ ):
406
+ mock_clear.return_value = None
407
+ mock_clear_content_cache.return_value = None
408
+ result = delivery.clear_webhook_cache()
409
+ mock_clear.assert_called_once_with(
410
+ content_type=None, event_name=None
411
+ )
412
+ mock_clear_content_cache.assert_called_once_with()
413
+ self.assertIsNone(result)
414
+
415
+ def test_get_webhook_cache_stats_function_on_calling_the_right_method(
416
+ self,
417
+ ):
418
+ with patch.object(self.processor, "get_cache_stats") as mock_stats:
419
+ mock_stats.return_value = {"key": "value"}
420
+ result = delivery.get_webhook_cache_stats()
421
+ mock_stats.assert_called_once_with()
422
+ self.assertEqual(result, {"key": "value"})
423
+
424
+ def test_warm_webhook_cache_on_warming_all_subscriptions(self):
425
+ # Generating subscription, and subscriber
426
+ content_type = ContentType.objects.get(
427
+ app_label="django_webhook_subscriber", model="webhooksubscriber"
428
+ )
429
+ subscriber = WebhookSubscriberFactory(content_type=content_type)
430
+ WebhookSubscriptionFactory(subscriber=subscriber, event_name="created")
431
+
432
+ with patch.object(
433
+ self.processor, "_get_subscriptions_cached"
434
+ ) as mock_cache:
435
+ results = delivery.warm_webhook_cache()
436
+ mock_cache.assert_called_once()
437
+ args = mock_cache.call_args[0]
438
+ self.assertEqual(len(args), 2)
439
+ self.assertIsInstance(args[0], models.WebhookSubscriber)
440
+ self.assertEqual(args[1], "created")
441
+ self.assertEqual(results["warmed"], 1)
442
+
443
+ def test_warm_webhook_cache_on_exception_raised(self):
444
+ # Generating subscription, and subscriber
445
+ content_type = ContentType.objects.get(
446
+ app_label="django_webhook_subscriber", model="webhooksubscriber"
447
+ )
448
+ subscriber = WebhookSubscriberFactory(content_type=content_type)
449
+ WebhookSubscriptionFactory(subscriber=subscriber, event_name="created")
450
+ with patch.object(
451
+ self.processor, "_get_subscriptions_cached"
452
+ ) as mock_cache:
453
+ mock_cache.side_effect = Exception("Test exception")
454
+ results = delivery.warm_webhook_cache()
455
+ mock_cache.assert_called_once()
456
+ args = mock_cache.call_args[0]
457
+ self.assertEqual(len(args), 2)
458
+ self.assertIsInstance(args[0], models.WebhookSubscriber)
459
+ self.assertEqual(args[1], "created")
460
+ self.assertEqual(results["warmed"], 0)