summaryrefslogtreecommitdiff
path: root/glanceclient/tests/utils.py
diff options
context:
space:
mode:
authorStuart McLaren <stuart.mclaren@hp.com>2015-04-17 14:02:33 +0000
committerStuart McLaren <stuart.mclaren@hp.com>2015-04-18 17:42:20 +0000
commitf2a8a520e76a129039b3c4043aeb8db75582b8c8 (patch)
tree1bdef9cffd95d747c515d16e6ea0bcea90cf8b54 /glanceclient/tests/utils.py
parent825c4a5df2e32a2d7c1665f0924cc5b9fa675673 (diff)
downloadpython-glanceclient-f2a8a520e76a129039b3c4043aeb8db75582b8c8.tar.gz
Move unit tests to standard directory
This patch moves the glanceclient unit tests to the standard directory (xxxclient/tests/unit) in preparation for adding functional gate tests 'check-glanceclient-dsvm-functional' in the same vein as existing client tests for other projects, eg: * check-novaclient-dsvm-functional * check-keystoneclient-dsvm-functional * check-neutronclient-dsvm-functional Change-Id: I29d4b9e3a428c851575ee9afde40d6df583456c4
Diffstat (limited to 'glanceclient/tests/utils.py')
-rw-r--r--glanceclient/tests/utils.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/glanceclient/tests/utils.py b/glanceclient/tests/utils.py
new file mode 100644
index 0000000..a47dcb3
--- /dev/null
+++ b/glanceclient/tests/utils.py
@@ -0,0 +1,215 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+import json
+import six
+import six.moves.urllib.parse as urlparse
+import testtools
+
+from glanceclient.v2.schemas import Schema
+
+
+class FakeAPI(object):
+ def __init__(self, fixtures):
+ self.fixtures = fixtures
+ self.calls = []
+
+ def _request(self, method, url, headers=None, data=None,
+ content_length=None):
+ call = build_call_record(method, sort_url_by_query_keys(url),
+ headers or {}, data)
+ if content_length is not None:
+ call = tuple(list(call) + [content_length])
+ self.calls.append(call)
+
+ fixture = self.fixtures[sort_url_by_query_keys(url)][method]
+
+ data = fixture[1]
+ if isinstance(fixture[1], six.string_types):
+ try:
+ data = json.loads(fixture[1])
+ except ValueError:
+ data = six.StringIO(fixture[1])
+
+ return FakeResponse(fixture[0], fixture[1]), data
+
+ def get(self, *args, **kwargs):
+ return self._request('GET', *args, **kwargs)
+
+ def post(self, *args, **kwargs):
+ return self._request('POST', *args, **kwargs)
+
+ def put(self, *args, **kwargs):
+ return self._request('PUT', *args, **kwargs)
+
+ def patch(self, *args, **kwargs):
+ return self._request('PATCH', *args, **kwargs)
+
+ def delete(self, *args, **kwargs):
+ return self._request('DELETE', *args, **kwargs)
+
+ def head(self, *args, **kwargs):
+ return self._request('HEAD', *args, **kwargs)
+
+
+class FakeSchemaAPI(FakeAPI):
+ def get(self, *args, **kwargs):
+ _, raw_schema = self._request('GET', *args, **kwargs)
+ return Schema(raw_schema)
+
+
+class RawRequest(object):
+ def __init__(self, headers, body=None,
+ version=1.0, status=200, reason="Ok"):
+ """
+ :param headers: dict representing HTTP response headers
+ :param body: file-like object
+ :param version: HTTP Version
+ :param status: Response status code
+ :param reason: Status code related message.
+ """
+ self.body = body
+ self.status = status
+ self.reason = reason
+ self.version = version
+ self.headers = headers
+
+ def getheaders(self):
+ return copy.deepcopy(self.headers).items()
+
+ def getheader(self, key, default):
+ return self.headers.get(key, default)
+
+ def read(self, amt):
+ return self.body.read(amt)
+
+
+class FakeResponse(object):
+ def __init__(self, headers=None, body=None,
+ version=1.0, status_code=200, reason="Ok"):
+ """
+ :param headers: dict representing HTTP response headers
+ :param body: file-like object
+ :param version: HTTP Version
+ :param status: Response status code
+ :param reason: Status code related message.
+ """
+ self.body = body
+ self.reason = reason
+ self.version = version
+ self.headers = headers
+ self.status_code = status_code
+ self.raw = RawRequest(headers, body=body, reason=reason,
+ version=version, status=status_code)
+
+ @property
+ def ok(self):
+ return (self.status_code < 400 or
+ self.status_code >= 600)
+
+ def read(self, amt):
+ return self.body.read(amt)
+
+ def close(self):
+ pass
+
+ @property
+ def content(self):
+ if hasattr(self.body, "read"):
+ return self.body.read()
+ return self.body
+
+ @property
+ def text(self):
+ if isinstance(self.content, six.binary_type):
+ return self.content.decode('utf-8')
+
+ return self.content
+
+ def json(self, **kwargs):
+ return self.body and json.loads(self.text) or ""
+
+ def iter_content(self, chunk_size=1, decode_unicode=False):
+ while True:
+ chunk = self.raw.read(chunk_size)
+ if not chunk:
+ break
+ yield chunk
+
+
+class TestCase(testtools.TestCase):
+ TEST_REQUEST_BASE = {
+ 'config': {'danger_mode': False},
+ 'verify': True}
+
+
+class FakeTTYStdout(six.StringIO):
+ """A Fake stdout that try to emulate a TTY device as much as possible."""
+
+ def isatty(self):
+ return True
+
+ def write(self, data):
+ # When a CR (carriage return) is found reset file.
+ if data.startswith('\r'):
+ self.seek(0)
+ data = data[1:]
+ return six.StringIO.write(self, data)
+
+
+class FakeNoTTYStdout(FakeTTYStdout):
+ """A Fake stdout that is not a TTY device."""
+
+ def isatty(self):
+ return False
+
+
+def sort_url_by_query_keys(url):
+ """A helper function which sorts the keys of the query string of a url.
+ For example, an input of '/v2/tasks?sort_key=id&sort_dir=asc&limit=10'
+ returns '/v2/tasks?limit=10&sort_dir=asc&sort_key=id'. This is to
+ prevent non-deterministic ordering of the query string causing
+ problems with unit tests.
+ :param url: url which will be ordered by query keys
+ :returns url: url with ordered query keys
+ """
+ parsed = urlparse.urlparse(url)
+ queries = urlparse.parse_qsl(parsed.query, True)
+ sorted_query = sorted(queries, key=lambda x: x[0])
+
+ encoded_sorted_query = urlparse.urlencode(sorted_query, True)
+
+ url_parts = (parsed.scheme, parsed.netloc, parsed.path,
+ parsed.params, encoded_sorted_query,
+ parsed.fragment)
+
+ return urlparse.urlunparse(url_parts)
+
+
+def build_call_record(method, url, headers, data):
+ """Key the request body be ordered if it's a dict type.
+ """
+ if isinstance(data, dict):
+ data = sorted(data.items())
+ if isinstance(data, six.string_types):
+ # NOTE(flwang): For image update, the data will be a 'list' which
+ # contains operation dict, such as: [{"op": "remove", "path": "/a"}]
+ try:
+ data = json.loads(data)
+ except ValueError:
+ return (method, url, headers or {}, data)
+ data = [sorted(d.items()) for d in data]
+ return (method, url, headers or {}, data)