From 896eab13c848f136102b9e3c49e7b68a6e6cb254 Mon Sep 17 00:00:00 2001 From: Malini Kamalambal Date: Mon, 14 Jul 2014 11:56:26 -0400 Subject: [PATCH] Add API Test Structure This patch adds the initial directory structure and helpers needed for API tests. --- tests/api/__init__.py | 0 tests/api/etc/tests.conf.sample | 11 +++ tests/api/services/__init__.py | 0 tests/api/services/create_service.json | 22 ++++++ tests/api/services/test_services.py | 46 +++++++++++++ tests/api/utils/__init__.py | 0 tests/api/utils/base.py | 49 ++++++++++++++ tests/api/utils/client.py | 92 ++++++++++++++++++++++++++ tests/api/utils/config.py | 44 ++++++++++++ tests/api/utils/models/__init__.py | 0 tests/api/utils/models/requests.py | 21 ++++++ tests/api/utils/models/response.py | 21 ++++++ tests/api/utils/schema/__init__.py | 0 tests/api/utils/schema/response.py | 66 ++++++++++++++++++ tests/test-requirements.txt | 6 +- 15 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 tests/api/__init__.py create mode 100644 tests/api/etc/tests.conf.sample create mode 100644 tests/api/services/__init__.py create mode 100644 tests/api/services/create_service.json create mode 100644 tests/api/services/test_services.py create mode 100644 tests/api/utils/__init__.py create mode 100644 tests/api/utils/base.py create mode 100644 tests/api/utils/client.py create mode 100644 tests/api/utils/config.py create mode 100644 tests/api/utils/models/__init__.py create mode 100644 tests/api/utils/models/requests.py create mode 100644 tests/api/utils/models/response.py create mode 100644 tests/api/utils/schema/__init__.py create mode 100644 tests/api/utils/schema/response.py diff --git a/tests/api/__init__.py b/tests/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/etc/tests.conf.sample b/tests/api/etc/tests.conf.sample new file mode 100644 index 00000000..21f2f73b --- /dev/null +++ b/tests/api/etc/tests.conf.sample @@ -0,0 +1,11 @@ +#============================================================================= +# Configuration file to execute API tests. +#============================================================================= + +[auth] +username={user name of the cloud account} +api_key={api key for this user name} +base_url=https://identity.api.rackspacecloud.com/v2.0/tokens + +[cdn] +base_url=https://private-ea1ca-cloudcdn.apiary.io \ No newline at end of file diff --git a/tests/api/services/__init__.py b/tests/api/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/services/create_service.json b/tests/api/services/create_service.json new file mode 100644 index 00000000..ea9d9417 --- /dev/null +++ b/tests/api/services/create_service.json @@ -0,0 +1,22 @@ +{ + "all_fields": { + "domain_list": [{"domain": "mywebsite.com"}, + {"domain": "blog.mywebsite.com"}], + "origin_list": [{"origins": "mywebsite.com", + "port": 443, + "ssl": false}], + "caching_list": [{"name": "default", "ttl": 3600}, + {"name": "home", + "ttl": 1200, + "rules": [{"name" : "index", + "request_url" : "/index.htm"}]}] + }, + "caching_empty": { + "domain_list": [{"domain": "mywebsite.com"}, + {"domain": "blog.mywebsite.com"}], + "origin_list": [{"origins": "mywebsite.com", + "port": 443, + "ssl": false}], + "caching_list": [] + } +} \ No newline at end of file diff --git a/tests/api/services/test_services.py b/tests/api/services/test_services.py new file mode 100644 index 00000000..ea2a920c --- /dev/null +++ b/tests/api/services/test_services.py @@ -0,0 +1,46 @@ +import ddt +import uuid + +from tests.api.utils import base +from tests.api.utils.schema import response + + +@ddt.ddt +class TestServices(base.TestBase): + + """Tests for Services.""" + + def setUp(self): + super(TestServices, self).setUp() + self.service_name = uuid.uuid1() + + @ddt.file_data('create_service.json') + def test_create_service(self, test_data): + + domain_list = test_data['domain_list'] + origin_list = test_data['origin_list'] + caching_list = test_data['caching_list'] + + resp = self.client.create_service(service_name=self.service_name, + domain_list=domain_list, + origin_list=origin_list, + caching_list=caching_list) + self.assertEqual(resp.status_code, 201) + + response_body = resp.json() + self.assertSchema(response_body, response.create_service) + + #Get on Created Service + resp = self.client.get_service(service_name=self.service_name) + self.assertEqual(resp.status_code, 200) + + body = resp.json() + self.assertEqual(body['domains'], domain_list) + self.assertEqual(body['origins'], origin_list) + self.assertEqual(body['caching_list'], caching_list) + + test_create_service.tags = ['smoke', 'positive'] + + def tearDown(self): + self.client.delete_service(service_name=self.service_name) + super(TestServices, self).tearDown() diff --git a/tests/api/utils/__init__.py b/tests/api/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/utils/base.py b/tests/api/utils/base.py new file mode 100644 index 00000000..b6d08ddc --- /dev/null +++ b/tests/api/utils/base.py @@ -0,0 +1,49 @@ +import jsonschema + +from cafe.drivers.unittest import fixtures + +from tests.api.utils import client +from tests.api.utils import config + + +class TestBase(fixtures.BaseTestFixture): + """Child class of fixtures.BaseTestFixture for testing CDN. + + Inherit from this and write your test methods. If the child class defines + a prepare(self) method, this method will be called before executing each + test method. + """ + + @classmethod + def setUpClass(cls): + + super(TestBase, cls).setUpClass() + + cls.auth_config = config.authConfig() + cls.auth_client = client.AuthClient() + auth_token = cls.auth_client.get_auth_token(cls.auth_config.base_url, + cls.auth_config.user_name, + cls.auth_config.api_key) + + cls.config = config.cdnConfig() + version = 'v1.0' + cls.url = cls.config.base_url + + cls.client = client.CDNClient(cls.url, auth_token, + serialize_format='json', + deserialize_format='json') + + def assertSchema(self, response_json, expected_schema): + """Verify response schema aligns with the expected schema + """ + try: + jsonschema.validate(response_json, expected_schema) + except jsonschema.ValidationError as message: + assert False, message + + @classmethod + def tearDownClass(cls): + """ + Deletes the added resources + """ + super(TestBase, cls).tearDownClass() diff --git a/tests/api/utils/client.py b/tests/api/utils/client.py new file mode 100644 index 00000000..31d8355b --- /dev/null +++ b/tests/api/utils/client.py @@ -0,0 +1,92 @@ +import json + +from cafe.engine.http import client +from models import requests + + +class AuthClient(client.HTTPClient): + + """ + Client Objects for Auth call + """ + + def __init__(self): + super(AuthClient, self).__init__() + + self.default_headers['Content-Type'] = 'application/json' + self.default_headers['Accept'] = 'application/json' + + def get_auth_token(self, url, user_name, api_key): + """ + Get Auth Token using api_key + @todo: Support getting token with password (or) api key. + """ + request_body = { + "auth": { + "RAX-KSKEY:apiKeyCredentials": { + "username": user_name, + "apiKey": api_key + }, + }, + } + request_body = json.dumps(request_body) + + response = self.request('POST', url, data=request_body) + token = response.json()['access']['token']['id'] + return token + + +class CDNClient(client.AutoMarshallingHTTPClient): + + """ + Client objects for all the CDN api calls + """ + + def __init__(self, url, auth_token, serialize_format="json", + deserialize_format="json"): + super(CDNClient, self).__init__(serialize_format, + deserialize_format) + self.url = url + self.auth_token = auth_token + self.default_headers['X-Auth-Token'] = auth_token + self.default_headers['Content-Type'] = 'application/json' + self.default_headers['Accept'] = 'application/json' + + self.serialize = serialize_format + self.deserialize_format = deserialize_format + + def create_service(self, service_name=None, + domain_list=None, origin_list=None, + caching_list=None, requestslib_kwargs=None): + """ + Creates Service + :return: Response Object containing response code 200 and body with + details of service + + PUT + services/{service_name} + """ + url = '{0}/services/{1}'.format(self.url, service_name) + request_object = requests.CreateService(domain_list=domain_list, + origin_list=origin_list, + caching_list=caching_list) + return self.request('PUT', url, + request_entity=request_object, + requestslib_kwargs=requestslib_kwargs) + + def get_service(self, service_name): + """Get Service + :return: Response Object containing response code 200 and body with + details of service + """ + + url = '{0}/services/{1}'.format(self.url, service_name) + return self.request('GET', url) + + def delete_service(self, service_name): + """Delete Service + :return: Response Object containing response code 204 + """ + + url = '{0}/services/{1}'.format(self.url, service_name) + return self.request('DELETE', url) diff --git a/tests/api/utils/config.py b/tests/api/utils/config.py new file mode 100644 index 00000000..d9d70df2 --- /dev/null +++ b/tests/api/utils/config.py @@ -0,0 +1,44 @@ +from cafe.engine.models.data_interfaces import ConfigSectionInterface + + +class cdnConfig(ConfigSectionInterface): + """ + Defines the config values for cdn + """ + SECTION_NAME = 'cdn' + + @property + def base_url(self): + """ + CDN endpoint + """ + return self.get('base_url') + + +class authConfig(ConfigSectionInterface): + """ + Defines the auth config values + """ + SECTION_NAME = 'auth' + + @property + def base_url(self): + """ + Auth endpoint + """ + return self.get('base_url') + + @property + def user_name(self): + """The name of the user, if applicable""" + return self.get("user_name") + + @property + def api_key(self): + """The user's api key, if applicable""" + return self.get_raw("api_key") + + @property + def tenant_id(self): + """The user's tenant_id, if applicable""" + return self.get("tenant_id") diff --git a/tests/api/utils/models/__init__.py b/tests/api/utils/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/utils/models/requests.py b/tests/api/utils/models/requests.py new file mode 100644 index 00000000..2505dfe3 --- /dev/null +++ b/tests/api/utils/models/requests.py @@ -0,0 +1,21 @@ +import json +from cafe.engine.models import base + + +class CreateService(base.AutoMarshallingModel): + """ + Marshalling for Create Service requests + """ + + def __init__(self, domain_list=None, origin_list=None, caching_list=None): + super(CreateService, self).__init__() + + self.domain_list = domain_list or [] + self.origin_list = origin_list or [] + self.caching_list = caching_list or [] + + def _obj_to_json(self): + create_service_request = {"domains": self.domain_list, + "origins": self.origin_list, + "caching": self.caching_list} + return json.dumps(create_service_request) diff --git a/tests/api/utils/models/response.py b/tests/api/utils/models/response.py new file mode 100644 index 00000000..2505dfe3 --- /dev/null +++ b/tests/api/utils/models/response.py @@ -0,0 +1,21 @@ +import json +from cafe.engine.models import base + + +class CreateService(base.AutoMarshallingModel): + """ + Marshalling for Create Service requests + """ + + def __init__(self, domain_list=None, origin_list=None, caching_list=None): + super(CreateService, self).__init__() + + self.domain_list = domain_list or [] + self.origin_list = origin_list or [] + self.caching_list = caching_list or [] + + def _obj_to_json(self): + create_service_request = {"domains": self.domain_list, + "origins": self.origin_list, + "caching": self.caching_list} + return json.dumps(create_service_request) diff --git a/tests/api/utils/schema/__init__.py b/tests/api/utils/schema/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/api/utils/schema/response.py b/tests/api/utils/schema/response.py new file mode 100644 index 00000000..cc3c77c1 --- /dev/null +++ b/tests/api/utils/schema/response.py @@ -0,0 +1,66 @@ +domain = { + 'type': 'object', + 'properties': { + 'domain': {'type': 'string', + 'pattern': '^([a-zA-Z0-9-.]+(.com))$'}}, + 'required': ['domain'] +} + +origin = { + 'type': 'object', + 'properties': { + 'origin': {'type': 'string', + 'pattern': '^([a-zA-Z0-9-.]{5,1000})$'}, + 'port': {'type': 'number', + 'minumum': 0, + 'maximum': 100000}, + 'ssl': {'type': 'boolean'}, + 'rules': {'type': 'array'}}, + 'required': ['origin', 'port', 'ssl'], + 'additionalProperties': False, +} + +cache = {'type': 'object', + 'properties': { + 'name': {'type': 'string', 'pattern': '^[a-zA-Z0-9_-]{1,64}$'}, + 'ttl': {'type': 'number', 'minimum': 1, 'maximum': 36000}, + 'rules': {'type': 'array'}}, + 'required': ['name', 'ttl'], + 'additionalProperties': False} + +links = {'type': 'object', + 'properties': { + 'href': {'type': 'string', + 'pattern': '^/v1.0/services/[a-zA-Z0-9_-]{1,64}$'}, + 'rel': {'type': 'string'}} + } + +restrictions = {'type': 'array'} + +#Response Schema Definition for Create Service API +create_service = { + 'type': 'object', + 'properties': { + 'domains': {'type': 'array', + 'items': domain, + 'minItems': 1, + 'maxItems': 10 + }, + 'origins': {'type': 'array', + 'items': origin, + 'minItems': 1, + 'maxItems': 10 + }, + 'caching': {'type': 'array', + 'items': cache, + 'minItems': 1, + 'maxItems': 10 + }, + 'links': {'type': 'array', + 'items': links, + 'minItems': 1, + 'maxItems': 1}, + 'restrictions': restrictions, + }, + 'required': ['domains', 'origins', 'caching', 'links', 'restrictions'], + 'additionalProperties': False} diff --git a/tests/test-requirements.txt b/tests/test-requirements.txt index 035f03ee..ccef7f2b 100644 --- a/tests/test-requirements.txt +++ b/tests/test-requirements.txt @@ -4,7 +4,7 @@ hacking>=0.5.6,<0.8 # Packaging mock>=1.0 -# Unit testing +# Unit Tests ddt>=0.4.0 discover fixtures>=0.3.14 @@ -16,6 +16,10 @@ testtools>=0.9.32 # Functional Tests requests>=1.1 +# API Tests +git+https://github.com/stackforge/opencafe.git#egg=opencafe +jsonschema + # Test runner nose nose-exclude