Replace existing functional tests with Gabbi

This gives a starting point for data-driven functional testing.

Change-Id: I22c2fcd593b92b2e27c809cbe28cc6f44d2774cb
This commit is contained in:
Mark Burnett 2017-08-18 08:52:31 -05:00
parent 5a4de9183e
commit ee3a96d518
7 changed files with 228 additions and 92 deletions

View File

@ -1,36 +0,0 @@
# 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
from falcon import testing as falcon_testing
from deckhand.control import api
from deckhand import factories
from deckhand.tests.unit import base as test_base
class TestFunctionalBase(test_base.DeckhandWithDBTestCase,
falcon_testing.TestCase):
"""Base class for functional testing."""
def setUp(self):
super(TestFunctionalBase, self).setUp()
self.app = falcon_testing.TestClient(api.start_api())
self.validation_policy_factory = factories.ValidationPolicyFactory()
@classmethod
def setUpClass(cls):
super(TestFunctionalBase, cls).setUpClass()
mock.patch.object(api, '__setup_logging').start()

View File

@ -0,0 +1,30 @@
# Test creation of a single document and verify duplciates will be ignored
defaults:
request_headers:
content-type: application/x-yaml
tests:
- name: create_single_document
desc: Create a sample document
POST: /api/v1.0/documents
status: 201
data: <@resources/sample-doc.yaml
response_headers:
content-type: application/x-yaml
- name: verify_single_document
desc: Check that a single document was created above
GET: /api/v1.0/revisions/$RESPONSE['$.documents[0].revision_id']/documents
status: 200
response_headers:
content-type: application/x-yaml
response_multidoc_jsonpaths:
$.documents[*].metadata.name: a-unique-config-name-12345
- name: create_duplicate_document
desc: Attempt to duplicate sample document
POST: /api/v1.0/documents
status: 204
data: <@resources/sample-doc.yaml

View File

@ -0,0 +1,45 @@
---
schema: promenade/ResourceType/v1.0
metadata:
schema: metadata/Document/v1.0
name: a-unique-config-name-12345
labels:
component: apiserver
hostname: server0
layeringDefinition:
layer: global
abstract: False
parentSelector:
required_key_a: required_label_a
required_key_b: required_label_b
actions:
- method: merge
path: .path.to.merge.into.parent
- method: delete
path: .path.to.delete
substitutions:
- dest:
path: .chart.values.tls.certificate
src:
schema: deckhand/Certificate/v1.0
name: example-cert
path: .
- dest:
path: .chart.values.tls.key
src:
schema: deckhand/CertificateKey/v1.0
name: example-key
path: .
- dest:
path: .chart.values.some_url
pattern: INSERT_[A-Z]+_HERE
src:
schema: deckhand/Passphrase/v1.0
name: example-password
path: .
data:
chart:
details:
data: here
values:
some_url: http://admin:INSERT_PASSWORD_HERE@service-name:8080/v1

View File

@ -1,55 +0,0 @@
# 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 os
import yaml
import falcon
from deckhand.tests.functional import base as test_base
class TestDocumentsApi(test_base.TestFunctionalBase):
def _read_test_resource(self, file_name):
dir_path = os.path.dirname(os.path.realpath(__file__))
test_yaml_path = os.path.abspath(os.path.join(
dir_path, os.pardir, 'unit', 'resources', file_name + '.yaml'))
with open(test_yaml_path, 'r') as yaml_file:
yaml_data = yaml_file.read()
return yaml_data
def test_create_document(self):
yaml_data = self._read_test_resource('sample_document')
resp = self.app.simulate_post('/api/v1.0/documents', body=yaml_data)
self.assertEqual(falcon.HTTP_201, resp.status)
# Validate that the correct number of documents were created: one
# document corresponding to ``yaml_data``.
resp_documents = [d for d in yaml.safe_load_all(resp.text)]
self.assertIsInstance(resp_documents, list)
self.assertEqual(1, len(resp_documents))
self.assertIn('revision_id', resp_documents[0])
def test_create_duplicate_document(self):
yaml_data = self._read_test_resource('sample_document')
# Second iteration will create the duplicate document.
for _ in range(2):
resp = self.app.simulate_post(
'/api/v1.0/documents', body=yaml_data)
self.assertEqual(falcon.HTTP_204, resp.status)
self.assertEmpty(resp.text)

View File

@ -0,0 +1,51 @@
# 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 gabbi.driver
import gabbi.handlers.jsonhandler
import gabbi.json_parser
import os
import yaml
TESTS_DIR = 'gabbits'
# This is quite similar to the existing JSONHandler, so use it as the base
# class instead of gabbi.handlers.base.ContentHandler
class MultidocJsonpaths(gabbi.handlers.jsonhandler.JSONHandler):
test_key_suffix = 'multidoc_jsonpaths'
@staticmethod
def accepts(content_type):
content_type = content_type.split(';', 1)[0].strip()
return (content_type.endswith('+yaml') or
content_type.startswith('application/yaml') or
content_type.startswith('application/x-yaml'))
@staticmethod
def dumps(data, pretty=False, test=None):
return yaml.safe_dump_all(data)
@staticmethod
def loads(string):
return {'documents': list(yaml.safe_load_all(string))}
def load_tests(loader, tests, pattern):
test_dir = os.path.join(os.path.dirname(__file__), TESTS_DIR)
return gabbi.driver.build_tests(test_dir, loader,
content_handlers=[MultidocJsonpaths],
verbose=True,
url=os.environ['DECKHAND_TEST_URL'])

97
tools/functional-tests.sh Executable file
View File

@ -0,0 +1,97 @@
#!/usr/bin/env bash
function log_section {
set +x
echo 1>&2
echo 1>&2
echo === $* === 1>&2
set -x
}
set -ex
log_section Starting Postgres
POSTGRES_ID=$(
sudo docker run \
--detach \
--publish :5432 \
-e POSTGRES_DB=deckhand \
-e POSTGRES_USER=deckhand \
-e POSTGRES_PASSWORD=password \
postgres:10
)
function cleanup {
sudo docker stop $POSTGRES_ID
kill %1
}
trap cleanup EXIT
POSTGRES_IP=$(
sudo docker inspect \
--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}' \
$POSTGRES_ID
)
POSTGRES_PORT=$(
sudo docker inspect \
--format='{{(index (index .NetworkSettings.Ports "5432/tcp") 0).HostPort}}' \
$POSTGRES_ID
)
export DECKHAND_TEST_URL=http://localhost:9000
export DATABASE_URL=postgres://deckhand:password@$POSTGRES_IP:$POSTGRES_PORT/deckhand
log_section Creating config file
CONF_DIR=$(mktemp -d)
cat <<EOCONF > $CONF_DIR/deckhand.conf
[DEFAULT]
debug = true
use_stderr = true
[barbican]
[database]
# XXX For now, connection to postgres is not setup.
#connection = $DATABASE_URL
connection = sqlite://
[keystone_authtoken]
EOCONF
echo $CONF_DIR/deckhand.conf 1>&2
cat $CONF_DIR/deckhand.conf 1>&2
log_section Starting server
rm -f deckhand.log
uwsgi \
--http :9000 \
-w deckhand.cmd \
--callable deckhand_callable \
--enable-threads \
-L \
--pyargv "--config-file $CONF_DIR/deckhand.conf" &
# Give the server a chance to come up. Better to poll a health check.
sleep 5
log_section Running tests
set +e
ostestr -c 1 $*
TEST_STATUS=$?
set -e
if [ "x$TEST_STATUS" = "x0" ]; then
log_section Done SUCCESS
else
log_section Deckhand Server Log
cat deckhand.log
log_section Done FAILURE
exit $TEST_STATUS
fi

View File

@ -33,9 +33,13 @@ usedevelop = True
setenv = VIRTUAL_ENV={envdir}
OS_TEST_PATH=./deckhand/tests/functional
LANGUAGE=en_US
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
gabbi==1.35.1
uwsgi
commands =
find . -type f -name "*.pyc" -delete
ostestr '{posargs}'
{toxinidir}/tools/functional-tests.sh '{posargs}'
[testenv:cover]
commands =