management: Support sending custom headers when testing a webhook.

this commit adds an option to specify custom headers when using the
`./manage.py send_webhook_fixture_message` tool.
This commit is contained in:
Hemanth V. Alluri
2019-03-09 15:31:20 +05:30
committed by Tim Abbott
parent e859ab7545
commit 99c3e2ecdc
3 changed files with 73 additions and 4 deletions

View File

@@ -217,6 +217,16 @@ Using either method will create a message in Zulip:
<img class="screenshot" src="/static/images/api/helloworld-webhook.png" /> <img class="screenshot" src="/static/images/api/helloworld-webhook.png" />
Some webhooks require custom HTTP headers, which can be passed using
`./manage.py send_webhook_fixture_message --custom-headers`. For
example:
--custom-headers='{"X-Custom-Header": "value"}'
The format is a JSON dictionary, so make sure that the header names do
not contain any spaces in them and that you use the precise quoting
approach shown above.
## Step 4: Create tests ## Step 4: Create tests
Every webhook integration should have a corresponding test file: Every webhook integration should have a corresponding test file:

View File

@@ -1,7 +1,7 @@
import os import os
import ujson import ujson
from typing import Union, Dict
from django.conf import settings from django.conf import settings
from django.core.management.base import CommandParser from django.core.management.base import CommandParser
from django.test import Client from django.test import Client
@@ -18,6 +18,14 @@ Example:
--fixture=zerver/webhooks/integration/fixtures/name.json \ --fixture=zerver/webhooks/integration/fixtures/name.json \
'--url=/api/v1/external/integration?stream=stream_name&api_key=api_key' '--url=/api/v1/external/integration?stream=stream_name&api_key=api_key'
To pass custom headers along with the webhook message use the --custom-headers
command line option.
Example:
--custom-headers='{"X-Custom-Header": "value"}'
The format is a JSON dictionary, so make sure that the header names do
not contain any spaces in them and that you use the precise quoting
approach shown above.
""" """
def add_arguments(self, parser: CommandParser) -> None: def add_arguments(self, parser: CommandParser) -> None:
@@ -33,8 +41,30 @@ Example:
help='The url on your Zulip server that you want ' help='The url on your Zulip server that you want '
'to post the fixture to') 'to post the fixture to')
parser.add_argument('-H', '--custom-headers',
dest='custom-headers',
type=str,
help='The headers you want to provide along with '
'your mock request to Zulip.')
self.add_realm_args(parser, help="Specify which realm/subdomain to connect to; default is zulip") self.add_realm_args(parser, help="Specify which realm/subdomain to connect to; default is zulip")
def parse_headers(self, custom_headers: Union[None, str]) -> Union[None, Dict[str, str]]:
headers = {}
if not custom_headers:
return None
try:
custom_headers_dict = ujson.loads(custom_headers)
for header in custom_headers_dict:
if len(header.split(" ")) > 1:
raise ValueError("custom header '%s' contains a space." % (header))
headers["HTTP_" + header.upper().replace("-", "_")] = str(custom_headers_dict[header])
return headers
except ValueError as ve:
print('Encountered an error while attempting to parse custom headers: %s' % (ve))
print('Note: all strings must be enclosed within "" instead of \'\'')
exit(1)
def handle(self, **options: str) -> None: def handle(self, **options: str) -> None:
if options['fixture'] is None or options['url'] is None: if options['fixture'] is None or options['url'] is None:
self.print_help('./manage.py', 'send_webhook_fixture_message') self.print_help('./manage.py', 'send_webhook_fixture_message')
@@ -46,14 +76,19 @@ Example:
print('Fixture {} does not exist'.format(options['fixture'])) print('Fixture {} does not exist'.format(options['fixture']))
exit(1) exit(1)
headers = self.parse_headers(options['custom-headers'])
json = self._get_fixture_as_json(full_fixture_path) json = self._get_fixture_as_json(full_fixture_path)
realm = self.get_realm(options) realm = self.get_realm(options)
if realm is None: if realm is None:
realm = get_realm("zulip") realm = get_realm("zulip")
client = Client() client = Client()
result = client.post(options['url'], json, content_type="application/json", if headers:
HTTP_HOST=realm.host) result = client.post(options['url'], json, content_type="application/json",
HTTP_HOST=realm.host, **headers)
else:
result = client.post(options['url'], json, content_type="application/json",
HTTP_HOST=realm.host)
if result.status_code != 200: if result.status_code != 200:
print('Error status %s: %s' % (result.status_code, result.content)) print('Error status %s: %s' % (result.status_code, result.content))
exit(1) exit(1)

View File

@@ -18,6 +18,7 @@ from zerver.lib.test_helpers import stdout_suppressed
from zerver.lib.test_runner import slow from zerver.lib.test_runner import slow
from zerver.models import Recipient, get_user_profile_by_email, get_stream from zerver.models import Recipient, get_user_profile_by_email, get_stream
from zerver.management.commands.send_webhook_fixture_message import Command
from zerver.lib.test_helpers import most_recent_message from zerver.lib.test_helpers import most_recent_message
from zerver.models import get_realm, UserProfile, Realm from zerver.models import get_realm, UserProfile, Realm
from confirmation.models import RealmCreationKey, generate_realm_creation_url from confirmation.models import RealmCreationKey, generate_realm_creation_url
@@ -213,6 +214,29 @@ class TestSendWebhookFixtureMessage(TestCase):
client.post.assert_called_once_with(self.url, {}, content_type="application/json", client.post.assert_called_once_with(self.url, {}, content_type="application/json",
HTTP_HOST="zulip.testserver") HTTP_HOST="zulip.testserver")
@patch('zerver.management.commands.send_webhook_fixture_message.Command._get_fixture_as_json')
@patch('zerver.management.commands.send_webhook_fixture_message.Command._does_fixture_path_exist')
@patch('zerver.management.commands.send_webhook_fixture_message.Command.parse_headers')
def test_check_post_request_with_improper_custom_header(self,
parse_headers_mock: MagicMock,
does_fixture_path_exist_mock: MagicMock,
get_fixture_as_json_mock: MagicMock) -> None:
does_fixture_path_exist_mock.return_value = True
get_fixture_as_json_mock.return_value = "{}"
improper_headers = '{"X-Custom - Headers": "some_val"}'
with self.assertRaises(SystemExit) as se:
call_command(self.COMMAND_NAME, fixture=self.fixture_path, url=self.url, custom_headers=improper_headers)
parse_headers_mock.assert_called_once_with(improper_headers)
def test_parse_headers_method(self) -> None:
command = Command()
self.assertEqual(command.parse_headers(None), None)
self.assertEqual(command.parse_headers('{"X-Custom-Header": "value"}'), {"HTTP_X_CUSTOM_HEADER": "value"})
with self.assertRaises(SystemExit):
command.parse_headers('{"X-Custom - Headers": "some_val"}')
class TestGenerateRealmCreationLink(ZulipTestCase): class TestGenerateRealmCreationLink(ZulipTestCase):
COMMAND_NAME = "generate_realm_creation_link" COMMAND_NAME = "generate_realm_creation_link"