From 2477249a4002c065462e46b74cd7e2c9ab019ff1 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 6 Jun 2016 14:23:58 +1000 Subject: [PATCH] Add initial tests To make os-http testable we do a little bit of rearranging of the shell so that it returns strings and exceptions rather than printing directly. With this rearranging we can then add a couple of tests to verify basic functionality. --- os_http/shell.py | 86 ++++++++++++--------- os_http/tests/base.py | 7 +- os_http/tests/test_os_http.py | 138 +++++++++++++++++++++++++++++++++- test-requirements.txt | 2 + 4 files changed, 194 insertions(+), 39 deletions(-) diff --git a/os_http/shell.py b/os_http/shell.py index 07873bc..4ee842c 100644 --- a/os_http/shell.py +++ b/os_http/shell.py @@ -45,8 +45,44 @@ try: except Exception: _occ_version = "unknown" +formatter_name = 'console' if sys.stdout.isatty() else 'text' -def main(argv=sys.argv[1:]): + +class ErrorExit(Exception): + + def __init__(self, message, exit_code=1): + self.message = message + self.exit_code = exit_code + + +def format_resp(resp): + # I can see no way to get the HTTP version + headers = ["HTTP/1.1 %d %s" % (resp.status_code, resp.reason or '')] + headers.extend('%s: %s' % k for k in resp.headers.items()) + headers = '\n'.join(headers) + + if 'json' in resp.headers.get('Content-Type', '').lower(): + body = json.dumps(resp.json(), sort_keys=True, indent=4) + else: + body = resp.content + + if pygments: + mime = resp.headers.get('Content-Type') + http_lexer = pygments.lexers.get_lexer_by_name('http') + formatter = pygments.formatters.get_formatter_by_name(formatter_name) + + try: + body_lexer = pygments.lexers.get_lexer_for_mimetype(mime) + except pygments.util.ClassNotFound: + body_lexer = pygments.lexers.get_lexer_by_name('text') + + headers = pygments.highlight(headers, http_lexer, formatter) + body = pygments.highlight(body, body_lexer, formatter) + + return '\n'.join([headers, '', body]) + + +def run(argv): parser = argparse.ArgumentParser( description='Simple HTTP testing for Openstack') @@ -69,7 +105,7 @@ def main(argv=sys.argv[1:]): nargs='*', help='Additional items') - opts = parser.parse_args() + opts = parser.parse_args(argv) if opts.debug: logging.basicConfig(level=logging.DEBUG) @@ -95,8 +131,7 @@ def main(argv=sys.argv[1:]): key, val = item.split(':', 1) headers[key] = val else: - LOG.error("Unknown item: %s", item) - sys.exit(1) + raise ErrorExit("Unknown item: %s" % item) try: resp = adap.request(opts.url, @@ -121,8 +156,7 @@ def main(argv=sys.argv[1:]): "result in an unscoped token. Please check your " "authentication credentials.") - LOG.error(message) - sys.exit(1) + raise ErrorExit(message) except exceptions.EndpointNotFound: service_params = ('service_type', @@ -133,35 +167,17 @@ def main(argv=sys.argv[1:]): query = ", ".join("%s=%s" % (p, getattr(adap, p)) for p in service_params if getattr(adap, p)) - LOG.error("Failed to find an endpoint in the service catalog that " - "matches your query: %s", query) - sys.exit(1) + raise ErrorExit("Failed to find an endpoint in the service catalog " + "that matches your query: %s" % query) - # I can see no way to get the HTTP version - headers = ["HTTP/1.1 %d %s" % (resp.status_code, resp.reason)] - headers.extend('%s: %s' % k for k in resp.headers.items()) - headers = '\n'.join(headers) + return format_resp(resp) - if 'json' in resp.headers.get('Content-Type', '').lower(): - body = json.dumps(resp.json(), sort_keys=True, indent=4) + +def main(argv=sys.argv[1:]): + try: + output = run(argv) + except ErrorExit as e: + LOG.error(e.message) + sys.exit(e.exit_code) else: - body = resp.content - - if pygments: - mime = resp.headers.get('Content-Type') - http_lexer = pygments.lexers.get_lexer_by_name('http') - - formatter_name = 'console' if sys.stdout.isatty() else 'text' - formatter = pygments.formatters.get_formatter_by_name(formatter_name) - - try: - body_lexer = pygments.lexers.get_lexer_for_mimetype(mime) - except pygments.util.ClassNotFound: - body_lexer = pygments.lexers.get_lexer_by_name('text') - - headers = pygments.highlight(headers, http_lexer, formatter) - body = pygments.highlight(body, body_lexer, formatter) - - print(headers) - print('') - print(body) + print(output) diff --git a/os_http/tests/base.py b/os_http/tests/base.py index 1c30cdb..7d295e7 100644 --- a/os_http/tests/base.py +++ b/os_http/tests/base.py @@ -15,9 +15,14 @@ # License for the specific language governing permissions and limitations # under the License. +import fixtures from oslotest import base +from requests_mock.contrib import fixture class TestCase(base.BaseTestCase): - """Test case base class for all unit tests.""" + def setUp(self): + super(TestCase, self).setUp() + self.requests_mock = self.useFixture(fixture.Fixture()) + self.logger_mock = self.useFixture(fixtures.FakeLogger()) diff --git a/os_http/tests/test_os_http.py b/os_http/tests/test_os_http.py index 8c60f57..e819e5e 100644 --- a/os_http/tests/test_os_http.py +++ b/os_http/tests/test_os_http.py @@ -19,10 +19,142 @@ test_os_http Tests for `os_http` module. """ +import re +import uuid + +import fixtures +from keystoneauth1 import fixture +import requests_mock +from testtools import matchers + +from os_http import shell from os_http.tests import base +AUTH_URL = 'http://openstack.example.com:5000' -class TestOs_http(base.TestCase): +PUBLIC_SERVICE_URL = 'http://public.example.com:9292' +ADMIN_SERVICE_URL = 'http://admin.example.com:9292' +INTERNAL_SERVICE_URL = 'http://internal.example.com:9292' +SERVICE_REGION = uuid.uuid4().hex - def test_something(self): - pass + +class TestInputs(base.TestCase): + + def setUp(self): + super(TestInputs, self).setUp() + + disc = fixture.DiscoveryList(href=AUTH_URL, v2=False) + + self.service_type = uuid.uuid4().hex + self.service_id = uuid.uuid4().hex + self.service_name = uuid.uuid4().hex + + self.user_id = uuid.uuid4().hex + self.username = uuid.uuid4().hex + self.project_id = uuid.uuid4().hex + self.project_name = uuid.uuid4().hex + + self.token = fixture.V3Token(user_id=self.user_id, + user_name=self.username, + project_id=self.project_id, + project_name=self.project_name) + + self.token.add_role() + self.token.add_role() + self.token_id = uuid.uuid4().hex + + service = self.token.add_service(self.service_type, + id=self.service_id, + name=self.service_name) + + service.add_standard_endpoints(public=PUBLIC_SERVICE_URL, + admin=ADMIN_SERVICE_URL, + internal=INTERNAL_SERVICE_URL, + region=SERVICE_REGION) + + self.requests_mock.get(AUTH_URL, json=disc, status_code=300) + self.auth_mock = self.requests_mock.post( + AUTH_URL + '/v3/auth/tokens', + json=self.token, + headers={'X-Subject-Token': self.token_id}) + + # don't do any console formatting markup + m = fixtures.MockPatchObject(shell, 'formatter_name', 'text') + self.useFixture(m) + + def shell(self, *args, **kwargs): + for k, v in kwargs.items(): + args.append('--os-%s' % k.replace('_', '-')) + args.append(v) + + return shell.run(args) + + def test_simple_get(self): + path = '/%s' % uuid.uuid4().hex + public_url = '%s%s' % (PUBLIC_SERVICE_URL, path) + + json_a = uuid.uuid4().hex + json_b = uuid.uuid4().hex + + service_mock = self.requests_mock.get( + public_url, + json={json_a: json_b}, + status_code=200, + reason='OK', + headers={'Content-Type': 'application/json'}) + + resp = self.shell('get', path, + '--os-service-type', self.service_type, + '--os-auth-type', 'password', + '--os-auth-url', AUTH_URL, + '--os-project-id', self.project_id, + '--os-user-id', self.user_id) + + self.assertEqual('GET', self.requests_mock.last_request.method) + self.assertEqual(public_url, self.requests_mock.last_request.url) + + self.assertTrue(service_mock.called) + + self.assertThat(resp, matchers.StartsWith('HTTP/1.1 200 OK')) + self.assertIn('Content-Type: application/json', resp) + + r = '.*{\s*"%s":\s*"%s"\s*}$' % (json_a, json_b) + self.assertThat(resp, matchers.MatchesRegex(r, re.M | re.S)) + + def test_endpoint_not_found(self): + path = '/%s' % uuid.uuid4().hex + public_url = '%s%s' % (PUBLIC_SERVICE_URL, path) + service_mock = self.requests_mock.get(public_url) + + e = self.assertRaises(shell.ErrorExit, + self.shell, + 'get', path, + '--os-service-type', uuid.uuid4().hex, + '--os-auth-type', 'password', + '--os-auth-url', AUTH_URL, + '--os-project-id', self.project_id, + '--os-user-id', self.user_id) + self.assertIn('Failed to find an endpoint in the service ', e.message) + + def test_headers(self): + path = '/%s' % uuid.uuid4().hex + public_url = '%s%s' % (PUBLIC_SERVICE_URL, path) + + json_a = uuid.uuid4().hex + json_b = uuid.uuid4().hex + + service_mock = self.requests_mock.get(public_url) + + header_key = uuid.uuid4().hex + header_val = uuid.uuid4().hex + + self.shell('get', path, + '%s:%s' % (header_key, header_val), + '--os-service-type', self.service_type, + '--os-auth-type', 'password', + '--os-auth-url', AUTH_URL, + '--os-project-id', self.project_id, + '--os-user-id', self.user_id) + + self.assertEqual(header_val, + self.requests_mock.last_request.headers[header_key]) diff --git a/test-requirements.txt b/test-requirements.txt index 0c885d3..40509cc 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,10 +6,12 @@ hacking<0.11,>=0.10.0 coverage>=3.6 discover +fixtures>=3.0.0 # Apache-2.0/BSD python-subunit>=0.0.18 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 oslosphinx>=2.5.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 +requests-mock>=0.7.0 # Apache-2.0 testrepository>=0.0.18 testscenarios>=0.4 testtools>=1.4.0