1
+ import json
2
+ import uuid
3
+ from unittest .mock import patch
4
+
1
5
import django_rq
2
6
from django .contrib .contenttypes .models import ContentType
7
+ from django .http import HttpResponse
3
8
from django .urls import reverse
9
+ from requests import Session
4
10
from rest_framework import status
5
11
6
12
from dcim .models import Site
7
13
from extras .choices import ObjectChangeActionChoices
8
14
from extras .models import Webhook
15
+ from extras .webhooks import enqueue_webhooks , generate_signature
16
+ from extras .webhooks_worker import process_webhook
9
17
from utilities .testing import APITestCase
10
18
11
19
@@ -22,11 +30,13 @@ def setUp(self):
22
30
def setUpTestData (cls ):
23
31
24
32
site_ct = ContentType .objects .get_for_model (Site )
25
- PAYLOAD_URL = "http://localhost/"
33
+ DUMMY_URL = "http://localhost/"
34
+ DUMMY_SECRET = "LOOKATMEIMASECRETSTRING"
35
+
26
36
webhooks = Webhook .objects .bulk_create ((
27
- Webhook (name = 'Site Create Webhook' , type_create = True , payload_url = PAYLOAD_URL ),
28
- Webhook (name = 'Site Update Webhook' , type_update = True , payload_url = PAYLOAD_URL ),
29
- Webhook (name = 'Site Delete Webhook' , type_delete = True , payload_url = PAYLOAD_URL ),
37
+ Webhook (name = 'Site Create Webhook' , type_create = True , payload_url = DUMMY_URL , secret = DUMMY_SECRET , additional_headers = { 'X-Foo' : 'Bar' } ),
38
+ Webhook (name = 'Site Update Webhook' , type_update = True , payload_url = DUMMY_URL , secret = DUMMY_SECRET ),
39
+ Webhook (name = 'Site Delete Webhook' , type_delete = True , payload_url = DUMMY_URL , secret = DUMMY_SECRET ),
30
40
))
31
41
for webhook in webhooks :
32
42
webhook .obj_type .set ([site_ct ])
@@ -87,3 +97,47 @@ def test_enqueue_webhook_delete(self):
87
97
self .assertEqual (job .args [1 ]['id' ], site .pk )
88
98
self .assertEqual (job .args [2 ], 'site' )
89
99
self .assertEqual (job .args [3 ], ObjectChangeActionChoices .ACTION_DELETE )
100
+
101
+ def test_webhooks_worker (self ):
102
+
103
+ request_id = uuid .uuid4 ()
104
+
105
+ def dummy_send (_ , request ):
106
+ """
107
+ A dummy implementation of Session.send() to be used for testing.
108
+ Always returns a 200 HTTP response.
109
+ """
110
+ webhook = Webhook .objects .get (type_create = True )
111
+ signature = generate_signature (request .body , webhook .secret )
112
+
113
+ # Validate the outgoing request headers
114
+ self .assertEqual (request .headers ['Content-Type' ], webhook .http_content_type )
115
+ self .assertEqual (request .headers ['X-Hook-Signature' ], signature )
116
+ self .assertEqual (request .headers ['X-Foo' ], 'Bar' )
117
+
118
+ # Validate the outgoing request body
119
+ body = json .loads (request .body )
120
+ self .assertEqual (body ['event' ], 'created' )
121
+ self .assertEqual (body ['timestamp' ], job .args [4 ])
122
+ self .assertEqual (body ['model' ], 'site' )
123
+ self .assertEqual (body ['username' ], 'testuser' )
124
+ self .assertEqual (body ['request_id' ], str (request_id ))
125
+ self .assertEqual (body ['data' ]['name' ], 'Site 1' )
126
+
127
+ return HttpResponse ()
128
+
129
+ # Enqueue a webhook for processing
130
+ site = Site .objects .create (name = 'Site 1' , slug = 'site-1' )
131
+ enqueue_webhooks (
132
+ instance = site ,
133
+ user = self .user ,
134
+ request_id = request_id ,
135
+ action = ObjectChangeActionChoices .ACTION_CREATE
136
+ )
137
+
138
+ # Retrieve the job from queue
139
+ job = self .queue .jobs [0 ]
140
+
141
+ # Patch the Session object with our dummy_send() method, then process the webhook for sending
142
+ with patch .object (Session , 'send' , dummy_send ) as mock_send :
143
+ process_webhook (* job .args )
0 commit comments