Skip to content

Commit

Permalink
Merge 2a7055a into e9a8839
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicole Thomas committed Sep 14, 2018
2 parents e9a8839 + 2a7055a commit e948fb3
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 15 deletions.
44 changes: 44 additions & 0 deletions tamarack/event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# Import Tamarack libs
import tamarack.github
import tamarack.pull_request
import tamarack.slack

LOG = logging.getLogger(__name__)

Expand All @@ -32,6 +33,8 @@ def handle_event(event_data, token):
'''
if event_data.get('pull_request'):
yield handle_pull_request(event_data, token)
elif event_data.get('ref_type'):
yield handle_create_event(event_data)


@gen.coroutine
Expand Down Expand Up @@ -73,3 +76,44 @@ def handle_pull_request(event_data, token):
LOG.info('PR #%s: Skipping. Action is \'%s\'. We only care about '
'\'opened\'.', pr_num, action)
return


@gen.coroutine
def handle_create_event(event_data):
'''
Handles Create events by examining the type of reference object that was
created and then decides what to do next.
For example, if a new branch is pushed to the repository, the bot needs to
send a slack message to the configured Slack App Webhook URL.
event_data
Payload sent from GitHub.
'''
event_type = event_data.get('ref_type')
ref_name = event_data.get('ref')

LOG.info('Received create event. Processing...')

# Send message to Slack when new branch is created.
if event_type == 'branch':
LOG.info('New branch \'%s\' was created in GitHub. Posting to Slack.', ref_name)
post_data = {'attachments': [
{'color': 'good',
'fields': [{
'value':
'A new branch named `{0}` was created in the Salt repo.'.format(
ref_name
)
}]}
]}

yield tamarack.slack.api_request(
method='POST',
post_data=post_data
)
else:
LOG.info('Skipping. Create event is of \'%s\' type. We only care about '
'\'branch\'.', event_type)
return
9 changes: 9 additions & 0 deletions tamarack/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

HOOK_SECRET_KEY = os.environ.get('HOOK_SECRET_KEY')
GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')
SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL')

LOG = logging.getLogger(__name__)

Expand Down Expand Up @@ -100,6 +101,14 @@ def _check_env_vars():
'"export GITHUB_TOKEN=your_token".'
)

if SLACK_WEBHOOK_URL is None:
LOG.warning(
'The bot was started without an optional Slack Webhook URL.\n'
'In order to interact with Slack, the webhook must be set.\n'
'Please set the SLACK_WEBHOOK_URL environment variable: '
'"export SLACK_WEBHOOK_URL=your_slack_webhook_url".'
)

return check_ok


Expand Down
63 changes: 63 additions & 0 deletions tamarack/slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
'''
Contains functions required for interacting with Slack. The main function
used is the ``api_request`` function. This function handles the GET/POST
interactions to Slack.
'''

# Import Python libs
import json
import logging
import os

# Import Tornado libs
from tornado import gen
import tornado.httpclient


LOG = logging.getLogger(__name__)
SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL')


@gen.coroutine
def api_request(method='GET', headers=None, post_data=None):
'''
The main function used to interact with the Slack API. This function
performs the actual requests to Slack when responding to various events.
method
Type of HTTP method. Defaults to ``GET``.
headers
HTTP Headers needed to make the request. Defaults to ``None``.
Note: There are a couple of hard-coded defaults defined here presently.
This is subject to change once configuration files or additional
environment variables are defined. ``Content-Type`` defaults to
``application/json``, as required by Slack.
post_data
The data to pass to the Slack API request. Defaults to ``None``.
This data will change based on the type of request made. For example,
sending text to a Slack channel requires something like the following:
``'{"text": "My comment message."}'``. Other API calls may require
different options and structures.
'''
if headers is None:
headers = {'Content-Type': 'application/json'}

data = None
if post_data:
data = json.dumps(post_data)
data = data.encode('utf-8')

http_client = tornado.httpclient.AsyncHTTPClient()
request = tornado.httpclient.HTTPRequest(
SLACK_WEBHOOK_URL,
method=method,
headers=headers,
body=data
)
yield http_client.fetch(request)
42 changes: 42 additions & 0 deletions tests/test_event_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ def test_pull_request_event(self):
ret = yield tamarack.event_processor.handle_event(event_data, '')
assert ret is None

@tornado.testing.gen_test
def test_create_event(self):
'''
Tests that a create even is handled
'''
event_data = {'ref_type': 'foo',
'ref': 'bar'}
ret = yield tamarack.event_processor.handle_event(event_data, '')
assert ret is None


class TestHandlePullRequest(tornado.testing.AsyncTestCase):
'''
Expand Down Expand Up @@ -73,3 +83,35 @@ def test_unknown_event(self):
'action': 'foo'}
ret = yield tamarack.event_processor.handle_pull_request(event_data, '')
assert ret is None


class TestHandleCreateEvent(tornado.testing.AsyncTestCase):
'''
TestCase for the handle_create_event function
'''

@tornado.testing.gen_test
def test_new_branch(self):
'''
Tests that a branch pushed to GitHub calls sends a slack message
'''
slack_url = tamarack.slack.SLACK_WEBHOOK_URL
tamarack.slack.SLACK_WEBHOOK_URL = 'https://slack.com/api/api.test'

event_data = {'ref_type': 'branch',
'ref': 'test-branch-name'}
ret = yield tamarack.event_processor.handle_create_event(event_data)
assert ret is None

# Reset variables for clean tests
tamarack.slack.SLACK_WEBHOOK_URL = slack_url

@tornado.testing.gen_test
def test_unknown_event(self):
'''
Tests that create events other than "branch" are ignored.
'''
event_data = {'ref_type': 'foo',
'ref': 'bar'}
ret = yield tamarack.event_processor.handle_create_event(event_data)
assert ret is None
50 changes: 35 additions & 15 deletions tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# Import Python libs
from unittest.mock import MagicMock, patch
import pytest

# Import Tornado libs
import tornado.web
Expand All @@ -13,6 +14,27 @@
import tamarack.server


@pytest.fixture(scope='module')
def preserve_globals(request):
'''
Test fixture used to preserve global variables
'''
hook_secret = tamarack.server.HOOK_SECRET_KEY
gh_token = tamarack.server.GITHUB_TOKEN

tamarack.server.HOOK_SECRET_KEY = None
tamarack.server.GITHUB_TOKEN = None

def restore_globals():
'''
Restore the previously defined global variables
'''
tamarack.server.HOOK_SECRET_KEY = hook_secret
tamarack.server.GITHUB_TOKEN = gh_token

request.addfinalizer(restore_globals)


class TestValidateGitHubSignature:
'''
TestCase for the validate_github_signature function.
Expand All @@ -26,7 +48,7 @@ def test_incorrect_sha_type(self):
setattr(self.request, 'headers', {'X-Hub-Signature': 'foo=bar'})
assert tamarack.server.validate_github_signature(self.request) is False

def test_valid_signature(self):
def test_valid_signature(self, preserve_globals): # pylint: disable=W0613,W0621
'''
Tests that a valid signature works and returns True
'''
Expand All @@ -37,32 +59,27 @@ def test_valid_signature(self):
setattr(self.request, 'body', 'hello world'.encode('utf-8'))
assert tamarack.server.validate_github_signature(self.request) is True

# Reset variables for clean tests
tamarack.server.HOOK_SECRET_KEY = None


class TestCheckEnvVars:
'''
TestCase for the _check_env_vars function.
'''

def test_missing_hook_secret(self):
def test_missing_hook_secret(self, preserve_globals): # pylint: disable=W0613,W0621
'''
Tests that False is returned when the HOOK_SECRET_KEY is missing.
'''
tamarack.server.HOOK_SECRET_KEY = None
assert tamarack.server._check_env_vars() is False

def test_missing_gh_token(self):
def test_missing_gh_token(self, preserve_globals): # pylint: disable=W0613,W0621
'''
Tests that False is returned when GITHUB_TOKEN is missing.
'''
tamarack.server.HOOK_SECRET_KEY = 'foo'
assert tamarack.server._check_env_vars() is False

# Reset variable for clean tests
tamarack.server.HOOK_SECRET_KEY = None

def test_env_variables_present(self):
def test_env_variables_present(self, preserve_globals): # pylint: disable=W0613,W0621
'''
Tests that True is returned when all the required environment variables
are present.
Expand All @@ -71,10 +88,6 @@ def test_env_variables_present(self):
tamarack.server.GITHUB_TOKEN = 'bar'
assert tamarack.server._check_env_vars() is True

# Reset variables for clean tests
tamarack.server.HOOK_SECRET_KEY = None
tamarack.server.GITHUB_TOKEN = None


class TestSetupLogging:
'''
Expand All @@ -97,9 +110,16 @@ def test_log_level_set_failed(self):
assert tamarack.server._setup_logging() is None

@patch('os.path.exists', MagicMock(return_value=True))
@patch('os.environ.get', MagicMock(return_value='WARN'))
def test_logging_success(self):
'''
Tests that logging was set up correctly.
'''
assert tamarack.server._setup_logging() is None

@patch('os.path.exists', MagicMock(return_value=True))
@patch('os.environ.get', MagicMock(return_value='WARN'))
def test_success_valid_level(self):
'''
Tests that logging was set up correctly when passing a log_level.
'''
assert tamarack.server._setup_logging() is None
32 changes: 32 additions & 0 deletions tests/test_slack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
'''
Tests for the functions in tamarack.slack.py
'''

# Import Tornado libs
import tornado.testing

# Import Tamarack libs
import tamarack.slack


class TestAPIRequest(tornado.testing.AsyncTestCase):
'''
TestCase for the api_request function
'''

@tornado.testing.gen_test
def test_request_no_data(self):
'''
Tests that a basic API call is made to Slack with minimal information
'''
slack_url = tamarack.slack.SLACK_WEBHOOK_URL
tamarack.slack.SLACK_WEBHOOK_URL = 'https://slack.com/api/api.test'
ret = yield tamarack.slack.api_request(
method='POST',
post_data={'text': 'foo'}
)
assert ret is None

# Reset variables for clean tests
tamarack.slack.SLACK_WEBHOOK_URL = slack_url

0 comments on commit e948fb3

Please sign in to comment.