github: more complete mocking for app setup
This mocks the /app/installations and /installations/repositories GitHub API calls to better validate the GitHub project initalization in the driver. It implements enough that we can use GithubClientManager:_prime_installation_map() directly and better tests the token issuing, etc. Change-Id: I608f1540ef33b1a95595393e546afba308fef66a
This commit is contained in:
parent
b3eb94e3b2
commit
403d1c2882
|
@ -6,3 +6,4 @@ PyMySQL
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
graphene
|
graphene
|
||||||
|
requests_mock
|
||||||
|
|
|
@ -67,6 +67,7 @@ import yaml
|
||||||
import paramiko
|
import paramiko
|
||||||
import prometheus_client.exposition
|
import prometheus_client.exposition
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
import requests_mock
|
||||||
|
|
||||||
from zuul.driver.sql.sqlconnection import DatabaseSession
|
from zuul.driver.sql.sqlconnection import DatabaseSession
|
||||||
from zuul import model
|
from zuul import model
|
||||||
|
@ -2742,6 +2743,8 @@ class FakeGithubClientManager(GithubClientManager):
|
||||||
github_class = tests.fakegithub.FakeGithubClient
|
github_class = tests.fakegithub.FakeGithubClient
|
||||||
github_enterprise_class = tests.fakegithub.FakeGithubEnterpriseClient
|
github_enterprise_class = tests.fakegithub.FakeGithubEnterpriseClient
|
||||||
|
|
||||||
|
log = logging.getLogger("zuul.test.FakeGithubClientManager")
|
||||||
|
|
||||||
def __init__(self, connection_config):
|
def __init__(self, connection_config):
|
||||||
super().__init__(connection_config)
|
super().__init__(connection_config)
|
||||||
self.record_clients = False
|
self.record_clients = False
|
||||||
|
@ -2769,19 +2772,93 @@ class FakeGithubClientManager(GithubClientManager):
|
||||||
return client
|
return client
|
||||||
|
|
||||||
def _prime_installation_map(self):
|
def _prime_installation_map(self):
|
||||||
|
# Only valid if installed as a github app
|
||||||
if not self.app_id:
|
if not self.app_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
# simulate one installation per org
|
# github_data.repos is a hash like
|
||||||
orgs = {}
|
# { ('org', 'project1'): <dataobj>
|
||||||
latest_inst_id = 0
|
# ('org', 'project2'): <dataobj>,
|
||||||
for repo in self.github_data.repos:
|
# ('org2', 'project1'): <dataobj>, ... }
|
||||||
inst_id = orgs.get(repo[0])
|
#
|
||||||
if not inst_id:
|
# we don't care about the value. index by org, e.g.
|
||||||
latest_inst_id += 1
|
#
|
||||||
inst_id = latest_inst_id
|
# {
|
||||||
orgs[repo[0]] = inst_id
|
# 'org': ('project1', 'project2')
|
||||||
self.installation_map['/'.join(repo)] = inst_id
|
# 'org2': ('project1', 'project2')
|
||||||
|
# }
|
||||||
|
orgs = defaultdict(list)
|
||||||
|
project_id = 1
|
||||||
|
for org, project in self.github_data.repos:
|
||||||
|
# Each entry is in the format for "repositories" response
|
||||||
|
# of GET /installation/repositories
|
||||||
|
orgs[org].append({
|
||||||
|
'id': project_id,
|
||||||
|
'name': project,
|
||||||
|
'full_name': '%s/%s' % (org, project)
|
||||||
|
# note, lots of other stuff that's not relevant
|
||||||
|
})
|
||||||
|
project_id += 1
|
||||||
|
|
||||||
|
self.log.debug("GitHub installation mapped to: %s" % orgs)
|
||||||
|
|
||||||
|
# Mock response to GET /app/installations
|
||||||
|
app_json = []
|
||||||
|
app_projects = []
|
||||||
|
app_id = 1
|
||||||
|
for org, projects in orgs.items():
|
||||||
|
# We respond as if each org is a different app instance
|
||||||
|
#
|
||||||
|
# Below we will be sent the app_id in a token to query
|
||||||
|
# what projects this app exports. Keep the projects in a
|
||||||
|
# sequential list so we can just look up "projects for app
|
||||||
|
# X" == app_projects[X]
|
||||||
|
app_projects.append(projects)
|
||||||
|
app_json.append(
|
||||||
|
{
|
||||||
|
'id': app_id,
|
||||||
|
# Acutally none of this matters, and there's lots
|
||||||
|
# more in a real response. Padded out just for
|
||||||
|
# example sake.
|
||||||
|
'account': {
|
||||||
|
'login': org,
|
||||||
|
'id': 1234,
|
||||||
|
'type': 'User',
|
||||||
|
},
|
||||||
|
'permissions': {
|
||||||
|
'checks': 'write',
|
||||||
|
'metadata': 'read',
|
||||||
|
'contents': 'read'
|
||||||
|
},
|
||||||
|
'events': ['push',
|
||||||
|
'pull_request'
|
||||||
|
],
|
||||||
|
'suspended_at': None,
|
||||||
|
'suspended_by': None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
app_id += 1
|
||||||
|
|
||||||
|
# TODO(ianw) : we could exercise the pagination paths ...
|
||||||
|
with requests_mock.Mocker() as m:
|
||||||
|
m.get('%s/app/installations' % self.base_url, json=app_json)
|
||||||
|
|
||||||
|
def repositories_callback(request, context):
|
||||||
|
# FakeGithubSession gives us an auth token "token
|
||||||
|
# token-X" where "X" corresponds to the app id we want
|
||||||
|
# the projects for. apps start at id "1", so the projects
|
||||||
|
# to return for this call are app_projects[token-1]
|
||||||
|
token = int(request.headers['Authorization'][12:])
|
||||||
|
projects = app_projects[token - 1]
|
||||||
|
return {
|
||||||
|
'total_count': len(projects),
|
||||||
|
'repositories': projects
|
||||||
|
}
|
||||||
|
m.get('%s/installation/repositories?per_page=100' % self.base_url,
|
||||||
|
json=repositories_callback)
|
||||||
|
|
||||||
|
# everything mocked now, call real implementation
|
||||||
|
super()._prime_installation_map()
|
||||||
|
|
||||||
|
|
||||||
class FakeGithubConnection(githubconnection.GithubConnection):
|
class FakeGithubConnection(githubconnection.GithubConnection):
|
||||||
|
|
|
@ -641,8 +641,9 @@ class FakeGithubSession(object):
|
||||||
if re.match(r'.*/app/installations/.*/access_tokens', url):
|
if re.match(r'.*/app/installations/.*/access_tokens', url):
|
||||||
expiry = (datetime.datetime.now(utc) + datetime.timedelta(
|
expiry = (datetime.datetime.now(utc) + datetime.timedelta(
|
||||||
minutes=60)).replace(microsecond=0).isoformat()
|
minutes=60)).replace(microsecond=0).isoformat()
|
||||||
|
install_id = url.split('/')[-2]
|
||||||
data = {
|
data = {
|
||||||
'token': 'fake',
|
'token': 'token-%s' % install_id,
|
||||||
'expires_at': expiry,
|
'expires_at': expiry,
|
||||||
}
|
}
|
||||||
return FakeResponse(data, 201)
|
return FakeResponse(data, 201)
|
||||||
|
@ -767,6 +768,12 @@ class FakeGithubData(object):
|
||||||
self.reports = []
|
self.reports = []
|
||||||
self.fail_check_run_creation = False
|
self.fail_check_run_creation = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return ("pull_requests:%s repos:%s reports:%s "
|
||||||
|
"fail_check_run_creation:%s" % (
|
||||||
|
self.pull_requests, self.repos, self.reports,
|
||||||
|
self.fail_check_run_creation))
|
||||||
|
|
||||||
|
|
||||||
class FakeGithubClient(object):
|
class FakeGithubClient(object):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue