Add API Test Structure
This patch adds the initial directory structure and helpers needed for API tests.
This commit is contained in:
parent
aaa8b274a0
commit
896eab13c8
0
tests/api/__init__.py
Normal file
0
tests/api/__init__.py
Normal file
11
tests/api/etc/tests.conf.sample
Normal file
11
tests/api/etc/tests.conf.sample
Normal file
@ -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
|
0
tests/api/services/__init__.py
Normal file
0
tests/api/services/__init__.py
Normal file
22
tests/api/services/create_service.json
Normal file
22
tests/api/services/create_service.json
Normal file
@ -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": []
|
||||||
|
}
|
||||||
|
}
|
46
tests/api/services/test_services.py
Normal file
46
tests/api/services/test_services.py
Normal file
@ -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()
|
0
tests/api/utils/__init__.py
Normal file
0
tests/api/utils/__init__.py
Normal file
49
tests/api/utils/base.py
Normal file
49
tests/api/utils/base.py
Normal file
@ -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()
|
92
tests/api/utils/client.py
Normal file
92
tests/api/utils/client.py
Normal file
@ -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)
|
44
tests/api/utils/config.py
Normal file
44
tests/api/utils/config.py
Normal file
@ -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")
|
0
tests/api/utils/models/__init__.py
Normal file
0
tests/api/utils/models/__init__.py
Normal file
21
tests/api/utils/models/requests.py
Normal file
21
tests/api/utils/models/requests.py
Normal file
@ -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)
|
21
tests/api/utils/models/response.py
Normal file
21
tests/api/utils/models/response.py
Normal file
@ -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)
|
0
tests/api/utils/schema/__init__.py
Normal file
0
tests/api/utils/schema/__init__.py
Normal file
66
tests/api/utils/schema/response.py
Normal file
66
tests/api/utils/schema/response.py
Normal file
@ -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}
|
@ -4,7 +4,7 @@ hacking>=0.5.6,<0.8
|
|||||||
# Packaging
|
# Packaging
|
||||||
mock>=1.0
|
mock>=1.0
|
||||||
|
|
||||||
# Unit testing
|
# Unit Tests
|
||||||
ddt>=0.4.0
|
ddt>=0.4.0
|
||||||
discover
|
discover
|
||||||
fixtures>=0.3.14
|
fixtures>=0.3.14
|
||||||
@ -16,6 +16,10 @@ testtools>=0.9.32
|
|||||||
# Functional Tests
|
# Functional Tests
|
||||||
requests>=1.1
|
requests>=1.1
|
||||||
|
|
||||||
|
# API Tests
|
||||||
|
git+https://github.com/stackforge/opencafe.git#egg=opencafe
|
||||||
|
jsonschema
|
||||||
|
|
||||||
# Test runner
|
# Test runner
|
||||||
nose
|
nose
|
||||||
nose-exclude
|
nose-exclude
|
||||||
|
Loading…
Reference in New Issue
Block a user