diff --git a/marconi/tests/queues/transport/wsgi/__init__.py b/marconi/tests/queues/transport/wsgi/__init__.py index c5852ff1d..635f1526c 100644 --- a/marconi/tests/queues/transport/wsgi/__init__.py +++ b/marconi/tests/queues/transport/wsgi/__init__.py @@ -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 diff --git a/marconi/tests/queues/transport/wsgi/base.py b/marconi/tests/queues/transport/wsgi/base.py index 7f6bb0f2b..0645ba3c0 100644 --- a/marconi/tests/queues/transport/wsgi/base.py +++ b/marconi/tests/queues/transport/wsgi/base.py @@ -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 diff --git a/marconi/tests/queues/transport/wsgi/v1/__init__.py b/marconi/tests/queues/transport/wsgi/v1/__init__.py new file mode 100644 index 000000000..e8ccd107c --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1/__init__.py @@ -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 diff --git a/marconi/tests/queues/transport/wsgi/test_auth.py b/marconi/tests/queues/transport/wsgi/v1/test_auth.py similarity index 93% rename from marconi/tests/queues/transport/wsgi/test_auth.py rename to marconi/tests/queues/transport/wsgi/v1/test_auth.py index 9faa3c966..6b695611c 100644 --- a/marconi/tests/queues/transport/wsgi/test_auth.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_auth.py @@ -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' diff --git a/marconi/tests/queues/transport/wsgi/test_claims.py b/marconi/tests/queues/transport/wsgi/v1/test_claims.py similarity index 94% rename from marconi/tests/queues/transport/wsgi/test_claims.py rename to marconi/tests/queues/transport/wsgi/v1/test_claims.py index 7ae6f682f..1ffa45dce 100644 --- a/marconi/tests/queues/transport/wsgi/test_claims.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_claims.py @@ -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' diff --git a/marconi/tests/queues/transport/wsgi/test_default_limits.py b/marconi/tests/queues/transport/wsgi/v1/test_default_limits.py similarity index 95% rename from marconi/tests/queues/transport/wsgi/test_default_limits.py rename to marconi/tests/queues/transport/wsgi/v1/test_default_limits.py index f035c3d93..21040fbff 100644 --- a/marconi/tests/queues/transport/wsgi/test_default_limits.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_default_limits.py @@ -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())}) diff --git a/marconi/tests/queues/transport/wsgi/test_home.py b/marconi/tests/queues/transport/wsgi/v1/test_home.py similarity index 95% rename from marconi/tests/queues/transport/wsgi/test_home.py rename to marconi/tests/queues/transport/wsgi/v1/test_home.py index c817d7d03..7dbcb695f 100644 --- a/marconi/tests/queues/transport/wsgi/test_home.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_home.py @@ -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' diff --git a/marconi/tests/queues/transport/wsgi/test_media_type.py b/marconi/tests/queues/transport/wsgi/v1/test_media_type.py similarity index 95% rename from marconi/tests/queues/transport/wsgi/test_media_type.py rename to marconi/tests/queues/transport/wsgi/v1/test_media_type.py index 5837d81cf..7ed834d1f 100644 --- a/marconi/tests/queues/transport/wsgi/test_media_type.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_media_type.py @@ -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' diff --git a/marconi/tests/queues/transport/wsgi/test_messages.py b/marconi/tests/queues/transport/wsgi/v1/test_messages.py similarity index 97% rename from marconi/tests/queues/transport/wsgi/test_messages.py rename to marconi/tests/queues/transport/wsgi/v1/test_messages.py index adbbcfbc2..b1c28fa87 100644 --- a/marconi/tests/queues/transport/wsgi/test_messages.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_messages.py @@ -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' diff --git a/marconi/tests/queues/transport/wsgi/test_queue_lifecycle.py b/marconi/tests/queues/transport/wsgi/v1/test_queue_lifecycle.py similarity index 98% rename from marconi/tests/queues/transport/wsgi/test_queue_lifecycle.py rename to marconi/tests/queues/transport/wsgi/v1/test_queue_lifecycle.py index 12c47307f..b9eab80a6 100644 --- a/marconi/tests/queues/transport/wsgi/test_queue_lifecycle.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_queue_lifecycle.py @@ -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' diff --git a/marconi/tests/queues/transport/wsgi/test_shards.py b/marconi/tests/queues/transport/wsgi/v1/test_shards.py similarity index 90% rename from marconi/tests/queues/transport/wsgi/test_shards.py rename to marconi/tests/queues/transport/wsgi/v1/test_shards.py index 561e6f8ed..3aeb559dd 100644 --- a/marconi/tests/queues/transport/wsgi/test_shards.py +++ b/marconi/tests/queues/transport/wsgi/v1/test_shards.py @@ -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): diff --git a/marconi/tests/queues/transport/wsgi/v1_1/__init__.py b/marconi/tests/queues/transport/wsgi/v1_1/__init__.py new file mode 100644 index 000000000..31b611291 --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/__init__.py @@ -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 diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_auth.py b/marconi/tests/queues/transport/wsgi/v1_1/test_auth.py new file mode 100644 index 000000000..9dd773337 --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_auth.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_claims.py b/marconi/tests/queues/transport/wsgi/v1_1/test_claims.py new file mode 100644 index 000000000..992486dcc --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_claims.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_default_limits.py b/marconi/tests/queues/transport/wsgi/v1_1/test_default_limits.py new file mode 100644 index 000000000..a5fac98dd --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_default_limits.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_home.py b/marconi/tests/queues/transport/wsgi/v1_1/test_home.py new file mode 100644 index 000000000..dcd55f428 --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_home.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_media_type.py b/marconi/tests/queues/transport/wsgi/v1_1/test_media_type.py new file mode 100644 index 000000000..d1c2775fd --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_media_type.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_messages.py b/marconi/tests/queues/transport/wsgi/v1_1/test_messages.py new file mode 100644 index 000000000..810243702 --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_messages.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_queue_lifecycle.py b/marconi/tests/queues/transport/wsgi/v1_1/test_queue_lifecycle.py new file mode 100644 index 000000000..ef58b6ed2 --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_queue_lifecycle.py @@ -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) diff --git a/marconi/tests/queues/transport/wsgi/v1_1/test_shards.py b/marconi/tests/queues/transport/wsgi/v1_1/test_shards.py new file mode 100644 index 000000000..27e170723 --- /dev/null +++ b/marconi/tests/queues/transport/wsgi/v1_1/test_shards.py @@ -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() diff --git a/tests/unit/queues/transport/wsgi/test_v1_0.py b/tests/unit/queues/transport/wsgi/test_v1_0.py index ff9dd1cfb..26fca7fc8 100644 --- a/tests/unit/queues/transport/wsgi/test_v1_0.py +++ b/tests/unit/queues/transport/wsgi/test_v1_0.py @@ -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' diff --git a/tests/unit/queues/transport/wsgi/test_v1_1.py b/tests/unit/queues/transport/wsgi/test_v1_1.py index df8656687..5cb94c358 100644 --- a/tests/unit/queues/transport/wsgi/test_v1_1.py +++ b/tests/unit/queues/transport/wsgi/test_v1_1.py @@ -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'