Add unit tests for db documents api.
This commit is contained in:
parent
bbd0292455
commit
d50c9cef2e
|
@ -0,0 +1,88 @@
|
|||
# Copyright 2017 AT&T Intellectual Property. All other 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.
|
||||
|
||||
"""
|
||||
Time related utilities and helper functions.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import iso8601
|
||||
from monotonic import monotonic as now # noqa
|
||||
from oslo_utils import encodeutils
|
||||
|
||||
# ISO 8601 extended time format with microseconds
|
||||
_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f'
|
||||
_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S'
|
||||
PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND
|
||||
|
||||
|
||||
def isotime(at=None, subsecond=False):
|
||||
"""Stringify time in ISO 8601 format."""
|
||||
if not at:
|
||||
at = utcnow()
|
||||
st = at.strftime(_ISO8601_TIME_FORMAT
|
||||
if not subsecond
|
||||
else _ISO8601_TIME_FORMAT_SUBSECOND)
|
||||
tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC'
|
||||
st += ('Z' if tz == 'UTC' else tz)
|
||||
return st
|
||||
|
||||
|
||||
def parse_isotime(timestr):
|
||||
"""Parse time from ISO 8601 format."""
|
||||
try:
|
||||
return iso8601.parse_date(timestr)
|
||||
except iso8601.ParseError as e:
|
||||
raise ValueError(encodeutils.exception_to_unicode(e))
|
||||
except TypeError as e:
|
||||
raise ValueError(encodeutils.exception_to_unicode(e))
|
||||
|
||||
|
||||
def utcnow(with_timezone=False):
|
||||
"""Overridable version of utils.utcnow that can return a TZ-aware datetime.
|
||||
"""
|
||||
if utcnow.override_time:
|
||||
try:
|
||||
return utcnow.override_time.pop(0)
|
||||
except AttributeError:
|
||||
return utcnow.override_time
|
||||
if with_timezone:
|
||||
return datetime.datetime.now(tz=iso8601.iso8601.UTC)
|
||||
return datetime.datetime.utcnow()
|
||||
|
||||
|
||||
def normalize_time(timestamp):
|
||||
"""Normalize time in arbitrary timezone to UTC naive object."""
|
||||
offset = timestamp.utcoffset()
|
||||
if offset is None:
|
||||
return timestamp
|
||||
return timestamp.replace(tzinfo=None) - offset
|
||||
|
||||
|
||||
def iso8601_from_timestamp(timestamp, microsecond=False):
|
||||
"""Returns an iso8601 formatted date from timestamp."""
|
||||
return isotime(datetime.datetime.utcfromtimestamp(timestamp), microsecond)
|
||||
|
||||
utcnow.override_time = None
|
||||
|
||||
|
||||
def delta_seconds(before, after):
|
||||
"""Return the difference between two timing objects.
|
||||
|
||||
Compute the difference in seconds between two date, time, or
|
||||
datetime objects (as a float, to microsecond resolution).
|
||||
"""
|
||||
delta = after - before
|
||||
return datetime.timedelta.total_seconds(delta)
|
|
@ -23,7 +23,6 @@ from deckhand.control import base as api_base
|
|||
from deckhand.control import documents
|
||||
from deckhand.control import secrets
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
from deckhand.db.sqlalchemy import models as db_models
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = None
|
||||
|
@ -54,7 +53,7 @@ def __setup_logging():
|
|||
|
||||
|
||||
def __setup_db():
|
||||
db_models.register_models(db_api.get_engine())
|
||||
db_api.setup_db()
|
||||
|
||||
|
||||
def start_api(state_manager=None):
|
||||
|
|
|
@ -19,6 +19,7 @@ import falcon
|
|||
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
from deckhand.control import base as api_base
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
|
@ -63,13 +64,14 @@ class DocumentsResource(api_base.BaseResource):
|
|||
return self.return_error(resp, falcon.HTTP_400, message=e)
|
||||
|
||||
try:
|
||||
db_api.document_create(document)
|
||||
created_document = db_api.document_create(document)
|
||||
except db_exc.DBDuplicateEntry as e:
|
||||
return self.return_error(resp, falcon.HTTP_409, message=e)
|
||||
except Exception as e:
|
||||
return self.return_error(resp, falcon.HTTP_500, message=e)
|
||||
|
||||
resp.status = falcon.HTTP_201
|
||||
resp.body = json.dumps(created_document)
|
||||
|
||||
def _check_document_exists(self):
|
||||
pass
|
||||
|
|
|
@ -20,6 +20,7 @@ import threading
|
|||
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as db_exception
|
||||
from oslo_db import options
|
||||
from oslo_db.sqlalchemy import session
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
@ -39,6 +40,8 @@ LOG = logging.getLogger(__name__)
|
|||
|
||||
CONF = cfg.CONF
|
||||
|
||||
options.set_defaults(CONF)
|
||||
|
||||
_FACADE = None
|
||||
_LOCK = threading.Lock()
|
||||
|
||||
|
@ -95,6 +98,14 @@ def clear_db_env():
|
|||
_FACADE = None
|
||||
|
||||
|
||||
def setup_db():
|
||||
models.register_models(get_engine())
|
||||
|
||||
|
||||
def drop_db():
|
||||
models.unregister_models(get_engine())
|
||||
|
||||
|
||||
def document_create(values, session=None):
|
||||
"""Create a document."""
|
||||
values = values.copy()
|
||||
|
@ -102,7 +113,9 @@ def document_create(values, session=None):
|
|||
values['schema_version'] = values.pop('schemaVersion')
|
||||
|
||||
session = session or get_session()
|
||||
document = models.Document()
|
||||
with session.begin():
|
||||
document = models.Document()
|
||||
document.update(values)
|
||||
document.save(session=session)
|
||||
|
||||
return document.to_dict()
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import uuid
|
||||
|
||||
from oslo_db.sqlalchemy import models
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils as json
|
||||
from oslo_utils import timeutils
|
||||
from sqlalchemy import Boolean
|
||||
|
@ -28,6 +29,9 @@ from sqlalchemy import String
|
|||
from sqlalchemy import Text
|
||||
from sqlalchemy.types import TypeDecorator
|
||||
|
||||
from deckhand.common import timeutils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# Declarative base class which maintains a catalog of classes and tables
|
||||
# relative to that base.
|
||||
|
@ -96,6 +100,11 @@ class DeckhandBase(models.ModelBase, models.TimestampMixin):
|
|||
# Remove private state instance, as it is not serializable and causes
|
||||
# CircularReference.
|
||||
d.pop("_sa_instance_state")
|
||||
|
||||
for k in ["created_at", "updated_at", "deleted_at", "deleted"]:
|
||||
if k in d and d[k]:
|
||||
d[k] = d[k].isoformat()
|
||||
|
||||
return d
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# Copyright 2017 AT&T Intellectual Property. All other 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 fixtures
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import testtools
|
||||
|
||||
from deckhand.conf import config
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
from deckhand.db.sqlalchemy import models as db_models
|
||||
|
||||
CONF = cfg.CONF
|
||||
logging.register_options(CONF)
|
||||
logging.setup(CONF, 'deckhand')
|
||||
|
||||
|
||||
class DeckhandTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DeckhandTestCase, self).setUp()
|
||||
self.useFixture(fixtures.FakeLogger('deckhand'))
|
||||
|
||||
def override_config(self, name, override, group=None):
|
||||
CONF.set_override(name, override, group)
|
||||
self.addCleanup(CONF.clear_override, name, group)
|
||||
|
||||
|
||||
class DeckhandWithDBTestCase(DeckhandTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DeckhandWithDBTestCase, self).setUp()
|
||||
self.override_config('connection', "sqlite://", group='database')
|
||||
db_api.setup_db()
|
||||
self.addCleanup(db_api.drop_db)
|
|
@ -23,13 +23,12 @@ from deckhand.control import base as api_base
|
|||
class TestApi(testtools.TestCase):
|
||||
|
||||
@mock.patch.object(api, 'db_api', autospec=True)
|
||||
@mock.patch.object(api, 'db_models', autospec=True)
|
||||
@mock.patch.object(api, 'config', autospec=True)
|
||||
@mock.patch.object(api, 'secrets', autospec=True)
|
||||
@mock.patch.object(api, 'documents', autospec=True)
|
||||
@mock.patch.object(api, 'falcon', autospec=True)
|
||||
def test_start_api(self, mock_falcon, mock_documents, mock_secrets,
|
||||
mock_config, mock_db_models, mock_db_api):
|
||||
mock_config, mock_db_api):
|
||||
mock_falcon_api = mock_falcon.API.return_value
|
||||
|
||||
result = api.start_api()
|
||||
|
@ -43,5 +42,4 @@ class TestApi(testtools.TestCase):
|
|||
mock.call('/api/v1.0/secrets', mock_secrets.SecretsResource())
|
||||
])
|
||||
mock_config.parse_args.assert_called_once_with()
|
||||
mock_db_models.register_models.assert_called_once_with(
|
||||
mock_db_api.get_engine())
|
||||
mock_db_api.setup_db.assert_called_once_with()
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright 2017 AT&T Intellectual Property. All other 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 mock
|
||||
|
||||
import testtools
|
||||
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
from deckhand.tests.unit import base
|
||||
|
||||
|
||||
class DocumentFixture(object):
|
||||
|
||||
def get_minimal_fixture(self, **kwargs):
|
||||
fixture = {'data': 'fake document data',
|
||||
'metadata': 'fake meta',
|
||||
'kind': 'FakeConfigType',
|
||||
'schemaVersion': 'deckhand/v1'}
|
||||
fixture.update(kwargs)
|
||||
return fixture
|
||||
|
||||
|
||||
class TestDocumentsApi(base.DeckhandWithDBTestCase):
|
||||
|
||||
def _validate_document(self, expected, actual):
|
||||
expected['doc_metadata'] = expected.pop('metadata')
|
||||
expected['schema_version'] = expected.pop('schemaVersion')
|
||||
|
||||
# TODO: Validate "status" fields, like created_at.
|
||||
self.assertIsInstance(actual, dict)
|
||||
for key, val in expected.items():
|
||||
self.assertIn(key, actual)
|
||||
self.assertEqual(val, actual[key])
|
||||
|
||||
def test_create_document(self):
|
||||
fixture = DocumentFixture().get_minimal_fixture()
|
||||
document = db_api.document_create(fixture)
|
||||
self._validate_document(fixture, document)
|
Loading…
Reference in New Issue