Merge "Decoupled Unit Tests"

This commit is contained in:
Jenkins 2014-06-12 06:24:14 +00:00 committed by Gerrit Code Review
commit f227a24651
22 changed files with 2088 additions and 115 deletions

View File

@ -16,30 +16,8 @@
# single module.
from marconi.tests.queues.transport.wsgi import base
from marconi.tests.queues.transport.wsgi import test_auth
from marconi.tests.queues.transport.wsgi import test_claims
from marconi.tests.queues.transport.wsgi import test_default_limits
from marconi.tests.queues.transport.wsgi import test_home
from marconi.tests.queues.transport.wsgi import test_media_type
from marconi.tests.queues.transport.wsgi import test_messages
from marconi.tests.queues.transport.wsgi import test_queue_lifecycle as lc
from marconi.tests.queues.transport.wsgi import test_shards
TestBase = base.TestBase
TestBaseFaulty = base.TestBaseFaulty
TestAuth = test_auth.TestAuth
TestClaimsFaultyDriver = test_claims.TestClaimsFaultyDriver
TestClaimsMongoDB = test_claims.TestClaimsMongoDB
TestClaimsSqlalchemy = test_claims.TestClaimsSqlalchemy
TestDefaultLimits = test_default_limits.TestDefaultLimits
TestHomeDocument = test_home.TestHomeDocument
TestMediaType = test_media_type.TestMediaType
TestMessagesFaultyDriver = test_messages.TestMessagesFaultyDriver
TestMessagesMongoDB = test_messages.TestMessagesMongoDB
TestMessagesMongoDBSharded = test_messages.TestMessagesMongoDBSharded
TestMessagesSqlalchemy = test_messages.TestMessagesSqlalchemy
TestQueueFaultyDriver = lc.TestQueueLifecycleFaultyDriver
TestQueueLifecycleMongoDB = lc.TestQueueLifecycleMongoDB
TestQueueLifecycleSqlalchemy = lc.TestQueueLifecycleSqlalchemy
TestShardsMongoDB = test_shards.TestShardsMongoDB
TestShardsSqlalchemy = test_shards.TestShardsSqlalchemy
V1Base = base.V1Base
V1_1Base = base.V1_1Base

View File

@ -106,3 +106,43 @@ class TestBase(testing.TestBase):
class TestBaseFaulty(TestBase):
"""This test ensures we aren't letting any exceptions go unhandled."""
class V1Base(TestBase):
"""Base class for V1 API Tests.
Should contain methods specific to V1 of the API
"""
pass
class V1BaseFaulty(TestBaseFaulty):
"""Base class for V1 API Faulty Tests.
Should contain methods specific to V1 exception testing
"""
pass
class V1_1Base(TestBase):
"""Base class for V1 API Tests.
Should contain methods specific to V1.1 of the API
"""
def simulate_request(self, path, **kwargs):
"""Simulate a request.
Simulates a WSGI request to the API for testing.
:param path: Request path for the desired resource
:param kwargs: Same as falcon.testing.create_environ()
:returns: standard WSGI iterable response
"""
return self.app(ftest.create_environ(path=path, **kwargs),
self.srmock)
class V1_1BaseFaulty(TestBaseFaulty):
"""Base class for V1.1 API Faulty Tests.
Should contain methods specific to V1.1 exception testing
"""
pass

View File

@ -0,0 +1,39 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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.
from marconi.tests.queues.transport.wsgi.v1 import test_auth
from marconi.tests.queues.transport.wsgi.v1 import test_claims
from marconi.tests.queues.transport.wsgi.v1 import test_default_limits
from marconi.tests.queues.transport.wsgi.v1 import test_home
from marconi.tests.queues.transport.wsgi.v1 import test_media_type
from marconi.tests.queues.transport.wsgi.v1 import test_messages
from marconi.tests.queues.transport.wsgi.v1 import test_queue_lifecycle as lc
from marconi.tests.queues.transport.wsgi.v1 import test_shards
TestAuth = test_auth.TestAuth
TestClaimsFaultyDriver = test_claims.TestClaimsFaultyDriver
TestClaimsMongoDB = test_claims.TestClaimsMongoDB
TestClaimsSqlalchemy = test_claims.TestClaimsSqlalchemy
TestDefaultLimits = test_default_limits.TestDefaultLimits
TestHomeDocument = test_home.TestHomeDocument
TestMediaType = test_media_type.TestMediaType
TestMessagesFaultyDriver = test_messages.TestMessagesFaultyDriver
TestMessagesMongoDB = test_messages.TestMessagesMongoDB
TestMessagesMongoDBSharded = test_messages.TestMessagesMongoDBSharded
TestMessagesSqlalchemy = test_messages.TestMessagesSqlalchemy
TestQueueFaultyDriver = lc.TestQueueLifecycleFaultyDriver
TestQueueLifecycleMongoDB = lc.TestQueueLifecycleMongoDB
TestQueueLifecycleSqlalchemy = lc.TestQueueLifecycleSqlalchemy
TestShardsMongoDB = test_shards.TestShardsMongoDB
TestShardsSqlalchemy = test_shards.TestShardsSqlalchemy

View File

@ -20,10 +20,10 @@ import falcon
from falcon import testing
from keystoneclient.middleware import auth_token
from . import base # noqa
from marconi.tests.queues.transport.wsgi import base
class TestAuth(base.TestBase):
class TestAuth(base.V1Base):
config_file = 'keystone_auth.conf'

View File

@ -14,7 +14,6 @@
# limitations under the License.
import datetime
import json
import uuid
import ddt
@ -22,13 +21,14 @@ import falcon
import mock
from testtools import matchers
from . import base # noqa
from marconi.openstack.common import jsonutils
from marconi.openstack.common import timeutils
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@ddt.ddt
class ClaimsBaseTest(base.TestBase):
class ClaimsBaseTest(base.V1Base):
def setUp(self):
super(ClaimsBaseTest, self).setUp()
@ -43,7 +43,7 @@ class ClaimsBaseTest(base.TestBase):
self.simulate_put(self.queue_path, self.project_id, body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
doc = json.dumps([{'body': 239, 'ttl': 300}] * 10)
doc = jsonutils.dumps([{'body': 239, 'ttl': 300}] * 10)
self.simulate_post(self.queue_path + '/messages', self.project_id,
body=doc, headers={'Client-ID': str(uuid.uuid4())})
self.assertEqual(self.srmock.status, falcon.HTTP_201)
@ -74,7 +74,7 @@ class ClaimsBaseTest(base.TestBase):
def test_unacceptable_ttl_or_grace(self, ttl_grace):
ttl, grace = ttl_grace
self.simulate_post(self.claims_path, self.project_id,
body=json.dumps({'ttl': ttl, 'grace': grace}))
body=jsonutils.dumps({'ttl': ttl, 'grace': grace}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ -83,7 +83,7 @@ class ClaimsBaseTest(base.TestBase):
href = self._get_a_claim()
self.simulate_patch(href, self.project_id,
body=json.dumps({'ttl': ttl}))
body=jsonutils.dumps({'ttl': ttl}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ -99,7 +99,7 @@ class ClaimsBaseTest(base.TestBase):
body = self.simulate_post(self.claims_path, self.project_id, body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
claimed = json.loads(body[0])
claimed = jsonutils.loads(body[0])
claim_href = self.srmock.headers_dict['Location']
message_href, params = claimed[0]['href'].split('?')
@ -121,7 +121,7 @@ class ClaimsBaseTest(base.TestBase):
body = self.simulate_get(self.messages_path, self.project_id,
query_string='include_claimed=true',
headers=headers)
listed = json.loads(body[0])
listed = jsonutils.loads(body[0])
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(len(listed['messages']), len(claimed))
@ -131,7 +131,7 @@ class ClaimsBaseTest(base.TestBase):
mock_utcnow.return_value = now
body = self.simulate_get(claim_href, self.project_id)
claim = json.loads(body[0])
claim = jsonutils.loads(body[0])
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(self.srmock.headers_dict['Content-Location'],
@ -166,7 +166,7 @@ class ClaimsBaseTest(base.TestBase):
# Get the claimed messages (again)
body = self.simulate_get(claim_href, self.project_id)
query = timeutils.utcnow()
claim = json.loads(body[0])
claim = jsonutils.loads(body[0])
message_href, params = claim['messages'][0]['href'].split('?')
self.assertEqual(claim['ttl'], 60)
@ -218,7 +218,7 @@ class ClaimsBaseTest(base.TestBase):
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_patch_nonexistent_claim_404s(self):
patch_data = json.dumps({'ttl': 100})
patch_data = jsonutils.dumps({'ttl': 100})
self.simulate_patch(self.claims_path + '/a', body=patch_data)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
@ -248,7 +248,7 @@ class TestClaimsSqlalchemy(ClaimsBaseTest):
config_file = 'wsgi_sqlalchemy.conf'
class TestClaimsFaultyDriver(base.TestBaseFaulty):
class TestClaimsFaultyDriver(base.V1BaseFaulty):
config_file = 'wsgi_faulty.conf'

View File

@ -14,18 +14,16 @@
# limitations under the License.
import contextlib
import json
import uuid
import falcon
from . import base # noqa
from marconi.openstack.common import jsonutils
from marconi.queues import storage
from marconi.tests.queues.transport.wsgi import base
class TestDefaultLimits(base.TestBase):
class TestDefaultLimits(base.V1Base):
config_file = 'wsgi_sqlalchemy_default_limits.conf'
@ -92,7 +90,7 @@ class TestDefaultLimits(base.TestBase):
self.simulate_delete(path)
def _prepare_messages(self, count):
doc = json.dumps([{'body': 239, 'ttl': 300}] * count)
doc = jsonutils.dumps([{'body': 239, 'ttl': 300}] * count)
self.simulate_post(self.messages_path, body=doc,
headers={'Client-ID': str(uuid.uuid4())})

View File

@ -15,11 +15,14 @@
import falcon
import six.moves.urllib.parse as urlparse
from . import base # noqa
from marconi.openstack.common import jsonutils
class TestHomeDocument(base.TestBase):
from marconi.tests.queues.transport.wsgi import base
class TestHomeDocument(base.V1Base):
config_file = 'wsgi_sqlalchemy.conf'

View File

@ -18,10 +18,11 @@ import uuid
import falcon
from falcon import testing
from . import base # noqa
from marconi.tests.queues.transport.wsgi import base
class TestMediaType(base.TestBase):
class TestMediaType(base.V1Base):
config_file = 'wsgi_sqlalchemy.conf'

View File

@ -14,7 +14,6 @@
# limitations under the License.
import datetime
import json
import uuid
import ddt
@ -23,15 +22,16 @@ import mock
import six
from testtools import matchers
from . import base # noqa
from marconi.openstack.common import jsonutils
from marconi.openstack.common import timeutils
from marconi.queues.transport import validation
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@ddt.ddt
class MessagesBaseTest(base.TestBase):
class MessagesBaseTest(base.V1Base):
def setUp(self):
super(MessagesBaseTest, self).setUp()
@ -41,7 +41,7 @@ class MessagesBaseTest(base.TestBase):
uri = self.conf['drivers:storage:mongodb'].uri
doc = {'weight': 100, 'uri': uri}
self.simulate_put(self.url_prefix + '/shards/' + str(i),
body=json.dumps(doc))
body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.project_id = '7e55e1a7e'
@ -68,7 +68,7 @@ class MessagesBaseTest(base.TestBase):
super(MessagesBaseTest, self).tearDown()
def _test_post(self, sample_messages):
sample_doc = json.dumps(sample_messages)
sample_doc = jsonutils.dumps(sample_messages)
result = self.simulate_post(self.messages_path, self.project_id,
body=sample_doc, headers=self.headers)
@ -234,7 +234,7 @@ class MessagesBaseTest(base.TestBase):
@ddt.data(-1, 59, 1209601)
def test_unacceptable_ttl(self, ttl):
self.simulate_post(self.queue_path + '/messages',
body=json.dumps([{'ttl': ttl,
body=jsonutils.dumps([{'ttl': ttl,
'body': None}]),
headers=self.headers)
@ -242,7 +242,8 @@ class MessagesBaseTest(base.TestBase):
def test_exceeded_message_posting(self):
# Total (raw request) size
doc = json.dumps([{'body': "some body", 'ttl': 100}] * 20, indent=4)
doc = jsonutils.dumps([{'body': "some body", 'ttl': 100}] * 20,
indent=4)
max_len = self.transport_cfg.max_message_size
long_doc = doc + (' ' * (max_len - len(doc) + 1))
@ -444,7 +445,7 @@ class MessagesBaseTest(base.TestBase):
messages[0]['href'])
def _post_messages(self, target, repeat=1):
doc = json.dumps([{'body': 239, 'ttl': 300}] * repeat)
doc = jsonutils.dumps([{'body': 239, 'ttl': 300}] * repeat)
return self.simulate_post(target, self.project_id, body=doc,
headers=self.headers)
@ -489,7 +490,7 @@ class TestMessagesMongoDBSharded(MessagesBaseTest):
self.skipTest("Need to implement sharded queue listing.")
class TestMessagesFaultyDriver(base.TestBaseFaulty):
class TestMessagesFaultyDriver(base.V1BaseFaulty):
config_file = 'wsgi_faulty.conf'

View File

@ -16,13 +16,13 @@ import ddt
import falcon
import six
from . import base # noqa
from marconi.openstack.common import jsonutils
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@ddt.ddt
class QueueLifecycleBaseTest(base.TestBase):
class QueueLifecycleBaseTest(base.V1Base):
config_file = None
@ -361,7 +361,7 @@ class TestQueueLifecycleSqlalchemy(QueueLifecycleBaseTest):
config_file = 'wsgi_sqlalchemy.conf'
class TestQueueLifecycleFaultyDriver(base.TestBaseFaulty):
class TestQueueLifecycleFaultyDriver(base.V1BaseFaulty):
config_file = 'wsgi_faulty.conf'

View File

@ -13,15 +13,15 @@
# the License.
import contextlib
import json
import uuid
import ddt
import falcon
from . import base # noqa
from marconi.openstack.common import jsonutils
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@contextlib.contextmanager
@ -42,7 +42,7 @@ def shard(test, name, weight, uri, options={}):
doc = {'weight': weight, 'uri': uri, 'options': options}
path = test.url_prefix + '/shards/' + name
test.simulate_put(path, body=json.dumps(doc))
test.simulate_put(path, body=jsonutils.dumps(doc))
try:
yield name, weight, uri, options
@ -69,7 +69,7 @@ def shards(test, count, uri):
for i in range(count)]
for path, weight, option in args:
doc = {'weight': weight, 'uri': uri, 'options': option}
test.simulate_put(path, body=json.dumps(doc))
test.simulate_put(path, body=jsonutils.dumps(doc))
try:
yield args
@ -79,13 +79,13 @@ def shards(test, count, uri):
@ddt.ddt
class ShardsBaseTest(base.TestBase):
class ShardsBaseTest(base.V1Base):
def setUp(self):
super(ShardsBaseTest, self).setUp()
self.doc = {'weight': 100, 'uri': 'sqlite://:memory:'}
self.shard = self.url_prefix + '/shards/' + str(uuid.uuid1())
self.simulate_put(self.shard, body=json.dumps(self.doc))
self.simulate_put(self.shard, body=jsonutils.dumps(self.doc))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
def tearDown(self):
@ -101,10 +101,12 @@ class ShardsBaseTest(base.TestBase):
def test_put_raises_if_missing_fields(self):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
self.simulate_put(path, body=json.dumps({'weight': 100}))
self.simulate_put(path, body=jsonutils.dumps({'weight': 100}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_put(path, body=json.dumps({'uri': 'sqlite://:memory:'}))
self.simulate_put(path,
body=jsonutils.dumps(
{'uri': 'sqlite://:memory:'}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 2**32+1, 'big')
@ -112,28 +114,28 @@ class ShardsBaseTest(base.TestBase):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
doc = {'weight': weight, 'uri': 'a'}
self.simulate_put(path,
body=json.dumps(doc))
body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 2**32+1, [], 'localhost:27017')
def test_put_raises_if_invalid_uri(self, uri):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
self.simulate_put(path,
body=json.dumps({'weight': 1, 'uri': uri}))
body=jsonutils.dumps({'weight': 1, 'uri': uri}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 'wee', [])
def test_put_raises_if_invalid_options(self, options):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
doc = {'weight': 1, 'uri': 'a', 'options': options}
self.simulate_put(path, body=json.dumps(doc))
self.simulate_put(path, body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_put_existing_overwrites(self):
# NOTE(cabrera): setUp creates default shard
expect = self.doc
self.simulate_put(self.shard,
body=json.dumps(expect))
body=jsonutils.dumps(expect))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
result = self.simulate_get(self.shard)
@ -180,12 +182,12 @@ class ShardsBaseTest(base.TestBase):
def test_patch_raises_if_missing_fields(self):
self.simulate_patch(self.shard,
body=json.dumps({'location': 1}))
body=jsonutils.dumps({'location': 1}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def _patch_test(self, doc):
self.simulate_patch(self.shard,
body=json.dumps(doc))
body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_200)
result = self.simulate_get(self.shard,
@ -208,24 +210,24 @@ class ShardsBaseTest(base.TestBase):
@ddt.data(-1, 2**32+1, 'big')
def test_patch_raises_400_on_invalid_weight(self, weight):
self.simulate_patch(self.shard,
body=json.dumps({'weight': weight}))
body=jsonutils.dumps({'weight': weight}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 2**32+1, [], 'localhost:27017')
def test_patch_raises_400_on_invalid_uri(self, uri):
self.simulate_patch(self.shard,
body=json.dumps({'uri': uri}))
body=jsonutils.dumps({'uri': uri}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 'wee', [])
def test_patch_raises_400_on_invalid_options(self, options):
self.simulate_patch(self.shard,
body=json.dumps({'options': options}))
body=jsonutils.dumps({'options': options}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_patch_raises_404_if_shard_not_found(self):
self.simulate_patch(self.url_prefix + '/shards/notexists',
body=json.dumps({'weight': 1}))
body=jsonutils.dumps({'weight': 1}))
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_empty_listing_returns_204(self):

View File

@ -0,0 +1,39 @@
# Copyright (c) 2014 Rackspace, Inc.
#
# 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.
from marconi.tests.queues.transport.wsgi.v1_1 import test_auth
from marconi.tests.queues.transport.wsgi.v1_1 import test_claims
from marconi.tests.queues.transport.wsgi.v1_1 import test_default_limits
from marconi.tests.queues.transport.wsgi.v1_1 import test_home
from marconi.tests.queues.transport.wsgi.v1_1 import test_media_type
from marconi.tests.queues.transport.wsgi.v1_1 import test_messages
from marconi.tests.queues.transport.wsgi.v1_1 import test_queue_lifecycle as lc
from marconi.tests.queues.transport.wsgi.v1_1 import test_shards
TestAuth = test_auth.TestAuth
TestClaimsFaultyDriver = test_claims.TestClaimsFaultyDriver
TestClaimsMongoDB = test_claims.TestClaimsMongoDB
TestClaimsSqlalchemy = test_claims.TestClaimsSqlalchemy
TestDefaultLimits = test_default_limits.TestDefaultLimits
TestHomeDocument = test_home.TestHomeDocument
TestMediaType = test_media_type.TestMediaType
TestMessagesFaultyDriver = test_messages.TestMessagesFaultyDriver
TestMessagesMongoDB = test_messages.TestMessagesMongoDB
TestMessagesMongoDBSharded = test_messages.TestMessagesMongoDBSharded
TestMessagesSqlalchemy = test_messages.TestMessagesSqlalchemy
TestQueueFaultyDriver = lc.TestQueueLifecycleFaultyDriver
TestQueueLifecycleMongoDB = lc.TestQueueLifecycleMongoDB
TestQueueLifecycleSqlalchemy = lc.TestQueueLifecycleSqlalchemy
TestShardsMongoDB = test_shards.TestShardsMongoDB
TestShardsSqlalchemy = test_shards.TestShardsSqlalchemy

View File

@ -0,0 +1,43 @@
# Copyright (c) 2013 Red Hat, Inc.
#
# 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.
"""Test Auth."""
import uuid
import falcon
from falcon import testing
from keystoneclient.middleware import auth_token
from marconi.tests.queues.transport.wsgi import base
class TestAuth(base.V1_1Base):
config_file = 'keystone_auth.conf'
def setUp(self):
super(TestAuth, self).setUp()
self.headers = {'Client-ID': str(uuid.uuid4())}
def test_auth_install(self):
self.assertIsInstance(self.app, auth_token.AuthProtocol)
def test_non_authenticated(self):
env = testing.create_environ(self.url_prefix + '/480924/queues/',
method='GET',
headers=self.headers)
self.app(env, self.srmock)
self.assertEqual(self.srmock.status, falcon.HTTP_401)

View File

@ -0,0 +1,316 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 datetime
import json
import uuid
import ddt
import falcon
from marconi.tests.queues.transport.wsgi import base
import mock
from testtools import matchers
from marconi.openstack.common import timeutils
from marconi import tests as testing
@ddt.ddt
class ClaimsBaseTest(base.V1_1Base):
def setUp(self):
super(ClaimsBaseTest, self).setUp()
self.project_id = '737_abc8332832'
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': self.project_id
}
self.queue_path = self.url_prefix + '/queues/fizbit'
self.claims_path = self.queue_path + '/claims'
self.messages_path = self.queue_path + '/messages'
doc = json.dumps({"_ttl": 60})
self.simulate_put(self.queue_path, body=doc, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
doc = json.dumps([{'body': 239, 'ttl': 300}] * 10)
self.simulate_post(self.queue_path + '/messages',
body=doc, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
def tearDown(self):
self.simulate_delete(self.queue_path, headers=self.headers)
super(ClaimsBaseTest, self).tearDown()
@ddt.data(None, '[', '[]', '{}', '.', '"fail"')
def test_bad_claim(self, doc):
self.simulate_post(self.claims_path, body=doc, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
href = self._get_a_claim()
self.simulate_patch(href, body=doc, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_exceeded_claim(self):
self.simulate_post(self.claims_path,
body='{"ttl": 100, "grace": 60}',
query_string='limit=21', headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data((-1, -1), (59, 60), (60, 59), (60, 43201), (43201, 60))
def test_unacceptable_ttl_or_grace(self, ttl_grace):
ttl, grace = ttl_grace
self.simulate_post(self.claims_path,
body=json.dumps({'ttl': ttl, 'grace': grace}),
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 59, 43201)
def test_unacceptable_new_ttl(self, ttl):
href = self._get_a_claim()
self.simulate_patch(href,
body=json.dumps({'ttl': ttl}),
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def _get_a_claim(self):
doc = '{"ttl": 100, "grace": 60}'
self.simulate_post(self.claims_path, body=doc, headers=self.headers)
return self.srmock.headers_dict['Location']
def test_lifecycle(self):
doc = '{"ttl": 100, "grace": 60}'
# First, claim some messages
body = self.simulate_post(self.claims_path, body=doc,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
claimed = json.loads(body[0])
claim_href = self.srmock.headers_dict['Location']
message_href, params = claimed[0]['href'].split('?')
# No more messages to claim
self.simulate_post(self.claims_path, body=doc,
query_string='limit=3', headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Listing messages, by default, won't include claimed, will echo
body = self.simulate_get(self.messages_path,
headers=self.headers,
query_string="echo=true")
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Listing messages, by default, won't include claimed, won't echo
body = self.simulate_get(self.messages_path,
headers=self.headers,
query_string="echo=false")
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# List messages, include_claimed, but don't echo
body = self.simulate_get(self.messages_path,
query_string='include_claimed=true'
'&echo=false',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# List messages with a different client-id and echo=false.
# Should return some messages
headers = self.headers.copy()
headers["Client-ID"] = str(uuid.uuid4())
body = self.simulate_get(self.messages_path,
query_string='include_claimed=true'
'&echo=false',
headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
# Include claimed messages this time, and echo
body = self.simulate_get(self.messages_path,
query_string='include_claimed=true'
'&echo=true',
headers=self.headers)
listed = json.loads(body[0])
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(len(listed['messages']), len(claimed))
now = timeutils.utcnow() + datetime.timedelta(seconds=10)
timeutils_utcnow = 'marconi.openstack.common.timeutils.utcnow'
with mock.patch(timeutils_utcnow) as mock_utcnow:
mock_utcnow.return_value = now
body = self.simulate_get(claim_href, headers=self.headers)
claim = json.loads(body[0])
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(self.srmock.headers_dict['Content-Location'],
claim_href)
self.assertEqual(claim['ttl'], 100)
## NOTE(cpp-cabrera): verify that claim age is non-negative
self.assertThat(claim['age'], matchers.GreaterThan(-1))
# Try to delete the message without submitting a claim_id
self.simulate_delete(message_href, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_403)
# Delete the message and its associated claim
self.simulate_delete(message_href,
query_string=params, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Try to get it from the wrong project
headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': 'bogusproject'
}
self.simulate_get(message_href, query_string=params, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Get the message
self.simulate_get(message_href, query_string=params,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Update the claim
new_claim_ttl = '{"ttl": 60}'
creation = timeutils.utcnow()
self.simulate_patch(claim_href, body=new_claim_ttl,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Get the claimed messages (again)
body = self.simulate_get(claim_href, headers=self.headers)
query = timeutils.utcnow()
claim = json.loads(body[0])
message_href, params = claim['messages'][0]['href'].split('?')
self.assertEqual(claim['ttl'], 60)
estimated_age = timeutils.delta_seconds(creation, query)
self.assertTrue(estimated_age > claim['age'])
# Delete the claim
self.simulate_delete(claim['href'], headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Try to delete a message with an invalid claim ID
self.simulate_delete(message_href,
query_string=params, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_403)
# Make sure it wasn't deleted!
self.simulate_get(message_href, query_string=params,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
# Try to get a claim that doesn't exist
self.simulate_get(claim['href'], headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Try to update a claim that doesn't exist
self.simulate_patch(claim['href'], body=doc,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_post_claim_nonexistent_queue(self):
path = self.url_prefix + '/queues/nonexistent/claims'
self.simulate_post(path,
body='{"ttl": 100, "grace": 60}',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_get_claim_nonexistent_queue(self):
path = self.url_prefix + '/queues/nonexistent/claims/aaabbbba'
self.simulate_get(path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# NOTE(cpp-cabrera): regression test against bug #1203842
def test_get_nonexistent_claim_404s(self):
self.simulate_get(self.claims_path + '/a', headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_delete_nonexistent_claim_204s(self):
self.simulate_delete(self.claims_path + '/a',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_patch_nonexistent_claim_404s(self):
patch_data = json.dumps({'ttl': 100})
self.simulate_patch(self.claims_path + '/a', body=patch_data,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
class TestClaimsMongoDB(ClaimsBaseTest):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestClaimsMongoDB, self).setUp()
def tearDown(self):
storage = self.boot.storage._storage
connection = storage.connection
connection.drop_database(storage.queues_database)
for db in storage.message_databases:
connection.drop_database(db)
super(TestClaimsMongoDB, self).tearDown()
class TestClaimsSqlalchemy(ClaimsBaseTest):
config_file = 'wsgi_sqlalchemy.conf'
class TestClaimsFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
self.project_id = '480924abc_'
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': self.project_id
}
claims_path = self.url_prefix + '/queues/fizbit/claims'
doc = '{"ttl": 100, "grace": 60}'
self.simulate_post(claims_path, body=doc, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_get(claims_path + '/nichts', headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_patch(claims_path + '/nichts', body=doc,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_delete(claims_path + '/foo', headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)

View File

@ -0,0 +1,122 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 contextlib
import uuid
import falcon
from marconi.openstack.common import jsonutils
from marconi.queues import storage
from marconi.tests.queues.transport.wsgi import base
class TestDefaultLimits(base.V1_1Base):
config_file = 'wsgi_sqlalchemy_default_limits.conf'
def setUp(self):
super(TestDefaultLimits, self).setUp()
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': '838383abc_'
}
self.queue_path = self.url_prefix + '/queues'
self.q1_queue_path = self.queue_path + '/q1'
self.messages_path = self.q1_queue_path + '/messages'
self.claims_path = self.q1_queue_path + '/claims'
self.simulate_put(self.q1_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
def tearDown(self):
self.simulate_delete(self.queue_path, headers=self.headers)
super(TestDefaultLimits, self).tearDown()
def test_queue_listing(self):
# 2 queues to list
self.simulate_put(self.queue_path + '/q2', headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
with self._prepare_queues(storage.DEFAULT_QUEUES_PER_PAGE + 1):
result = self.simulate_get(self.queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
queues = jsonutils.loads(result[0])['queues']
self.assertEqual(len(queues), storage.DEFAULT_QUEUES_PER_PAGE)
def test_message_listing_different_id(self):
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_PAGE + 1)
headers = self.headers.copy()
headers['Client-ID'] = str(uuid.uuid4())
result = self.simulate_get(self.messages_path,
headers=headers,
query_string='echo=false')
self.assertEqual(self.srmock.status, falcon.HTTP_200)
messages = jsonutils.loads(result[0])['messages']
self.assertEqual(len(messages), storage.DEFAULT_MESSAGES_PER_PAGE)
def test_message_listing_same_id(self):
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_PAGE + 1)
result = self.simulate_get(self.messages_path,
headers=self.headers,
query_string='echo=false')
self.assertEqual(self.srmock.status, falcon.HTTP_204)
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_PAGE + 1)
result = self.simulate_get(self.messages_path,
headers=self.headers,
query_string='echo=true')
messages = jsonutils.loads(result[0])['messages']
self.assertEqual(len(messages), storage.DEFAULT_MESSAGES_PER_PAGE)
def test_claim_creation(self):
self._prepare_messages(storage.DEFAULT_MESSAGES_PER_CLAIM + 1)
result = self.simulate_post(self.claims_path,
body='{"ttl": 60, "grace": 60}',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
messages = jsonutils.loads(result[0])
self.assertEqual(len(messages), storage.DEFAULT_MESSAGES_PER_CLAIM)
@contextlib.contextmanager
def _prepare_queues(self, count):
queue_paths = [self.queue_path + '/multi-{0}'.format(i)
for i in range(count)]
for path in queue_paths:
self.simulate_put(path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
yield
for path in queue_paths:
self.simulate_delete(path, headers=self.headers)
def _prepare_messages(self, count):
doc = jsonutils.dumps([{'body': 239, 'ttl': 300}] * count)
self.simulate_post(self.messages_path, body=doc,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)

View File

@ -0,0 +1,67 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 uuid
import falcon
import six.moves.urllib.parse as urlparse
from marconi.openstack.common import jsonutils
from marconi.tests.queues.transport.wsgi import base
class TestHomeDocument(base.V1_1Base):
config_file = 'wsgi_sqlalchemy.conf'
def test_json_response(self):
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': '8383830383abc_'
}
body = self.simulate_get(self.url_prefix, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
content_type = self.srmock.headers_dict['Content-Type']
self.assertEqual(content_type, 'application/json-home')
try:
jsonutils.loads(body[0])
except ValueError:
self.fail('Home document is not valid JSON')
def test_href_template(self):
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': '8383830383'
}
body = self.simulate_get(self.url_prefix, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
resp = jsonutils.loads(body[0])
queue_href_template = resp['resources']['rel/queue']['href-template']
path_1 = 'https://marconi.example.com' + self.url_prefix
path_2 = 'https://marconi.example.com' + self.url_prefix + '/'
# Verify all the href template start with the correct version prefix
for resource in list(resp['resources']):
self.assertTrue(resp['resources'][resource]['href-template'].
startswith(self.url_prefix))
url = urlparse.urljoin(path_1, queue_href_template)
expected = ('https://marconi.example.com' + self.url_prefix +
'/queues/foo')
self.assertEqual(url.format(queue_name='foo'), expected)
url = urlparse.urljoin(path_2, queue_href_template)
self.assertEqual(url.format(queue_name='foo'), expected)

View File

@ -0,0 +1,51 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 uuid
import falcon
from falcon import testing
from marconi.tests.queues.transport.wsgi import base
class TestMediaType(base.V1_1Base):
config_file = 'wsgi_sqlalchemy.conf'
def test_json_only_endpoints(self):
endpoints = (
('GET', self.url_prefix + '/queues'),
('GET', self.url_prefix + '/queues/nonexistent/metadata'),
('GET', self.url_prefix + '/queues/nonexistent/stats'),
('POST', self.url_prefix + '/queues/nonexistent/messages'),
('GET', self.url_prefix + '/queues/nonexistent/messages/deadbeaf'),
('POST', self.url_prefix + '/queues/nonexistent/claims'),
('GET', self.url_prefix + '/queues/nonexistent/claims/0ad'),
('GET', self.url_prefix + '/health'),
)
for method, endpoint in endpoints:
headers = {
'Client-ID': str(uuid.uuid4()),
'Accept': 'application/xml',
}
env = testing.create_environ(endpoint,
method=method,
headers=headers)
self.app(env, self.srmock)
self.assertEqual(self.srmock.status, falcon.HTTP_406)

View File

@ -0,0 +1,524 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 datetime
import uuid
import ddt
import falcon
import mock
import six
from testtools import matchers
from marconi.openstack.common import jsonutils
from marconi.openstack.common import timeutils
from marconi.queues.transport import validation
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@ddt.ddt
class MessagesBaseTest(base.V1_1Base):
def setUp(self):
super(MessagesBaseTest, self).setUp()
if self.conf.sharding:
for i in range(4):
uri = self.conf['drivers:storage:mongodb'].uri
doc = {'weight': 100, 'uri': uri}
self.simulate_put(self.url_prefix + '/shards/' + str(i),
body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.project_id = '7e55e1a7e'
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': self.project_id
}
# TODO(kgriffs): Add support in self.simulate_* for a "base path"
# so that we don't have to concatenate against self.url_prefix
# all over the place.
self.queue_path = self.url_prefix + '/queues/fizbit'
self.messages_path = self.queue_path + '/messages'
doc = '{"_ttl": 60}'
self.simulate_put(self.queue_path, body=doc, headers=self.headers)
def tearDown(self):
self.simulate_delete(self.queue_path, headers=self.headers)
if self.conf.sharding:
for i in range(4):
self.simulate_delete(self.url_prefix + '/shards/' + str(i),
headers=self.headers)
super(MessagesBaseTest, self).tearDown()
def _test_post(self, sample_messages):
sample_doc = jsonutils.dumps(sample_messages)
result = self.simulate_post(self.messages_path,
body=sample_doc, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
result_doc = jsonutils.loads(result[0])
msg_ids = self._get_msg_ids(self.srmock.headers_dict)
self.assertEqual(len(msg_ids), len(sample_messages))
expected_resources = [six.text_type(self.messages_path + '/' + id)
for id in msg_ids]
self.assertEqual(expected_resources, result_doc['resources'])
self.assertFalse(result_doc['partial'])
self.assertEqual(len(msg_ids), len(sample_messages))
lookup = dict([(m['ttl'], m['body']) for m in sample_messages])
# Test GET on the message resource directly
# NOTE(cpp-cabrera): force the passing of time to age a message
timeutils_utcnow = 'marconi.openstack.common.timeutils.utcnow'
now = timeutils.utcnow() + datetime.timedelta(seconds=10)
with mock.patch(timeutils_utcnow) as mock_utcnow:
mock_utcnow.return_value = now
for msg_id in msg_ids:
message_uri = self.messages_path + '/' + msg_id
headers = self.headers.copy()
headers['X-Project-ID'] = '777777'
# Wrong project ID
self.simulate_get(message_uri, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Correct project ID
result = self.simulate_get(message_uri, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(self.srmock.headers_dict['Content-Location'],
message_uri)
# Check message properties
message = jsonutils.loads(result[0])
self.assertEqual(message['href'], message_uri)
self.assertEqual(message['body'], lookup[message['ttl']])
# no negative age
# NOTE(cpp-cabrera): testtools lacks GreaterThanEqual on py26
self.assertThat(message['age'],
matchers.GreaterThan(-1))
# Test bulk GET
query_string = 'ids=' + ','.join(msg_ids)
result = self.simulate_get(self.messages_path,
query_string=query_string,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
result_doc = jsonutils.loads(result[0])
expected_ttls = set(m['ttl'] for m in sample_messages)
actual_ttls = set(m['ttl'] for m in result_doc)
self.assertFalse(expected_ttls - actual_ttls)
def test_exceeded_payloads(self):
# Get a valid message id
self._post_messages(self.messages_path)
msg_id = self._get_msg_id(self.srmock.headers_dict)
# Bulk GET restriction
query_string = 'ids=' + ','.join([msg_id] * 21)
self.simulate_get(self.messages_path,
query_string=query_string, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
# Listing restriction
self.simulate_get(self.messages_path,
query_string='limit=21',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
# Bulk deletion restriction
query_string = 'ids=' + ','.join([msg_id] * 22)
self.simulate_delete(self.messages_path,
query_string=query_string, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_post_single(self):
sample_messages = [
{'body': {'key': 'value'}, 'ttl': 200},
]
self._test_post(sample_messages)
def test_post_multiple(self):
sample_messages = [
{'body': 239, 'ttl': 100},
{'body': {'key': 'value'}, 'ttl': 200},
{'body': [1, 3], 'ttl': 300},
]
self._test_post(sample_messages)
def test_post_to_non_ascii_queue(self):
# NOTE(kgriffs): This test verifies that routes with
# embedded queue name params go through the validation
# hook, regardless of the target resource.
path = self.url_prefix + u'/queues/non-ascii-n\u0153me/messages'
if six.PY2:
path = path.encode('utf-8')
self._post_messages(path)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_post_with_long_queue_name(self):
# NOTE(kgriffs): This test verifies that routes with
# embedded queue name params go through the validation
# hook, regardless of the target resource.
queues_path = self.url_prefix + '/queues/'
game_title = 'v' * validation.QUEUE_NAME_MAX_LEN
self._post_messages(queues_path + game_title + '/messages')
self.assertEqual(self.srmock.status, falcon.HTTP_404)
game_title += 'v'
self._post_messages(queues_path + game_title + '/messages')
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_post_to_missing_queue(self):
self._post_messages(self.url_prefix + '/queues/nonexistent/messages')
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_get_from_missing_queue(self):
self.simulate_get(self.url_prefix + '/queues/nonexistent/messages',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
@ddt.data('', '0xdeadbeef', '550893e0-2b6e-11e3-835a-5cf9dd72369')
def test_bad_client_id(self, text_id):
self.simulate_post(self.queue_path + '/messages',
body='{"ttl": 60, "body": ""}',
headers={'Client-ID': text_id})
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_get(self.queue_path + '/messages',
query_string='limit=3&echo=true',
headers={'Client-ID': text_id})
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(None, '[', '[]', '{}', '.')
def test_post_bad_message(self, document):
self.simulate_post(self.queue_path + '/messages',
body=document,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 59, 1209601)
def test_unacceptable_ttl(self, ttl):
self.simulate_post(self.queue_path + '/messages',
body=jsonutils.dumps([{'ttl': ttl,
'body': None}]),
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_exceeded_message_posting(self):
# Total (raw request) size
doc = jsonutils.dumps([{'body': "some body", 'ttl': 100}] * 20,
indent=4)
max_len = self.transport_cfg.max_message_size
long_doc = doc + (' ' * (max_len - len(doc) + 1))
self.simulate_post(self.queue_path + '/messages',
body=long_doc,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data('{"overflow": 9223372036854775808}',
'{"underflow": -9223372036854775809}')
def test_unsupported_json(self, document):
self.simulate_post(self.queue_path + '/messages',
body=document,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_delete(self):
self._post_messages(self.messages_path)
msg_id = self._get_msg_id(self.srmock.headers_dict)
target = self.messages_path + '/' + msg_id
self.simulate_get(target, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.simulate_delete(target, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
self.simulate_get(target, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Safe to delete non-existing ones
self.simulate_delete(target, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_bulk_delete(self):
path = self.queue_path + '/messages'
self._post_messages(path, repeat=5)
[target, params] = self.srmock.headers_dict['location'].split('?')
# Deleting the whole collection is denied
self.simulate_delete(path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_delete(target, query_string=params, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
self.simulate_get(target, query_string=params, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Safe to delete non-existing ones
self.simulate_delete(target, query_string=params, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Even after the queue is gone
self.simulate_delete(self.queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
self.simulate_delete(target, query_string=params, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_list(self):
path = self.queue_path + '/messages'
self._post_messages(path, repeat=10)
query_string = 'limit=3&echo=true'
body = self.simulate_get(path,
query_string=query_string,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(self.srmock.headers_dict['Content-Location'],
path + '?' + query_string)
cnt = 0
while self.srmock.status == falcon.HTTP_200:
contents = jsonutils.loads(body[0])
[target, params] = contents['links'][0]['href'].split('?')
for msg in contents['messages']:
self.simulate_get(msg['href'], headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
body = self.simulate_get(target,
query_string=params,
headers=self.headers)
cnt += 1
self.assertEqual(cnt, 4)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Stats
body = self.simulate_get(self.queue_path + '/stats',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
message_stats = jsonutils.loads(body[0])['messages']
self.assertEqual(self.srmock.headers_dict['Content-Location'],
self.queue_path + '/stats')
# NOTE(kgriffs): The other parts of the stats are tested
# in tests.storage.base and so are not repeated here.
expected_pattern = self.queue_path + '/messages/[^/]+$'
for message_stat_name in ('oldest', 'newest'):
self.assertThat(message_stats[message_stat_name]['href'],
matchers.MatchesRegex(expected_pattern))
# NOTE(kgriffs): Try to get messages for a missing queue
self.simulate_get(self.url_prefix + '/queues/nonexistent/messages',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_list_with_bad_marker(self):
path = self.queue_path + '/messages'
self._post_messages(path, repeat=5)
query_string = 'limit=3&echo=true&marker=sfhlsfdjh2048'
self.simulate_get(path,
query_string=query_string,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_no_uuid(self):
headers = {
'Client-ID': "textid",
'X-Project-ID': '7e7e7e'
}
path = self.queue_path + '/messages'
self.simulate_post(path, body='[{"body": 0, "ttl": 100}]',
headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_get(path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
# NOTE(cpp-cabrera): regression test against bug #1210633
def test_when_claim_deleted_then_messages_unclaimed(self):
path = self.queue_path
self._post_messages(path + '/messages', repeat=5)
# post claim
self.simulate_post(path + '/claims',
body='{"ttl": 100, "grace": 100}',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
location = self.srmock.headers_dict['location']
# release claim
self.simulate_delete(location, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# get unclaimed messages
self.simulate_get(path + '/messages',
query_string='echo=true',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
# NOTE(cpp-cabrera): regression test against bug #1203842
def test_get_nonexistent_message_404s(self):
path = self.url_prefix + '/queues/notthere/messages/a'
self.simulate_get(path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_get_multiple_invalid_messages_204s(self):
path = self.url_prefix + '/queues/notthere/messages'
self.simulate_get(path, query_string='ids=a,b,c',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_delete_multiple_invalid_messages_204s(self):
path = self.url_prefix + '/queues/notthere/messages'
self.simulate_delete(path, query_string='ids=a,b,c',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_delete_message_with_invalid_claim_doesnt_delete_message(self):
path = self.queue_path
resp = self._post_messages(path + '/messages', 1)
location = jsonutils.loads(resp[0])['resources'][0]
self.simulate_delete(location, query_string='claim_id=invalid',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
self.simulate_get(location, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
def test_no_duplicated_messages_path_in_href(self):
"""Fixes bug 1240897
"""
path = self.queue_path + '/messages'
self._post_messages(path, repeat=1)
msg_id = self._get_msg_id(self.srmock.headers_dict)
query_string = 'ids=%s' % msg_id
body = self.simulate_get(path,
query_string=query_string,
headers=self.headers)
messages = jsonutils.loads(body[0])
self.assertNotIn(self.queue_path + '/messages/messages',
messages[0]['href'])
def _post_messages(self, target, repeat=1):
doc = jsonutils.dumps([{'body': 239, 'ttl': 300}] * repeat)
return self.simulate_post(target, body=doc,
headers=self.headers)
def _get_msg_id(self, headers):
return self._get_msg_ids(headers)[0]
def _get_msg_ids(self, headers):
return headers['location'].rsplit('=', 1)[-1].split(',')
class TestMessagesSqlalchemy(MessagesBaseTest):
config_file = 'wsgi_sqlalchemy.conf'
class TestMessagesMongoDB(MessagesBaseTest):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestMessagesMongoDB, self).setUp()
def tearDown(self):
super(TestMessagesMongoDB, self).tearDown()
class TestMessagesMongoDBSharded(MessagesBaseTest):
config_file = 'wsgi_mongodb_sharded.conf'
@testing.requires_mongodb
def setUp(self):
super(TestMessagesMongoDBSharded, self).setUp()
def tearDown(self):
super(TestMessagesMongoDBSharded, self).tearDown()
# TODO(cpp-cabrera): remove this skipTest once sharded queue
# listing is implemented
def test_list(self):
self.skipTest("Need to implement sharded queue listing.")
class TestMessagesFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
project_id = 'xyz'
path = self.url_prefix + '/queues/fizbit/messages'
doc = '[{"body": 239, "ttl": 100}]'
headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': project_id
}
self.simulate_post(path,
body=doc,
headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_get(path,
headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_get(path + '/nonexistent', headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_delete(path + '/nada', headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)

View File

@ -0,0 +1,440 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 uuid
import ddt
import falcon
import six
from marconi.openstack.common import jsonutils
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@ddt.ddt
class QueueLifecycleBaseTest(base.V1_1Base):
config_file = None
def setUp(self):
super(QueueLifecycleBaseTest, self).setUp()
self.queue_path = self.url_prefix + '/queues'
self.gumshoe_queue_path = self.queue_path + '/gumshoe'
self.fizbat_queue_path = self.queue_path + '/fizbat'
self.fizbat_queue_path_metadata = self.fizbat_queue_path + '/metadata'
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': '3387309841abc_'
}
def test_empty_project_id(self):
headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': ''
}
self.simulate_get(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_put(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_head(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_delete(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data('480924', 'foo')
def test_basics_thoroughly(self, project_id):
headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': project_id
}
gumshoe_queue_path_metadata = self.gumshoe_queue_path + '/metadata'
gumshoe_queue_path_stats = self.gumshoe_queue_path + '/stats'
# Stats not found - queue not created yet
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Metadata not found - queue not created yet
self.simulate_get(gumshoe_queue_path_metadata, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Create
self.simulate_put(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
location = self.srmock.headers_dict['Location']
self.assertEqual(location, self.gumshoe_queue_path)
# Ensure queue existence
self.simulate_head(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Add metadata
doc = '{"messages": {"ttl": 600}}'
self.simulate_put(gumshoe_queue_path_metadata,
headers=headers, body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Fetch metadata
result = self.simulate_get(gumshoe_queue_path_metadata,
headers=headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(result_doc, jsonutils.loads(doc))
# Stats empty queue
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
# Delete
self.simulate_delete(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Get non-existent queue
self.simulate_get(self.gumshoe_queue_path, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Get non-existent stats
self.simulate_get(gumshoe_queue_path_stats, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
# Get non-existent metadata
self.simulate_get(gumshoe_queue_path_metadata, headers=headers)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_name_restrictions(self):
self.simulate_put(self.queue_path + '/Nice-Boat_2',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.simulate_put(self.queue_path + '/Nice-Bo@t',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_put(self.queue_path + '/_' + 'niceboat' * 8,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_project_id_restriction(self):
muvluv_queue_path = self.queue_path + '/Muv-Luv'
self.simulate_put(muvluv_queue_path,
headers={'Client-ID': str(uuid.uuid4()),
'X-Project-ID': 'JAM Project' * 24})
self.assertEqual(self.srmock.status, falcon.HTTP_400)
# no charset restrictions
self.simulate_put(muvluv_queue_path,
headers={'Client-ID': str(uuid.uuid4()),
'X-Project-ID': 'JAM Project'})
self.assertEqual(self.srmock.status, falcon.HTTP_201)
def test_non_ascii_name(self):
test_params = ((u'/queues/non-ascii-n\u0153me', 'utf-8'),
(u'/queues/non-ascii-n\xc4me', 'iso8859-1'))
for uri, enc in test_params:
uri = self.url_prefix + uri
if six.PY2:
uri = uri.encode(enc)
self.simulate_put(uri, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_get(uri, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_delete(uri, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_no_metadata(self):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.simulate_put(self.fizbat_queue_path_metadata,
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_put(self.fizbat_queue_path_metadata, body='',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data('{', '[]', '.', ' ', '')
def test_bad_metadata(self, document):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
self.simulate_put(self.fizbat_queue_path_metadata,
headers=self.headers,
body=document)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_too_much_metadata(self):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
doc = '{{"messages": {{"ttl": 600}}, "padding": "{pad}"}}'
max_size = self.transport_cfg.max_queue_metadata
padding_len = max_size - (len(doc) - 10) + 1
doc = doc.format(pad='x' * padding_len)
self.simulate_put(self.fizbat_queue_path_metadata,
headers=self.headers,
body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_way_too_much_metadata(self):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
doc = '{{"messages": {{"ttl": 600}}, "padding": "{pad}"}}'
max_size = self.transport_cfg.max_queue_metadata
padding_len = max_size * 100
doc = doc.format(pad='x' * padding_len)
self.simulate_put(self.fizbat_queue_path_metadata,
headers=self.headers, body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_custom_metadata(self):
self.simulate_put(self.fizbat_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
# Set
doc = '{{"messages": {{"ttl": 600}}, "padding": "{pad}"}}'
max_size = self.transport_cfg.max_queue_metadata
padding_len = max_size - (len(doc) - 2)
doc = doc.format(pad='x' * padding_len)
self.simulate_put(self.fizbat_queue_path_metadata,
headers=self.headers,
body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Get
result = self.simulate_get(self.fizbat_queue_path_metadata,
headers=self.headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(result_doc, jsonutils.loads(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_200)
def test_update_metadata(self):
xyz_queue_path = self.url_prefix + '/queues/xyz'
xyz_queue_path_metadata = xyz_queue_path + '/metadata'
# Create
self.simulate_put(xyz_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_201)
# Set meta
doc1 = '{"messages": {"ttl": 600}}'
self.simulate_put(xyz_queue_path_metadata,
headers=self.headers,
body=doc1)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Update
doc2 = '{"messages": {"ttl": 100}}'
self.simulate_put(xyz_queue_path_metadata,
headers=self.headers,
body=doc2)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Get
result = self.simulate_get(xyz_queue_path_metadata,
headers=self.headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(result_doc, jsonutils.loads(doc2))
self.assertEqual(self.srmock.headers_dict['Content-Location'],
xyz_queue_path_metadata)
def test_list(self):
arbitrary_number = 644079696574693
project_id = str(arbitrary_number)
header = {
'X-Project-ID': project_id
}
# NOTE(kgriffs): It's important that this one sort after the one
# above. This is in order to prove that bug/1236605 is fixed, and
# stays fixed!
alt_project_id = str(arbitrary_number + 1)
# List empty
self.simulate_get(self.queue_path, headers=header)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# Payload exceeded
self.simulate_get(self.queue_path, headers=header,
query_string='limit=21')
self.assertEqual(self.srmock.status, falcon.HTTP_400)
# Create some
def create_queue(name, project_id, body):
altheader = {}
if project_id is not None:
altheader['X-Project-ID'] = project_id
uri = self.queue_path + '/' + name
self.simulate_put(uri, headers=altheader)
self.simulate_put(uri + '/metadata', headers=altheader, body=body)
create_queue('g1', None, '{"answer": 42}')
create_queue('g2', None, '{"answer": 42}')
create_queue('q1', project_id, '{"node": 31}')
create_queue('q2', project_id, '{"node": 32}')
create_queue('q3', project_id, '{"node": 33}')
create_queue('q3', alt_project_id, '{"alt": 1}')
# List (global queues)
result = self.simulate_get(self.queue_path,
query_string='limit=2&detailed=true')
result_doc = jsonutils.loads(result[0])
queues = result_doc['queues']
self.assertEqual(len(queues), 2)
for queue in queues:
self.assertEqual(queue['metadata'], {'answer': 42})
# List (limit)
result = self.simulate_get(self.queue_path, headers=header,
query_string='limit=2')
result_doc = jsonutils.loads(result[0])
self.assertEqual(len(result_doc['queues']), 2)
# List (no metadata, get all)
result = self.simulate_get(self.queue_path,
headers=header, query_string='limit=5')
result_doc = jsonutils.loads(result[0])
[target, params] = result_doc['links'][0]['href'].split('?')
self.assertEqual(self.srmock.status, falcon.HTTP_200)
self.assertEqual(self.srmock.headers_dict['Content-Location'],
self.queue_path + '?limit=5')
# Ensure we didn't pick up the queue from the alt project.
queues = result_doc['queues']
self.assertEqual(len(queues), 3)
for queue in queues:
self.simulate_get(queue['href'] + '/metadata', headers=header)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
altheader = header.copy()
altheader['X-Project-ID'] = 'imnothere'
self.simulate_get(queue['href'] + '/metadata', headers=altheader)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
self.assertNotIn('metadata', queue)
# List with metadata
result = self.simulate_get(self.queue_path, headers=header,
query_string='detailed=true')
self.assertEqual(self.srmock.status, falcon.HTTP_200)
result_doc = jsonutils.loads(result[0])
[target, params] = result_doc['links'][0]['href'].split('?')
queue = result_doc['queues'][0]
result = self.simulate_get(queue['href'] + '/metadata', headers=header)
result_doc = jsonutils.loads(result[0])
self.assertEqual(result_doc, queue['metadata'])
self.assertEqual(result_doc, {'node': 31})
# List tail
self.simulate_get(target, headers=header, query_string=params)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
# List manually-constructed tail
self.simulate_get(target, headers=header, query_string='marker=zzz')
self.assertEqual(self.srmock.status, falcon.HTTP_204)
class TestQueueLifecycleMongoDB(QueueLifecycleBaseTest):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestQueueLifecycleMongoDB, self).setUp()
def tearDown(self):
storage = self.boot.storage._storage
connection = storage.connection
connection.drop_database(storage.queues_database)
for db in storage.message_databases:
connection.drop_database(db)
super(TestQueueLifecycleMongoDB, self).tearDown()
class TestQueueLifecycleSqlalchemy(QueueLifecycleBaseTest):
config_file = 'wsgi_sqlalchemy.conf'
class TestQueueLifecycleFaultyDriver(base.V1_1BaseFaulty):
config_file = 'wsgi_faulty.conf'
def test_simple(self):
self.headers = {
'Client-ID': str(uuid.uuid4()),
'X-Project-ID': '338730984abc_1'
}
gumshoe_queue_path = self.url_prefix + '/queues/gumshoe'
doc = '{"messages": {"ttl": 600}}'
self.simulate_put(gumshoe_queue_path,
headers=self.headers,
body=doc)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
location = ('Location', gumshoe_queue_path)
self.assertNotIn(location, self.srmock.headers)
result = self.simulate_get(gumshoe_queue_path + '/metadata',
headers=self.headers)
result_doc = jsonutils.loads(result[0])
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.assertNotEqual(result_doc, jsonutils.loads(doc))
self.simulate_get(gumshoe_queue_path + '/stats',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_get(self.url_prefix + '/queues',
headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)
self.simulate_delete(gumshoe_queue_path, headers=self.headers)
self.assertEqual(self.srmock.status, falcon.HTTP_503)

View File

@ -0,0 +1,308 @@
# Copyright (c) 2013 Rackspace, Inc.
#
# 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 contextlib
import uuid
import ddt
import falcon
from marconi.openstack.common import jsonutils
from marconi import tests as testing
from marconi.tests.queues.transport.wsgi import base
@contextlib.contextmanager
def shard(test, name, weight, uri, options={}):
"""A context manager for constructing a shard for use in testing.
Deletes the shard after exiting the context.
:param test: Must expose simulate_* methods
:param name: Name for this shard
:type name: six.text_type
:type weight: int
:type uri: six.text_type
:type options: dict
:returns: (name, weight, uri, options)
:rtype: see above
"""
doc = {'weight': weight, 'uri': uri, 'options': options}
path = test.url_prefix + '/shards/' + name
test.simulate_put(path, body=jsonutils.dumps(doc))
try:
yield name, weight, uri, options
finally:
test.simulate_delete(path)
@contextlib.contextmanager
def shards(test, count, uri):
"""A context manager for constructing shards for use in testing.
Deletes the shards after exiting the context.
:param test: Must expose simulate_* methods
:param count: Number of shards to create
:type count: int
:returns: (paths, weights, uris, options)
:rtype: ([six.text_type], [int], [six.text_type], [dict])
"""
base = test.url_prefix + '/shards/'
args = [(base + str(i), i,
{str(i): i})
for i in range(count)]
for path, weight, option in args:
doc = {'weight': weight, 'uri': uri, 'options': option}
test.simulate_put(path, body=jsonutils.dumps(doc))
try:
yield args
finally:
for path, _, _ in args:
test.simulate_delete(path)
@ddt.ddt
class ShardsBaseTest(base.V1_1Base):
def setUp(self):
super(ShardsBaseTest, self).setUp()
self.doc = {'weight': 100, 'uri': 'sqlite://:memory:'}
self.shard = self.url_prefix + '/shards/' + str(uuid.uuid1())
self.simulate_put(self.shard, body=jsonutils.dumps(self.doc))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
def tearDown(self):
super(ShardsBaseTest, self).tearDown()
self.simulate_delete(self.shard)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def test_put_shard_works(self):
name = str(uuid.uuid1())
weight, uri = self.doc['weight'], self.doc['uri']
with shard(self, name, weight, uri):
self.assertEqual(self.srmock.status, falcon.HTTP_201)
def test_put_raises_if_missing_fields(self):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
self.simulate_put(path, body=jsonutils.dumps({'weight': 100}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
self.simulate_put(path,
body=jsonutils.dumps(
{'uri': 'sqlite://:memory:'}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 2**32+1, 'big')
def test_put_raises_if_invalid_weight(self, weight):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
doc = {'weight': weight, 'uri': 'a'}
self.simulate_put(path,
body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 2**32+1, [], 'localhost:27017')
def test_put_raises_if_invalid_uri(self, uri):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
self.simulate_put(path,
body=jsonutils.dumps({'weight': 1, 'uri': uri}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 'wee', [])
def test_put_raises_if_invalid_options(self, options):
path = self.url_prefix + '/shards/' + str(uuid.uuid1())
doc = {'weight': 1, 'uri': 'a', 'options': options}
self.simulate_put(path, body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_put_existing_overwrites(self):
# NOTE(cabrera): setUp creates default shard
expect = self.doc
self.simulate_put(self.shard,
body=jsonutils.dumps(expect))
self.assertEqual(self.srmock.status, falcon.HTTP_201)
result = self.simulate_get(self.shard)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
doc = jsonutils.loads(result[0])
self.assertEqual(doc['weight'], expect['weight'])
self.assertEqual(doc['uri'], expect['uri'])
def test_delete_works(self):
self.simulate_delete(self.shard)
self.assertEqual(self.srmock.status, falcon.HTTP_204)
self.simulate_get(self.shard)
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_get_nonexisting_raises_404(self):
self.simulate_get(self.url_prefix + '/shards/nonexisting')
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def _shard_expect(self, shard, xhref, xweight, xuri):
self.assertIn('href', shard)
self.assertEqual(shard['href'], xhref)
self.assertIn('weight', shard)
self.assertEqual(shard['weight'], xweight)
self.assertIn('uri', shard)
self.assertEqual(shard['uri'], xuri)
def test_get_works(self):
result = self.simulate_get(self.shard)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
shard = jsonutils.loads(result[0])
self._shard_expect(shard, self.shard, self.doc['weight'],
self.doc['uri'])
def test_detailed_get_works(self):
result = self.simulate_get(self.shard,
query_string='?detailed=True')
self.assertEqual(self.srmock.status, falcon.HTTP_200)
shard = jsonutils.loads(result[0])
self._shard_expect(shard, self.shard, self.doc['weight'],
self.doc['uri'])
self.assertIn('options', shard)
self.assertEqual(shard['options'], {})
def test_patch_raises_if_missing_fields(self):
self.simulate_patch(self.shard,
body=jsonutils.dumps({'location': 1}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def _patch_test(self, doc):
self.simulate_patch(self.shard,
body=jsonutils.dumps(doc))
self.assertEqual(self.srmock.status, falcon.HTTP_200)
result = self.simulate_get(self.shard,
query_string='?detailed=True')
self.assertEqual(self.srmock.status, falcon.HTTP_200)
shard = jsonutils.loads(result[0])
self._shard_expect(shard, self.shard, doc['weight'],
doc['uri'])
self.assertEqual(shard['options'], doc['options'])
def test_patch_works(self):
doc = {'weight': 101, 'uri': 'sqlite://:memory:', 'options': {'a': 1}}
self._patch_test(doc)
def test_patch_works_with_extra_fields(self):
doc = {'weight': 101, 'uri': 'sqlite://:memory:', 'options': {'a': 1},
'location': 100, 'partition': 'taco'}
self._patch_test(doc)
@ddt.data(-1, 2**32+1, 'big')
def test_patch_raises_400_on_invalid_weight(self, weight):
self.simulate_patch(self.shard,
body=jsonutils.dumps({'weight': weight}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 2**32+1, [], 'localhost:27017')
def test_patch_raises_400_on_invalid_uri(self, uri):
self.simulate_patch(self.shard,
body=jsonutils.dumps({'uri': uri}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
@ddt.data(-1, 'wee', [])
def test_patch_raises_400_on_invalid_options(self, options):
self.simulate_patch(self.shard,
body=jsonutils.dumps({'options': options}))
self.assertEqual(self.srmock.status, falcon.HTTP_400)
def test_patch_raises_404_if_shard_not_found(self):
self.simulate_patch(self.url_prefix + '/shards/notexists',
body=jsonutils.dumps({'weight': 1}))
self.assertEqual(self.srmock.status, falcon.HTTP_404)
def test_empty_listing_returns_204(self):
self.simulate_delete(self.shard)
self.simulate_get(self.url_prefix + '/shards')
self.assertEqual(self.srmock.status, falcon.HTTP_204)
def _listing_test(self, count=10, limit=10,
marker=None, detailed=False):
# NOTE(cpp-cabrera): delete initial shard - it will interfere
# with listing tests
self.simulate_delete(self.shard)
query = '?limit={0}&detailed={1}'.format(limit, detailed)
if marker:
query += '&marker={2}'.format(marker)
with shards(self, count, self.doc['uri']) as expected:
result = self.simulate_get(self.url_prefix + '/shards',
query_string=query)
self.assertEqual(self.srmock.status, falcon.HTTP_200)
results = jsonutils.loads(result[0])
self.assertIsInstance(results, dict)
self.assertIn('shards', results)
shard_list = results['shards']
self.assertEqual(len(shard_list), min(limit, count))
for s in shard_list:
# NOTE(flwang): It can't assumed that both sqlalchemy and
# mongodb can return query result with the same order. Just
# like the order they're inserted. Actually, sqlalchemy can't
# guarantee that. So we're leveraging the relationship between
# shard weight and the index of shards fixture to get the
# right shard to verify.
expect = expected[s['weight']]
path, weight = expect[:2]
self._shard_expect(s, path, weight, self.doc['uri'])
if detailed:
self.assertIn('options', s)
self.assertEqual(s['options'], expect[-1])
else:
self.assertNotIn('options', s)
def test_listing_works(self):
self._listing_test()
def test_detailed_listing_works(self):
self._listing_test(detailed=True)
@ddt.data(1, 5, 10, 15)
def test_listing_works_with_limit(self, limit):
self._listing_test(count=15, limit=limit)
def test_listing_marker_is_respected(self):
self.simulate_delete(self.shard)
with shards(self, 10, self.doc['uri']) as expected:
result = self.simulate_get(self.url_prefix + '/shards',
query_string='?marker=3')
self.assertEqual(self.srmock.status, falcon.HTTP_200)
shard_list = jsonutils.loads(result[0])['shards']
self.assertEqual(len(shard_list), 6)
path, weight = expected[4][:2]
self._shard_expect(shard_list[0], path, weight, self.doc['uri'])
class TestShardsMongoDB(ShardsBaseTest):
config_file = 'wsgi_mongodb.conf'
@testing.requires_mongodb
def setUp(self):
super(TestShardsMongoDB, self).setUp()
class TestShardsSqlalchemy(ShardsBaseTest):
config_file = 'wsgi_sqlalchemy.conf'
def setUp(self):
super(TestShardsSqlalchemy, self).setUp()

View File

@ -14,7 +14,8 @@
import falcon
from marconi.tests.queues.transport import wsgi
from marconi.tests.queues.transport.wsgi import base
from marconi.tests.queues.transport.wsgi import v1
#----------------------------------------------------------------------------
@ -24,67 +25,67 @@ from marconi.tests.queues.transport import wsgi
URL_PREFIX = '/v1'
class TestAuth(wsgi.TestAuth):
class TestAuth(v1.TestAuth):
url_prefix = URL_PREFIX
class TestClaimsFaultyDriver(wsgi.TestClaimsFaultyDriver):
class TestClaimsFaultyDriver(v1.TestClaimsFaultyDriver):
url_prefix = URL_PREFIX
class TestClaimsMongoDB(wsgi.TestClaimsMongoDB):
class TestClaimsMongoDB(v1.TestClaimsMongoDB):
url_prefix = URL_PREFIX
class TestClaimsSqlalchemy(wsgi.TestClaimsSqlalchemy):
class TestClaimsSqlalchemy(v1.TestClaimsSqlalchemy):
url_prefix = URL_PREFIX
class TestDefaultLimits(wsgi.TestDefaultLimits):
class TestDefaultLimits(v1.TestDefaultLimits):
url_prefix = URL_PREFIX
class TestHomeDocument(wsgi.TestHomeDocument):
class TestHomeDocument(v1.TestHomeDocument):
url_prefix = URL_PREFIX
class TestMediaType(wsgi.TestMediaType):
class TestMediaType(v1.TestMediaType):
url_prefix = URL_PREFIX
class TestMessagesFaultyDriver(wsgi.TestMessagesFaultyDriver):
class TestMessagesFaultyDriver(v1.TestMessagesFaultyDriver):
url_prefix = URL_PREFIX
class TestMessagesMongoDB(wsgi.TestMessagesMongoDB):
class TestMessagesMongoDB(v1.TestMessagesMongoDB):
url_prefix = URL_PREFIX
class TestMessagesMongoDBSharded(wsgi.TestMessagesMongoDBSharded):
class TestMessagesMongoDBSharded(v1.TestMessagesMongoDBSharded):
url_prefix = URL_PREFIX
class TestMessagesSqlalchemy(wsgi.TestMessagesSqlalchemy):
class TestMessagesSqlalchemy(v1.TestMessagesSqlalchemy):
url_prefix = URL_PREFIX
class TestQueueFaultyDriver(wsgi.TestQueueFaultyDriver):
class TestQueueFaultyDriver(v1.TestQueueFaultyDriver):
url_prefix = URL_PREFIX
class TestQueueLifecycleMongoDB(wsgi.TestQueueLifecycleMongoDB):
class TestQueueLifecycleMongoDB(v1.TestQueueLifecycleMongoDB):
url_prefix = URL_PREFIX
class TestQueueLifecycleSqlalchemy(wsgi.TestQueueLifecycleSqlalchemy):
class TestQueueLifecycleSqlalchemy(v1.TestQueueLifecycleSqlalchemy):
url_prefix = URL_PREFIX
class TestShardsMongoDB(wsgi.TestShardsMongoDB):
class TestShardsMongoDB(v1.TestShardsMongoDB):
url_prefix = URL_PREFIX
class TestShardsSqlalchemy(wsgi.TestShardsSqlalchemy):
class TestShardsSqlalchemy(v1.TestShardsSqlalchemy):
url_prefix = URL_PREFIX
@ -92,7 +93,7 @@ class TestShardsSqlalchemy(wsgi.TestShardsSqlalchemy):
# v1.0 only
#----------------------------------------------------------------------------
class TestHealth(wsgi.TestBase):
class TestHealth(base.V1Base):
config_file = 'wsgi_sqlalchemy.conf'

View File

@ -14,8 +14,8 @@
import falcon
from marconi.tests.queues.transport import wsgi
from marconi.tests.queues.transport.wsgi import base
from marconi.tests.queues.transport.wsgi import v1_1
#----------------------------------------------------------------------------
# Identical or just minor variations across versions
@ -24,69 +24,69 @@ from marconi.tests.queues.transport import wsgi
URL_PREFIX = '/v1.1'
class TestAuth(wsgi.TestAuth):
class TestAuth(v1_1.TestAuth):
url_prefix = URL_PREFIX
class TestClaimsFaultyDriver(wsgi.TestClaimsFaultyDriver):
class TestClaimsFaultyDriver(v1_1.TestClaimsFaultyDriver):
url_prefix = URL_PREFIX
class TestClaimsMongoDB(wsgi.TestClaimsMongoDB):
class TestClaimsMongoDB(v1_1.TestClaimsMongoDB):
url_prefix = URL_PREFIX
class TestClaimsSqlalchemy(wsgi.TestClaimsSqlalchemy):
class TestClaimsSqlalchemy(v1_1.TestClaimsSqlalchemy):
url_prefix = URL_PREFIX
class TestDefaultLimits(wsgi.TestDefaultLimits):
class TestDefaultLimits(v1_1.TestDefaultLimits):
url_prefix = URL_PREFIX
class TestHomeDocument(wsgi.TestHomeDocument):
class TestHomeDocument(v1_1.TestHomeDocument):
url_prefix = URL_PREFIX
class TestMediaType(wsgi.TestMediaType):
class TestMediaType(v1_1.TestMediaType):
url_prefix = URL_PREFIX
class TestMessagesFaultyDriver(wsgi.TestMessagesFaultyDriver):
class TestMessagesFaultyDriver(v1_1.TestMessagesFaultyDriver):
url_prefix = URL_PREFIX
class TestMessagesMongoDB(wsgi.TestMessagesMongoDB):
class TestMessagesMongoDB(v1_1.TestMessagesMongoDB):
url_prefix = URL_PREFIX
class TestMessagesMongoDBSharded(wsgi.TestMessagesMongoDBSharded):
class TestMessagesMongoDBSharded(v1_1.TestMessagesMongoDBSharded):
url_prefix = URL_PREFIX
class TestMessagesSqlalchemy(wsgi.TestMessagesSqlalchemy):
class TestMessagesSqlalchemy(v1_1.TestMessagesSqlalchemy):
url_prefix = URL_PREFIX
class TestQueueFaultyDriver(wsgi.TestQueueFaultyDriver):
class TestQueueFaultyDriver(v1_1.TestQueueFaultyDriver):
url_prefix = URL_PREFIX
# TODO(kgriffs): Having to list a separate test for each backend is
# sort of a pain; is there a better way?
class TestQueueLifecycleMongoDB(wsgi.TestQueueLifecycleMongoDB):
class TestQueueLifecycleMongoDB(v1_1.TestQueueLifecycleMongoDB):
url_prefix = URL_PREFIX
class TestQueueLifecycleSqlalchemy(wsgi.TestQueueLifecycleSqlalchemy):
class TestQueueLifecycleSqlalchemy(v1_1.TestQueueLifecycleSqlalchemy):
url_prefix = URL_PREFIX
class TestShardsMongoDB(wsgi.TestShardsMongoDB):
class TestShardsMongoDB(v1_1.TestShardsMongoDB):
url_prefix = URL_PREFIX
class TestShardsSqlalchemy(wsgi.TestShardsSqlalchemy):
class TestShardsSqlalchemy(v1_1.TestShardsSqlalchemy):
url_prefix = URL_PREFIX
@ -94,7 +94,7 @@ class TestShardsSqlalchemy(wsgi.TestShardsSqlalchemy):
# v1.1 only
#----------------------------------------------------------------------------
class TestPing(wsgi.TestBase):
class TestPing(base.V1_1Base):
config_file = 'wsgi_sqlalchemy.conf'
@ -114,7 +114,7 @@ class TestPing(wsgi.TestBase):
self.assertEqual(response, [])
class TestHealth(wsgi.TestBase):
class TestHealth(base.V1_1Base):
config_file = 'wsgi_sqlalchemy.conf'