|
2 | 2 | import hmac
|
3 | 3 | import json
|
4 | 4 | from typing import Type
|
5 |
| -from unittest import TestCase, mock |
| 5 | +from unittest import mock |
| 6 | +from unittest.mock import MagicMock |
6 | 7 |
|
7 | 8 | import pytest
|
8 | 9 | import responses
|
|
13 | 14 |
|
14 | 15 | from environments.models import Environment, Webhook
|
15 | 16 | from organisations.models import Organisation, OrganisationWebhook
|
16 |
| -from projects.models import Project |
17 | 17 | from webhooks.sample_webhook_data import (
|
18 | 18 | environment_webhook_data,
|
19 | 19 | organisation_webhook_data,
|
|
29 | 29 | )
|
30 | 30 |
|
31 | 31 |
|
32 |
| -@pytest.mark.django_db |
33 |
| -class WebhooksTestCase(TestCase): |
34 |
| - def setUp(self) -> None: |
35 |
| - organisation = Organisation.objects.create(name="Test organisation") |
36 |
| - project = Project.objects.create(name="Test project", organisation=organisation) |
37 |
| - self.environment = Environment.objects.create( |
38 |
| - name="Test environment", project=project |
39 |
| - ) |
40 |
| - |
41 |
| - @mock.patch("webhooks.webhooks.requests") |
42 |
| - def test_requests_made_to_all_urls_for_environment(self, mock_requests): |
43 |
| - # Given |
44 |
| - webhook_1 = Webhook.objects.create( |
45 |
| - url="http://url.1.com", enabled=True, environment=self.environment |
46 |
| - ) |
47 |
| - webhook_2 = Webhook.objects.create( |
48 |
| - url="http://url.2.com", enabled=True, environment=self.environment |
49 |
| - ) |
50 |
| - |
51 |
| - # When |
52 |
| - call_environment_webhooks( |
53 |
| - environment_id=self.environment.id, |
54 |
| - data={}, |
55 |
| - event_type=WebhookEventType.FLAG_UPDATED.value, |
56 |
| - ) |
57 |
| - |
58 |
| - # Then |
59 |
| - assert len(mock_requests.post.call_args_list) == 2 |
60 |
| - |
61 |
| - # and |
62 |
| - call_1_args, _ = mock_requests.post.call_args_list[0] |
63 |
| - call_2_args, _ = mock_requests.post.call_args_list[1] |
64 |
| - all_call_args = call_1_args + call_2_args |
65 |
| - assert all( |
66 |
| - str(webhook.url) in all_call_args for webhook in (webhook_1, webhook_2) |
67 |
| - ) |
68 |
| - |
69 |
| - @mock.patch("webhooks.webhooks.requests") |
70 |
| - def test_request_not_made_to_disabled_webhook(self, mock_requests): |
71 |
| - # Given |
72 |
| - Webhook.objects.create( |
73 |
| - url="http://url.1.com", enabled=False, environment=self.environment |
74 |
| - ) |
75 |
| - |
76 |
| - # When |
77 |
| - call_environment_webhooks( |
78 |
| - environment_id=self.environment.id, |
79 |
| - data={}, |
80 |
| - event_type=WebhookEventType.FLAG_UPDATED.value, |
81 |
| - ) |
82 |
| - |
83 |
| - # Then |
84 |
| - mock_requests.post.assert_not_called() |
85 |
| - |
86 |
| - @mock.patch("webhooks.webhooks.requests") |
87 |
| - def test_trigger_sample_webhook_makes_correct_post_request_for_environment( |
88 |
| - self, mock_request |
89 |
| - ): |
90 |
| - url = "http://test.test" |
91 |
| - webhook = Webhook(url=url) |
92 |
| - trigger_sample_webhook(webhook, WebhookType.ENVIRONMENT) |
93 |
| - args, kwargs = mock_request.post.call_args |
94 |
| - assert json.loads(kwargs["data"]) == environment_webhook_data |
95 |
| - assert args[0] == url |
96 |
| - |
97 |
| - @mock.patch("webhooks.webhooks.requests") |
98 |
| - def test_trigger_sample_webhook_makes_correct_post_request_for_organisation( |
99 |
| - self, mock_request |
100 |
| - ): |
101 |
| - url = "http://test.test" |
102 |
| - webhook = OrganisationWebhook(url=url) |
103 |
| - |
104 |
| - trigger_sample_webhook(webhook, WebhookType.ORGANISATION) |
105 |
| - args, kwargs = mock_request.post.call_args |
106 |
| - assert json.loads(kwargs["data"]) == organisation_webhook_data |
107 |
| - assert args[0] == url |
108 |
| - |
109 |
| - @mock.patch("webhooks.webhooks.WebhookSerializer") |
110 |
| - @mock.patch("webhooks.webhooks.requests") |
111 |
| - def test_request_made_with_correct_signature( |
112 |
| - self, mock_requests, webhook_serializer |
113 |
| - ): |
114 |
| - # Given |
115 |
| - payload = {"key": "value"} |
116 |
| - webhook_serializer.return_value.data = payload |
117 |
| - secret = "random_key" |
118 |
| - Webhook.objects.create( |
119 |
| - url="http://url.1.com", |
120 |
| - enabled=True, |
121 |
| - environment=self.environment, |
122 |
| - secret=secret, |
123 |
| - ) |
124 |
| - |
125 |
| - expected_signature = hmac.new( |
126 |
| - key=secret.encode(), |
127 |
| - msg=json.dumps(payload).encode(), |
128 |
| - digestmod=hashlib.sha256, |
129 |
| - ).hexdigest() |
130 |
| - |
131 |
| - call_environment_webhooks( |
132 |
| - environment_id=self.environment.id, |
133 |
| - data={}, |
134 |
| - event_type=WebhookEventType.FLAG_UPDATED.value, |
135 |
| - ) |
136 |
| - # When |
137 |
| - _, kwargs = mock_requests.post.call_args_list[0] |
138 |
| - # Then |
139 |
| - received_signature = kwargs["headers"][FLAGSMITH_SIGNATURE_HEADER] |
140 |
| - assert hmac.compare_digest(expected_signature, received_signature) is True |
141 |
| - |
142 |
| - @mock.patch("webhooks.webhooks.requests") |
143 |
| - def test_request_does_not_have_signature_header_if_secret_is_not_set( |
144 |
| - self, mock_requests |
145 |
| - ): |
146 |
| - # Given |
147 |
| - Webhook.objects.create( |
148 |
| - url="http://url.1.com", enabled=True, environment=self.environment |
149 |
| - ) |
150 |
| - # When |
151 |
| - call_environment_webhooks( |
152 |
| - environment_id=self.environment.id, |
153 |
| - data={}, |
154 |
| - event_type=WebhookEventType.FLAG_UPDATED.value, |
155 |
| - ) |
156 |
| - |
157 |
| - # Then |
158 |
| - _, kwargs = mock_requests.post.call_args_list[0] |
159 |
| - assert FLAGSMITH_SIGNATURE_HEADER not in kwargs["headers"] |
| 32 | +@mock.patch("webhooks.webhooks.requests") |
| 33 | +def test_webhooks_requests_made_to_all_urls_for_environment( |
| 34 | + mock_requests: MagicMock, |
| 35 | + environment: Environment, |
| 36 | +) -> None: |
| 37 | + # Given |
| 38 | + webhook_1 = Webhook.objects.create( |
| 39 | + url="http://url.1.com", enabled=True, environment=environment |
| 40 | + ) |
| 41 | + webhook_2 = Webhook.objects.create( |
| 42 | + url="http://url.2.com", enabled=True, environment=environment |
| 43 | + ) |
| 44 | + |
| 45 | + # When |
| 46 | + call_environment_webhooks( |
| 47 | + environment_id=environment.id, |
| 48 | + data={}, |
| 49 | + event_type=WebhookEventType.FLAG_UPDATED.value, |
| 50 | + ) |
| 51 | + |
| 52 | + # Then |
| 53 | + assert len(mock_requests.post.call_args_list) == 2 |
| 54 | + |
| 55 | + # and |
| 56 | + call_1_args, _ = mock_requests.post.call_args_list[0] |
| 57 | + call_2_args, _ = mock_requests.post.call_args_list[1] |
| 58 | + all_call_args = call_1_args + call_2_args |
| 59 | + assert all(str(webhook.url) in all_call_args for webhook in (webhook_1, webhook_2)) |
| 60 | + |
| 61 | + |
| 62 | +@mock.patch("webhooks.webhooks.requests") |
| 63 | +def test_webhooks_request_not_made_to_disabled_webhook( |
| 64 | + mock_requests: MagicMock, |
| 65 | + environment: Environment, |
| 66 | +) -> None: |
| 67 | + # Given |
| 68 | + Webhook.objects.create( |
| 69 | + url="http://url.1.com", enabled=False, environment=environment |
| 70 | + ) |
| 71 | + |
| 72 | + # When |
| 73 | + call_environment_webhooks( |
| 74 | + environment_id=environment.id, |
| 75 | + data={}, |
| 76 | + event_type=WebhookEventType.FLAG_UPDATED.value, |
| 77 | + ) |
| 78 | + |
| 79 | + # Then |
| 80 | + mock_requests.post.assert_not_called() |
| 81 | + |
| 82 | + |
| 83 | +@mock.patch("webhooks.webhooks.requests") |
| 84 | +def test_trigger_sample_webhook_makes_correct_post_request_for_environment( |
| 85 | + mock_request: MagicMock, |
| 86 | +) -> None: |
| 87 | + url = "http://test.test" |
| 88 | + webhook = Webhook(url=url) |
| 89 | + trigger_sample_webhook(webhook, WebhookType.ENVIRONMENT) |
| 90 | + args, kwargs = mock_request.post.call_args |
| 91 | + assert json.loads(kwargs["data"]) == environment_webhook_data |
| 92 | + assert args[0] == url |
| 93 | + |
| 94 | + |
| 95 | +@mock.patch("webhooks.webhooks.requests") |
| 96 | +def test_trigger_sample_webhook_makes_correct_post_request_for_organisation( |
| 97 | + mock_request: MagicMock, |
| 98 | +) -> None: |
| 99 | + url = "http://test.test" |
| 100 | + webhook = OrganisationWebhook(url=url) |
| 101 | + |
| 102 | + trigger_sample_webhook(webhook, WebhookType.ORGANISATION) |
| 103 | + args, kwargs = mock_request.post.call_args |
| 104 | + assert json.loads(kwargs["data"]) == organisation_webhook_data |
| 105 | + assert args[0] == url |
| 106 | + |
| 107 | + |
| 108 | +@mock.patch("webhooks.webhooks.WebhookSerializer") |
| 109 | +@mock.patch("webhooks.webhooks.requests") |
| 110 | +def test_request_made_with_correct_signature( |
| 111 | + mock_requests: MagicMock, |
| 112 | + webhook_serializer: MagicMock, |
| 113 | + environment: Environment, |
| 114 | +) -> None: |
| 115 | + # Given |
| 116 | + payload = {"key": "value"} |
| 117 | + webhook_serializer.return_value.data = payload |
| 118 | + secret = "random_key" |
| 119 | + Webhook.objects.create( |
| 120 | + url="http://url.1.com", |
| 121 | + enabled=True, |
| 122 | + environment=environment, |
| 123 | + secret=secret, |
| 124 | + ) |
| 125 | + |
| 126 | + expected_signature = hmac.new( |
| 127 | + key=secret.encode(), |
| 128 | + msg=json.dumps(payload).encode(), |
| 129 | + digestmod=hashlib.sha256, |
| 130 | + ).hexdigest() |
| 131 | + |
| 132 | + call_environment_webhooks( |
| 133 | + environment_id=environment.id, |
| 134 | + data={}, |
| 135 | + event_type=WebhookEventType.FLAG_UPDATED.value, |
| 136 | + ) |
| 137 | + # When |
| 138 | + _, kwargs = mock_requests.post.call_args_list[0] |
| 139 | + # Then |
| 140 | + received_signature = kwargs["headers"][FLAGSMITH_SIGNATURE_HEADER] |
| 141 | + assert hmac.compare_digest(expected_signature, received_signature) is True |
| 142 | + |
| 143 | + |
| 144 | +@mock.patch("webhooks.webhooks.requests") |
| 145 | +def test_request_does_not_have_signature_header_if_secret_is_not_set( |
| 146 | + mock_requests: MagicMock, |
| 147 | + environment: Environment, |
| 148 | +) -> None: |
| 149 | + # Given |
| 150 | + Webhook.objects.create( |
| 151 | + url="http://url.1.com", enabled=True, environment=environment |
| 152 | + ) |
| 153 | + # When |
| 154 | + call_environment_webhooks( |
| 155 | + environment_id=environment.id, |
| 156 | + data={}, |
| 157 | + event_type=WebhookEventType.FLAG_UPDATED.value, |
| 158 | + ) |
| 159 | + |
| 160 | + # Then |
| 161 | + _, kwargs = mock_requests.post.call_args_list[0] |
| 162 | + assert FLAGSMITH_SIGNATURE_HEADER not in kwargs["headers"] |
160 | 163 |
|
161 | 164 |
|
162 | 165 | @pytest.mark.parametrize("expected_error", [ConnectionError, Timeout])
|
|
0 commit comments