API with DB
Change-Id: Iedf19086d7e0fde059cfbaedc92b8ac1ff297cfc
This commit is contained in:
parent
c76114ea23
commit
88a24238fa
@ -1,4 +1,4 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./refstack -s ./refstack/tests $LISTOPT $IDOPTION
|
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./refstack -s ./refstack/tests/unit $LISTOPT $IDOPTION
|
||||||
test_id_option=--load-list $IDFILE
|
test_id_option=--load-list $IDFILE
|
||||||
test_list_option=--list
|
test_list_option=--list
|
||||||
|
11
alembic.ini
11
alembic.ini
@ -11,15 +11,8 @@ script_location = alembic
|
|||||||
# the 'revision' command, regardless of autogenerate
|
# the 'revision' command, regardless of autogenerate
|
||||||
# revision_environment = false
|
# revision_environment = false
|
||||||
|
|
||||||
#sqlalchemy.url = driver://user:pass@localhost/dbname
|
#sqlalchemy.url = driver://user:pass@127.0.0.1/dbname
|
||||||
sqlalchemy.url = sqlite:///db.sqlite
|
sqlalchemy.url = mysql://root:r00t@127.0.0.1/refstack
|
||||||
|
|
||||||
[alembic_sqlite]
|
|
||||||
# path to migration scripts
|
|
||||||
script_location = alembic
|
|
||||||
|
|
||||||
sqlalchemy.url = sqlite:///db.sqlite
|
|
||||||
|
|
||||||
|
|
||||||
# Logging configuration
|
# Logging configuration
|
||||||
[loggers]
|
[loggers]
|
||||||
|
@ -19,16 +19,11 @@ import sys
|
|||||||
sys.path.append("./")
|
sys.path.append("./")
|
||||||
from alembic import context
|
from alembic import context
|
||||||
from sqlalchemy import engine_from_config, pool
|
from sqlalchemy import engine_from_config, pool
|
||||||
from logging.config import fileConfig
|
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
config = context.config
|
config = context.config
|
||||||
|
|
||||||
# Interpret the config file for Python logging.
|
|
||||||
# This line sets up loggers basically.
|
|
||||||
fileConfig(config.config_file_name)
|
|
||||||
|
|
||||||
# add your model's MetaData object here
|
# add your model's MetaData object here
|
||||||
# for 'autogenerate' support
|
# for 'autogenerate' support
|
||||||
from refstack.models import Base
|
from refstack.models import Base
|
||||||
|
@ -19,6 +19,7 @@ def upgrade():
|
|||||||
op.create_table(
|
op.create_table(
|
||||||
'test',
|
'test',
|
||||||
sa.Column('id', sa.String(length=36), nullable=False),
|
sa.Column('id', sa.String(length=36), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||||
sa.Column('cpid', sa.String(length=128), nullable=False),
|
sa.Column('cpid', sa.String(length=128), nullable=False),
|
||||||
sa.Column('duration_seconds', sa.Integer(), nullable=False),
|
sa.Column('duration_seconds', sa.Integer(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.PrimaryKeyConstraint('id')
|
||||||
@ -37,7 +38,7 @@ def upgrade():
|
|||||||
'results',
|
'results',
|
||||||
sa.Column('_id', sa.Integer(), nullable=False),
|
sa.Column('_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('test_id', sa.String(length=36), nullable=False),
|
sa.Column('test_id', sa.String(length=36), nullable=False),
|
||||||
sa.Column('name', sa.String(length=1024), nullable=True),
|
sa.Column('name', sa.String(length=512), nullable=True),
|
||||||
sa.Column('uid', sa.String(length=36), nullable=True),
|
sa.Column('uid', sa.String(length=36), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['test_id'], ['test.id'], ),
|
sa.ForeignKeyConstraint(['test_id'], ['test.id'], ),
|
||||||
sa.PrimaryKeyConstraint('_id'),
|
sa.PrimaryKeyConstraint('_id'),
|
||||||
|
@ -13,15 +13,81 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from pecan import make_app
|
"""App factory."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pecan
|
||||||
|
from pecan import hooks
|
||||||
|
import webob
|
||||||
|
|
||||||
|
from refstack import backend
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BackendHook(hooks.PecanHook):
|
||||||
|
|
||||||
|
"""Pecan Hook for providing backend functionality."""
|
||||||
|
|
||||||
|
def __init__(self, app_config):
|
||||||
|
"""Hook init."""
|
||||||
|
self.global_backend = backend.Backend(app_config)
|
||||||
|
|
||||||
|
def before(self, state):
|
||||||
|
"""Before request."""
|
||||||
|
state.request.backend = self.global_backend.create_local()
|
||||||
|
|
||||||
|
def after(self, state):
|
||||||
|
"""After request."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class JSONErrorHook(hooks.PecanHook):
|
||||||
|
"""
|
||||||
|
A pecan hook that translates webob HTTP errors into a JSON format.
|
||||||
|
"""
|
||||||
|
def __init__(self, app_config):
|
||||||
|
"""Hook init."""
|
||||||
|
self.debug = app_config.get('debug', False)
|
||||||
|
|
||||||
|
def on_error(self, state, exc):
|
||||||
|
"""Request error handler."""
|
||||||
|
if isinstance(exc, webob.exc.HTTPError):
|
||||||
|
body = {'code': exc.status_int,
|
||||||
|
'title': exc.title}
|
||||||
|
if self.debug:
|
||||||
|
body['detail'] = str(exc)
|
||||||
|
return webob.Response(
|
||||||
|
body=json.dumps(body),
|
||||||
|
status=exc.status,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.exception(exc)
|
||||||
|
body = {'code': 500,
|
||||||
|
'title': 'Internal Server Error'}
|
||||||
|
if self.debug:
|
||||||
|
body['detail'] = str(exc)
|
||||||
|
return webob.Response(
|
||||||
|
body=json.dumps(body),
|
||||||
|
status=500,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup_app(config):
|
def setup_app(config):
|
||||||
|
"""App factory."""
|
||||||
app_conf = dict(config.app)
|
app_conf = dict(config.app)
|
||||||
|
|
||||||
return make_app(
|
app = pecan.make_app(
|
||||||
app_conf.pop('root'),
|
app_conf.pop('root'),
|
||||||
logging=getattr(config, 'logging', {}),
|
logging=getattr(config, 'logging', {}),
|
||||||
|
hooks=[JSONErrorHook(app_conf), hooks.RequestViewerHook(
|
||||||
|
{'items': ['status', 'method', 'controller', 'path', 'body']}
|
||||||
|
), BackendHook(app_conf)],
|
||||||
**app_conf
|
**app_conf
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return app
|
||||||
|
@ -33,6 +33,7 @@ server = {
|
|||||||
app = {
|
app = {
|
||||||
'root': 'refstack.api.controllers.root.RootController',
|
'root': 'refstack.api.controllers.root.RootController',
|
||||||
'modules': ['refstack.api'],
|
'modules': ['refstack.api'],
|
||||||
|
'db_url': 'mysql://root:r00t@127.0.0.1/refstack',
|
||||||
'static_root': '%(confdir)s/public',
|
'static_root': '%(confdir)s/public',
|
||||||
'template_path': '%(confdir)s/${package}/templates',
|
'template_path': '%(confdir)s/${package}/templates',
|
||||||
'debug': False,
|
'debug': False,
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
"""Root controller."""
|
||||||
|
|
||||||
from pecan import expose
|
from pecan import expose
|
||||||
|
|
||||||
from refstack.api.controllers import v1
|
from refstack.api.controllers import v1
|
||||||
@ -20,6 +22,8 @@ from refstack.api.controllers import v1
|
|||||||
|
|
||||||
class RootController(object):
|
class RootController(object):
|
||||||
|
|
||||||
|
"""root handler."""
|
||||||
|
|
||||||
v1 = v1.V1Controller()
|
v1 = v1.V1Controller()
|
||||||
|
|
||||||
@expose('json')
|
@expose('json')
|
||||||
|
@ -13,20 +13,40 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""Version 1 of the API.
|
"""Version 1 of the API."""
|
||||||
"""
|
import logging
|
||||||
from pecan import expose
|
|
||||||
|
import pecan
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ResultsController(rest.RestController):
|
class ResultsController(rest.RestController):
|
||||||
|
|
||||||
@expose('json')
|
"""/v1/results handler."""
|
||||||
def index(self):
|
|
||||||
return {'Results': 'OK'}
|
@pecan.expose('json')
|
||||||
|
def get(self, ):
|
||||||
|
"""GET handler."""
|
||||||
|
return {'Result': 'Ok'}
|
||||||
|
|
||||||
|
@pecan.expose(template='json')
|
||||||
|
def post(self, ):
|
||||||
|
"""POST handler."""
|
||||||
|
try:
|
||||||
|
results = pecan.request.json
|
||||||
|
except ValueError:
|
||||||
|
return pecan.abort(400,
|
||||||
|
detail='Request body \'%s\' could not '
|
||||||
|
'be decoded as JSON.'
|
||||||
|
'' % pecan.request.body)
|
||||||
|
test_id = pecan.request.backend.store_results(results)
|
||||||
|
return {'test_id': test_id}
|
||||||
|
|
||||||
|
|
||||||
class V1Controller(object):
|
class V1Controller(object):
|
||||||
|
|
||||||
"""Version 1 API controller root."""
|
"""Version 1 API controller root."""
|
||||||
|
|
||||||
results = ResultsController()
|
results = ResultsController()
|
||||||
|
70
refstack/backend.py
Normal file
70
refstack/backend.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Backend provider."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
|
from refstack import models
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Backend(object):
|
||||||
|
|
||||||
|
"""Global backend provider."""
|
||||||
|
|
||||||
|
def __init__(self, app_config):
|
||||||
|
"""Backend factory."""
|
||||||
|
engine = sa.create_engine(app_config['db_url'])
|
||||||
|
self.session_maker = orm.sessionmaker()
|
||||||
|
self.session_maker.configure(bind=engine)
|
||||||
|
|
||||||
|
def create_local(self):
|
||||||
|
"""Create request-local Backend instance."""
|
||||||
|
return LocalBackend(self)
|
||||||
|
|
||||||
|
|
||||||
|
class LocalBackend(object):
|
||||||
|
|
||||||
|
"""Request-local backend provider."""
|
||||||
|
|
||||||
|
def __init__(self, global_backend):
|
||||||
|
"""Request-local backend instance."""
|
||||||
|
self.db_session = global_backend.session_maker()
|
||||||
|
|
||||||
|
def store_results(self, results):
|
||||||
|
"""Storing results into database.
|
||||||
|
|
||||||
|
:param results: Dict describes test results.
|
||||||
|
"""
|
||||||
|
session = self.db_session
|
||||||
|
|
||||||
|
test_id = str(uuid.uuid4())
|
||||||
|
test = models.Test(id=test_id, cpid=results.get('cpid'),
|
||||||
|
duration_seconds=results.get('duration_seconds'))
|
||||||
|
test_results = results.get('results', [])
|
||||||
|
for result in test_results:
|
||||||
|
session.add(models.TestResults(
|
||||||
|
test_id=test_id, name=result['name'],
|
||||||
|
uid=result.get('uid', None)
|
||||||
|
))
|
||||||
|
session.add(test)
|
||||||
|
session.commit()
|
||||||
|
return test_id
|
@ -14,6 +14,10 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
"""DB models"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy import orm
|
from sqlalchemy import orm
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
@ -22,9 +26,14 @@ Base = declarative_base()
|
|||||||
|
|
||||||
|
|
||||||
class Test(Base):
|
class Test(Base):
|
||||||
|
|
||||||
|
"""Test."""
|
||||||
|
|
||||||
__tablename__ = 'test'
|
__tablename__ = 'test'
|
||||||
|
|
||||||
id = sa.Column(sa.String(36), primary_key=True)
|
id = sa.Column(sa.String(36), primary_key=True)
|
||||||
|
created_at = sa.Column(sa.DateTime(), default=datetime.datetime.utcnow,
|
||||||
|
nullable=False)
|
||||||
cpid = sa.Column(sa.String(128), index=True, nullable=False)
|
cpid = sa.Column(sa.String(128), index=True, nullable=False)
|
||||||
duration_seconds = sa.Column(sa.Integer, nullable=False)
|
duration_seconds = sa.Column(sa.Integer, nullable=False)
|
||||||
results = orm.relationship('TestResults', backref='test')
|
results = orm.relationship('TestResults', backref='test')
|
||||||
@ -32,6 +41,9 @@ class Test(Base):
|
|||||||
|
|
||||||
|
|
||||||
class TestResults(Base):
|
class TestResults(Base):
|
||||||
|
|
||||||
|
"""Test results."""
|
||||||
|
|
||||||
__tablename__ = 'results'
|
__tablename__ = 'results'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.UniqueConstraint('test_id', 'name'),
|
sa.UniqueConstraint('test_id', 'name'),
|
||||||
@ -40,11 +52,14 @@ class TestResults(Base):
|
|||||||
_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
|
||||||
test_id = sa.Column(sa.String(36), sa.ForeignKey('test.id'),
|
test_id = sa.Column(sa.String(36), sa.ForeignKey('test.id'),
|
||||||
index=True, nullable=False, unique=False)
|
index=True, nullable=False, unique=False)
|
||||||
name = sa.Column(sa.String(1024))
|
name = sa.Column(sa.String(512))
|
||||||
uid = sa.Column(sa.String(36))
|
uid = sa.Column(sa.String(36))
|
||||||
|
|
||||||
|
|
||||||
class TestMeta(Base):
|
class TestMeta(Base):
|
||||||
|
|
||||||
|
"""Test metadata."""
|
||||||
|
|
||||||
__tablename__ = 'meta'
|
__tablename__ = 'meta'
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
sa.UniqueConstraint('test_id', 'meta_key'),
|
sa.UniqueConstraint('test_id', 'meta_key'),
|
||||||
|
@ -13,36 +13,96 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""Base classes for API tests.
|
"""Base classes for API tests."""
|
||||||
"""
|
import inspect
|
||||||
from unittest import TestCase
|
import os
|
||||||
|
|
||||||
|
import alembic
|
||||||
|
import alembic.config
|
||||||
from pecan import set_config
|
from pecan import set_config
|
||||||
from pecan.testing import load_test_app
|
from pecan.testing import load_test_app
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlalchemy.exc
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
import refstack
|
||||||
|
from refstack.models import Base
|
||||||
|
|
||||||
|
|
||||||
class FunctionalTest(TestCase):
|
class FunctionalTest(TestCase):
|
||||||
"""
|
|
||||||
Used for functional tests where you need to test your
|
"""Functional test case.
|
||||||
|
|
||||||
|
Used for functional tests where you need to test your.
|
||||||
literal application and its integration with the framework.
|
literal application and its integration with the framework.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
"""Test setup."""
|
||||||
self.config = {
|
self.config = {
|
||||||
'app': {
|
'app': {
|
||||||
'root': 'refstack.api.controllers.root.RootController',
|
'root': 'refstack.api.controllers.root.RootController',
|
||||||
|
'db_url': os.environ.get(
|
||||||
|
'TEST_DB_URL',
|
||||||
|
'mysql://root:r00t@127.0.0.1/refstack_test'
|
||||||
|
),
|
||||||
'modules': ['refstack.api'],
|
'modules': ['refstack.api'],
|
||||||
'static_root': '%(confdir)s/public',
|
'static_root': '%(confdir)s/public',
|
||||||
'template_path': '%(confdir)s/${package}/templates',
|
'template_path': '%(confdir)s/${package}/templates',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.project_path = os.path.abspath(
|
||||||
|
os.path.join(inspect.getabsfile(refstack), '..', '..'))
|
||||||
|
|
||||||
|
self.prepare_test_db()
|
||||||
|
self.migrate_test_db()
|
||||||
|
|
||||||
self.app = load_test_app(self.config)
|
self.app = load_test_app(self.config)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
"""Test teardown."""
|
||||||
set_config({}, overwrite=True)
|
set_config({}, overwrite=True)
|
||||||
|
|
||||||
|
def prepare_test_db(self):
|
||||||
|
"""Create/clear test database."""
|
||||||
|
db_url = self.config['app']['db_url']
|
||||||
|
db_name = db_url.split('/')[-1]
|
||||||
|
short_db_url = '/'.join(db_url.split('/')[0:-1])
|
||||||
|
try:
|
||||||
|
engine = sa.create_engine(db_url)
|
||||||
|
conn = engine.connect()
|
||||||
|
conn.execute('commit')
|
||||||
|
conn.execute('drop database %s' % db_name)
|
||||||
|
conn.close()
|
||||||
|
except sqlalchemy.exc.OperationalError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
engine = sa.create_engine('/'.join((short_db_url, 'mysql')))
|
||||||
|
conn = engine.connect()
|
||||||
|
conn.execute('commit')
|
||||||
|
conn.execute('create database %s' % db_name)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
engine = sa.create_engine(db_url)
|
||||||
|
conn = engine.connect()
|
||||||
|
conn.execute('commit')
|
||||||
|
for tbl in reversed(Base.metadata.sorted_tables):
|
||||||
|
if engine.has_table(tbl.name):
|
||||||
|
conn.execute('drop table %s' % tbl.name)
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def migrate_test_db(self):
|
||||||
|
"""Apply migrations to test database."""
|
||||||
|
alembic_cfg = alembic.config.Config()
|
||||||
|
alembic_cfg.set_main_option("script_location",
|
||||||
|
os.path.join(self.project_path, 'alembic'))
|
||||||
|
alembic_cfg.set_main_option("sqlalchemy.url",
|
||||||
|
self.config['app']['db_url'])
|
||||||
|
alembic.command.upgrade(alembic_cfg, 'head')
|
||||||
|
|
||||||
def get_json(self, url, headers=None, extra_environ=None,
|
def get_json(self, url, headers=None, extra_environ=None,
|
||||||
status=None, expect_errors=False, **params):
|
status=None, expect_errors=False, **params):
|
||||||
"""Sends HTTP GET request.
|
"""Send HTTP GET request.
|
||||||
|
|
||||||
:param url: url path to target service
|
:param url: url path to target service
|
||||||
:param headers: a dictionary of extra headers to send
|
:param headers: a dictionary of extra headers to send
|
||||||
@ -70,3 +130,36 @@ class FunctionalTest(TestCase):
|
|||||||
if not expect_errors:
|
if not expect_errors:
|
||||||
response = response.json
|
response = response.json
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def post_json(self, url, headers=None, extra_environ=None,
|
||||||
|
status=None, expect_errors=False,
|
||||||
|
content_type='application/json', **params):
|
||||||
|
"""Send HTTP POST request.
|
||||||
|
|
||||||
|
:param url: url path to target service
|
||||||
|
:param headers: a dictionary of extra headers to send
|
||||||
|
:param extra_environ: a dictionary of environmental variables that
|
||||||
|
should be added to the request
|
||||||
|
:param status: integer or string of the HTTP status code you expect
|
||||||
|
in response (if not 200 or 3xx). You can also use a
|
||||||
|
wildcard, like '3*' or '*'
|
||||||
|
:param expect_errors: boolean value, if this is False, then if
|
||||||
|
anything is written to environ wsgi.errors it
|
||||||
|
will be an error. If it is True, then
|
||||||
|
non-200/3xx responses are also okay
|
||||||
|
:param params: a query string, or a dictionary that will be encoded
|
||||||
|
into a query string. You may also include a URL query
|
||||||
|
string on the url
|
||||||
|
|
||||||
|
"""
|
||||||
|
response = self.app.post(url,
|
||||||
|
headers=headers,
|
||||||
|
extra_environ=extra_environ,
|
||||||
|
status=status,
|
||||||
|
expect_errors=expect_errors,
|
||||||
|
content_type=content_type,
|
||||||
|
**params)
|
||||||
|
|
||||||
|
if not expect_errors:
|
||||||
|
response = response.json
|
||||||
|
return response
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
from refstack.tests import api
|
from refstack.tests import api
|
||||||
|
|
||||||
@ -19,11 +21,25 @@ from refstack.tests import api
|
|||||||
class TestRefStackApi(api.FunctionalTest):
|
class TestRefStackApi(api.FunctionalTest):
|
||||||
|
|
||||||
def test_root_controller(self):
|
def test_root_controller(self):
|
||||||
|
"""Test request to root."""
|
||||||
actual_response = self.get_json('/')
|
actual_response = self.get_json('/')
|
||||||
expected_response = {'Root': 'OK'}
|
expected_response = {'Root': 'OK'}
|
||||||
self.assertEqual(expected_response, actual_response)
|
self.assertEqual(expected_response, actual_response)
|
||||||
|
|
||||||
def test_results_controller(self):
|
def test_results_controller(self):
|
||||||
actual_response = self.get_json('/v1/results/')
|
"""Test results endpoint."""
|
||||||
expected_response = {'Results': 'OK'}
|
results = json.dumps({
|
||||||
self.assertEqual(expected_response, actual_response)
|
'cpid': 'foo',
|
||||||
|
'duration_seconds': 10,
|
||||||
|
'results': [
|
||||||
|
{'name': 'tempest.foo.bar'},
|
||||||
|
{'name': 'tempest.buzz',
|
||||||
|
'uid': '42'}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
actual_response = self.post_json('/v1/results/', params=results)
|
||||||
|
self.assertIn('test_id', actual_response)
|
||||||
|
try:
|
||||||
|
uuid.UUID(actual_response.get('test_id'), version=4)
|
||||||
|
except ValueError:
|
||||||
|
self.fail("actual_response doesn't contain test_is")
|
||||||
|
0
refstack/tests/unit/__init__.py
Normal file
0
refstack/tests/unit/__init__.py
Normal file
24
refstack/tests/unit/tests.py
Normal file
24
refstack/tests/unit/tests.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#
|
||||||
|
# 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 unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_nothing(self):
|
||||||
|
# make sure the shuffled sequence does not lose any elements
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -4,3 +4,4 @@ flake8==2.0
|
|||||||
python-subunit>=0.0.18
|
python-subunit>=0.0.18
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testtools>=0.9.34
|
testtools>=0.9.34
|
||||||
|
mysqlclient
|
12
tox.ini
12
tox.ini
@ -18,6 +18,18 @@ deps = -r{toxinidir}/requirements.txt
|
|||||||
commands = python setup.py testr --testr-args='{posargs}'
|
commands = python setup.py testr --testr-args='{posargs}'
|
||||||
distribute = false
|
distribute = false
|
||||||
|
|
||||||
|
[testenv:func]
|
||||||
|
usedevelop = True
|
||||||
|
install_command = pip install -U {opts} {packages}
|
||||||
|
setenv = VIRTUAL_ENV={envdir}
|
||||||
|
LANG=en_US.UTF-8
|
||||||
|
LANGUAGE=en_US:en
|
||||||
|
LC_ALL=C
|
||||||
|
deps = -r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
commands = python -m unittest discover ./refstack/tests/api
|
||||||
|
distribute = false
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
commands = flake8
|
commands = flake8
|
||||||
distribute = false
|
distribute = false
|
||||||
|
Loading…
Reference in New Issue
Block a user