diff --git a/buildtrigger/gitlabhandler.py b/buildtrigger/gitlabhandler.py index 19d701ca0..90be9f698 100644 --- a/buildtrigger/gitlabhandler.py +++ b/buildtrigger/gitlabhandler.py @@ -48,6 +48,9 @@ GITLAB_WEBHOOK_PAYLOAD_SCHEMA = { 'items': { 'type': 'object', 'properties': { + 'id': { + 'type': 'string', + }, 'url': { 'type': 'string', }, @@ -67,7 +70,7 @@ GITLAB_WEBHOOK_PAYLOAD_SCHEMA = { 'required': ['email'], }, }, - 'required': ['url', 'message', 'timestamp'], + 'required': ['id', 'url', 'message', 'timestamp'], }, }, }, @@ -282,7 +285,8 @@ class GitLabBuildTrigger(BuildTriggerHandler): 'id': namespace['path'], 'title': namespace['name'], 'avatar_url': repo['owner']['avatar_url'], - 'score': 0, + 'score': 1, + 'url': gl_client.host + '/' + namespace['path'], } return list(namespaces.values()) @@ -486,18 +490,18 @@ class GitLabBuildTrigger(BuildTriggerHandler): def get_tag_sha(tag_name): tags = gl_client.getrepositorytags(repo['id']) if tags is False: - raise TriggerStartException('Could not find tags') + raise TriggerStartException('Could not find tag in repository') for tag in tags: if tag['name'] == tag_name: return tag['commit']['id'] - raise TriggerStartException('Could not find commit') + raise TriggerStartException('Could not find tag in repository') def get_branch_sha(branch_name): branch = gl_client.getbranch(repo['id'], branch_name) if branch is False: - raise TriggerStartException('Could not find branch') + raise TriggerStartException('Could not find branch in repository') return branch['commit']['id'] diff --git a/buildtrigger/test/gitlabmock.py b/buildtrigger/test/gitlabmock.py new file mode 100644 index 000000000..8a2212be9 --- /dev/null +++ b/buildtrigger/test/gitlabmock.py @@ -0,0 +1,186 @@ +from datetime import datetime +from mock import Mock + +from buildtrigger.gitlabhandler import GitLabBuildTrigger +from util.morecollections import AttrDict + +def get_gitlab_trigger(subdir=''): + trigger_obj = AttrDict(dict(auth_token='foobar', id='sometrigger')) + trigger = GitLabBuildTrigger(trigger_obj, { + 'build_source': 'foo/bar', + 'subdir': subdir, + 'username': 'knownuser' + }) + + trigger._get_authorized_client = get_mock_gitlab + return trigger + +def adddeploykey_mock(project_id, name, public_key): + return {'id': 'foo'} + +def addprojecthook_mock(project_id, webhook_url, push=False): + return {'id': 'foo'} + +def get_currentuser_mock(): + return { + 'username': 'knownuser' + } + +def project(namespace, name): + return { + 'id': '%s/%s' % (namespace, name), + 'default_branch': 'master', + 'namespace': { + 'id': namespace, + 'path': namespace, + 'name': namespace, + }, + 'path': name, + 'path_with_namespace': '%s/%s' % (namespace, name), + 'description': 'some %s repo' % name, + 'last_activity_at': str(datetime.utcfromtimestamp(0)), + 'web_url': 'https://bitbucket.org/%s/%s' % (namespace, name), + 'ssh_url_to_repo': 'git://%s/%s' % (namespace, name), + 'public': name != 'somerepo', + 'permissions': { + 'project_access': { + 'access_level': 50 if namespace == 'knownuser' else 0, + } + }, + 'owner': { + 'avatar_url': 'avatarurl', + } + } + +def getprojects_mock(page=1, per_page=100): + return [ + project('knownuser', 'somerepo'), + project('someorg', 'somerepo'), + project('someorg', 'anotherrepo'), + ] + +def getproject_mock(project_name): + if project_name == 'knownuser/somerepo': + return project('knownuser', 'somerepo') + + if project_name == 'foo/bar': + return project('foo', 'bar') + + return False + + +def getbranches_mock(project_id): + return [ + { + 'name': 'master', + 'commit': { + 'id': 'aaaaaaa', + } + }, + { + 'name': 'otherbranch', + 'commit': { + 'id': 'aaaaaaa', + } + }, + ] + +def getrepositorytags_mock(project_id): + return [ + { + 'name': 'sometag', + 'commit': { + 'id': 'aaaaaaa', + } + }, + { + 'name': 'someothertag', + 'commit': { + 'id': 'aaaaaaa', + } + }, + ] + +def getrepositorytree_mock(project_id, ref_name='master'): + return [ + {'name': 'README'}, + {'name': 'Dockerfile'}, + ] + +def getrepositorycommit_mock(project_id, commit_sha): + if commit_sha != 'aaaaaaa': + return False + + return { + 'id': 'aaaaaaa', + 'message': 'some message', + 'committed_date': 'now', + } + +def getusers_mock(search=None): + if search == 'knownuser': + return [ + { + 'username': 'knownuser', + 'avatar_url': 'avatarurl', + } + ] + + return False + +def getbranch_mock(repo_id, branch): + if branch != 'master' and branch != 'otherbranch': + return False + + return { + 'name': branch, + 'commit': { + 'id': 'aaaaaaa', + } + } + +def gettag_mock(repo_id, tag): + if tag != 'sometag' and tag != 'someothertag': + return False + + return { + 'name': tag, + 'commit': { + 'id': 'aaaaaaa', + } + } + +def getrawfile_mock(repo_id, branch_name, path): + if path == '/Dockerfile': + return 'hello world' + + if path == 'somesubdir/Dockerfile': + return 'hi universe' + + return False + +def get_mock_gitlab(): + mock_gitlab = Mock() + mock_gitlab.host = 'https://bitbucket.org' + + mock_gitlab.currentuser = Mock(side_effect=get_currentuser_mock) + mock_gitlab.getusers = Mock(side_effect=getusers_mock) + + mock_gitlab.getprojects = Mock(side_effect=getprojects_mock) + mock_gitlab.getproject = Mock(side_effect=getproject_mock) + mock_gitlab.getbranches = Mock(side_effect=getbranches_mock) + + mock_gitlab.getbranch = Mock(side_effect=getbranch_mock) + mock_gitlab.gettag = Mock(side_effect=gettag_mock) + + mock_gitlab.getrepositorytags = Mock(side_effect=getrepositorytags_mock) + mock_gitlab.getrepositorytree = Mock(side_effect=getrepositorytree_mock) + mock_gitlab.getrepositorycommit = Mock(side_effect=getrepositorycommit_mock) + + mock_gitlab.getrawfile = Mock(side_effect=getrawfile_mock) + + mock_gitlab.adddeploykey = Mock(side_effect=adddeploykey_mock) + mock_gitlab.addprojecthook = Mock(side_effect=addprojecthook_mock) + mock_gitlab.deletedeploykey = Mock(return_value=True) + mock_gitlab.deleteprojecthook = Mock(return_value=True) + return mock_gitlab diff --git a/buildtrigger/test/test_githosthandler.py b/buildtrigger/test/test_githosthandler.py index 04b87fa3c..2cf4f9175 100644 --- a/buildtrigger/test/test_githosthandler.py +++ b/buildtrigger/test/test_githosthandler.py @@ -3,9 +3,12 @@ import pytest from buildtrigger.triggerutil import TriggerStartException from buildtrigger.test.bitbucketmock import get_bitbucket_trigger from buildtrigger.test.githubmock import get_github_trigger +from buildtrigger.test.gitlabmock import get_gitlab_trigger from endpoints.building import PreparedBuild -@pytest.fixture(params=[get_github_trigger(), get_bitbucket_trigger()]) +# Note: This test suite executes a common set of tests against all the trigger types specified +# in this fixture. Each trigger's mock is expected to return the same data for all of these calls. +@pytest.fixture(params=[get_github_trigger(), get_bitbucket_trigger(), get_gitlab_trigger()]) def githost_trigger(request): return request.param @@ -111,7 +114,6 @@ def test_list_build_sources_for_namespace(namespace, expected, githost_trigger): def test_activate(githost_trigger): config, private_key = githost_trigger.activate('http://some/url') - assert 'deploy_key_id' in config assert 'private_key' in private_key diff --git a/buildtrigger/test/test_gitlabhandler.py b/buildtrigger/test/test_gitlabhandler.py new file mode 100644 index 000000000..25170cbca --- /dev/null +++ b/buildtrigger/test/test_gitlabhandler.py @@ -0,0 +1,88 @@ +import json +import pytest + +from buildtrigger.test.gitlabmock import get_gitlab_trigger +from buildtrigger.triggerutil import (SkipRequestException, ValidationRequestException, + InvalidPayloadException) +from endpoints.building import PreparedBuild +from util.morecollections import AttrDict + +@pytest.fixture +def gitlab_trigger(): + return get_gitlab_trigger() + + +def test_list_build_subdirs(gitlab_trigger): + assert gitlab_trigger.list_build_subdirs() == [''] + + +@pytest.mark.parametrize('subdir, contents', [ + ('', 'hello world'), + ('somesubdir', 'hi universe'), + ('unknownpath', None), +]) +def test_load_dockerfile_contents(subdir, contents): + trigger = get_gitlab_trigger(subdir) + assert trigger.load_dockerfile_contents() == contents + + +@pytest.mark.parametrize('email, expected_response', [ + ('unknown@email.com', None), + ('knownuser', {'username': 'knownuser', 'html_url': 'https://bitbucket.org/knownuser', + 'avatar_url': 'avatarurl'}), +]) +def test_lookup_user(email, expected_response, gitlab_trigger): + assert gitlab_trigger.lookup_user(email) == expected_response + + +@pytest.mark.parametrize('payload, expected_error, expected_message', [ + ('{}', SkipRequestException, ''), + + # Valid payload: + ('''{ + "ref": "refs/heads/master", + "checkout_sha": "aaaaaaa", + "repository": { + "git_ssh_url": "foobar" + }, + "commits": [ + { + "id": "aaaaaaa", + "url": "someurl", + "message": "hello there!", + "timestamp": "now" + } + ] + }''', None, None), + + # Skip message: + ('''{ + "ref": "refs/heads/master", + "checkout_sha": "aaaaaaa", + "repository": { + "git_ssh_url": "foobar" + }, + "commits": [ + { + "id": "aaaaaaa", + "url": "someurl", + "message": "[skip build] hello there!", + "timestamp": "now" + } + ] + }''', SkipRequestException, ''), +]) +def test_handle_trigger_request(gitlab_trigger, payload, expected_error, expected_message): + def get_payload(): + return json.loads(payload) + + request = AttrDict(dict(get_json=get_payload)) + + if expected_error is not None: + with pytest.raises(expected_error) as ipe: + gitlab_trigger.handle_trigger_request(request) + assert ipe.value.message == expected_message + else: + assert isinstance(gitlab_trigger.handle_trigger_request(request), PreparedBuild) + +