V1.1 Functional Tests

Added tests for new V1.1 functions like pop and shard
Updated existing tests for V1.1 return calls
Added new json schemas
Edited functional test base to support multiple schemas
Change-Id: I35dfb92af22540609ecb50c1eb5c8fa65e6cadc8
Partially-Implements blueprint: api-v1.1-functional-tests
This commit is contained in:
abettadapur 2014-06-10 11:40:28 -04:00
parent 22584f484c
commit c1715c6fb1
15 changed files with 1898 additions and 50 deletions

View File

View File

@ -0,0 +1,169 @@
# 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.
from marconi.common import api
class RequestSchema(api.Api):
schema = {
'queue_list': {
'ref': 'queues',
'method': 'GET',
'properties': {
'marker': {'type': 'string'},
'limit': {'type': 'integer'},
'detailed': {'type': 'boolean'}
}
},
'queue_create': {
'ref': 'queues/{queue_name}',
'method': 'PUT',
'required': ['queue_name'],
'properties': {
'queue_name': {'type': 'string'}
},
},
'queue_delete': {
'ref': 'queues/{queue_name}',
'method': 'DELETE',
'required': ['queue_name'],
'properties': {
'queue_name': {'type': 'string'}
}
},
'queue_set_metadata': {
'ref': 'queues/{queue_name}/metadata',
'method': 'PUT',
'required': ['queue_name'],
'properties': {
# NOTE(flaper87): Metadata is part
# of the request content. No need to
# add it here.
'queue_name': {'type': 'string'}
}
},
'queue_get_metadata': {
'ref': 'queues/{queue_name}/metadata',
'method': 'GET',
'required': ['queue_name'],
'properties': {
'queue_name': {'type': 'string'}
}
},
'queue_get_stats': {
'ref': 'queues/{queue_name}/stats',
'method': 'GET',
'required': ['queue_name'],
'admin': True,
'properties': {
'queue_name': {'type': 'string'},
}
},
'message_list': {
'ref': 'queues/{queue_name}/messages',
'method': 'GET',
'required': ['queue_name'],
'properties': {
'queue_name': {'type': 'string'},
'marker': {'type': 'string'},
'limit': {'type': 'integer'},
'echo': {'type': 'boolean'},
'include_claimed': {'type': 'boolean'}
}
},
'message_post': {
'ref': 'queues/{queue_name}/messages',
'method': 'POST',
'required': ['queue_name'],
'properties': {
'queue_name': {'type': 'string'}
}
},
'message_delete': {
'ref': 'queues/{queue_name}/messages/{message_id}',
'method': 'DELETE',
'required': ['queue_name', 'message_id'],
'properties': {
'queue_name': {'type': 'string'},
'message_id': {'type': 'string'},
'claim_id': {'type': 'string'}
}
},
'message_delete_many': {
'ref': 'queues/{queue_name}/messages',
'method': 'DELETE',
'required': ['queue_name', 'ids'],
'properties': {
'queue_name': {'type': 'string'},
'ids': {'type': 'array'},
'pop': {'type': 'integer'}
}
},
'claim_create': {
'ref': 'queues/{queue_name}/claims',
'method': 'POST',
'required': ['queue_name'],
'properties': {
'queue_name': {'type': 'string'},
'limit': {'type': 'integer'}
}
},
'claim_get': {
'ref': 'queues/{queue_name}/claims/{claim_id}',
'method': 'GET',
'required': ['queue_name', 'claim_id'],
'properties': {
'queue_name': {'type': 'string'},
'claim_id': {'type': 'string'}
}
},
'claim_update': {
'ref': 'queues/{queue_name}/claims/{claim_id}',
'method': 'PATCH',
'required': ['queue_name', 'claim_id'],
'properties': {
'queue_name': {'type': 'string'},
'claim_id': {'type': 'string'}
}
},
'claim_delete': {
'ref': 'queues/{queue_name}/claims/{claim_id}',
'method': 'DELETE',
'required': ['queue_name', 'claim_id'],
'properties': {
'queue_name': {'type': 'string'},
'claim_id': {'type': 'string'}
}
},
'check_node_health': {
'ref': '/v1/health',
'method': 'GET',
},
}

View File

@ -0,0 +1,307 @@
# 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.
from marconi.common import api
class ResponseSchema(api.Api):
"""Define validation schema for json response."""
def __init__(self, limits):
self.limits = limits
age = {
"type": "number",
"minimum": 0
}
message = {
"type": "object",
"properties": {
"href": {
"type": "string",
"pattern": "^(/v1\.1/queues/[a-zA-Z0-9_-]"
"{1,64}/messages/[a-zA-Z0-9_-]+)$"
},
"age": age,
"ttl": {
"type": "number",
"minimum": 1,
"maximum": self.limits.max_message_ttl
},
"body": {
"type": "object"
}
},
"required": ["href", "ttl", "age", "body"],
"additionalProperties": False,
}
claim_href = {
"type": "string",
"pattern": "^(/v1\.1/queues/[a-zA-Z0-9_-]{1,64}"
"/messages/[a-zA-Z0-9_-]+)"
"\?claim_id=[a-zA-Z0-9_-]+$"
}
self.schema = {
'message_get_many': {
"type": "array",
"items": message,
"minItems": 1,
"maxItems": self.limits.max_messages_per_page
},
'queue_list': {
'type': 'object',
'properties': {
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'rel': {
'type': 'string',
'enum': ['next'],
},
'href': {
'type': 'string',
"pattern": "^/v1\.1/queues\?",
}
},
'required': ['rel', 'href'],
'additionalProperties': False,
},
'minItems': 1,
'maxItems': 1,
},
'queues': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string',
'pattern': '^[a-zA-Z0-9_-]{1,64}$'
},
'href': {
'type': 'string',
'pattern': '^/v1\.1/queues/'
'[a-zA-Z0-9_-]{1,64}$',
},
'metadata': {
'type': 'object',
}
},
'required': ['name', 'href'],
'additionalProperties': False,
},
'minItems': 1,
'maxItems': self.limits.max_queues_per_page,
}
},
'required': ['links', 'queues'],
'additionalProperties': False,
},
'queue_stats': {
'type': 'object',
'properties': {
'messages': {
'type': 'object',
'properties': {
'free': {
'type': 'number',
'minimum': 0
},
'claimed': {
'type': 'number',
'minimum': 0
},
'total': {
'type': 'number',
'minimum': 0
},
'oldest': {
'type': 'object'
},
'newest': {
'type': 'object'
}
},
'required': ['free', 'claimed', 'total'],
'additionalProperties': False
}
},
'required': ['messages'],
'additionalProperties': False
},
'shard-list': {
'type': 'object',
'properties': {
'shards': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'href': {
'type': 'string',
'pattern': '^/v1\.1/'
'shards/[a-zA-Z0-9_-]{1,64}$'
},
'weight': {
'type': 'number',
'minimum': -1
},
'uri': {
'type': 'string'
},
'options': {
'type': 'object',
'additionalProperties': True
}
},
'required': ['href', 'weight', 'uri'],
'additionalProperties': False,
},
}
},
'required': ['shards'],
'additionalProperties': False
},
'message_list': {
'type': 'object',
'properties': {
'links': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'rel': {
'type': 'string'
},
'href': {
'type': 'string',
'pattern': '^/v1\.1/queues/[a-zA-Z0-9_-]+'
'/messages\?(.)*$'
}
},
'required': ['rel', 'href'],
'additionalProperties': False
}
},
'messages': {
"type": "array",
"items": message,
"minItems": 0,
"maxItems": self.limits.max_messages_per_claim
}
}
},
'shard-detail': {
'type': 'object',
'properties': {
'uri': {
'type': 'string'
},
'weight': {
'type': 'number',
'minimum': -1
},
'href': {
'type': 'string',
'pattern': '^/v1\.1/shards/'
'[a-zA-Z0-9_\-]+$'
},
'options': {
'type': 'object',
'additionalProperties': True
}
},
'required': ['uri', 'weight', 'href'],
'additionalProperties': False
},
'claim_create': {
"type": "array",
"items": {
"type": "object",
"properties": {
"href": claim_href,
"ttl": {
"type": "number",
"minimum": 1,
"maximum": self.limits.max_message_ttl
},
"age": age,
"body": {
"type": "object"
}
},
"required": ["href", "ttl", "age", "body"],
"additionalProperties": False,
},
"minItems": 1,
"maxItems": self.limits.max_messages_per_page
},
'claim_get': {
'type': 'object',
'properties': {
'age': age,
'ttl': {
'type': 'number',
'minimum': 0,
'maximum': self.limits.max_claim_ttl
},
'href': {
'type': 'string',
'pattern': '^/v1\.1/queues/[a-zA-Z0-9_-]+'
'/claims/[a-zA-Z0-9_-]+$'
},
'messages': {
"type": "array",
"items": {
"type": "object",
"properties": {
"href": claim_href,
"ttl": {
"type": "number",
"minimum": 1,
"maximum": self.limits.max_message_ttl
},
"age": age,
"body": {
"type": "object"
}
},
"required": ["href", "ttl", "age", "body"],
"additionalProperties": False,
},
"minItems": 1,
"maxItems": self.limits.max_messages_per_page
}
},
'required': ['age', 'ttl', 'messages', 'href'],
'additionalProperties': False
}
}

View File

@ -22,7 +22,8 @@ import jsonschema
import six import six
from marconi.openstack.common import timeutils from marconi.openstack.common import timeutils
from marconi.queues.api.v1 import response from marconi.queues.api.v1 import response as response_v1
from marconi.queues.api.v1_1 import response as response_v1_1
from marconi.queues import bootstrap from marconi.queues import bootstrap
# TODO(flaper87): This is necessary to register, # TODO(flaper87): This is necessary to register,
# wsgi configs and won't be permanent. It'll be # wsgi configs and won't be permanent. It'll be
@ -49,7 +50,6 @@ class FunctionalTestBase(testing.TestBase):
def setUp(self): def setUp(self):
super(FunctionalTestBase, self).setUp() super(FunctionalTestBase, self).setUp()
# NOTE(flaper87): Config can't be a class # NOTE(flaper87): Config can't be a class
# attribute because it may be necessary to # attribute because it may be necessary to
# modify it at runtime which will affect # modify it at runtime which will affect
@ -63,8 +63,6 @@ class FunctionalTestBase(testing.TestBase):
validator = validation.Validator(self.mconf) validator = validation.Validator(self.mconf)
self.limits = validator._limits_conf self.limits = validator._limits_conf
self.response = response.ResponseSchema(self.limits)
if _TEST_INTEGRATION: if _TEST_INTEGRATION:
# TODO(kgriffs): This code should be replaced to use # TODO(kgriffs): This code should be replaced to use
# an external wsgi server instance. # an external wsgi server instance.
@ -118,20 +116,6 @@ class FunctionalTestBase(testing.TestBase):
', actual count = {1}'.format(expectedCount, actualCount)) ', actual count = {1}'.format(expectedCount, actualCount))
self.assertTrue(actualCount <= expectedCount, msg) self.assertTrue(actualCount <= expectedCount, msg)
def assertSchema(self, response, expectedSchemaName):
"""Compares the json response with the expected schema
:param response: response json returned by the API.
:type response: dict
:param expectedSchema: expected schema definition for response.
:type expectedSchema: string
"""
try:
expectedSchema = self.response.get_schema(expectedSchemaName)
jsonschema.validate(response, expectedSchema)
except jsonschema.ValidationError as message:
assert False, message
def assertQueueStats(self, result_json, claimed): def assertQueueStats(self, result_json, claimed):
"""Checks the Queue Stats results """Checks the Queue Stats results
@ -154,6 +138,20 @@ class FunctionalTestBase(testing.TestBase):
newest_message = result_json['messages']['newest'] newest_message = result_json['messages']['newest']
self.verify_message_stats(newest_message) self.verify_message_stats(newest_message)
def assertSchema(self, response, expectedSchemaName):
"""Compares the json response with the expected schema
:param response: response json returned by the API.
:type response: dict
:param expectedSchema: expected schema definition for response.
:type expectedSchema: string
"""
try:
expectedSchema = self.response.get_schema(expectedSchemaName)
jsonschema.validate(response, expectedSchema)
except jsonschema.ValidationError as message:
assert False, message
def verify_message_stats(self, message): def verify_message_stats(self, message):
"""Verifies the oldest & newest message stats """Verifies the oldest & newest message stats
@ -276,3 +274,15 @@ class MarconiAdminServer(Server):
server = bootstrap.Bootstrap(conf) server = bootstrap.Bootstrap(conf)
conf.admin_mode = False conf.admin_mode = False
return server.run return server.run
class V1FunctionalTestBase(FunctionalTestBase):
def setUp(self):
super(V1FunctionalTestBase, self).setUp()
self.response = response_v1.ResponseSchema(self.limits)
class V1_1FunctionalTestBase(FunctionalTestBase):
def setUp(self):
super(V1_1FunctionalTestBase, self).setUp()
self.response = response_v1_1.ResponseSchema(self.limits)

View File

@ -33,7 +33,6 @@ _AUTH_OPTIONS = (
_MARCONI_OPTIONS = ( _MARCONI_OPTIONS = (
cfg.BoolOpt("run_server", default=True), cfg.BoolOpt("run_server", default=True),
cfg.StrOpt("url", default="http://127.0.0.1:8888"), cfg.StrOpt("url", default="http://127.0.0.1:8888"),
cfg.StrOpt("version", default="v1"),
cfg.StrOpt("config", default="functional-marconi.conf"), cfg.StrOpt("config", default="functional-marconi.conf"),
) )

View File

@ -99,3 +99,16 @@ def create_message_body(**kwargs):
""" """
message_count = kwargs['messagecount'] message_count = kwargs['messagecount']
return [single_message_body(**kwargs) for i in range(message_count)] return [single_message_body(**kwargs) for i in range(message_count)]
def create_shard_body(**kwargs):
shard_body = {
'weight': kwargs['weight'],
'uri': kwargs['uri'],
'options': {
'max_retry_sleep': 1,
'partitions': 8
}
}
return shard_body

View File

@ -10,7 +10,6 @@
[marconi] [marconi]
# run_server = True # run_server = True
# url = http://0.0.0.0:8888 # url = http://0.0.0.0:8888
# version = v1
# config = functional-marconi.conf # config = functional-marconi.conf
[headers] [headers]

View File

@ -22,7 +22,7 @@ from marconi.tests.functional import helpers
@ddt.ddt @ddt.ddt
class TestClaims(base.FunctionalTestBase): class TestClaims(base.V1FunctionalTestBase):
"""Tests for Claims.""" """Tests for Claims."""
server_class = base.MarconiServer server_class = base.MarconiServer
@ -33,7 +33,7 @@ class TestClaims(base.FunctionalTestBase):
self.queue = uuid.uuid1() self.queue = uuid.uuid1()
self.queue_url = ("{url}/{version}/queues/{queue}".format( self.queue_url = ("{url}/{version}/queues/{queue}".format(
url=self.cfg.marconi.url, url=self.cfg.marconi.url,
version=self.cfg.marconi.version, version="v1",
queue=self.queue)) queue=self.queue))
self.client.put(self.queue_url) self.client.put(self.queue_url)
@ -52,8 +52,7 @@ class TestClaims(base.FunctionalTestBase):
@ddt.data({}, dict(limit=2)) @ddt.data({}, dict(limit=2))
def test_claim_messages(self, params): def test_claim_messages(self, params):
"""Claim messages.""" """Claim messages."""
message_count = params.get('limit', message_count = params.get('limit', self.limits.max_messages_per_claim)
self.limits.max_messages_per_claim)
doc = {"ttl": 300, "grace": 100} doc = {"ttl": 300, "grace": 100}
@ -65,7 +64,6 @@ class TestClaims(base.FunctionalTestBase):
response_headers = set(result.headers.keys()) response_headers = set(result.headers.keys())
self.assertIsSubset(self.headers_response_with_body, response_headers) self.assertIsSubset(self.headers_response_with_body, response_headers)
self.assertSchema(result.json(), 'claim_create') self.assertSchema(result.json(), 'claim_create')
test_claim_messages.tags = ['smoke', 'positive'] test_claim_messages.tags = ['smoke', 'positive']

View File

@ -24,7 +24,7 @@ from marconi.tests.functional import helpers
@ddt.ddt @ddt.ddt
class TestMessages(base.FunctionalTestBase): class TestMessages(base.V1FunctionalTestBase):
"""Tests for Messages.""" """Tests for Messages."""
@ -36,7 +36,7 @@ class TestMessages(base.FunctionalTestBase):
self.queue = uuid.uuid1() self.queue = uuid.uuid1()
self.queue_url = ("{url}/{version}/queues/{queue}".format( self.queue_url = ("{url}/{version}/queues/{queue}".format(
url=self.cfg.marconi.url, url=self.cfg.marconi.url,
version=self.cfg.marconi.version, version="v1",
queue=self.queue)) queue=self.queue))
self.client.put(self.queue_url) self.client.put(self.queue_url)
@ -139,7 +139,7 @@ class TestMessages(base.FunctionalTestBase):
test_message_bulk_insert.tags = ['smoke', 'positive'] test_message_bulk_insert.tags = ['smoke', 'positive']
@ddt.data({'limit': 10}, {'limit': 5}) @ddt.data({}, {'limit': 5})
def test_get_message(self, params): def test_get_message(self, params):
"""Get Messages.""" """Get Messages."""
@ -164,6 +164,7 @@ class TestMessages(base.FunctionalTestBase):
if result.status_code == 200: if result.status_code == 200:
actual_msg_count = len(result.json()['messages']) actual_msg_count = len(result.json()['messages'])
self.assertMessageCount(actual_msg_count, expected_msg_count) self.assertMessageCount(actual_msg_count, expected_msg_count)
self.assertSchema(result.json(), 'message_list') self.assertSchema(result.json(), 'message_list')
href = result.json()['links'][0]['href'] href = result.json()['links'][0]['href']
@ -248,6 +249,7 @@ class TestMessages(base.FunctionalTestBase):
url += ',nonexisting' url += ',nonexisting'
result = self.client.get(url) result = self.client.get(url)
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), "message_get_many") self.assertSchema(result.json(), "message_get_many")
test_message_partial_get.tags = ['negative'] test_message_partial_get.tags = ['negative']

View File

@ -68,7 +68,7 @@ def annotated(test_name, test_input):
@ddt.ddt @ddt.ddt
class TestInsertQueue(base.FunctionalTestBase): class TestInsertQueue(base.V1FunctionalTestBase):
"""Tests for Insert queue.""" """Tests for Insert queue."""
@ -77,7 +77,7 @@ class TestInsertQueue(base.FunctionalTestBase):
def setUp(self): def setUp(self):
super(TestInsertQueue, self).setUp() super(TestInsertQueue, self).setUp()
self.base_url = '{0}/{1}'.format(self.cfg.marconi.url, self.base_url = '{0}/{1}'.format(self.cfg.marconi.url,
self.cfg.marconi.version) "v1")
self.header = helpers.create_marconi_headers(self.cfg) self.header = helpers.create_marconi_headers(self.cfg)
self.headers_response_empty = set(['location']) self.headers_response_empty = set(['location'])
@ -189,7 +189,7 @@ class TestInsertQueue(base.FunctionalTestBase):
@ddt.ddt @ddt.ddt
class TestQueueMetaData(base.FunctionalTestBase): class TestQueueMetaData(base.V1FunctionalTestBase):
"""Tests for queue metadata.""" """Tests for queue metadata."""
@ -199,7 +199,7 @@ class TestQueueMetaData(base.FunctionalTestBase):
super(TestQueueMetaData, self).setUp() super(TestQueueMetaData, self).setUp()
self.base_url = '{0}/{1}'.format(self.cfg.marconi.url, self.base_url = '{0}/{1}'.format(self.cfg.marconi.url,
self.cfg.marconi.version) "v1")
self.queue_url = self.base_url + '/queues/{0}'.format(uuid.uuid1()) self.queue_url = self.base_url + '/queues/{0}'.format(uuid.uuid1())
self.client.put(self.queue_url) self.client.put(self.queue_url)
@ -254,7 +254,7 @@ class TestQueueMetaData(base.FunctionalTestBase):
@ddt.ddt @ddt.ddt
class TestQueueMisc(base.FunctionalTestBase): class TestQueueMisc(base.V1FunctionalTestBase):
server_class = base.MarconiServer server_class = base.MarconiServer
@ -264,19 +264,16 @@ class TestQueueMisc(base.FunctionalTestBase):
self.base_url = self.cfg.marconi.url self.base_url = self.cfg.marconi.url
self.client.set_base_url(self.base_url) self.client.set_base_url(self.base_url)
template = self.base_url + '/{0}/queues/{1}' self.queue_url = (self.base_url + '/{0}/queues/{1}'
.format("v1", uuid.uuid1()))
self.queue_url = template.format(self.cfg.marconi.version,
uuid.uuid1())
def test_list_queues(self): def test_list_queues(self):
"""List Queues.""" """List Queues."""
self.client.put(self.queue_url) self.client.put(self.queue_url)
self.addCleanup(self.client.delete, self.queue_url) self.addCleanup(self.client.delete, self.queue_url)
result = self.client.get('/{0}/queues'
url = '/{0}/queues'.format(self.cfg.marconi.version) .format('v1'))
result = self.client.get(url)
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'queue_list') self.assertSchema(result.json(), 'queue_list')
@ -291,7 +288,7 @@ class TestQueueMisc(base.FunctionalTestBase):
params = {'detailed': True} params = {'detailed': True}
result = self.client.get('/{0}/queues' result = self.client.get('/{0}/queues'
.format(self.cfg.marconi.version), .format("v1"),
params=params) params=params)
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'queue_list') self.assertSchema(result.json(), 'queue_list')
@ -307,7 +304,7 @@ class TestQueueMisc(base.FunctionalTestBase):
params = {'limit': limit} params = {'limit': limit}
result = self.client.get('/{0}/queues' result = self.client.get('/{0}/queues'
.format(self.cfg.marconi.version), .format("v1"),
params=params) params=params)
self.assertEqual(result.status_code, 400) self.assertEqual(result.status_code, 400)
@ -317,7 +314,7 @@ class TestQueueMisc(base.FunctionalTestBase):
"""Test health endpoint.""" """Test health endpoint."""
result = self.client.get('/{0}/health' result = self.client.get('/{0}/health'
.format(self.cfg.marconi.version)) .format("v1"))
self.assertEqual(result.status_code, 204) self.assertEqual(result.status_code, 204)
test_check_health.tags = ['positive'] test_check_health.tags = ['positive']
@ -337,7 +334,7 @@ class TestQueueMisc(base.FunctionalTestBase):
def test_check_queue_exists_negative(self): def test_check_queue_exists_negative(self):
"""Checks non-existing queue.""" """Checks non-existing queue."""
path = '/{0}/queues/nonexistingqueue'.format(self.cfg.marconi.version) path = '/{0}/queues/nonexistingqueue'.format("v1")
result = self.client.get(path) result = self.client.get(path)
self.assertEqual(result.status_code, 404) self.assertEqual(result.status_code, 404)
@ -349,7 +346,7 @@ class TestQueueMisc(base.FunctionalTestBase):
def test_get_queue_malformed_marker(self): def test_get_queue_malformed_marker(self):
"""List queues with invalid marker.""" """List queues with invalid marker."""
path = '/{0}/queues?marker=zzz'.format(self.cfg.marconi.version) path = '/{0}/queues?marker=zzz'.format("v1")
result = self.client.get(path) result = self.client.get(path)
self.assertEqual(result.status_code, 204) self.assertEqual(result.status_code, 204)
@ -371,7 +368,6 @@ class TestQueueMisc(base.FunctionalTestBase):
expected_response = {'messages': expected_response = {'messages':
{'claimed': 0, 'total': 0, 'free': 0}} {'claimed': 0, 'total': 0, 'free': 0}}
self.assertEqual(result.json(), expected_response) self.assertEqual(result.json(), expected_response)
self.assertSchema(result.json(), 'queue_stats')
test_get_stats_empty_queue.tags = ['positive'] test_get_stats_empty_queue.tags = ['positive']
@ -402,7 +398,6 @@ class TestQueueMisc(base.FunctionalTestBase):
self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200)
self.assertQueueStats(result.json(), claimed) self.assertQueueStats(result.json(), claimed)
self.assertSchema(result.json(), 'queue_stats')
test_get_queue_stats_claimed.tags = ['positive'] test_get_queue_stats_claimed.tags = ['positive']
@ -410,7 +405,7 @@ class TestQueueMisc(base.FunctionalTestBase):
super(TestQueueMisc, self).tearDown() super(TestQueueMisc, self).tearDown()
class TestQueueNonExisting(base.FunctionalTestBase): class TestQueueNonExisting(base.V1FunctionalTestBase):
"""Test Actions on non existing queue.""" """Test Actions on non existing queue."""
@ -418,10 +413,10 @@ class TestQueueNonExisting(base.FunctionalTestBase):
def setUp(self): def setUp(self):
super(TestQueueNonExisting, self).setUp() super(TestQueueNonExisting, self).setUp()
self.base_url = '{0}/{1}'.format(self.cfg.marconi.url, self.base_url = '{0}/{1}'.format(self.cfg.marconi.url, "v1")
self.cfg.marconi.version)
self.queue_url = (self.base_url + self.queue_url = (self.base_url +
'/queues/0a5b1b85-4263-11e3-b034-28cfe91478b9') '/queues/0a5b1b85-4263-11e3-b034-28cfe91478b9')
self.client.set_base_url(self.queue_url) self.client.set_base_url(self.queue_url)
self.header = helpers.create_marconi_headers(self.cfg) self.header = helpers.create_marconi_headers(self.cfg)

View File

View File

@ -0,0 +1,254 @@
# 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.
import uuid
import ddt
from marconi.tests.functional import base
from marconi.tests.functional import helpers
@ddt.ddt
class TestClaims(base.V1_1FunctionalTestBase):
"""Tests for Claims."""
server_class = base.MarconiServer
def setUp(self):
super(TestClaims, self).setUp()
self.headers = helpers.create_marconi_headers(self.cfg)
self.client.headers = self.headers
self.queue = uuid.uuid1()
self.queue_url = ("{url}/{version}/queues/{queue}".format(
url=self.cfg.marconi.url,
version="v1.1",
queue=self.queue))
self.client.put(self.queue_url)
self.claim_url = self.queue_url + '/claims'
self.client.set_base_url(self.claim_url)
# Post Messages
url = self.queue_url + '/messages'
doc = helpers.create_message_body(
messagecount=self.limits.max_messages_per_page)
for i in range(10):
self.client.post(url, data=doc)
@ddt.data({}, {'limit': 2})
def test_claim_messages(self, params):
"""Claim messages."""
message_count = params.get('limit', self.limits.max_messages_per_claim)
doc = {"ttl": 300, "grace": 100}
result = self.client.post(params=params, data=doc)
self.assertEqual(result.status_code, 201)
actual_message_count = len(result.json())
self.assertMessageCount(actual_message_count, message_count)
response_headers = set(result.headers.keys())
self.assertIsSubset(self.headers_response_with_body, response_headers)
test_claim_messages.tags = ['smoke', 'positive']
def test_query_claim(self):
"""Query Claim."""
params = {'limit': 1}
doc = {"ttl": 300, "grace": 100}
result = self.client.post(params=params, data=doc)
location = result.headers['Location']
url = self.cfg.marconi.url + location
result = self.client.get(url)
self.assertEqual(result.status_code, 200)
test_query_claim.tags = ['smoke', 'positive']
def test_claim_more_than_allowed(self):
"""Claim more than max allowed per request.
Marconi allows a maximum of 20 messages per claim by default.
"""
params = {"limit": self.limits.max_messages_per_claim + 1}
doc = {"ttl": 300, "grace": 100}
result = self.client.post(params=params, data=doc)
self.assertEqual(result.status_code, 400)
test_claim_more_than_allowed.tags = ['negative']
def test_claim_patch(self):
"""Update Claim."""
# Test Setup - Post Claim
doc = {"ttl": 300, "grace": 400}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Patch Claim
claim_location = result.headers['Location']
url = self.cfg.marconi.url + claim_location
doc_updated = {"ttl": 300}
result = self.client.patch(url, data=doc_updated)
self.assertEqual(result.status_code, 204)
# verify that the claim TTL is updated
result = self.client.get(url)
new_ttl = result.json()['ttl']
self.assertEqual(new_ttl, 300)
test_claim_patch.tags = ['smoke', 'positive']
def test_delete_claimed_message(self):
"""Delete message belonging to a Claim."""
# Test Setup - Post claim
doc = {"ttl": 60, "grace": 60}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Delete Claimed Messages
for rst in result.json():
href = rst['href']
url = self.cfg.marconi.url + href
result = self.client.delete(url)
self.assertEqual(result.status_code, 204)
test_delete_claimed_message.tags = ['smoke', 'positive']
def test_claim_release(self):
"""Release Claim."""
doc = {"ttl": 300, "grace": 100}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Extract claim location and construct the claim URL.
location = result.headers['Location']
url = self.cfg.marconi.url + location
# Release Claim.
result = self.client.delete(url)
self.assertEqual(result.status_code, 204)
test_claim_release.tags = ['smoke', 'positive']
@ddt.data(10000000000000000000, -100, 1, 59, 43201, -10000000000000000000)
def test_claim_invalid_ttl(self, ttl):
"""Post Claim with invalid TTL.
The request JSON body will have a TTL value
outside the allowed range.Allowed ttl values is
60 <= ttl <= 43200.
"""
doc = {"ttl": ttl, "grace": 100}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 400)
test_claim_invalid_ttl.tags = ['negative']
@ddt.data(10000000000000000000, -100, 1, 59, 43201, -10000000000000000000)
def test_claim_invalid_grace(self, grace):
"""Post Claim with invalid grace.
The request JSON body will have a grace value
outside the allowed range.Allowed grace values is
60 <= grace <= 43200.
"""
doc = {"ttl": 100, "grace": grace}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 400)
test_claim_invalid_grace.tags = ['negative']
@ddt.data(0, -100, 30, 10000000000000000000)
def test_claim_invalid_limit(self, grace):
"""Post Claim with invalid limit.
The request url will have a limit outside the allowed range.
Allowed limit values are 0 < limit <= 20(default max).
"""
doc = {"ttl": 100, "grace": grace}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 400)
test_claim_invalid_limit.tags = ['negative']
@ddt.data(10000000000000000000, -100, 1, 59, 43201, -10000000000000000000)
def test_patch_claim_invalid_ttl(self, ttl):
"""Patch Claim with invalid TTL.
The request JSON body will have a TTL value
outside the allowed range.Allowed ttl values is
60 <= ttl <= 43200.
"""
doc = {"ttl": 100, "grace": 100}
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Extract claim location and construct the claim URL.
location = result.headers['Location']
url = self.cfg.marconi.url + location
# Patch Claim.
doc = {"ttl": ttl}
result = self.client.patch(url, data=doc)
self.assertEqual(result.status_code, 400)
test_patch_claim_invalid_ttl.tags = ['negative']
def test_query_non_existing_claim(self):
"""Query Non Existing Claim."""
path = '/non-existing-claim'
result = self.client.get(path)
self.assertEqual(result.status_code, 404)
test_query_non_existing_claim.tags = ['negative']
def test_patch_non_existing_claim(self):
"""Patch Non Existing Claim."""
path = '/non-existing-claim'
doc = {"ttl": 400}
result = self.client.patch(path, data=doc)
self.assertEqual(result.status_code, 404)
test_patch_non_existing_claim.tags = ['negative']
def test_delete_non_existing_claim(self):
"""Patch Non Existing Claim."""
path = '/non-existing-claim'
result = self.client.delete(path)
self.assertEqual(result.status_code, 204)
test_delete_non_existing_claim.tags = ['negative']
def tearDown(self):
"""Delete Queue after Claim Test."""
super(TestClaims, self).tearDown()
self.client.delete(self.queue_url)

View File

@ -0,0 +1,455 @@
# 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 __future__ import division
import uuid
import ddt
from marconi.tests.functional import base
from marconi.tests.functional import helpers
@ddt.ddt
class TestMessages(base.V1_1FunctionalTestBase):
"""Message Tests Specific to V1.1."""
server_class = base.MarconiServer
def setUp(self):
super(TestMessages, self).setUp()
self.queue = uuid.uuid1() # Generate a random queue ID
self.queue_url = ("{url}/{version}/queues/{queue}".format(
url=self.cfg.marconi.url,
version="v1.1",
queue=self.queue))
self.headers = helpers.create_marconi_headers(self.cfg)
self.client.headers = self.headers
self.client.put(self.queue_url) # Create the queue
self.message_url = self.queue_url+'/messages'
self.client.set_base_url(self.message_url)
def tearDown(self):
self.client.delete(self.queue_url) # Remove the queue
super(TestMessages, self).tearDown()
# TODO(abettadapur) Not sure if return format is right
def test_message_single_pop(self):
"""Pop a message."""
# Setup
self.skipTest("Not supported")
doc = helpers.create_message_body(messagecount=1)
result = self.client.post(data=doc)
href = result.json()['links'][0]['href']
index = href.find('/')
id = href[index+1:]
self.assertEqual(result.status_code, 200)
# Pop a message, compare the HREFs. They should be the same
url = self.message_url+'?pop=1'
result = self.client.delete(url)
claimid = result.json()['messages'][0]['id']
self.assertEqual(claimid, id)
# Make sure there are no messages left in the queue
result = self.client.get(self.message_url)
self.assertEqual(result.status_code, 204)
# TODO(abettadapur) Not sure if return format is right
def test_message_bulk_pop(self):
"""Pop multiple messages."""
# Setup
self.skipTest("Not supported")
doc = helpers.create_message_body(messagecount=10)
result = self.client.post(data=doc)
links = result.json()["links"]
# Gather inserted ids
ids = []
for item in links:
href = item['href']
index = href.find('/')
ids.append(href[index:])
# Pop the 10 messages
url = self.message_url+'?pop=10'
result = self.client.delete(url)
self.assertEqual(result.status_code, 200)
claims = result.json()
# compare the HREFS
match = True
for i in range(0, 10):
if ids[i] != claims['messages'][i]['id']:
match = False
break
self.assertEqual(match, True)
# Make sure there are no messages left in the queue
result = self.client.get(self.message_url)
self.assertEqual(result.status_code, 204)
def test_message_pop_too_high(self):
self.skipTest("Not supported")
url = self.message_url+'?pop=21'
result = self.client.delete(url)
self.assertEqual(result.status_code, 400)
def _post_large_bulk_insert(self, offset):
"""Insert just under than max allowed messages."""
doc = '[{{"body": "{0}", "ttl": 300}}, {{"body": "{1}", "ttl": 120}}]'
overhead = len(doc.format('', ''))
half_size = (self.limits.max_message_size - overhead) // 2
doc = doc.format(helpers.generate_random_string(half_size),
helpers.generate_random_string(half_size + offset))
return self.client.post(data=doc)
def test_message_single_insert(self):
"""Insert Single Message into the Queue.
This test also verifies that claimed messages are
retuned (or not) depending on the include_claimed flag.
"""
doc = helpers.create_message_body(messagecount=1)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
response_headers = set(result.headers.keys())
self.assertIsSubset(self.headers_response_with_body, response_headers)
# GET on posted message
href = result.json()['resources'][0]
url = self.cfg.marconi.url + href
result = self.client.get(url)
self.assertEqual(result.status_code, 200)
# Compare message metadata
result_body = result.json()['body']
posted_metadata = doc[0]['body']
self.assertEqual(result_body, posted_metadata)
# Post a claim & verify the include_claimed flag.
url = self.queue_url + '/claims'
doc = {"ttl": 300, "grace": 100}
result = self.client.post(url, data=doc)
self.assertEqual(result.status_code, 201)
params = {'include_claimed': True,
'echo': True}
result = self.client.get(params=params)
self.assertEqual(result.status_code, 200)
response_message_body = result.json()["messages"][0]["body"]
self.assertEqual(response_message_body, posted_metadata)
# By default, include_claimed = false
result = self.client.get(self.message_url)
self.assertEqual(result.status_code, 200)
test_message_single_insert.tags = ['smoke', 'positive']
def test_message_bulk_insert(self):
"""Bulk Insert Messages into the Queue."""
message_count = self.limits.max_messages_per_page
doc = helpers.create_message_body(messagecount=message_count)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# GET on posted messages
location = result.headers['location']
url = self.cfg.marconi.url + location
result = self.client.get(url)
self.assertEqual(result.status_code, 200)
self.skipTest('Bug #1273335 - Get set of messages returns wrong hrefs '
'(happens randomly)')
# Verify that the response json schema matches the expected schema
self.assertSchema(result.json(), 'message_get_many')
# Compare message metadata
result_body = [result.json()[i]['body']
for i in range(len(result.json()))]
result_body.sort()
posted_metadata = [doc[i]['body']
for i in range(message_count)]
posted_metadata.sort()
self.assertEqual(result_body, posted_metadata)
test_message_bulk_insert.tags = ['smoke', 'positive']
@ddt.data({}, {'limit': 5})
def test_get_message(self, params):
"""Get Messages."""
# Note(abettadapur): This will now return 200s and [].
# Needs to be addressed when feature patch goes in
self.skipTest("Not supported")
expected_msg_count = params.get('limit',
self.limits.max_messages_per_page)
# Test Setup
doc = helpers.create_message_body(
messagecount=self.limits.max_messages_per_page)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
url = ''
params['echo'] = True
# Follow the hrefs & perform GET, till the end of messages i.e. http
# 204
while result.status_code in [201, 200]:
result = self.client.get(url, params=params)
self.assertIn(result.status_code, [200, 204])
if result.status_code == 200:
actual_msg_count = len(result.json()['messages'])
self.assertMessageCount(actual_msg_count, expected_msg_count)
href = result.json()['links'][0]['href']
url = self.cfg.marconi.url + href
self.assertEqual(result.status_code, 204)
test_get_message.tags = ['smoke', 'positive']
def test_message_delete(self):
"""Delete Message."""
# Test Setup
doc = helpers.create_message_body(messagecount=1)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Delete posted message
href = result.json()['resources'][0]
url = self.cfg.marconi.url + href
result = self.client.delete(url)
self.assertEqual(result.status_code, 204)
result = self.client.get(url)
self.assertEqual(result.status_code, 404)
test_message_delete.tags = ['smoke', 'positive']
def test_message_bulk_delete(self):
"""Bulk Delete Messages."""
doc = helpers.create_message_body(messagecount=10)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Delete posted messages
location = result.headers['Location']
url = self.cfg.marconi.url + location
result = self.client.delete(url)
self.assertEqual(result.status_code, 204)
result = self.client.get(url)
self.assertEqual(result.status_code, 404)
test_message_bulk_delete.tags = ['smoke', 'positive']
def test_message_delete_nonexisting(self):
"""Delete non-existing Messages."""
result = self.client.delete('/non-existing')
self.assertEqual(result.status_code, 204)
test_message_delete_nonexisting.tags = ['negative']
def test_message_partial_delete(self):
"""Delete Messages will be partially successful."""
doc = helpers.create_message_body(messagecount=3)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Delete posted message
location = result.headers['Location']
url = self.cfg.marconi.url + location
url += ',nonexisting'
result = self.client.delete(url)
self.assertEqual(result.status_code, 204)
test_message_partial_delete.tags = ['negative']
def test_message_partial_get(self):
"""Get Messages will be partially successful."""
doc = helpers.create_message_body(messagecount=3)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 201)
# Get posted message and a nonexisting message
location = result.headers['Location']
url = self.cfg.marconi.url + location
url += ',nonexisting'
result = self.client.get(url)
self.assertEqual(result.status_code, 200)
test_message_partial_get.tags = ['negative']
@ddt.data(-10, -1, 0)
def test_message_bulk_insert_large_bodies(self, offset):
"""Insert just under than max allowed messages."""
result = self._post_large_bulk_insert(offset)
self.assertEqual(result.status_code, 201)
test_message_bulk_insert_large_bodies.tags = ['positive']
@ddt.data(1, 10)
def test_message_bulk_insert_large_bodies(self, offset):
"""Insert just under than max allowed messages."""
result = self._post_large_bulk_insert(offset)
self.assertEqual(result.status_code, 400)
test_message_bulk_insert_large_bodies.tags = ['negative']
def test_message_bulk_insert_oversized(self):
"""Insert more than max allowed size."""
doc = '[{{"body": "{0}", "ttl": 300}}, {{"body": "{1}", "ttl": 120}}]'
overhead = len(doc.format('', ''))
half_size = (self.limits.max_message_size - overhead) // 2
doc = doc.format(helpers.generate_random_string(half_size),
helpers.generate_random_string(half_size + 1))
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 400)
test_message_bulk_insert_oversized.tags = ['negative']
@ddt.data(10000000000000000000, -100, 0, 30, -10000000000000000000)
def test_message_get_invalid_limit(self, limit):
"""Get Messages with invalid value for limit.
Allowed values for limit are 0 < limit <= 20(configurable).
"""
params = {'limit': limit}
result = self.client.get(params=params)
self.assertEqual(result.status_code, 400)
test_message_get_invalid_limit.tags = ['negative']
def test_message_bulk_delete_negative(self):
"""Delete more messages than allowed in a single request.
By default, max messages that can be deleted in a single
request is 20.
"""
url = (self.message_url + '?ids='
+ ','.join(str(i) for i in
range(self.limits.max_messages_per_page + 1)))
result = self.client.delete(url)
self.assertEqual(result.status_code, 400)
test_message_bulk_delete_negative.tags = ['negative']
def test_message_bulk_get_negative(self):
"""GET more messages by id than allowed in a single request.
By default, max messages that can be fetched in a single
request is 20.
"""
url = (self.message_url + '?ids='
+ ','.join(str(i) for i in
range(self.limits.max_messages_per_page + 1)))
result = self.client.get(url)
self.assertEqual(result.status_code, 400)
test_message_bulk_get_negative.tags = ['negative']
def test_get_messages_malformed_marker(self):
"""Get messages with non-existing marker."""
url = self.message_url + '?marker=invalid'
result = self.client.get(url, headers=self.headers)
self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'message_list')
test_get_messages_malformed_marker.tags = ['negative']
@ddt.data(None, '1234', 'aa2-bb3',
'103e09c6-31b7-11e3-86bc-b8ca3ad0f5d81',
'103e09c6-31b7-11e3-86bc-b8ca3ad0f5d')
def test_get_messages_invalid_client_id(self, client_id):
"""Get messages with invalid client id."""
url = self.message_url
header = helpers.create_marconi_headers(self.cfg)
header['Client-ID'] = client_id
result = self.client.get(url, headers=header)
self.assertEqual(result.status_code, 400)
test_get_messages_invalid_client_id.tags = ['negative']
def test_query_non_existing_message(self):
"""Get Non Existing Message."""
path = '/non-existing-message'
result = self.client.get(path)
self.assertEqual(result.status_code, 404)
test_query_non_existing_message.tags = ['negative']
def test_query_non_existing_message_set(self):
"""Get Set of Non Existing Messages."""
path = '?ids=not_there1,not_there2'
result = self.client.get(path)
self.assertEqual(result.status_code, 404)
test_query_non_existing_message_set.tags = ['negative']
def test_delete_non_existing_message(self):
"""Delete Non Existing Message."""
path = '/non-existing-message'
result = self.client.delete(path)
self.assertEqual(result.status_code, 204)
test_delete_non_existing_message.tags = ['negative']
def test_message_bad_header_single_insert(self):
"""Insert Single Message into the Queue.
This should fail because of the lack of a Client-ID header
"""
self.skipTest("Not supported")
del self.client.headers["Client-ID"]
doc = helpers.create_message_body(messagecount=1)
result = self.client.post(data=doc)
self.assertEqual(result.status_code, 400)

View File

@ -0,0 +1,405 @@
# 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.
import copy
import uuid
import ddt
import six
from marconi.tests.functional import base # noqa
from marconi.tests.functional import helpers
class NamedBinaryStr(six.binary_type):
"""Wrapper for six.binary_type to facilitate overriding __name__."""
class NamedUnicodeStr(six.text_type):
"""Unicode string look-alike to facilitate overriding __name__."""
def __init__(self, value):
self.value = value
def __str__(self):
return self.value
def encode(self, enc):
return self.value.encode(enc)
def __format__(self, formatstr):
"""Workaround for ddt bug.
DDT will always call __format__ even when __name__ exists,
which blows up for Unicode strings under Py2.
"""
return ''
class NamedDict(dict):
"""Wrapper for dict to facilitate overriding __name__."""
def annotated(test_name, test_input):
if isinstance(test_input, dict):
annotated_input = NamedDict(test_input)
elif isinstance(test_input, six.text_type):
annotated_input = NamedUnicodeStr(test_input)
else:
annotated_input = NamedBinaryStr(test_input)
setattr(annotated_input, '__name__', test_name)
return annotated_input
@ddt.ddt
class TestInsertQueue(base.V1_1FunctionalTestBase):
"""Tests for Insert queue."""
server_class = base.MarconiServer
def setUp(self):
super(TestInsertQueue, self).setUp()
self.base_url = '{0}/{1}'.format(self.cfg.marconi.url,
"v1.1")
self.header = helpers.create_marconi_headers(self.cfg)
self.headers_response_empty = set(['location'])
self.client.set_base_url(self.base_url)
self.client.headers = self.header
@ddt.data('qtestqueue', 'TESTqueue', 'hyphen-name', '_undersore',
annotated('test_insert_queue_long_name', 'i' * 64))
def test_insert_queue(self, queue_name):
"""Create Queue."""
self.url = self.base_url + '/queues/' + queue_name
self.addCleanup(self.client.delete, self.url)
result = self.client.put(self.url)
self.assertEqual(result.status_code, 201)
response_headers = set(result.headers.keys())
self.assertIsSubset(self.headers_response_empty, response_headers)
result = self.client.get(self.url)
self.assertEqual(result.status_code, 204)
test_insert_queue.tags = ['positive', 'smoke']
@ddt.data(annotated('test_insert_queue_non_ascii_name',
u'\u6c49\u5b57\u6f22\u5b57'),
'@$@^qw',
annotated('test_insert_queue_invalid_name_length', 'i' * 65))
def test_insert_queue_invalid_name(self, queue_name):
"""Create Queue."""
if six.PY2 and isinstance(queue_name, NamedUnicodeStr):
queue_name = queue_name.encode('utf-8')
self.url = self.base_url + '/queues/' + queue_name
self.addCleanup(self.client.delete, self.url)
result = self.client.put(self.url)
self.assertEqual(result.status_code, 400)
test_insert_queue_invalid_name.tags = ['negative']
def test_insert_queue_invalid_authtoken(self):
"""Insert Queue with invalid authtoken."""
# NOTE(flaper87): Currently, tearDown
# depends on this attribute. Needs to
# be fixed.
self.url = self.base_url + '/queues/invalidauthtoken'
self.addCleanup(self.client.delete, self.url)
if not self.cfg.auth.auth_on:
self.skipTest("Auth is not on!")
header = copy.copy(self.header)
header['X-Auth-Token'] = 'invalid'
result = self.client.put(self.url, headers=header)
self.assertEqual(result.status_code, 401)
test_insert_queue_invalid_authtoken.tags = ['negative']
def test_insert_queue_header_plaintext(self):
"""Insert Queue with 'Accept': 'plain/text'."""
path = '/queues/plaintextheader'
self.addCleanup(self.client.delete, path)
header = {"Accept": 'plain/text'}
result = self.client.put(path, headers=header)
self.assertEqual(result.status_code, 406)
test_insert_queue_header_plaintext.tags = ['negative']
def test_insert_queue_header_asterisk(self):
"""Insert Queue with 'Accept': '*/*'."""
path = '/queues/asteriskinheader'
headers = {"Accept": '*/*'}
self.addCleanup(self.client.delete, url=path, headers=headers)
result = self.client.put(path, headers=headers)
self.assertEqual(result.status_code, 201)
test_insert_queue_header_asterisk.tags = ['positive']
def test_insert_queue_with_metadata(self):
"""Insert queue with a non-empty request body."""
self.url = self.base_url + '/queues/hasmetadata'
doc = {"queue": "Has Metadata"}
self.addCleanup(self.client.delete, self.url)
result = self.client.put(self.url, data=doc)
self.assertEqual(result.status_code, 201)
self.url = self.base_url + '/queues/hasmetadata/metadata'
result = self.client.get(self.url)
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json(), {})
test_insert_queue_with_metadata.tags = ['negative']
def tearDown(self):
super(TestInsertQueue, self).tearDown()
@ddt.ddt
class TestQueueMisc(base.V1_1FunctionalTestBase):
server_class = base.MarconiServer
def setUp(self):
super(TestQueueMisc, self).setUp()
self.base_url = self.cfg.marconi.url
self.client.set_base_url(self.base_url)
self.queue_url = self.base_url + ('/{0}/queues/{1}'
.format("v1.1", uuid.uuid1()))
def test_list_queues(self):
"""List Queues."""
self.client.put(self.queue_url)
self.addCleanup(self.client.delete, self.queue_url)
result = self.client.get('/{0}/queues'
.format("v1.1"))
self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'queue_list')
test_list_queues.tags = ['smoke', 'positive']
def test_list_queues_detailed(self):
"""List Queues with detailed = True."""
self.client.put(self.queue_url)
self.addCleanup(self.client.delete, self.queue_url)
params = {'detailed': True}
result = self.client.get('/{0}/queues'
.format("v1.1"),
params=params)
self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'queue_list')
response_keys = result.json()['queues'][0].keys()
self.assertIn('metadata', response_keys)
test_list_queues_detailed.tags = ['smoke', 'positive']
@ddt.data(0, -1, 1001)
def test_list_queue_invalid_limit(self, limit):
"""List Queues with a limit value that is not allowed."""
params = {'limit': limit}
result = self.client.get('/{0}/queues'
.format("v1.1"),
params=params)
self.assertEqual(result.status_code, 400)
test_list_queue_invalid_limit.tags = ['negative']
def test_check_health(self):
"""Test health endpoint."""
result = self.client.get('/{0}/health'
.format("v1.1"))
self.assertEqual(result.status_code, 204)
test_check_health.tags = ['positive']
def test_check_queue_exists(self):
"""Checks if queue exists."""
self.client.put(self.queue_url)
self.addCleanup(self.client.delete, self.queue_url)
result = self.client.get(self.queue_url)
self.assertEqual(result.status_code, 204)
result = self.client.head(self.queue_url)
self.assertEqual(result.status_code, 204)
test_check_queue_exists.tags = ['positive']
def test_check_queue_exists_negative(self):
"""Checks non-existing queue."""
path = '/{0}/queues/nonexistingqueue'.format("v1.1")
result = self.client.get(path)
self.assertEqual(result.status_code, 404)
result = self.client.head(path)
self.assertEqual(result.status_code, 404)
test_check_queue_exists_negative.tags = ['negative']
def test_get_queue_malformed_marker(self):
"""List queues with invalid marker."""
path = '/{0}/queues?marker=zzz'.format("v1.1")
result = self.client.get(path)
self.assertEqual(result.status_code, 204)
test_get_queue_malformed_marker.tags = ['negative']
def test_get_stats_empty_queue(self):
"""Get queue stats on an empty queue."""
result = self.client.put(self.queue_url)
self.addCleanup(self.client.delete, self.queue_url)
self.assertEqual(result.status_code, 201)
stats_url = self.queue_url + '/stats'
# Get stats on an empty queue
result = self.client.get(stats_url)
self.assertEqual(result.status_code, 200)
expected_response = {'messages':
{'claimed': 0, 'total': 0, 'free': 0}}
self.assertEqual(result.json(), expected_response)
test_get_stats_empty_queue.tags = ['positive']
@ddt.data(0, 1)
def test_get_queue_stats_claimed(self, claimed):
"""Get stats on a queue."""
result = self.client.put(self.queue_url)
self.addCleanup(self.client.delete, self.queue_url)
self.assertEqual(result.status_code, 201)
# Post Messages to the test queue
doc = helpers.create_message_body(
messagecount=self.limits.max_messages_per_claim)
message_url = self.queue_url + '/messages'
result = self.client.post(message_url, data=doc)
self.assertEqual(result.status_code, 201)
if claimed > 0:
claim_url = self.queue_url + '/claims?limit=' + str(claimed)
doc = {'ttl': 300, 'grace': 300}
result = self.client.post(claim_url, data=doc)
self.assertEqual(result.status_code, 201)
# Get stats on the queue.
stats_url = self.queue_url + '/stats'
result = self.client.get(stats_url)
self.assertEqual(result.status_code, 200)
self.assertQueueStats(result.json(), claimed)
test_get_queue_stats_claimed.tags = ['positive']
def test_ping_queue(self):
pass
def tearDown(self):
super(TestQueueMisc, self).tearDown()
class TestQueueNonExisting(base.V1_1FunctionalTestBase):
"""Test Actions on non existing queue."""
server_class = base.MarconiServer
def setUp(self):
super(TestQueueNonExisting, self).setUp()
if self.cfg.version != "v1":
self.skipTest("Not Supported")
self.base_url = '{0}/{1}'.format(self.cfg.marconi.url,
"v1.1")
self.queue_url = (self.base_url +
'/queues/0a5b1b85-4263-11e3-b034-28cfe91478b9')
self.client.set_base_url(self.queue_url)
self.header = helpers.create_marconi_headers(self.cfg)
self.headers_response_empty = set(['location'])
self.header = helpers.create_marconi_headers(self.cfg)
def test_get_queue(self):
"""Get non existing Queue."""
result = self.client.get()
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json(), [])
def test_get_stats(self):
"""Get stats on non existing Queue."""
result = self.client.get('/stats')
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json(), [])
def test_get_metadata(self):
"""Get metadata on non existing Queue."""
result = self.client.get('/metadata')
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json(), [])
def test_get_messages(self):
"""Get messages on non existing Queue."""
result = self.client.get('/messages')
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json(), [])
def test_post_messages(self):
"""Post messages to a non existing Queue."""
doc = [{"ttl": 200, "body": {"Home": ""}}]
result = self.client.post('/messages', data=doc)
self.assertEqual(result.status_code, 201)
# check existence of queue
result = self.client.get()
self.assertEqual(result.status_code, 200)
self.assertNotEqual(result.json(), [])
def test_claim_messages(self):
"""Claim messages from a non existing Queue."""
doc = {"ttl": 200, "grace": 300}
result = self.client.post('/claims', data=doc)
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json(), [])
def test_delete_queue(self):
"""Delete non existing Queue."""
result = self.client.delete()
self.assertEqual(result.status_code, 204)

View File

@ -0,0 +1,242 @@
# 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.
import ddt
from marconi.tests.functional import base
from marconi.tests.functional import helpers
@ddt.ddt
class TestShards(base.V1_1FunctionalTestBase):
server_class = base.MarconiServer
def setUp(self):
super(TestShards, self).setUp()
self.shard_url = ("{url}/{version}/shards".format(
url=self.cfg.marconi.url,
version="v1.1"
))
self.cfg.marconi.version = "v1.1"
self.skipTest("NOT IMPLEMENTED")
self.headers = helpers.create_marconi_headers(self.cfg)
self.client.headers = self.headers
self.client.set_base_url(self.shard_url)
def tearDown(self):
super(TestShards, self).tearDown()
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_insert_shard(self, params):
"""Test the registering of one shard."""
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
self.addCleanup(self.client.delete, url='/'+shard_name)
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 201)
# Test existence
result = self.client.get('/'+shard_name)
self.assertEqual(result.status_code, 200)
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_shard_details(self, params):
"""Get the details of a shard. Assert the respective schema."""
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
self.addCleanup(self.client.delete, url='/'+shard_name)
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 201)
# Test existence
result = self.client.get('/'+shard_name+'?detailed=true')
self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'shard-detail')
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_delete_shard(self, params):
"""Create a shard, then delete it.
Make sure operation is successful.
"""
# Create the shard
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 201)
# Make sure it exists
result = self.client.get('/'+shard_name)
self.assertEqual(result.status_code, 200)
# Delete it
result = self.client.delete('/'+shard_name)
self.assertEqual(result.status_code, 204)
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_list_shards(self, params):
"""Add a shard. Get the list of all the shards.
Assert respective schema
"""
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
self.addCleanup(self.client.delete, url='/'+shard_name)
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 201)
result = self.client.get()
self.assertEqual(result.status_code, 200)
self.assertSchema(result.json(), 'shard-list')
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_patch_shard(self, params):
"""Create a shard. Issue a patch command,
make sure command was successful. Check details to be sure.
"""
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
self.addCleanup(self.client.delete, url='/'+shard_name)
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 201)
# Update that shard
patchdoc = helpers.create_shard_body(
weight=5,
uri="mongodb://127.0.0.1:27017"
)
result = self.client.patch('/'+shard_name, data=patchdoc)
self.assertEqual(result.status_code, 200)
# Get the shard, check update#
result = self.client.get('/'+shard_name)
self.assertEqual(result.status_code, 200)
self.assertEqual(result.json()["weight"], 5)
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_patch_shard_bad_data(self, params):
"""Issue a patch command without a body. Assert 400."""
# create a shard
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
self.addCleanup(self.client.delete, url='/'+shard_name)
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 201)
# Update shard with bad post data. Ensure 400
result = self.client.patch('/'+shard_name)
self.assertEqual(result.status_code, 400)
@ddt.data(
{
'name': "newshard",
'weight': 10,
'uri': "mongodb://127.0.0.1:27017"
}
)
def test_patch_shard_non_exist(self, params):
"""Issue patch command to shard that doesn't exist. Assert 404."""
doc = helpers.create_shard_body(
weight=5,
uri=params.get('uri', "mongodb://127.0.0.1:27018")
)
result = self.client.patch('/nonexistshard', data=doc)
self.assertEqual(result.status_code, 404)
@ddt.data(
{'name': u'\u6c49\u5b57\u6f22\u5b57'},
{'name': 'i'*65},
{'weight': -1}
)
def test_insert_shard_bad_data(self, params):
"""Create shards with invalid names and weights. Assert 400."""
doc = helpers.create_shard_body(
weight=params.get('weight', 10),
uri=params.get('uri', "mongodb://127.0.0.1:27017")
)
shard_name = params.get('name', "newshard")
self.addCleanup(self.client.delete, url='/'+shard_name)
result = self.client.put('/'+shard_name, data=doc)
self.assertEqual(result.status_code, 400)
def test_delete_shard_non_exist(self):
"""Delete a shard that doesn't exist. Assert 404."""
result = self.client.delete('/nonexistshard')
self.assertEqual(result.status_code, 204)