-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_webhooks.py
More file actions
241 lines (185 loc) · 8.45 KB
/
test_webhooks.py
File metadata and controls
241 lines (185 loc) · 8.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import hmac
import json
from http import HTTPStatus
from typing import cast
from unittest.mock import patch
import pytest
from allauth.socialaccount.models import SocialAccount
from django.conf import settings
from django.http import HttpResponse
from django.http import JsonResponse
from django.test import RequestFactory
from django.utils.encoding import force_bytes
from factory.faker import faker
from democrasite.users.models import User
from democrasite.webiscite.models import Bill
from democrasite.webiscite.models import PullRequest
from democrasite.webiscite.tests.factories import BillFactory
from democrasite.webiscite.tests.factories import GithubPullRequestFactory
from democrasite.webiscite.webhooks import GithubWebhookView
from democrasite.webiscite.webhooks import PullRequestHandler
from democrasite.webiscite.webhooks import github_webhook_view
class TestGithubHookView:
def check_response(self, request, status, content):
response = cast(HttpResponse, github_webhook_view(request))
assert response.status_code == status
assert response.content == content
def test_no_signature(self, rf: RequestFactory):
request = rf.post("/fake-url/")
self.check_response(
request, 400, b"Request does not contain X-HUB-SIGNATURE-256 header"
)
def test_no_event(self, rf: RequestFactory):
request = rf.post("/fake-url/", HTTP_X_HUB_SIGNATURE_256="test")
self.check_response(
request, 400, b"Request does not contain X-GITHUB-EVENT header"
)
def test_invalid_signature_digest(self, rf: RequestFactory):
request = rf.post(
"/fake-url/",
HTTP_X_HUB_SIGNATURE_256="test=test",
HTTP_X_GITHUB_EVENT="test",
)
self.check_response(
request, 400, b"Unsupported X-HUB-SIGNATURE-256 digest mode: test"
)
def test_invalid_signature(self, rf: RequestFactory):
request = rf.post(
"/fake-url/",
HTTP_X_HUB_SIGNATURE_256="sha256=test",
HTTP_X_GITHUB_EVENT="test",
)
self.check_response(request, 403, b"Invalid X-HUB-SIGNATURE-256 signature")
@patch("requests.get")
def test_validate_remote_addr(self, mock_get):
mock_get().json.return_value = {"hooks": ["127.0.0.1"]}
response = GithubWebhookView()._validate_remote_addr("127.0.0.1") # noqa: SLF001
assert response == ""
@patch("requests.get")
def test_validate_remote_addr_invalid(self, mock_get):
mock_get().json.return_value = {"hooks": ["127.0.0.1"]}
response = GithubWebhookView()._validate_remote_addr("127.0.0.2") # noqa: SLF001
assert response == "Invalid remote address for GitHub webhook request"
@pytest.fixture
def signed_request(self, rf: RequestFactory):
request = rf.post(
"/fake-url/",
data=json.dumps({"action": "none", "pull_request": "-1"}),
content_type="application/json",
HTTP_X_GITHUB_EVENT="test",
)
request.META["HTTP_X_HUB_SIGNATURE_256"] = (
"sha256="
+ hmac.new(
force_bytes(settings.WEBISCITE_GITHUB_WEBHOOK_SECRET),
msg=request.body,
digestmod="sha256",
).hexdigest()
)
return request
def test_unsupported_event(self, signed_request):
self.check_response(
signed_request, 400, b"Unsupported X-GITHUB-EVENT header found: test"
)
def test_valid_request(self, signed_request):
signed_request.META["HTTP_X_GITHUB_EVENT"] = "ping"
self.check_response(signed_request, 200, b"pong")
def test_push(self):
response = GithubWebhookView().push({})
assert response.status_code == HTTPStatus.OK
assert response.content == b"push received"
class TestPullRequestHandler:
@pytest.fixture
def pr_handler(self):
return GithubWebhookView().pull_request
@patch.object(PullRequestHandler, "opened")
def test_pr_handler_dispatch(self, mock_opened, bill, pr_handler):
mock_opened.return_value = (bill.pull_request, bill)
response = pr_handler({"action": "opened", "pull_request": {"number": 1}})
mock_opened.assert_called_once_with({"number": 1})
assert isinstance(response, JsonResponse)
def test_pr_handler_dispatch_invalid(self, pr_handler: PullRequestHandler):
response = pr_handler({"action": "test", "pull_request": {"number": 1}})
assert response.status_code == HTTPStatus.BAD_REQUEST
assert response.content == b"Unsupported action: test"
@patch("requests.get")
def test_opened(self, mock_get, user: User, pr_handler: PullRequestHandler):
pr = GithubPullRequestFactory.create()
pr["user"]["id"] = SocialAccount.objects.create(
user=user,
provider="github",
uid=faker.Faker().random_int(),
).uid
pull_request, bill = pr_handler.opened(pr)
assert pull_request is not None
assert bill is not None
assert bill.author == user
@patch("requests.get")
def test_opened_draft(self, mock_get, user: User, pr_handler: PullRequestHandler):
pr = GithubPullRequestFactory.create(draft=True)
pr["user"]["id"] = SocialAccount.objects.create(
user=user,
provider="github",
uid=faker.Faker().random_int(),
).uid
pull_request, bill = pr_handler.opened(pr)
assert pull_request is not None
assert pull_request.draft is True
assert bill is not None
assert bill.status == Bill.Status.DRAFT
assert bill._submit_task is None # noqa: SLF001
def test_ready_for_review(self, pr_handler: PullRequestHandler):
bill = BillFactory.create(status=Bill.Status.DRAFT)
bill.pull_request.draft = True
bill.pull_request.save()
pull_request, published_bill = pr_handler.ready_for_review(
{"number": bill.pull_request.number}
)
assert pull_request is not None
assert pull_request.draft is False
assert published_bill is not None
published_bill.refresh_from_db()
assert published_bill.status == Bill.Status.OPEN
assert published_bill._submit_task is not None # noqa: SLF001
assert published_bill._submit_task.enabled is True # noqa: SLF001
def test_ready_for_review_no_pr(self, pr_handler: PullRequestHandler):
result = pr_handler.ready_for_review({"number": 1})
assert result == (None, None)
def test_ready_for_review_no_draft_bill(
self, pr_handler: PullRequestHandler, bill: Bill
):
pull_request, result_bill = pr_handler.ready_for_review(
{"number": bill.pull_request.number}
)
assert pull_request is not None
assert result_bill is None
@patch.object(PullRequestHandler, "opened")
def test_reopened(self, mock_opened, pr_handler: PullRequestHandler):
# Basically just for 100% coverage
response = pr_handler.reopened({})
mock_opened.assert_called_once_with({})
assert response == mock_opened.return_value
def test_closed_no_pr(self, pr_handler: PullRequestHandler):
response = pr_handler.closed({"number": 1})
assert response == (None, None)
@patch.object(PullRequest, "close")
def test_closed(self, mock_close, pr_handler: PullRequestHandler, bill: Bill):
response = pr_handler.closed({"number": bill.pull_request.number})
assert response == (bill.pull_request, mock_close.return_value)
mock_close.assert_called_once()
def test_synchronize(self, pr_handler: PullRequestHandler):
bill = BillFactory.create(status=Bill.Status.OPEN)
pr = GithubPullRequestFactory.create(bill=bill)
pull_request, amended_bill = pr_handler.synchronize(pr)
assert pull_request is not None
assert amended_bill is not None
amended_bill.refresh_from_db()
assert amended_bill.status == Bill.Status.AMENDED
assert amended_bill._submit_task is not None # noqa: SLF001
assert amended_bill._submit_task.enabled is False # noqa: SLF001
def test_synchronize_no_open_bill(self, pr_handler: PullRequestHandler):
# Default GithubPullRequestFactory creates a closed bill (no active bill)
pr = GithubPullRequestFactory.create()
pull_request, bill = pr_handler.synchronize(pr)
assert pull_request is not None
assert bill is None