def test_promote_post(self): b = Build.create(self.project) db.session.add(Run(b, 'run0')) db.session.add(Run(b, 'run1')) url = 'http://localhost/projects/proj-1/builds/%d/promote' % b.build_id headers = { 'Content-type': 'application/json', } data = { 'name': 'release-x', 'annotation': 'foo bar', } # you can't promote an in-progress build _sign(url, headers, 'POST') self._post(url, json.dumps(data), headers, 400) for r in b.runs: r.set_status(BuildStatus.PASSED) self._post(url, json.dumps(data), headers, 201) db.session.refresh(b) self.assertEqual(BuildStatus.PROMOTED, b.status) self.assertEqual(data['name'], b.name) self.assertEqual(data['annotation'], b.annotation)
def get_signed_json(self, url, status_code=200, query_string=None): headers = {} if not url.startswith('http://'): # signed url handling requires complete url url = 'http://localhost' + url permissions._sign(url, headers, 'GET') return self.get_json(url, status_code, query_string, headers)
def test_project_trigger_create(self): self.create_projects("proj-1") url = "http://localhost/projects/proj-1/triggers/" headers = {"Content-type": "application/json"} _sign(url, headers, "POST") data = { "owner": "gavin.gavel", "type": "git_poller", "secret1": "ThisIsThePassword", } r = self.client.post(url, headers=headers, data=json.dumps(data)) self.assertEqual(201, r.status_code, r.data) p = Project.query.filter(Project.name == "proj-1").one() self.assertEqual(1, len(p.triggers)) t = p.triggers[0] self.assertEqual(TriggerTypes.git_poller.value, t.type) self.assertEqual(data["owner"], t.user) self.assertEqual(data["secret1"], t.secret_data["secret1"]) trigger = self.get_signed_json(url)[0] self.assertEqual([{"name": "secret1"}], trigger["secrets"]) # now patch the secrets and make sure it works data = { "secrets": [ {"name": "secret1", "value": "newval"}, # update one {"name": "secret2", "value": "hax0r"}, # add one ] } self.patch_signed_json(url + str(trigger["id"]) + "/", data) p = Project.query.filter(Project.name == "proj-1").one() t = p.triggers[0] self.assertEqual(t.secret_data["secret1"], "newval") self.assertEqual(t.secret_data["secret2"], "hax0r")
def test_promote_post(self): b = Build.create(self.project) db.session.add(Run(b, "run0")) db.session.add(Run(b, "run1")) url = "http://localhost/projects/proj-1/builds/%d/promote" % b.build_id headers = { "Content-type": "application/json", } data = { "name": "release-x", "annotation": "foo bar", } # you can't promote an in-progress build _sign(url, headers, "POST") self._post(url, json.dumps(data), headers, 400) for r in b.runs: r.set_status(BuildStatus.PASSED) self._post(url, json.dumps(data), headers, 201) db.session.refresh(b) self.assertEqual(BuildStatus.PROMOTED, b.status) self.assertEqual(data["name"], b.name) self.assertEqual(data["annotation"], b.annotation)
def test_build_unexpected(self, storage): """Ensure unexpected storage errors are handled gracefully.""" storage().set_run_definition.side_effect = RuntimeError("edge case!!!") headers = {"Content-type": "application/json"} data = { "trigger-name": "git", "project-definition": { "timeout": 5, "email": { "users": "*****@*****.**", }, "triggers": [ { "name": "git", "type": "git_poller", "runs": [{ "name": "run0", "host-tag": "foo*", "container": "alpine", "script": "test", }], }, ], "scripts": { "test": "#test#", }, }, } _sign("http://localhost/projects/proj-1/builds/", headers, "POST") self._post(self.urlbase, json.dumps(data), headers, 500) self.assertEqual([BuildStatus.FAILED], [x.status for x in Run.query])
def test_build_trigger_fails(self): # ensure we have a graceful failure when we are triggered headers = {} r = self.client.post(self.urlbase, data={}, headers=headers) self.assertEqual(401, r.status_code) # not signed _sign('http://localhost/projects/proj-1/builds/', headers, 'POST') r = self.client.post(self.urlbase, data={}, headers=headers) self.assertEqual(500, r.status_code) data = json.loads(r.data.decode()) self.assertEqual('error', data['status'])
def patch_signed_json(self, url, data, status_code=200): headers = {} if not url.startswith('http://'): # signed url handling requires complete url url = 'http://localhost' + url permissions._sign(url, headers, 'PATCH') resp = self.client.patch(url, headers=headers, json=data) if status_code != resp.status_code: print('response text:', resp.data) self.assertEqual(status_code, resp.status_code, resp.data) data = json.loads(resp.data) return data['data']
def test_run_rerun(self): r = Run(self.build, 'run0') r.status = BuildStatus.FAILED db.session.add(r) db.session.commit() url = 'http://localhost' + self.urlbase + 'run0/rerun' headers = {} self._post(url, 'message', headers, 401) permissions._sign(url, headers, 'POST') self._post(url, 'message', headers, 200)
def test_run_rerun(self): r = Run(self.build, "run0") r.status = BuildStatus.FAILED db.session.add(r) db.session.commit() url = "http://localhost" + self.urlbase + "run0/rerun" headers = {} self._post(url, "message", headers, 401) permissions._sign(url, headers, "POST") self._post(url, "message", headers, 200)
def test_build_trigger_simple(self, trigger_build): """Assert we can trigger a minimal build.""" trigger_build.return_value.build_id = 1 headers = {"Content-type": "application/json"} data = {} _sign("http://localhost/projects/proj-1/builds/", headers, "POST") self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({}, trigger_build.call_args[0][4]) trigger_build.reset_mock() data = {"secrets": {"foo": "bar"}} self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual(data["secrets"], trigger_build.call_args[0][4])
def test_build_trigger_simple(self, trigger_build): """Assert we can trigger a minimal build.""" trigger_build.return_value.build_id = 1 headers = {'Content-type': 'application/json'} data = {} _sign('http://localhost/projects/proj-1/builds/', headers, 'POST') self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({}, trigger_build.call_args[0][4]) trigger_build.reset_mock() data = {'secrets': {'foo': 'bar'}} self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual(data['secrets'], trigger_build.call_args[0][4])
def test_run_cancel(self, storage): r = Run(self.build, 'run0') db.session.add(r) db.session.commit() headers = {} url = 'http://localhost' + self.urlbase + 'run0/cancel' self._post(url, 'message', headers, 401) permissions._sign(url, headers, 'POST') self._post(url, '', headers, 202) db.session.refresh(r) self.assertEqual(BuildStatus.CANCELLING, r.status)
def test_run_cancel(self, storage): r = Run(self.build, "run0") db.session.add(r) db.session.commit() headers = {} url = "http://localhost" + self.urlbase + "run0/cancel" self._post(url, "message", headers, 401) permissions._sign(url, headers, "POST") self._post(url, "", headers, 202) db.session.refresh(r) self.assertEqual(BuildStatus.CANCELLING, r.status)
def test_build_trigger_with_secrets(self, trigger_build): """Assert we honor the trigger-type and trigger-id params.""" trigger_build.return_value.build_id = 1 pt = ProjectTrigger('user', TriggerTypes.simple.value, self.project, None, None, {'foo': 'simple'}) db.session.add(pt) db.session.commit() # try first trigger type headers = {'Content-type': 'application/json'} data = {'trigger-type': 'simple'} _sign('http://localhost/projects/proj-1/builds/', headers, 'POST') self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({'foo': 'simple'}, trigger_build.call_args[0][4]) # try "optional" trigger type (when there is no "optional") data = {'trigger-type': 'git-poller-optional'} _sign('http://localhost/projects/proj-1/builds/', headers, 'POST') self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({}, trigger_build.call_args[0][4]) # try override data = {'trigger-type': 'simple', 'secrets': {'foo': 'override'}} _sign('http://localhost/projects/proj-1/builds/', headers, 'POST') self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({'foo': 'override'}, trigger_build.call_args[0][4]) # try by trigger-id data = {'trigger-id': pt.id} _sign('http://localhost/projects/proj-1/builds/', headers, 'POST') self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({'foo': 'simple'}, trigger_build.call_args[0][4])
def test_project_create(self): url = "http://localhost/projects/" headers = {"Content-type": "application/json"} _sign(url, headers, "POST") r = self.client.post(url, headers=headers, data=json.dumps({"name": "foo"})) self.assertEqual(201, r.status_code, r.data) Project.query.filter(Project.name == "foo").one() r = self.client.post( url, headers=headers, data=json.dumps({"name": "foo2", "synchronous-builds": True}), ) self.assertEqual(201, r.status_code, r.data) p = Project.query.filter(Project.name == "foo2").one() self.assertTrue(p.synchronous_builds)
def test_project_delete_denied(self): self.create_projects("proj-1") url = "http://localhost/projects/proj-1/" r = self.client.delete(url) self.assertEqual(401, r.status_code) self.assertEqual("X-JobServ-Sig not provided", r.json["message"]) headers = {"Content-type": "application/json"} _sign(url, headers, "DELETE") r = self.client.delete(url, headers=headers, data=json.dumps({})) self.assertEqual(401, r.status_code, r.data) headers = {"Content-type": "application/json"} _sign(url, headers, "DELETE") data = {"I_REALLY_MEAN_TO_DO_THIS": "YES"} r = self.client.delete(url, headers=headers, data=json.dumps(data)) self.assertEqual(200, r.status_code, r.data)
def test_project_delete_denied(self): self.create_projects('proj-1') url = 'http://localhost/projects/proj-1/' r = self.client.delete(url) self.assertEqual(401, r.status_code) self.assertEqual('X-JobServ-Sig not provided', r.json['message']) headers = {'Content-type': 'application/json'} _sign(url, headers, 'DELETE') r = self.client.delete(url, headers=headers, data=json.dumps({})) self.assertEqual(401, r.status_code, r.data) headers = {'Content-type': 'application/json'} _sign(url, headers, 'DELETE') data = {'I_REALLY_MEAN_TO_DO_THIS': 'YES'} r = self.client.delete(url, headers=headers, data=json.dumps(data)) self.assertEqual(200, r.status_code, r.data)
def test_project_create(self): url = 'http://localhost/projects/' headers = {'Content-type': 'application/json'} _sign(url, headers, 'POST') r = self.client.post(url, headers=headers, data=json.dumps({'name': 'foo'})) self.assertEqual(201, r.status_code, r.data) Project.query.filter(Project.name == 'foo').one() r = self.client.post(url, headers=headers, data=json.dumps({ 'name': 'foo2', 'synchronous-builds': True })) self.assertEqual(201, r.status_code, r.data) p = Project.query.filter(Project.name == 'foo2').one() self.assertTrue(p.synchronous_builds)
def test_project_trigger_secret_removal(self): project_name = 'projectUpdateTest' trigger_url = f'http://localhost/projects/{project_name}/triggers/' trigger_headers = {'Content-type': 'application/json'} _sign(trigger_url, trigger_headers, 'POST') secret_a = (str(uuid4()), str(uuid4())) # (key, value) tuple secret_b = (str(uuid4()), str(uuid4())) secret_c = (str(uuid4()), str(uuid4())) secrets = dict([secret_a, secret_b, secret_c]) create_data = { 'owner': 'tester.testing', 'type': TriggerTypes.git_poller.name, **secrets } self.create_projects(project_name) response = self.client.post(trigger_url, headers=trigger_headers, data=json.dumps(create_data)) self.assertEqual(201, response.status_code, msg=response.data) actual_triggers = self.get_signed_json(url=trigger_url) self.assertEqual(1, len(actual_triggers)) actual_trigger = actual_triggers[0] actual_secrets = [ secret['name'] for secret in actual_trigger['secrets'] ] self.assertEqual(3, len(actual_secrets)) self.assertIn(secret_a[0], actual_secrets) self.assertIn(secret_b[0], actual_secrets) self.assertIn(secret_c[0], actual_secrets) remove_data = {'secrets': [{'name': secret_b[0], 'value': None}]} self.patch_signed_json(f"{trigger_url}{actual_trigger['id']}/", remove_data) actual_trigger = self.get_signed_json(url=trigger_url)[0] actual_secrets = [ secret['name'] for secret in actual_trigger['secrets'] ] self.assertEqual(2, len(actual_secrets)) self.assertIn(secret_a[0], actual_secrets) self.assertNotIn(secret_b[0], actual_secrets) self.assertIn(secret_c[0], actual_secrets)
def test_project_trigger_create(self): self.create_projects('proj-1') url = 'http://localhost/projects/proj-1/triggers/' headers = {'Content-type': 'application/json'} _sign(url, headers, 'POST') data = { 'owner': 'gavin.gavel', 'type': 'git_poller', 'secret1': 'ThisIsThePassword', } r = self.client.post(url, headers=headers, data=json.dumps(data)) self.assertEqual(201, r.status_code, r.data) p = Project.query.filter(Project.name == 'proj-1').one() self.assertEqual(1, len(p.triggers)) t = p.triggers[0] self.assertEqual(TriggerTypes.git_poller.value, t.type) self.assertEqual(data['owner'], t.user) self.assertEqual(data['secret1'], t.secret_data['secret1']) trigger = self.get_signed_json(url)[0] self.assertEqual([{'name': 'secret1'}], trigger['secrets']) # now patch the secrets and make sure it works data = { 'secrets': [ { 'name': 'secret1', 'value': 'newval' }, # update one { 'name': 'secret2', 'value': 'hax0r' }, # add one ] } self.patch_signed_json(url + str(trigger['id']) + '/', data) p = Project.query.filter(Project.name == 'proj-1').one() t = p.triggers[0] self.assertEqual(t.secret_data['secret1'], 'newval') self.assertEqual(t.secret_data['secret2'], 'hax0r')
def test_cancel(self): b = Build.create(self.project) db.session.add(Run(b, "run0")) r = Run(b, "run1") r.status = BuildStatus.RUNNING db.session.add(r) url = "http://localhost/projects/proj-1/builds/%d/cancel" % b.build_id headers = { "Content-type": "application/json", } _sign(url, headers, "POST") self._post(url, "", headers, 202) expected = [BuildStatus.FAILED, BuildStatus.CANCELLING] if db.engine.dialect.name == "sqlite": # sqlite doesn't handle the query properly expected[0] = BuildStatus.CANCELLING self.assertEqual(expected, [x.status for x in b.runs])
def test_project_trigger_secret_removal(self): project_name = "projectUpdateTest" trigger_url = f"http://localhost/projects/{project_name}/triggers/" trigger_headers = {"Content-type": "application/json"} _sign(trigger_url, trigger_headers, "POST") secret_a = (str(uuid4()), str(uuid4())) # (key, value) tuple secret_b = (str(uuid4()), str(uuid4())) secret_c = (str(uuid4()), str(uuid4())) secrets = dict([secret_a, secret_b, secret_c]) create_data = { "owner": "tester.testing", "type": TriggerTypes.git_poller.name, **secrets, } self.create_projects(project_name) response = self.client.post( trigger_url, headers=trigger_headers, data=json.dumps(create_data) ) self.assertEqual(201, response.status_code, msg=response.data) actual_triggers = self.get_signed_json(url=trigger_url) self.assertEqual(1, len(actual_triggers)) actual_trigger = actual_triggers[0] actual_secrets = [secret["name"] for secret in actual_trigger["secrets"]] self.assertEqual(3, len(actual_secrets)) self.assertIn(secret_a[0], actual_secrets) self.assertIn(secret_b[0], actual_secrets) self.assertIn(secret_c[0], actual_secrets) remove_data = {"secrets": [{"name": secret_b[0], "value": None}]} self.patch_signed_json(f"{trigger_url}{actual_trigger['id']}/", remove_data) actual_trigger = self.get_signed_json(url=trigger_url)[0] actual_secrets = [secret["name"] for secret in actual_trigger["secrets"]] self.assertEqual(2, len(actual_secrets)) self.assertIn(secret_a[0], actual_secrets) self.assertNotIn(secret_b[0], actual_secrets) self.assertIn(secret_c[0], actual_secrets)
def test_build_trigger_with_secrets(self, trigger_build): """Assert we honor the trigger-type and trigger-id params.""" trigger_build.return_value.build_id = 1 pt = ProjectTrigger( "user", TriggerTypes.simple.value, self.project, None, None, {"foo": "simple"}, ) db.session.add(pt) db.session.commit() # try first trigger type headers = {"Content-type": "application/json"} data = {"trigger-type": "simple"} _sign("http://localhost/projects/proj-1/builds/", headers, "POST") self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({"foo": "simple"}, trigger_build.call_args[0][4]) # try "optional" trigger type (when there is no "optional") data = {"trigger-type": "git-poller-optional"} _sign("http://localhost/projects/proj-1/builds/", headers, "POST") self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({}, trigger_build.call_args[0][4]) # try override data = {"trigger-type": "simple", "secrets": {"foo": "override"}} _sign("http://localhost/projects/proj-1/builds/", headers, "POST") self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({"foo": "override"}, trigger_build.call_args[0][4]) # try by trigger-id data = {"trigger-id": pt.id} _sign("http://localhost/projects/proj-1/builds/", headers, "POST") self._post(self.urlbase, json.dumps(data), headers, 201) self.assertEqual({"foo": "simple"}, trigger_build.call_args[0][4])