# Copyright (C) 2018 Lars Wirzenius # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import json import unittest import ick2 json_type = 'application/json' class HttpAPITests(unittest.TestCase): def setUp(self): self.session = FakeHttpSession() self.session.token = 'SECRET-TOKEN' self.client = ick2.HttpAPI() self.client.set_session(self.session) self.client.set_token(self.session.token) def test_get_dict_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.client.get_dict('http://controller/version') def test_get_dict_raises_exception_on_not_json(self): self.session.response = FakeResponse(200, body=json.dumps('{}')) with self.assertRaises(ick2.HttpError): self.client.get_dict('http://controller/version') def test_get_dict_raises_exception_on_malformed_json(self): self.session.response = FakeResponse( 200, body='this is not really JSON', content_type=json_type) with self.assertRaises(ick2.HttpError): self.client.get_dict('http://controller/version') def test_get_dict_returns_response(self): version = { 'version': '1.0', } self.session.response = FakeResponse( 200, body=json.dumps(version), content_type=json_type) obj = self.client.get_dict('http://controller/version') self.assertEqual(obj, version) def test_get_blob_raises_exception_on_error(self): self.session.response = FakeResponse(404) with self.assertRaises(ick2.HttpError): self.client.get_blob('http://blobs/foo') def test_get_blob_returns_response(self): blob = b'hello, world\n' self.session.response = FakeResponse(200, body=blob) obj = self.client.get_blob('http://blobs/foo') self.assertEqual(obj, blob) def test_post_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.client.post('http://controller/work', body='') def test_post_succeeds(self): work = { 'project': 'foo', 'stdout': 'hello, world\n', } self.session.response = FakeResponse( 201, body=json.dumps(work), content_type=json_type) obj = self.client.post('http://controller/work', body=work) self.assertEqual(obj, None) def test_put_raises_exception_on_error(self): blob = b'fooblob' self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.client.put('http://blobs/foo', body=blob) def test_put_succeeds(self): blob = b'fooblob' self.session.response = FakeResponse(201, body=None) obj = self.client.put('http://controller/work', body=blob) self.assertEqual(obj, None) def test_post_auth_does_basic_auth(self): token = 'this is a token' scope = 'this and that' response = { 'access_token': token, 'token_type': 'bearer', 'scope': scope, } self.session.response = FakeResponse(200, body=response) client_id = 'this-is--my-client' client_secret = '*****' auth = (client_id, client_secret) body = 'foo=bar' self.client.post_auth( 'http://auth.example.com/token', auth=auth, body=body) self.assertEqual(self.session.auth, auth) authz = self.session.headers['Authorization'] self.assertTrue(authz.startswith('Basic ')) self.assertEqual(self.session.body, body) class ControllerClientTests(unittest.TestCase): def setUp(self): self.session = FakeHttpSession() self.session.token = 'SECRET-TOKEN' self.client = ick2.HttpAPI() self.client.set_session(self.session) self.client.set_token(self.session.token) self.controller = ick2.ControllerClient() self.controller.set_http_api(self.client) self.controller.set_controller_url('https://controller') self.controller.set_token('SECRET-TOKEN') self.controller.set_client_name('asterix') def test_register_succeeds_on_error(self): self.session.response = FakeResponse(400) self.assertEqual(self.controller.register(), None) def test_register_succeeds(self): self.session.response = FakeResponse(200) self.assertEqual(self.controller.register(), None) def test_get_work_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.controller.get_work() def test_get_work_succeeds(self): work = { 'build_id': 'rome/1' } self.session.response = FakeResponse( 200, body=json.dumps(work), content_type=json_type) self.assertEqual(self.controller.get_work(), work) def test_report_work_raises_exception_on_error(self): work = { 'stdout': 'hello, world', } self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.controller.report_work(work) def test_report_work_succeeds(self): work = { 'stdout': 'hello, world', } self.session.response = FakeResponse(200) self.assertEqual(self.controller.report_work(work), None) def test_get_apt_server_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.controller.get_apt_server() def test_get_apt_server_succeeds(self): server = 'apt.example.com' version = { 'apt_server': server, } self.session.response = FakeResponse( 200, body=json.dumps(version), content_type=json_type) self.assertEqual(self.controller.get_apt_server(), server) def test_get_artifact_store_url_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.controller.get_artifact_store_url() def test_get_artifact_store_url_succeeds(self): url = 'https://blobs' version = { 'artifact_store': url, } self.session.response = FakeResponse( 200, body=json.dumps(version), content_type=json_type) self.assertEqual(self.controller.get_artifact_store_url(), url) def test_get_auth_url_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.controller.get_auth_url() def test_get_auth_url_succeeds(self): url = 'https://blobs' version = { 'auth_url': url, } self.session.response = FakeResponse( 200, body=json.dumps(version), content_type=json_type) self.assertEqual(self.controller.get_auth_url(), url) def test_get_auth_client_returns_object(self): url = 'https://blobs' version = { 'auth_url': url, } self.session.response = FakeResponse( 200, body=json.dumps(version), content_type=json_type) ac = self.controller.get_auth_client() self.assertTrue(isinstance(ac, ick2.AuthClient)) class AuthClientTests(unittest.TestCase): def setUp(self): self.session = FakeHttpSession() self.client = ick2.HttpAPI() self.client.set_session(self.session) def test_raises_exception_on_error(self): self.session.response = FakeResponse(400) url = 'https://auth.example.com' client_id = 'test-client' client_secret = 'hunter2' ac = ick2.AuthClient() ac.set_auth_url(url) ac.set_http_api(self.client) ac.set_client_creds(client_id, client_secret) with self.assertRaises(ick2.HttpError): ac.get_token('') def test_returns_token(self): token = 'this-is-my-token' token_response = { 'access_token': token, } self.session.response = FakeResponse( 200, body=json.dumps(token_response), content_type=json_type) url = 'https://auth.example.com' client_id = 'test-client' client_secret = 'hunter2' ac = ick2.AuthClient() ac.set_auth_url(url) ac.set_http_api(self.client) ac.set_client_creds(client_id, client_secret) self.assertEqual(ac.get_token(''), token) class BlobServiceClientTests(unittest.TestCase): def setUp(self): self.session = FakeHttpSession() self.session.token = 'SECRET-TOKEN' self.client = ick2.HttpAPI() self.client.set_session(self.session) self.client.set_token(self.session.token) self.controller = ick2.ControllerClient() self.controller.set_http_api(self.client) self.controller.set_controller_url('https://controller') self.controller.set_token('SECRET-TOKEN') self.controller.set_client_name('asterix') def get_blob_client(self): url = 'https://blobs' version = { 'artifact_store': url, } self.session.response = FakeResponse( 200, body=json.dumps(version), content_type=json_type) return self.controller.get_blob_client() def test_get_blob_client_raises_exception_on_error(self): self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): self.controller.get_blob_client() def test_get_blob_client_succeeds(self): blobs = self.get_blob_client() self.assertTrue(isinstance(blobs, ick2.BlobClient)) def test_download_raises_exception_on_error(self): blobs = self.get_blob_client() self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): blobs.download('foo') def test_download_succeeds(self): blobs = self.get_blob_client() blob = b'hello, world' self.session.response = FakeResponse(200, body=blob) self.assertEqual(blobs.download('foo'), blob) def test_upload_raises_exception_on_error(self): blobs = self.get_blob_client() blob = b'hello, world' self.session.response = FakeResponse(400) with self.assertRaises(ick2.HttpError): blobs.upload('foo', blob) def test_upload_succeeds(self): blobs = self.get_blob_client() blob = b'hello, world' self.session.response = FakeResponse(200) self.assertEqual(blobs.upload('foo', blob), None) class FakeHttpSession: def __init__(self): self.response = None self.token = None self.auth = None self.headers = None self.body = None def get(self, url, headers=None, verify=None): assert self.response is not None assert self.is_authorized(headers) return self.response def post(self, url, headers=None, data=None, verify=None, auth=None): assert self.response is not None assert auth is not None or self.is_authorized(headers) self.auth = auth self.headers = headers self.body = data return self.response def put(self, url, headers=None, data=None, verify=None): assert self.response is not None assert self.is_authorized(headers) return self.response def is_authorized(self, headers): assert self.token is not None assert 'Authorization' in headers v = headers['Authorization'] return v == 'Bearer {}'.format(self.token) class FakeResponse: def __init__(self, status_code, body=None, content_type=None): if content_type is None: content_type = 'application/octet-stream' self.status_code = status_code self.headers = { 'Content-Type': content_type, } self.content = body self.text = body @property def ok(self): return self.status_code in (200, 201) def json(self): return json.loads(self.content)