66# See https://aboutcode.org for more information about AboutCode FOSS projects.
77#
88
9+ import json
10+ from unittest .mock import MagicMock
11+ from unittest .mock import patch
12+
913from django .test import TestCase
1014from django .test .utils import override_settings
1115
16+ import requests as req
17+
1218from dje .models import Dataspace
1319from dje .tests import create_superuser
20+ from notification .models import WebhookDelivery
1421from notification .models import WebhookSubscription
22+ from notification .models import fire_webhooks
23+
24+
25+ class WebhookSubscriptionQuerySetTestCase (TestCase ):
26+ def setUp (self ):
27+ self .nexb_dataspace = Dataspace .objects .create (name = "nexB" )
28+ self .active_webhook = WebhookSubscription .objects .create (
29+ dataspace = self .nexb_dataspace ,
30+ target_url = "http://1.2.3.4/" ,
31+ event = "request.added" ,
32+ is_active = True ,
33+ )
34+ self .inactive_webhook = WebhookSubscription .objects .create (
35+ dataspace = self .nexb_dataspace ,
36+ target_url = "http://1.2.3.5/" ,
37+ event = "request.added" ,
38+ is_active = False ,
39+ )
40+
41+ def test_active_returns_only_active_subscriptions (self ):
42+ active = list (WebhookSubscription .objects .active ())
43+ self .assertIn (self .active_webhook , active )
44+ self .assertNotIn (self .inactive_webhook , active )
1545
1646
1747class WebhookSubscriptionModelTestCase (TestCase ):
@@ -36,6 +66,15 @@ def test_webhook_subscription_dict(self):
3666 }
3767 self .assertEqual (expected , self .webhook .dict ())
3868
69+ def test_webhook_subscription_get_headers_default (self ):
70+ self .assertEqual ({"Content-Type" : "application/json" }, self .webhook .get_headers ())
71+
72+ def test_webhook_subscription_get_headers_with_extra (self ):
73+ self .webhook .extra_headers = {"X-Token" : "abc" }
74+ self .webhook .save ()
75+ expected = {"Content-Type" : "application/json" , "X-Token" : "abc" }
76+ self .assertEqual (expected , self .webhook .get_headers ())
77+
3978 def test_webhook_subscription_get_extra_headers (self ):
4079 self .webhook .extra_headers = {"Header" : "{{ENV_VALUE}}" }
4180 self .webhook .save ()
@@ -46,3 +85,103 @@ def test_webhook_subscription_get_extra_headers(self):
4685 expected = {"Header" : "some_value" }
4786 with override_settings (DEJACODE_WEBHOOK_ENV = {"ENV_VALUE" : "some_value" }):
4887 self .assertEqual (expected , self .webhook .get_extra_headers ())
88+
89+ @patch ("requests.post" )
90+ def test_webhook_subscription_deliver_inactive_returns_false (self , mock_post ):
91+ self .webhook .is_active = False
92+ self .webhook .save ()
93+ result = self .webhook .deliver (None , payload_override = {"key" : "val" })
94+ self .assertFalse (result )
95+ mock_post .assert_not_called ()
96+
97+ @patch ("requests.post" )
98+ def test_webhook_subscription_deliver_request_exception_saves_error (self , mock_post ):
99+ mock_post .side_effect = req .exceptions .ConnectionError ("Connection refused" )
100+ delivery = self .webhook .deliver (None , payload_override = {"key" : "val" })
101+ self .assertIsNotNone (delivery )
102+ self .assertFalse (delivery .delivered )
103+ self .assertIn ("Connection refused" , delivery .delivery_error )
104+
105+ @patch ("requests.post" )
106+ def test_webhook_subscription_deliver_payload_override_merges_extra_payload (self , mock_post ):
107+ mock_response = MagicMock ()
108+ mock_response .status_code = 200
109+ mock_response .text = ""
110+ mock_post .return_value = mock_response
111+ self .webhook .extra_payload = {"env" : "prod" }
112+ self .webhook .save ()
113+ self .webhook .deliver (None , payload_override = {"key" : "val" })
114+ call_data = json .loads (mock_post .call_args [1 ]["data" ])
115+ self .assertEqual ("prod" , call_data ["env" ])
116+ self .assertEqual ("val" , call_data ["key" ])
117+
118+ @patch ("requests.post" )
119+ def test_webhook_subscription_deliver_payload_override_without_extra_payload (self , mock_post ):
120+ mock_response = MagicMock ()
121+ mock_response .status_code = 200
122+ mock_response .text = ""
123+ mock_post .return_value = mock_response
124+ self .webhook .deliver (None , payload_override = {"key" : "val" })
125+ call_data = json .loads (mock_post .call_args [1 ]["data" ])
126+ self .assertEqual ({"key" : "val" }, call_data )
127+
128+
129+ class WebhookDeliveryModelTestCase (TestCase ):
130+ def setUp (self ):
131+ self .nexb_dataspace = Dataspace .objects .create (name = "nexB" )
132+ self .webhook = WebhookSubscription .objects .create (
133+ dataspace = self .nexb_dataspace ,
134+ target_url = "http://1.2.3.4/" ,
135+ event = "request.added" ,
136+ )
137+ self .delivery = WebhookDelivery .objects .create (
138+ dataspace = self .nexb_dataspace ,
139+ webhook_subscription = self .webhook ,
140+ target_url = self .webhook .target_url ,
141+ payload = {"key" : "value" },
142+ )
143+
144+ def test_webhook_delivery_str (self ):
145+ self .assertIn (f"uuid={ self .delivery .uuid } " , str (self .delivery ))
146+
147+ def test_webhook_delivery_delivered_false_when_no_status_code (self ):
148+ self .assertFalse (self .delivery .delivered )
149+
150+ def test_webhook_delivery_delivered_true_when_status_code_set (self ):
151+ self .delivery .response_status_code = 200
152+ self .assertTrue (self .delivery .delivered )
153+
154+ def test_webhook_delivery_success_on_2xx (self ):
155+ for status_code in (200 , 201 , 202 ):
156+ self .delivery .response_status_code = status_code
157+ self .assertTrue (self .delivery .success , f"Expected success for { status_code } " )
158+
159+ def test_webhook_delivery_success_false_on_non_2xx (self ):
160+ for status_code in (400 , 404 , 500 ):
161+ self .delivery .response_status_code = status_code
162+ self .assertFalse (self .delivery .success , f"Expected failure for { status_code } " )
163+
164+
165+ class FireWebhooksTestCase (TestCase ):
166+ def setUp (self ):
167+ self .nexb_dataspace = Dataspace .objects .create (name = "nexB" )
168+
169+ def test_fire_webhooks_requires_dataspace_or_instance (self ):
170+ with self .assertRaises (AttributeError ):
171+ fire_webhooks ("request.added" , None )
172+
173+ @patch ("requests.post" )
174+ def test_fire_webhooks_no_matching_event_makes_no_request (self , mock_post ):
175+ fire_webhooks ("no.such.event" , None , dataspace = self .nexb_dataspace )
176+ mock_post .assert_not_called ()
177+
178+ @patch ("requests.post" )
179+ def test_fire_webhooks_skips_inactive_subscriptions (self , mock_post ):
180+ WebhookSubscription .objects .create (
181+ dataspace = self .nexb_dataspace ,
182+ target_url = "http://1.2.3.4/" ,
183+ event = "request.added" ,
184+ is_active = False ,
185+ )
186+ fire_webhooks ("request.added" , None , dataspace = self .nexb_dataspace )
187+ mock_post .assert_not_called ()
0 commit comments