Skip to content

Commit 49a51c4

Browse files
committed
Add unittests for testing github actions post build success
1 parent d4a2510 commit 49a51c4

File tree

3 files changed

+194
-16
lines changed

3 files changed

+194
-16
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
python -m pip install --upgrade pip
3232
if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
3333
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
34-
- name: Apply dogdy
34+
- name: Apply dodgy
3535
run: |
3636
dodgy
3737
- name: Apply isort

mod_ci/controllers.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -736,11 +736,6 @@ def start_ci():
736736
user_id = payload['pull_request']['user']['id']
737737
if BlockedUsers.query.filter(BlockedUsers.user_id == user_id).first() is not None:
738738
g.log.warning("User Blacklisted")
739-
github_status.post(
740-
state=Status.ERROR,
741-
description="CI start aborted. You may be blocked from accessing this functionality",
742-
target_url=url_for('home.index', _external=True)
743-
)
744739
return 'ERROR'
745740
add_test_entry(g.db, github_status, commit_hash, TestType.pull_request, pr_nr=pr_nr)
746741

@@ -864,8 +859,19 @@ def start_ci():
864859
elif is_complete:
865860
if payload['workflow_run']['event'] == "pull_request":
866861
# In case of pull request run tests of only if it is still in open state
862+
# and user is not blacklisted
867863
for pull_request in repository.pulls.get(state="open"):
868864
if pull_request['head']['sha'] == commit_hash and any(builds.values()):
865+
user_id = pull_request['user']['id']
866+
if BlockedUsers.query.filter(BlockedUsers.user_id == user_id).first() is not None:
867+
g.log.warning("User Blacklisted")
868+
github_status.post(
869+
state=Status.ERROR,
870+
description="CI start aborted. \
871+
You may be blocked from accessing this functionality",
872+
target_url=url_for('home.index', _external=True)
873+
)
874+
return 'ERROR'
869875
queue_test(g.db, github_status, commit_hash,
870876
TestType.pull_request, pr_nr=pull_request['number'])
871877
elif any(builds.values()):
@@ -874,10 +880,7 @@ def start_ci():
874880
deschedule_test(github_status, commit_hash, TestType.commit,
875881
message="Not ran - no code changes", state=Status.SUCCESS)
876882
elif payload['action'] == 'requested':
877-
workflow_name = payload['workflow_run']['name']
878-
879-
if workflow_name in ["Build CCExtractor on Linux", "Build CCExtractor on Windows"]:
880-
schedule_test(github_status, commit_hash, TestType.commit)
883+
schedule_test(github_status, commit_hash, TestType.commit)
881884
else:
882885
g.log.warning('Unknown action type in workflow_run! Dumping payload for analysis')
883886
g.log.warning(payload)

tests/test_ci/TestControllers.py

Lines changed: 181 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import json
22
from importlib import reload
33
from unittest import mock
4-
from unittest.mock import MagicMock
4+
from unittest.mock import MagicMock, Mock
55

66
from flask import g
77
from werkzeug.datastructures import Headers
88

99
from mod_auth.models import Role
10-
from mod_ci.controllers import get_info_for_pr_comment, start_platforms
10+
from mod_ci.controllers import (Workflow_builds, get_info_for_pr_comment,
11+
start_platforms)
1112
from mod_ci.models import BlockedUsers
1213
from mod_customized.models import CustomizedTest
1314
from mod_home.models import CCExtractorVersion, GeneralData
@@ -675,9 +676,8 @@ def __init__(self):
675676

676677
@mock.patch('mod_ci.controllers.BlockedUsers')
677678
@mock.patch('mod_ci.controllers.GitHub')
678-
@mock.patch('mod_ci.controllers.queue_test')
679679
@mock.patch('requests.get', side_effect=mock_api_request_github)
680-
def test_webhook_pr_opened_blocked(self, mock_request, mock_queue_test, mock_github, mock_blocked):
680+
def test_webhook_pr_opened_blocked(self, mock_request, mock_github, mock_blocked):
681681
"""Test webhook triggered with pull_request event with opened action for blocked user."""
682682
class MockTest:
683683
def __init__(self):
@@ -712,6 +712,180 @@ def test_webhook_pr_opened(self, mock_request, mock_add_test_entry, mock_github,
712712
mock_blocked.query.filter.assert_called_once_with(mock_blocked.user_id == 'test')
713713
mock_add_test_entry.assert_called_once()
714714

715+
@mock.patch('mod_ci.controllers.GitHub')
716+
@mock.patch('mod_ci.controllers.schedule_test')
717+
@mock.patch('requests.get', side_effect=mock_api_request_github)
718+
def test_webhook_workflow_run_event_requested_action_with_valid_workflow_name(self, mock_request,
719+
mock_schedule_test, mock_github):
720+
"""Test webhook triggered with workflow run event with action requested with a valid workflow name."""
721+
data = {'action': 'requested', 'workflow_run': {
722+
'name': 'Build CCExtractor on Linux', 'head_sha': 'abcd1234'}}
723+
with self.app.test_client() as c:
724+
response = c.post(
725+
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
726+
data=json.dumps(data), headers=self.generate_header(data, 'workflow_run'))
727+
self.assertEqual(response.data, b'{"msg": "EOL"}')
728+
mock_schedule_test.assert_called_once()
729+
730+
@mock.patch('mod_ci.controllers.queue_test')
731+
@mock.patch('requests.get', side_effect=mock_api_request_github)
732+
def test_webhook_workflow_run_event_completed_action_successful(self, mock_request, mock_queue_test):
733+
"""Test webhook triggered with workflow run event with action completed and status success."""
734+
data = {'action': 'completed',
735+
'workflow_run': {'event': 'push',
736+
'name': 'Build CCExtractor on Linux', 'head_sha': '1',
737+
'head_branch': 'master'}, 'sender': {'login': 'test_owner'}}
738+
fakedata = {'workflow_runs': [
739+
{'head_sha': '1', 'status': 'completed',
740+
'conclusion': 'success', 'name': Workflow_builds.LINUX},
741+
{'head_sha': '1', 'status': 'completed',
742+
'conclusion': 'success', 'name': Workflow_builds.WINDOWS}
743+
]}
744+
745+
class MockedRepository:
746+
def statuses(self, *args):
747+
return None
748+
749+
class actions:
750+
class runs:
751+
def get(*args, **kwargs):
752+
return fakedata
753+
754+
class MockedGitHub:
755+
def repos(self, *args):
756+
return MockedRepository
757+
758+
with self.app.test_client() as c:
759+
from github import GitHub
760+
GitHub.repos = Mock(return_value=MockedGitHub.repos)
761+
762+
response = c.post(
763+
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
764+
data=json.dumps(data), headers=self.generate_header(data, 'workflow_run'))
765+
mock_queue_test.assert_called_once()
766+
767+
@mock.patch('mod_ci.controllers.deschedule_test')
768+
@mock.patch('requests.get', side_effect=mock_api_request_github)
769+
def test_webhook_workflow_run_event_completed_action_failure(self, mock_request, mock_deschedule_test):
770+
"""Test webhook triggered with workflow run event with action completed and status failure."""
771+
data = {'action': 'completed',
772+
'workflow_run': {'event': 'push',
773+
'name': Workflow_builds.LINUX, 'head_sha': '1',
774+
'head_branch': 'master'}, 'sender': {'login': 'test_owner'}}
775+
fakedata = {'workflow_runs': [
776+
{'head_sha': '1', 'status': 'completed',
777+
'conclusion': 'failure', 'name': Workflow_builds.LINUX}
778+
]}
779+
780+
class MockedRepository:
781+
def statuses(self, *args):
782+
return None
783+
784+
class actions:
785+
class runs:
786+
def get(*args, **kwargs):
787+
return fakedata
788+
789+
class MockedGitHub:
790+
def repos(self, *args):
791+
return MockedRepository
792+
793+
with self.app.test_client() as c:
794+
from github import GitHub
795+
GitHub.repos = Mock(return_value=MockedGitHub.repos)
796+
797+
response = c.post(
798+
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
799+
data=json.dumps(data), headers=self.generate_header(data, 'workflow_run'))
800+
mock_deschedule_test.assert_called_once()
801+
802+
@mock.patch('mod_ci.controllers.GitHub')
803+
@mock.patch('mod_ci.controllers.schedule_test')
804+
@mock.patch('requests.get', side_effect=mock_api_request_github)
805+
def test_webhook_workflow_run_requested_action_with_invalid_workflow_name(self, mock_request,
806+
mock_schedule_test, mock_github):
807+
"""Test webhook triggered with workflow run event with an invalid action."""
808+
data = {'action': 'requested', 'workflow_run': {
809+
'name': 'Invalid', 'head_sha': 'abcd1234'}}
810+
with self.app.test_client() as c:
811+
response = c.post(
812+
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
813+
data=json.dumps(data), headers=self.generate_header(data, 'workflow_run'))
814+
mock_schedule_test.assert_not_called()
815+
self.assertEqual(response.data, b'{"msg": "EOL"}')
816+
817+
@mock.patch('mod_ci.controllers.schedule_test')
818+
@mock.patch('mod_ci.controllers.deschedule_test')
819+
@mock.patch('mod_ci.controllers.add_test_entry')
820+
@mock.patch('requests.get', side_effect=mock_api_request_github)
821+
def test_webhook_with_unrecognized_event(self, mock_github, mock_schedule_test,
822+
mock_deschedule_test, mock_add_test_entry):
823+
"""Test webhook with an unrecognised event triggered via GitHub Actions."""
824+
with self.app.test_client() as c:
825+
response = c.post(
826+
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
827+
data=json.dumps({}), headers=self.generate_header({}, 'workflow_job'))
828+
mock_schedule_test.assert_not_called()
829+
mock_deschedule_test.assert_not_called()
830+
mock_add_test_entry.assert_not_called()
831+
self.assertEqual(response.data, b'{"msg": "EOL"}')
832+
833+
@mock.patch('flask.g.log.warning')
834+
@mock.patch('requests.get', side_effect=mock_api_request_github)
835+
def test_webhook_with_invalid_ci_signature(self, mock_github, mock_warning):
836+
"""Test webhook if an invalid X-Hub-Signature is passed within headers."""
837+
with self.app.test_client() as c:
838+
response = c.post(
839+
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
840+
data=json.dumps({}), headers=self.generate_header({}, 'workflow_run', "1"))
841+
mock_warning.assert_called_once()
842+
843+
def test_start_ci_with_a_get_request(self):
844+
"""Test start_ci function with a request method other than post."""
845+
from mod_ci.controllers import start_ci
846+
response = start_ci()
847+
self.assertEqual(response, 'OK')
848+
849+
@mock.patch('github.GitHub')
850+
@mock.patch('run.log.debug')
851+
def test_queue_test_with_pull_request(self, mock_debug, git_mock):
852+
"""Check queue_test function with pull request as test type."""
853+
from mod_ci.controllers import add_test_entry, queue_test
854+
repository = git_mock(access_token=g.github['bot_token']).repos(
855+
g.github['repository_owner'])(g.github['repository'])
856+
add_test_entry(g.db, repository.statuses("1"), 'customizedcommitcheck', TestType.pull_request)
857+
mock_debug.assert_called_once_with('pull request test type detected')
858+
queue_test(g.db, repository.statuses("1"), 'customizedcommitcheck', TestType.pull_request)
859+
mock_debug.assert_called_with('Created tests, waiting for cron...')
860+
861+
@mock.patch('run.log.critical')
862+
@mock.patch('run.log.debug')
863+
@mock.patch('github.GitHub')
864+
def test_schedule_test_function(self, git_mock, mock_debug, mock_critical):
865+
"""Check the functioning of schedule_test function."""
866+
from mod_ci.controllers import schedule_test
867+
repository = git_mock(access_token=g.github['bot_token']).repos(
868+
g.github['repository_owner'])(g.github['repository'])
869+
schedule_test(repository.statuses(1), "1", TestType.commit)
870+
mock_debug.assert_not_called()
871+
schedule_test(None, None, TestType.commit)
872+
mock_debug.assert_not_called()
873+
schedule_test(repository.statuses(1), "1", TestType.pull_request)
874+
mock_debug.assert_called_once_with('pull request test type detected')
875+
876+
@mock.patch('run.log.critical')
877+
@mock.patch('run.log.debug')
878+
@mock.patch('github.GitHub')
879+
def test_deschedule_test_function(self, git_mock, mock_debug, mock_critical):
880+
"""Check the functioning of deschedule_test function."""
881+
from mod_ci.controllers import deschedule_test
882+
repository = git_mock(access_token=g.github['bot_token']).repos(
883+
g.github['repository_owner'])(g.github['repository'])
884+
deschedule_test(repository.statuses(1), "1", TestType.commit)
885+
mock_debug.assert_not_called()
886+
deschedule_test(None, None, TestType.commit)
887+
mock_debug.assert_not_called()
888+
715889
@mock.patch('mod_ci.controllers.inform_mailing_list')
716890
@mock.patch('requests.get', side_effect=mock_api_request_github)
717891
@mock.patch('mod_ci.controllers.Issue')
@@ -1143,7 +1317,7 @@ def test_in_maintenance_mode_windows(self):
11431317
self.assertIsNotNone(response.data)
11441318

11451319
@staticmethod
1146-
def generate_header(data, event):
1320+
def generate_header(data, event, ci_key=None):
11471321
"""
11481322
Generate headers for various REST methods.
11491323
@@ -1152,6 +1326,7 @@ def generate_header(data, event):
11521326
:param event: the GitHub event to be triggered
11531327
:type event: str
11541328
"""
1155-
sig = generate_signature(str(json.dumps(data)).encode('utf-8'), g.github['ci_key'])
1329+
sig = generate_signature(str(json.dumps(data)).encode(
1330+
'utf-8'), g.github['ci_key'] if ci_key is None else ci_key)
11561331
headers = generate_git_api_header(event, sig)
11571332
return headers

0 commit comments

Comments
 (0)