class ApiHandler: """Wrapper around circleci API calls, specific for artifact access.""" def __init__(self, token, user='******', project='connectedhomeip'): self.api = Api(token) self.user = user self.project = project self.vcs_type = 'github' def fetchCompletedBuilds(self, branch='master'): """Gets a snapshot of the latest successful builds in the specified branch.""" for build in self.api.get_project_build_summary( self.user, self.project, branch=branch, status_filter='successful'): logging.debug('Fetched summary: %r', build) yield CompletedBuild(job_name=build['workflows']['job_name'], build_num=build['build_num']) def fetchLatestBuildNumber(self, job_name, branch='master'): """Fetch the latest build number for the specified job name.""" logging.info('Searching for the latest job "%s"', job_name) for build in self.fetchCompletedBuilds(branch): logging.info('Found a completed build %r', build) if build.job_name == job_name: logging.info('Found the latest job with id %d', build.build_num) return build.build_num logging.error('No usable latest build was found.') raise Exception('No build found for job "%s"' % job_name) def getArtifacts(self, build_num): """Fetch artifacts for the specified build number.""" for artifact in self.api.get_artifacts(self.user, self.project, build_num, self.vcs_type): logging.debug('Fetched artifact info: %r', artifact) yield ArtifactInfo(path=artifact['path'], url=artifact['url'])
class TestCircleCIApi(unittest.TestCase): def setUp(self): self.c = Api(os.getenv('CIRCLE_TOKEN')) def test_trigger_with_build_params(self): params = {"build_parameters[CIRCLE_JOB]": "integration"} resp = self.c.trigger_build('thomasrockhu', 'circleci.py', params=params) self.assertEqual(resp['build_parameters']['CIRCLE_JOB'], 'integration') def test_add_ssh_key(self): # note this is a throwaway key key = """ -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAr74mGZIij/V59DTGRPaijyDolWA33FlyopBVSys09MOOF7HT EWLMwyyRIR3t6mjl7kcS3rTPWORLF4U+8iMv1EyitV+fV+pQIJmK8gZvyyNePCR0 wvAizhNTcYgtZF86D/EBNHwOdN0o4P+4qlbBlPjFiW7S5X6iDbrV9H4ADQDI2R+c EBets+aVRrtCdR+cGrOS9BRVf4NK6ADQaKOcgTYRTJuxI48O18iUj9dZSCAlO6xQ obpALlQKj0srGcx9++vTzED3Wr9JpOfsC8LqNdJdaF6KJfBsc1xczSw980hcXwWx /zrQaii5W7nFU2lRrwdXyscqutL1I5lLKDYhLQIDAQABAoIBACA9mxG/3HVajGf/ sov+Ty5A1EprH3ReOIiYP/2NTKbGpW+1YMpkvLnlmC5iJj6FxgDjqxOOSie9ogUL ndOgHusssADkLQBc7Rw97t6dza6Pq38PFRiaI1h49Srz15f9XFKGXTk6tRA9bn1w jHk7d0IULXEcErald6dbKlszLmE0AHvWHWNrABwbNBzG2PrFFbrWbYiUDhIx8Ebj 9IKDu8JqYr5o6Kv8agOAWkq4S3iGQ9S+suTiV+3/kyK7XL5TI5gVPdZR4NIAGFKO +1TBNtCiYl+LQ46km5cmirESTsObNM3JrF8VWBlVZoVrxZiIhYUAKrFzcJ905Vrh PN0rwmECgYEA6LWeorGs9kyVgNI4KnvVI1AXACnpy4L48ypqIns5A71j+4OiVI64 dWAlHB64ZoMVVBqTCv/uiloqzlK+FCe8cxi3Xh/hBDmKmGZygpFoRdGwmNA+1CoA DZftaswUQ64Qt8jc9HmQnufvkxniNAewRxuZDP462WgBwZhRe8hvUqUCgYEAwVT0 HojCaLm2TMY2XE9EzCHNF1XlfqzB0i0dJ4pQ2SZ8st1F8vkEjHsPEOURRkLzWFC4 A/QWFsmhv4UKpDplV5QV3S+FgbpzLdV64vUYBuo06OZGcJgCdtZyt6vGSB4f+mq2 VQt+j6ZYlI5nWwkz1Yvg8AmBemuXNFui8o/c9ekCgYB2vwa9+nBKFnZLj/n9I8d1 B49VFA4rPSAP5VrXUY2cbO4yD8+r2lAiBPeqy7pJBSbDDfRurn5otu4U7n/0BPrS uJAJRbcq0rn4Xn6cRdqxlfjJYapN1UjFpvsNfinxB0ecoLCvR8EWdT/5DkIxTqMT BfApgylAeyQ6R6F8yqCTyQKBgQCN5XFrO8scnDmt7ckWRWPkQ2bJGtVe/SMgxNXi IIWoa7QYf4mIhLaO+P8c0lO0cw0yI8R7ulnADet2qwodcXLSLbFCb0+Y4KUK3eXc 0DD7WkjNK75Fg3xDhrAaGKxmYB3uaQY8MzyH6HqZRk+bpIxzzr+gzglHNdJ7rkpR p79wiQKBgQCwFyjcLAfA5uJDZ9jVEZ2BKgd9IGo16HrRnd+mpggUBGf2mbE2JGgk rAUZ8tU0o5Ec6T0ZQkcous7OwBZGE+JLuFa3S6JfISLw42brjQ9dE5mosm7m2d4H 3C9cAnmV6aJkTwT46OiNvUYZ8je5WP+4Kqb2ke9xw3ddk5X1n5AB4w== -----END RSA PRIVATE KEY----- """ resp = self.c.add_ssh_key('thomasrockhu', 'circleci-sandbox', key, hostname='localhost') # there is no response when success, so we test to make sure # that there is no other message as well. self.assertTrue(len(resp) == 0) def test_get_user_info(self): # exercise a real "GET" resp = self.c.get_user_info() self.assertEqual(resp['login'], 'thomasrockhu') def test_clear_cache(self): # execrise a real "DELETE" resp = self.c.clear_cache('thomasrockhu', 'circleci-sandbox') self.assertEqual(resp['status'], 'build dependency caches deleted') def test_retry_build_no_cache(self): # use Experimental API self.e = Experimental(os.getenv('CIRCLE_TOKEN')) resp = self.e.retry_no_cache('thomasrockhu', 'circleci-sandbox', 1) self.assertTrue(resp['no_dependency_cache']) def test_add_heroku_key(self): key = os.getenv('HEROKU_KEY') resp = self.c.add_heroku_key(key) # there is no response when success, so we test to make sure # that there is no other message as well. self.assertTrue(len(resp) == 0) def test_download_artifact(self): resp = self.c.get_artifacts('thomasrockhu', 'circleci.py', 87) artifact = self.c.download_artifact(resp[0]['url']) self.assertIn('circleci_api_py.html', artifact) artifact_with_destdir = self.c.download_artifact( resp[0]['url'], '/tmp') self.assertIn('tmp', artifact_with_destdir) artifact_with_destdir_and_filename = self.c.download_artifact( resp[0]['url'], '/tmp', 'myartifact.txt') self.assertIn('myartifact.txt', artifact_with_destdir_and_filename)
class TestCircleCIApi(unittest.TestCase): def setUp(self): self.c = Api(os.getenv('CIRCLE_TOKEN')) def loadMock(self, filename): """helper function to open mock responses""" filename = 'tests/mocks/{0}'.format(filename) with open(filename, 'r') as f: self.c._request = MagicMock(return_value=f.read()) def test_bad_verb(self): with self.assertRaises(BadVerbError) as e: self.c._request('BAD', 'dummy') self.assertEqual('BAD', e.exception.argument) self.assertIn('DELETE', e.exception.message) def test_get_user_info(self): self.loadMock('mock_user_info_response') resp = json.loads(self.c.get_user_info()) self.assertEqual(resp["selected_email"], '*****@*****.**') def test_get_projects(self): self.loadMock('mock_get_projects_response') resp = json.loads(self.c.get_projects()) self.assertEqual(resp[0]['vcs_url'], 'MOCK+https://ghe-dev.circleci.com/ccie-tester/testing') def test_follow_project(self): self.loadMock('mock_follow_project_response') resp = json.loads(self.c.follow_project('ccie-tester', 'testing')) self.assertEqual(resp["mock+following"], True) def test_get_project_build_summary(self): self.loadMock('mock_project_build_summary_response') resp = json.loads(self.c.get_project_build_summary('ccie-tester', 'testing')) self.assertEqual(len(resp), 6) self.assertEqual(resp[0]['username'], 'MOCK+ccie-tester') # with invalid status filter with self.assertRaises(InvalidFilterError) as e: json.loads(self.c.get_project_build_summary('ccie-tester', 'testing', status_filter='dummy')) self.assertEqual('dummy', e.exception.argument) self.assertIn('running', e.exception.message) # with branch resp = json.loads(self.c.get_project_build_summary('ccie-tester', 'testing', branch='master')) self.assertEqual(len(resp), 6) self.assertEqual(resp[0]['username'], 'MOCK+ccie-tester') def test_get_recent_builds(self): self.loadMock('mock_get_recent_builds_response') resp = json.loads(self.c.get_recent_builds()) self.assertEqual(len(resp), 7) self.assertEqual(resp[0]['reponame'], 'MOCK+testing') def test_get_build_info(self): self.loadMock('mock_get_build_info_response') resp = json.loads(self.c.get_build_info('ccie-tester', 'testing', '1')) self.assertEqual(resp['reponame'], 'MOCK+testing') def test_get_artifacts(self): self.loadMock('mock_get_artifacts_response') resp = json.loads(self.c.get_artifacts('ccie-tester', 'testing', '1')) self.assertEqual(resp[0]['path'], 'MOCK+raw-test-output/go-test-report.xml') def test_retry_build(self): self.loadMock('mock_retry_build_response') resp = json.loads(self.c.retry_build('ccie-tester', 'testing', '1')) self.assertEqual(resp['reponame'], 'MOCK+testing') # with SSH resp = json.loads(self.c.retry_build('ccie-tester', 'testing', '1', ssh=True)) self.assertEqual(resp['reponame'], 'MOCK+testing') def test_cancel_build(self): self.loadMock('mock_cancel_build_response') resp = json.loads(self.c.cancel_build('ccie-tester', 'testing', '11')) self.assertEqual(resp['reponame'], 'MOCK+testing') self.assertEqual(resp['build_num'], 11) self.assertTrue(resp['canceled']) def test_add_ssh_user(self): self.loadMock('mock_add_ssh_user_response') resp = json.loads(self.c.add_ssh_user('ccie-tester', 'testing', '11')) self.assertEqual(resp['reponame'], 'MOCK+testing') self.assertEqual(resp['ssh_users'][0]['login'], 'ccie-tester') def test_trigger_build(self): self.loadMock('mock_trigger_build_response') resp = json.loads(self.c.trigger_build('ccie-tester', 'testing')) self.assertEqual(resp['reponame'], 'MOCK+testing') def test_list_checkout_keys(self): self.loadMock('mock_list_checkout_keys_response') resp = json.loads(self.c.list_checkout_keys('levlaz', 'circleci-sandbox')) self.assertEqual(resp[0]['type'], 'deploy-key') self.assertIn('public_key', resp[0]) def test_create_checkout_key(self): with self.assertRaises(BadKeyError) as e: self.c.create_checkout_key('levlaz', 'test', 'bad') self.assertEqual('bad', e.exception.argument) self.assertIn('deploy-key', e.exception.message) self.loadMock('mock_create_checkout_key_response') resp = json.loads(self.c.create_checkout_key('levlaz', 'test', 'deploy-key')) self.assertEqual(resp['type'], 'deploy-key') self.assertIn('public_key', resp) def test_get_checkout_key(self): self.loadMock('mock_get_checkout_key_response') resp = json.loads(self.c.get_checkout_key('levlaz', 'circleci-sandbox', '94:19:ab:a9:f4:2b:21:1c:a5:87:dd:ee:3d:c2:90:4e')) self.assertEqual(resp['type'], 'deploy-key') self.assertIn('public_key', resp) def test_delete_checkout_key(self): self.loadMock('mock_delete_checkout_key_response') resp = json.loads(self.c.delete_checkout_key('levlaz', 'circleci-sandbox', '94:19:ab:a9:f4:2b:21:1c:a5:87:dd:ee:3d:c2:90:4e')) self.assertEqual(resp['message'], 'ok') def test_clear_cache(self): self.loadMock('mock_clear_cache_response') resp = json.loads(self.c.clear_cache('levlaz', 'circleci-sandbox')) self.assertEqual('build dependency caches deleted', resp['status']) def test_get_test_metadata(self): self.loadMock('mock_get_test_metadata_response') resp = json.loads(self.c.get_test_metadata('levlaz', 'circleci-demo-javascript-express', 127)) self.assertEqual(len(resp), 2) self.assertIn('tests', resp) def test_list_envvars(self): self.loadMock('mock_list_envvars_response') resp = json.loads(self.c.list_envvars('levlaz', 'circleci-sandbox')) self.assertEqual(len(resp), 4) self.assertEqual(resp[0]['name'], 'BAR') def test_add_envvar(self): self.loadMock('mock_add_envvar_response') resp = json.loads(self.c.add_envvar('levlaz', 'circleci-sandbox', 'foo', 'bar')) self.assertEqual(resp['name'], 'foo') self.assertNotEqual(resp['value'], 'bar') def test_get_envvar(self): self.loadMock('mock_get_envvar_response') resp = json.loads(self.c.get_envvar('levlaz', 'circleci-sandbox', 'foo')) self.assertEqual(resp['name'], 'foo') self.assertNotEqual(resp['value'], 'bar') def test_delete_envvar(self): self.loadMock('mock_delete_envvar_response') resp = json.loads(self.c.delete_envvar('levlaz', 'circleci-sandbox', 'foo')) self.assertEqual(resp['message'], 'ok') def test_get_latest_artifact(self): self.loadMock('mock_get_latest_artifacts_response') resp = json.loads(self.c.get_latest_artifact('levlaz', 'circleci-sandbox')) self.assertEqual(resp[0]['path'],'circleci-docs/index.html') resp = json.loads(self.c.get_latest_artifact('levlaz', 'circleci-sandbox', 'master')) self.assertEqual(resp[0]['path'],'circleci-docs/index.html') with self.assertRaises(InvalidFilterError): self.c.get_latest_artifact('levlaz', 'circleci-sandbox', 'master', 'invalid')