Replace existing functional tests with Gabbi
This gives a starting point for data-driven functional testing. Change-Id: I22c2fcd593b92b2e27c809cbe28cc6f44d2774cb
This commit is contained in:
parent
5a4de9183e
commit
ee3a96d518
@ -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()
|
30
deckhand/tests/functional/gabbits/document-crud.yaml
Normal file
30
deckhand/tests/functional/gabbits/document-crud.yaml
Normal 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
|
45
deckhand/tests/functional/gabbits/resources/sample-doc.yaml
Normal file
45
deckhand/tests/functional/gabbits/resources/sample-doc.yaml
Normal 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
|
@ -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)
|
51
deckhand/tests/functional/test_gabbi.py
Normal file
51
deckhand/tests/functional/test_gabbi.py
Normal 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
97
tools/functional-tests.sh
Executable 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
|
6
tox.ini
6
tox.ini
@ -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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user