diff --git a/system-test-requirements.txt b/functional-test-requirements.txt similarity index 100% rename from system-test-requirements.txt rename to functional-test-requirements.txt diff --git a/marconi/tests/system/etc/system-tests.conf-sample b/marconi/tests/etc/functional-tests.conf-sample similarity index 82% rename from marconi/tests/system/etc/system-tests.conf-sample rename to marconi/tests/etc/functional-tests.conf-sample index 167dc57..7b4d2ca 100644 --- a/marconi/tests/system/etc/system-tests.conf-sample +++ b/marconi/tests/etc/functional-tests.conf-sample @@ -1,6 +1,5 @@ [auth] -auth_on = true -#auth endpoint - url to get the auth token +auth_on = false url = https://identity.xxx.xxxx.com/v2.0/tokens username = user password = secret diff --git a/marconi/tests/functional/README.rst b/marconi/tests/functional/README.rst new file mode 100644 index 0000000..f739513 --- /dev/null +++ b/marconi/tests/functional/README.rst @@ -0,0 +1,49 @@ +Marconi Functional Tests +==================== + +Marconi's functional tests treat Marconi as a black box. In other +words, the API calls attempt to simulate an actual user. Unlike unit tests, +the functional tests do not use mockendpoints. + + +Running the Functional Tests +------------------------ + +#. Setup a Marconi server. Refer to the Marconi `README`_ on + how to run Marconi locally, or simply use an existing server. + +#. Install functional tests dependencies. :: + + pip install -r functional-test-requirements.txt + +#. cd to the marconi/tests/functional directory + +#. Copy marconi/tests/etc/functional-tests.conf-sample to one of the following locations:: + + ~/.marconi/functional-tests.conf + /etc/marconi/functional-tests.conf + +#. Update the config file to point to the Marconi server you want to run + the tests against + +#. If leaving keystone auth enabled, update system-tests.conf with a + valid set of credentials. + +#. Now, to run the sytem tests, simply use the nosetests commands, + from the marconi/tests/functional directory. e.g.: + + Run all test suites: :: + + nosetests -v + +Adding New Tests +---------------- + +#. Add test case to an appropriate test case file: :: + + queue/test_queue.py + messages/test_messages.py + claim/test_claims.py + +.. _README : https://github.com/stackforge/marconi/blob/master/README.rst +.. _requests : https://pypi.python.org/pypi/requests diff --git a/marconi/tests/functional/__init__.py b/marconi/tests/functional/__init__.py new file mode 100644 index 0000000..14a86d6 --- /dev/null +++ b/marconi/tests/functional/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) 2013 Rackspace Hosting, 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. + +"""Marconi Functional Tests""" diff --git a/marconi/tests/functional/test_claims.py b/marconi/tests/functional/test_claims.py new file mode 100644 index 0000000..fec567f --- /dev/null +++ b/marconi/tests/functional/test_claims.py @@ -0,0 +1,235 @@ +# 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.tests.functional.util import base +from marconi.tests.functional.util import config +from marconi.tests.functional.util import helpers +from marconi.tests.functional.util import http + +import ddt +import json +import uuid + + +@ddt.ddt +class TestClaims(base.FunctionalTestBase): + """Tests for Claims.""" + + @classmethod + def setUpClass(cls): + """Create Queue, Post Messages for Claim Tests.""" + cls.cfg = config.Config() + cls.header = helpers.create_marconi_headers() + + cls.headers_response_with_body = set(['location', + 'content-type']) + + def setUp(self): + super(TestClaims, self).setUp() + + self.queue_url = self.cfg.base_url + '/queues/{}'.format(uuid.uuid1()) + http.put(self.queue_url, self.header) + + self.claim_url = self.queue_url + '/claims' + + #Post Messages + url = self.queue_url + '/messages' + doc = helpers.get_message_body(messagecount= + self.cfg.message_paging_uplimit) + for i in range(25): + http.post(url, self.header, doc) + + @ddt.data('', '?limit=2') + def test_claim_messages(self, url_appender): + """Claim messages.""" + if url_appender: + message_count = int(url_appender.split('?limit=')[1]) + else: + message_count = 10 + + url = self.claim_url + url_appender + doc = '{"ttl": 300, "grace": 100}' + + result = http.post(url, self.header, doc) + self.assertEqual(result.status_code, 201) + + actual_message_count = len(result.json()) + self.assertMessageCount(message_count, actual_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.""" + url = self.claim_url + '?limit=1' + doc = '{"ttl": 300, "grace": 100}' + + result = http.post(url, self.header, doc) + location = result.headers['Location'] + + url = self.cfg.base_server + location + + result = http.get(url, self.header) + 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. + """ + url = self.claim_url + '?limit=' + \ + str(self.cfg.message_paging_uplimit + 1) + doc = '{"ttl": 300, "grace": 100}' + + result = http.post(url, self.header, 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 = http.post(self.claim_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + #Patch Claim + claim_location = result.headers['Location'] + url = self.cfg.base_server + claim_location + doc_updated = '{"ttl": 300}' + + result = http.patch(url, self.header, doc_updated) + self.assertEqual(result.status_code, 204) + + #verify that the claim TTL is updated + result = http.get(url, self.header) + 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 = http.post(self.claim_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + href_list = [result.json()[i]['href'] + for i in range(len(result.json()))] + url_list = [self.cfg.base_server + href + for href in href_list] + + #Delete Claimed Messages + for url in url_list: + result = http.delete(url, self.header) + 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 = http.post(self.claim_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + #Extract claim location and construct the claim URL. + location = result.headers['Location'] + url = self.cfg.base_server + location + + #Release Claim. + result = http.delete(url, self.header) + 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 = http.post(self.claim_url, self.header, json.dumps(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 = http.post(self.claim_url, self.header, json.dumps(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 = http.post(self.claim_url, self.header, json.dumps(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 = http.post(self.claim_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + #Extract claim location and construct the claim URL. + location = result.headers['Location'] + url = self.cfg.base_server + location + + #Patch Claim. + doc = {"ttl": ttl} + result = http.patch(url, self.header, json.dumps(doc)) + self.assertEqual(result.status_code, 400) + + test_patch_claim_invalid_ttl.tags = ['negative'] + + def tearDown(self): + """Delete Queue after Claim Test.""" + super(TestClaims, self).tearDown() + http.delete(self.queue_url, self.header) diff --git a/marconi/tests/functional/test_messages.py b/marconi/tests/functional/test_messages.py new file mode 100644 index 0000000..da54fcd --- /dev/null +++ b/marconi/tests/functional/test_messages.py @@ -0,0 +1,292 @@ +# 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.tests.functional.util import base +from marconi.tests.functional.util import config +from marconi.tests.functional.util import helpers +from marconi.tests.functional.util import http + +import ddt +import uuid + + +@ddt.ddt +class TestMessages(base.FunctionalTestBase): + + """Tests for Messages.""" + + @classmethod + def setUpClass(cls): + cls.cfg = config.Config() + cls.header = helpers.create_marconi_headers() + + cls.headers_response_with_body = set(['location', + 'content-type']) + + def setUp(self): + super(TestMessages, self).setUp() + + self.queue_url = self.cfg.base_url + '/queues/{}'.format(uuid.uuid1()) + http.put(self.queue_url, self.header) + + self.message_url = self.queue_url + '/messages' + + 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.get_message_body(messagecount=1) + + result = http.post(self.message_url, self.header, 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.base_server + href + + result = http.get(url, self.header) + 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 = http.post(url, self.header, doc) + self.assertEqual(result.status_code, 201) + + url = self.message_url + '?include_claimed=true&echo=true' + result = http.get(url, self.header) + 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 = http.get(self.message_url, self.header) + self.assertEqual(result.status_code, 204) + + test_message_single_insert.tags = ['smoke', 'positive'] + + def test_message_bulk_insert(self): + """Bulk Insert Messages into the Queue.""" + message_count = 10 + doc = helpers.get_message_body(messagecount=message_count) + + result = http.post(self.message_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + # GET on posted messages + location = result.headers['location'] + url = self.cfg.base_server + location + result = http.get(url, self.header) + self.assertEqual(result.status_code, 200) + + # 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, url_param): + """Get Messages.""" + if url_param: + expected_msg_count = int(url_param.split('&limit=')[1]) + else: + expected_msg_count = 10 + + # Test Setup + doc = helpers.get_message_body(messagecount=20) + result = http.post(self.message_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + url = self.message_url + '?echo=True' + url_param + + #Follow the hrefs & perform GET, till the end of messages i.e. http 204 + while result.status_code in [201, 200]: + result = http.get(url, self.header) + 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.base_server + href + + self.assertEqual(result.status_code, 204) + + test_get_message.tags = ['smoke', 'positive'] + + def test_message_delete(self): + """Delete Message.""" + # Test Setup + doc = helpers.get_message_body(messagecount=1) + result = http.post(self.message_url, self.header, doc) + self.assertEqual(result.status_code, 201) + + # Delete posted message + href = result.json()['resources'][0] + url = self.cfg.base_server + href + + result = http.delete(url, self.header) + self.assertEqual(result.status_code, 204) + + result = http.get(url, self.header) + self.assertEqual(result.status_code, 404) + + test_message_delete.tags = ['smoke', 'positive'] + + def test_message_bulk_delete(self): + """Bulk Delete Messages.""" + doc = helpers.get_message_body(messagecount=10) + result = http.post(self.message_url, self.header, doc) + + self.assertEqual(result.status_code, 201) + + # Delete posted messages + location = result.headers['Location'] + url = self.cfg.base_server + location + + result = http.delete(url, self.header) + self.assertEqual(result.status_code, 204) + + result = http.get(url, self.header) + self.assertEqual(result.status_code, 204) + + test_message_bulk_delete.tags = ['smoke', 'positive'] + + def test_message_delete_nonexisting(self): + """Delete non-existing Messages.""" + url = self.message_url + '/non-existing' + result = http.delete(url, self.header) + + 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.get_message_body(messagecount=3) + result = http.post(self.message_url, self.header, doc) + + self.assertEqual(result.status_code, 201) + + # Delete posted message + location = result.headers['Location'] + url = self.cfg.base_server + location + url += ',nonexisting' + result = http.delete(url, self.header) + 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.get_message_body(messagecount=3) + result = http.post(self.message_url, self.header, doc) + + self.assertEqual(result.status_code, 201) + + # Get posted message and a nonexisting message + location = result.headers['Location'] + url = self.cfg.base_server + location + url += ',nonexisting' + result = http.get(url, self.header) + self.assertEqual(result.status_code, 200) + + test_message_partial_get.tags = ['negative'] + + def test_message_bulk_insert_60(self): + """Insert more than max allowed messages. + + Marconi allows a maximum of 50 message per POST. + """ + doc = helpers.get_message_body(messagecount=60) + + result = http.post(self.message_url, self.header, doc) + self.assertEqual(result.status_code, 400) + + test_message_bulk_insert_60.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). + """ + url = self.message_url + '?limit=' + str(limit) + result = http.get(url, self.header) + 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.cfg.message_paging_uplimit + 1)) + result = http.delete(url, self.header) + + 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.cfg.message_paging_uplimit + 1)) + result = http.get(url, self.header) + + 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 = http.get(url, self.header) + self.assertEqual(result.status_code, 204) + + test_get_messages_malformed_marker.tags = ['negative'] + + def tearDown(self): + super(TestMessages, self).tearDown() + http.delete(self.queue_url, self.header) diff --git a/marconi/tests/functional/test_queue.py b/marconi/tests/functional/test_queue.py new file mode 100644 index 0000000..497e8f8 --- /dev/null +++ b/marconi/tests/functional/test_queue.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# 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.tests.functional.util import base +from marconi.tests.functional.util import config +from marconi.tests.functional.util import helpers +from marconi.tests.functional.util import http + +import copy +import ddt +import json +import uuid + + +@ddt.ddt +class TestInsertQueue(base.FunctionalTestBase): + + """Tests for Insert queue.""" + + @classmethod + def setUpClass(cls): + cls.cfg = config.Config() + cls.header = helpers.create_marconi_headers() + + cls.headers_response_empty = set(['location']) + cls.headers_response_with_body = set(['content-location', + 'content-type']) + + @ddt.data('qtestqueue', 'TESTqueue', 'hyphen-name', '_undersore', + 'i' * 64) + def test_insert_queue(self, queue_name): + """Create Queue.""" + self.url = self.cfg.base_url + '/queues/' + queue_name + + result = http.put(self.url, self.header) + self.assertEqual(result.status_code, 201) + + response_headers = set(result.headers.keys()) + self.assertIsSubset(self.headers_response_empty, response_headers) + + self.url = self.url + '/metadata' + result = http.get(self.url, self.header) + self.assertEqual(result.status_code, 200) + self.assertEqual(result.json(), {}) + + test_insert_queue.tags = ['positive', 'smoke'] + + @ddt.data('汉字漢字', '@$@^qw', 'i' * 65) + def test_insert_queue_invalid_name(self, queue_name): + """Create Queue.""" + self.url = self.cfg.base_url + '/queues/' + queue_name + + result = http.put(self.url, self.header) + self.assertEqual(result.status_code, 400) + + self.url = self.url + '/metadata' + result = http.get(self.url, self.header) + self.assertEqual(result.status_code, 404) + + test_insert_queue_invalid_name.tags = ['negative'] + + def test_insert_queue_invalid_authtoken(self): + """Insert Queue with invalid authtoken.""" + self.url = self.cfg.base_url + '/queues/invalidauthtoken' + + header = copy.copy(self.header) + header["X-Auth-Token"] = 'invalid' + + result = http.put(self.url, 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'.""" + self.url = self.cfg.base_url + '/queues/plaintextheader' + header = copy.copy(self.header) + header["Accept"] = 'plain/text' + + result = http.put(self.url, 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': '*/*'.""" + self.url = self.cfg.base_url + '/queues/asteriskinheader' + header = copy.copy(self.header) + header["Accept"] = '*/*' + + result = http.put(self.url, header) + 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.cfg.base_url + '/queues/hasmetadata' + doc = '{"queue": "Has Metadata"}' + result = http.put(self.url, self.header, doc) + + self.assertEqual(result.status_code, 201) + + self.url = self.cfg.base_url + '/queues/hasmetadata/metadata' + result = http.get(self.url, self.header) + + self.assertEqual(result.status_code, 200) + self.assertEqual(result.json(), {}) + + test_insert_queue_with_metadata.tags = ['negative'] + + def tearDown(self): + super(TestInsertQueue, self).tearDown() + http.delete(self.url, self.header) + + +@ddt.ddt +class TestQueueMetaData(base.FunctionalTestBase): + + """Tests for queue metadata.""" + + @classmethod + def setUpClass(cls): + cls.cfg = config.Config() + cls.header = helpers.create_marconi_headers() + + cls.headers_response_with_body = set(['location', + 'content-type']) + + def setUp(self): + super(TestQueueMetaData, self).setUp() + + self.queue_url = self.cfg.base_url + '/queues/{}'.format(uuid.uuid1()) + http.put(self.queue_url, self.header) + + self.queue_metadata_url = self.queue_url + '/metadata' + + @ddt.data({}, + {"_queue": "Top Level field with _"}, + {"汉字": "non ASCII metadata"}, + {"queue": "#$%^&Apple"}, + {"queue": "i" * 65000} + ) + def test_insert_queue_metadata(self, doc): + """Insert Queue with empty json.""" + result = http.put(self.queue_metadata_url, self.header, + json.dumps(doc)) + self.assertEqual(result.status_code, 204) + + result = http.get(self.queue_metadata_url, self.header) + self.assertEqual(result.status_code, 200) + self.assertEqual(result.json(), doc) + + test_insert_queue_metadata.tags = ['smoke', 'positive'] + + @ddt.data('not_a_dict', + {"queue": "i" * 65537} + ) + def test_insert_queue_invalid_metadata(self, doc): + """Insert invalid metadata.""" + + result = http.put(self.queue_metadata_url, self.header, str(doc)) + self.assertEqual(result.status_code, 400) + + test_insert_queue_invalid_metadata.tags = ['negative'] + + def tearDown(self): + super(TestQueueMetaData, self).tearDown() + http.delete(self.queue_url, self.header) + + +@ddt.ddt +class TestQueueMisc(base.FunctionalTestBase): + + @classmethod + def setUpClass(cls): + cls.cfg = config.Config() + cls.header = helpers.create_marconi_headers() + + cls.headers_response_empty = set(['location']) + cls.headers_response_with_body = set(['content-location', + 'content-type']) + + cls.queue_url = cls.cfg.base_url + '/queues/{}'.format(uuid.uuid1()) + http.put(cls.queue_url, cls.header) + + url = cls.queue_url + '/metadata' + metadata = {"queue_metadata": "TEST METADATA"} + http.put(url, cls.header, json.dumps(metadata)) + + def test_list_queues(self): + """List Queues.""" + url = self.cfg.base_url + '/queues' + result = http.get(url, self.header) + + self.assertEqual(result.status_code, 200) + + response_keys_actual = result.json().keys() + response_keys_actual.sort() + response_keys_expected = ['links', 'queues'] + self.assertEqual(response_keys_actual, response_keys_expected) + + test_list_queues.tags = ['smoke', 'positive'] + + def test_list_queues_detailed(self): + """List Queues with detailed = True.""" + url = self.cfg.base_url + '/queues?detailed=True' + result = http.get(url, self.header) + + self.assertEqual(result.status_code, 200) + + response_keys_actual = result.json()['queues'][0].keys() + response_keys_actual.sort() + response_keys_expected = ['href', 'metadata', 'name'] + self.assertEqual(response_keys_actual, response_keys_expected) + + test_list_queues_detailed.tags = ['smoke', 'positive'] + + @ddt.data(0, -1, 30) + def test_list_queue_invalid_limit(self, limit): + """List Queues with a limit value that is not allowed.""" + url = self.cfg.base_url + '/queues?limit=' + str(limit) + result = http.get(url, self.header) + + self.assertEqual(result.status_code, 400) + + test_list_queue_invalid_limit.tags = ['negative'] + + def test_check_health(self): + """Test health endpoint.""" + url = self.cfg.base_url + '/health' + result = http.get(url, self.header) + + self.assertEqual(result.status_code, 204) + + test_check_health.tags = ['positive'] + + def test_check_queue_exists(self): + """Checks if queue exists.""" + url = self.cfg.base_url + '/queues/testqueue' + http.put(url, self.header) + + result = http.get(url, self.header) + self.assertEqual(result.status_code, 204) + + result = http.head(url, self.header) + self.assertEqual(result.status_code, 204) + + test_check_queue_exists.tags = ['positive'] + + def test_check_queue_exists_negative(self): + """Checks non-existing queue.""" + url = self.cfg.base_url + '/queues/nonexistingqueue' + + result = http.get(url, self.header) + self.assertEqual(result.status_code, 404) + + result = http.head(url, self.header) + 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.""" + url = self.cfg.base_url + '/queues?marker=invalid' + + result = http.get(url, self.header) + self.assertEqual(result.status_code, 204) + + test_get_queue_malformed_marker.tags = ['negative'] + + @classmethod + def tearDownClass(cls): + """Delete Queue.""" + url = cls.cfg.base_url + '/queues/testqueue' + http.delete(url, cls.header) diff --git a/marconi/tests/functional/util/__init__.py b/marconi/tests/functional/util/__init__.py new file mode 100644 index 0000000..841dae8 --- /dev/null +++ b/marconi/tests/functional/util/__init__.py @@ -0,0 +1,16 @@ +# Copyright (c) 2013 Rackspace Hosting, 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. + +"""Functional Test Utilities""" diff --git a/marconi/tests/functional/util/base.py b/marconi/tests/functional/util/base.py new file mode 100644 index 0000000..11636b9 --- /dev/null +++ b/marconi/tests/functional/util/base.py @@ -0,0 +1,38 @@ +# 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 testtools + + +class FunctionalTestBase(testtools.TestCase): + + def assertIsSubset(self, required_values, actual_values): + """Checks if a list is subset of another. + + :param required_values: superset list. + :param required_values: subset list. + """ + + form = 'Missing Header(s) - {}' + self.assertTrue(required_values.issubset(actual_values), + msg=form.format((required_values - actual_values))) + + def assertMessageCount(self, expectedCount, actualCount): + """Checks if number of messages returned <= limit + + :param expectedCount: limit value passed in the url (OR) default(10). + :param actualCount: number of messages returned in the API response. + """ + self.assertTrue(actualCount <= expectedCount, + msg='More Messages returned than allowed') diff --git a/marconi/tests/system/common/config.py b/marconi/tests/functional/util/config.py similarity index 93% rename from marconi/tests/system/common/config.py rename to marconi/tests/functional/util/config.py index 1348ae8..7634230 100644 --- a/marconi/tests/system/common/config.py +++ b/marconi/tests/functional/util/config.py @@ -21,11 +21,11 @@ class Config(object): def __init__(self, config_path=None): if config_path is None: - if os.path.exists('/etc/marconi/system-tests.conf'): - config_path = '/etc/marconi/system-tests.conf' + if os.path.exists('/etc/marconi/functional-tests.conf'): + config_path = '/etc/marconi/functional-tests.conf' else: config_path = os.path.expanduser('~/.marconi' - '/system-tests.conf') + '/functional-tests.conf') self.parser = ConfigParser.SafeConfigParser() self.parser.read(config_path) diff --git a/marconi/tests/functional/util/helpers.py b/marconi/tests/functional/util/helpers.py new file mode 100644 index 0000000..742cfc2 --- /dev/null +++ b/marconi/tests/functional/util/helpers.py @@ -0,0 +1,109 @@ +# 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.tests.functional.util import config +from marconi.tests.functional.util import http + +import json +import random +import string + + +CFG = config.Config() + + +def get_keystone_token(): + """Gets Keystone Auth token.""" + req_json = { + 'auth': { + 'passwordCredentials': { + 'username': CFG.username, + 'password': CFG.password + }, + }, + } + + header = {"Content-Type": "application/json", + "Accept": "application/json"} + url = CFG.auth_url + + response = http.post(url=url, header=header, body=req_json) + response_body = json.loads(response.text) + + auth_token = response_body['access']['token']['id'] + + return auth_token + + +def create_marconi_headers(): + """Returns headers to be used for all Marconi requests.""" + auth_token = get_keystone_token() + + headers = {"Host": CFG.host, + "User-Agent": CFG.user_agent, + "Accept": "application/json", + "X-Project-ID": CFG.project_id, + "X-Auth-Token": auth_token, + "Client-ID": CFG.uuid + } + + return headers + + +def generate_dict(dict_length): + """Returns dictionary of specified length. Key:Value is random data. + + :param dict_length: length of the dictionary + """ + return dict([(generate_random_string(), generate_random_string()) + for _ in range(dict_length)]) + + +def generate_random_string(length=10): + """Returns an ASCII string of specified length.""" + chars = string.ascii_letters + string.digits + return ''.join(random.choice(chars) for i in range(length)) + + +def single_message_body(**kwargs): + """Returns message body for one message . + + The ttl will be a random value (60 <= TTL <= 1209600). + The message body will be random dict. + :param **kwargs: can be {messagesize: x} , where x is message size + :param **kwargs: can be {ttl: x} , where x is ttl in seconds + """ + valid_ttl = random.randint(60, 1209600) + + if 'messagesize' in kwargs.keys(): + body = generate_dict(kwargs['messagesize']) + else: + body = generate_dict(2) + + if 'ttl' in kwargs: + ttl = kwargs['ttl'] + else: + ttl = valid_ttl + + message_body = {'ttl': ttl, 'body': body} + return message_body + + +def get_message_body(**kwargs): + """Returns request body for post message tests. + + :param **kwargs: can be {messagecount: x} , x is the # of messages. + """ + message_count = kwargs['messagecount'] + return [single_message_body(**kwargs) for i in range(message_count)] diff --git a/marconi/tests/system/common/http.py b/marconi/tests/functional/util/http.py similarity index 93% rename from marconi/tests/system/common/http.py rename to marconi/tests/functional/util/http.py index 21f9b14..0f8a2bf 100755 --- a/marconi/tests/system/common/http.py +++ b/marconi/tests/functional/util/http.py @@ -12,15 +12,11 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. - -import json import requests def get(url, header=''): """Does http GET.""" - if header: - header = json.loads(header) try: response = requests.get(url, headers=header) except requests.ConnectionError as detail: @@ -36,8 +32,6 @@ def get(url, header=''): def head(url, header=''): """Does http HEAD.""" - if header: - header = json.loads(header) try: response = requests.head(url, headers=header) except requests.ConnectionError as detail: @@ -53,8 +47,6 @@ def head(url, header=''): def post(url, header='', body=''): """Does http POST.""" - if header: - header = json.loads(header) body = str(body) body = body.replace("'", '"') try: @@ -73,9 +65,6 @@ def post(url, header='', body=''): def put(url, header='', body=''): """Does http PUT.""" response = None - if header: - header = json.loads(header) - try: response = requests.put(url, headers=header, data=body) except requests.ConnectionError as detail: @@ -92,9 +81,6 @@ def put(url, header='', body=''): def delete(url, header=''): """Does http DELETE.""" response = None - if header: - header = json.loads(header) - try: response = requests.delete(url, headers=header) except requests.ConnectionError as detail: @@ -111,9 +97,6 @@ def delete(url, header=''): def patch(url, header='', body=''): """Does http PATCH.""" response = None - if header: - header = json.loads(header) - try: response = requests.patch(url, headers=header, data=body) except requests.ConnectionError as detail: diff --git a/marconi/tests/system/README.rst b/marconi/tests/system/README.rst deleted file mode 100644 index 8ae392a..0000000 --- a/marconi/tests/system/README.rst +++ /dev/null @@ -1,59 +0,0 @@ -Marconi System Tests -==================== - -Marconi's system tests treat Marconi as a black box. In other -words, the API calls attempt to simulate an actual user. For -example, unlike unit tests, the system tests do not use mock -endpoints. - - -Running the System Tests ------------------------- - -#. Setup a Marconi server. Refer to the Marconi `README`_ on - how to run Marconi locally, or simply use an existing server. - -#. System tests require the `requests` package. Run - the following to install it: :: - - pip install -r system-test-requirements.txt - -#. cd to the marconi/tests/system directory - -#. Copy etc/system-tests.conf-sample to one of the following locations:: - - ~/.marconi/system-tests.conf - /etc/marconi/system-tests.conf - -#. Update the config file to point to the Marconi server you want to run - the tests against - -#. If leaving keystone auth enabled, update system-tests.conf with a - valid set of credentials. - -#. Now, to run the sytem tests, simply use the nosetests commands, - from the marconi/tests/system directory. e.g.: - - Run all test suites: :: - - nosetests -v - -Adding New Tests ----------------- - -#. Add test case to an appropriate test case file: :: - - queue/test_queue.py - messages/test_messages.py - claim/test_claims.py - -#. Add any validation logic you might need, to the following utility modules: - - * corresponing \*fnlib.py (e.g. queue/queuefnlib.py) - * common/functionlib.py (i.e., if the code can be used - across multiple test suites) - - - -.. _README : https://github.com/stackforge/marconi/blob/master/README.rst -.. _requests : https://pypi.python.org/pypi/requests diff --git a/marconi/tests/system/__init__.py b/marconi/tests/system/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/marconi/tests/system/claim/__init__.py b/marconi/tests/system/claim/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/marconi/tests/system/claim/claimfnlib.py b/marconi/tests/system/claim/claimfnlib.py deleted file mode 100644 index 94bab5e..0000000 --- a/marconi/tests/system/claim/claimfnlib.py +++ /dev/null @@ -1,223 +0,0 @@ -# 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 __future__ import print_function -import json - -from marconi.tests.system.common import functionlib -from marconi.tests.system.common import http - - -def verify_claim_msg(count, *claim_response): - """Verifies claim messages. - - Validation steps include - verifying the - 1. number of messages returned is <= limit specified. - 2. query claim & verifying the response. - :param count: limit specified in the claim request. - :param claim_response : [header, body] returned for post claim request. - """ - test_result_flag = False - - headers = claim_response[0] - body = claim_response[1] - - test_result_flag = verify_claim_msglength(count, body) - if test_result_flag: - test_result_flag = query_claim(headers, body) - else: - print('More msgs returned than specified in limit') - return test_result_flag - - -def verify_claim_msglength(count, *body): - """Validates that number of messages returned is <= limit specified. - - :param count: value of limit specified in the post claim. - :param *body: response body returned for the post claim. - """ - msg_list = body - msg_list = json.loads(msg_list[0]) - return (len(msg_list) <= count) - - -def query_claim(headers, *body): - """Verifies the response of post claim. - - Does a Query Claim using the href in post claim. - Compares the messages returned in Query claim with the messages - returned on Post Claim. - :param headers: headers returned in the post claim response. - :param *body: message list returned in the post claim response. - """ - test_result_flag = False - - msg_list = body[0] - msg_list = json.loads(msg_list) - - location = headers['Location'] - url = functionlib.create_url_from_appender(location) - header = functionlib.create_marconi_headers() - - get_msg = http.get(url, header) - if get_msg.status_code == 200: - query_body = json.loads(get_msg.text) - query_msgs = query_body['messages'] - test_result_flag = verify_query_msgs(query_msgs, msg_list) - - if not test_result_flag: - print('URL') - print(url) - print('HEADER') - print(header) - print('Messages returned by Query Claim') - print(query_msgs) - print('# of Messages returned by Query Claim', len(query_msgs)) - print('Messages returned by Claim Messages') - print(msg_list) - print('# of Messages returned by Claim messages', len(msg_list)) - print('Query Claim Failed') - return test_result_flag - - -def verify_query_msgs(querymsgs, msg_list): - """Verifies response from Query claim. - - Compares the messages returned in Query Claim with the messages - returned when the claim was posted. - :param querymsgs: response body returned for Query Claim. - :param msg_list: message list returned for the original claim. - """ - test_result_flag = True - idx = 0 - - for msg in querymsgs: - if ((msg['body'] != msg_list[idx]['body']) or - (msg['href'] != msg_list[idx]['href']) or - (msg['ttl'] != msg_list[idx]['ttl'])): - test_result_flag = False - idx = idx + 1 - - return test_result_flag - - -def verify_patch_claim(url, header, ttl_extended): - """Verifies if patch claim was successful. - - The following steps are performed for the verification. - 1. GETs the claim - 2. Checks tht the actual claim TTL value is > TTL in the patch request - - :param ttl_extended : TTL posted in the patch request. - """ - test_result_flag = True - - get_claim = http.get(url, header) - response_body = json.loads(get_claim.text) - - ttl = response_body['ttl'] - if ttl < ttl_extended: - print(get_claim.status_code) - print(get_claim.headers) - print(get_claim.text) - test_result_flag = False - - return test_result_flag - - -def create_urllist_fromhref(*response): - """EXtracts href & creates a url list. - - :param *response : http response containing the list of messages. - """ - rspbody = json.loads(response[1]) - urllist = [functionlib.create_url_from_appender(item['href']) - for item in rspbody] - return urllist - - -def delete_claimed_msgs(*claim_response): - """Deletes claimed messages. - - Verifies that the deletes were successful with a GET on the deleted msg. - :param *claim_response: [header, body] returned for post claim request. - """ - test_result_flag = False - - urllist = create_urllist_fromhref(*claim_response) - header = functionlib.create_marconi_headers() - - for url in urllist: - delete_response = http.delete(url, header) - if delete_response.status_code == 204: - test_result_flag = functionlib.verify_delete(url, header) - else: - print('DELETE message with claim ID: {}'.format(url)) - print(delete_response.status_code) - print(delete_response.headers) - print(delete_response.text) - print('Delete Claimed Message Failed') - - return test_result_flag - - -def get_claimed_msgs(*claim_response): - """Does get on all messages returned in the claim. - - :param *claim_response: [header, body] returned for post claim request. - """ - test_result_flag = True - - urllist = create_urllist_fromhref(*claim_response) - header = functionlib.create_marconi_headers() - - for url in urllist: - get_response = http.get(url, header) - if get_response.status_code != 200: - print(url) - print(header) - print('Get Response Code: {}'.format(get_response.status_code)) - test_result_flag = False - - if not test_result_flag: - assert test_result_flag, 'Get Claimed message Failed' - - -def release_claim(*claim_response): - """Deletes claim & verifies the delete was successful. - - Extracts claim id from the POST response input & deletes the claim. - If DELETE claim succeeds, verifies that a GET claim returns 404. - :param *claim_response: [header, body] returned for post claim request. - """ - test_result_flag = False - - headers = claim_response[0] - location = headers['Location'] - url = functionlib.create_url_from_appender(location) - header = functionlib.create_marconi_headers() - - release_response = http.delete(url, header) - if release_response.status_code == 204: - test_result_flag = functionlib.verify_delete(url, header) - else: - print('Release Claim HTTP code:{}'.format( - release_response.status_code)) - print(release_response.headers) - print(release_response.text) - assert test_result_flag, 'Release Claim Failed' - - if not test_result_flag: - assert test_result_flag, 'Get claim after the release failed' diff --git a/marconi/tests/system/claim/test_claims.py b/marconi/tests/system/claim/test_claims.py deleted file mode 100644 index b6389dc..0000000 --- a/marconi/tests/system/claim/test_claims.py +++ /dev/null @@ -1,240 +0,0 @@ -# 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.tests.system.claim import claimfnlib -from marconi.tests.system.common import config -from marconi.tests.system.common import functionlib -from marconi.tests.system.common import http -from marconi.tests.system.messages import msgfnlib - -import ddt -import json - - -@ddt.ddt -class TestClaims(functionlib.TestUtils): - """Tests for Claims.""" - - def setUp(self): - super(TestClaims, self).setUp() - self.cfg = config.Config() - self.header = functionlib.create_marconi_headers() - - self.headers_response_with_body = set(['location', - 'content-type']) - - def test_000_claim_setup(self): - """Create Queue, Post Messages for Claim Tests.""" - url = self.cfg.base_url + '/queues/claimtestqueue' - - result = http.put(url, self.header) - self.assertEqual(result.status_code, 201) - - #Post Messages - url = self.cfg.base_url + '/queues/claimtestqueue/messages' - doc = msgfnlib.get_message_body(messagecount= - self.cfg.message_paging_uplimit) - for i in range(25): - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - test_000_claim_setup.tags = ['smoke', 'positive'] - - def test_001_claim_2messages(self): - """Claim 2 messages.""" - message_count = 2 - url = self.cfg.base_url + '/queues/claimtestqueue/claims?limit=2' - doc = '{"ttl": 300, "grace": 100}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - test_result_flag = claimfnlib.verify_claim_msg( - message_count, result.headers, result.text) - self.assertEqual(test_result_flag, True) - - response_headers = set(result.headers.keys()) - self.assertIsSubset(self.headers_response_with_body, response_headers) - - test_001_claim_2messages.tags = ['smoke', 'positive'] - - def test_002_claim_default_messages(self): - """Claim messages with no URL parameters. - - By default, Marconi will return 10 messages. - """ - default_message_count = 10 - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = '{"ttl": 300, "grace": 100}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - test_result_flag = claimfnlib.verify_claim_msg( - default_message_count, result.headers, result.text) - self.assertEqual(test_result_flag, True) - - test_002_claim_default_messages.tags = ['smoke', 'positive'] - - def test_003_claim_more_than_allowed(self): - """Claim more than max allowed per request. - - Marconi allows a maximum of 20 messages per claim. - """ - url = self.cfg.base_url + '/queues/claimtestqueue/claims?limit=' + \ - str(self.cfg.message_paging_uplimit + 1) - doc = '{"ttl": 300, "grace": 100}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 400) - - test_003_claim_more_than_allowed.tags = ['negative'] - - def test_004_claim_patch(self): - """Update Claim.""" - #Test Setup - Post Claim - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = '{"ttl": 300, "grace": 400}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - #Patch Claim - claim_location = result.headers['Location'] - url = self.cfg.base_server + claim_location - doc_updated = '{"ttl": 300}' - - result = http.patch(url, self.header, doc_updated) - self.assertEqual(result.status_code, 204) - - test_result_flag = claimfnlib.verify_patch_claim(url, - self.header, 300) - self.assertEqual(test_result_flag, True) - - test_004_claim_patch.tags = ['smoke', 'positive'] - - def test_005_delete_claimed_message(self): - """Delete message belonging to a Claim.""" - #Test Setup - Post claim - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = '{"ttl": 60, "grace": 60}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - #Delete Claimed Message & Verify the delete - test_result_flag = claimfnlib.delete_claimed_msgs( - result.headers, result.text) - self.assertEqual(test_result_flag, True) - - test_005_delete_claimed_message.tags = ['smoke', 'positive'] - - def test_006_claim_release(self): - """Release Claim.""" - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = '{"ttl": 300, "grace": 100}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - #Extract claim location and construct the claim URL. - location = result.headers['Location'] - url = self.cfg.base_server + location - - #Release Claim. - result = http.delete(url, self.header) - self.assertEqual(result.status_code, 204) - - test_006_claim_release.tags = ['smoke', 'positive'] - - @ddt.data(10000000000000000000, -100, 1, 59, 43201, -10000000000000000000) - def test_007_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. - """ - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = {"ttl": ttl, "grace": 100} - - result = http.post(url, self.header, json.dumps(doc)) - self.assertEqual(result.status_code, 400) - - test_007_claim_invalid_ttl.tags = ['negative'] - - @ddt.data(10000000000000000000, -100, 1, 59, 43201, -10000000000000000000) - def test_008_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. - """ - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = {"ttl": 100, "grace": grace} - - result = http.post(url, self.header, json.dumps(doc)) - self.assertEqual(result.status_code, 400) - - test_008_claim_invalid_grace.tags = ['negative'] - - @ddt.data(0, -100, 30, 10000000000000000000) - def test_009_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). - """ - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = {"ttl": 100, "grace": grace} - - result = http.post(url, self.header, json.dumps(doc)) - self.assertEqual(result.status_code, 400) - - test_009_claim_invalid_limit.tags = ['negative'] - - @ddt.data(10000000000000000000, -100, 1, 59, 43201, -10000000000000000000) - def test_010_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. - """ - url = self.cfg.base_url + '/queues/claimtestqueue/claims' - doc = '{"ttl": 100, "grace": 100}' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - #Extract claim location and construct the claim URL. - location = result.headers['Location'] - url = self.cfg.base_server + location - - #Patch Claim. - doc = {"ttl": ttl} - result = http.patch(url, self.header, json.dumps(doc)) - self.assertEqual(result.status_code, 400) - - test_010_patch_claim_invalid_ttl.tags = ['negative'] - - def test_999_claim_teardown(self): - """Delete Queue after Claim Tests.""" - url = self.cfg.base_url + '/queues/claimtestqueue' - - result = http.delete(url, self.header) - self.assertEqual(result.status_code, 204) - - test_999_claim_teardown.tags = ['smoke', 'positive'] diff --git a/marconi/tests/system/common/__init__.py b/marconi/tests/system/common/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/marconi/tests/system/common/functionlib.py b/marconi/tests/system/common/functionlib.py deleted file mode 100644 index f78ed0d..0000000 --- a/marconi/tests/system/common/functionlib.py +++ /dev/null @@ -1,185 +0,0 @@ -# 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 __future__ import print_function -import binascii -import json -import os -import string -import testtools - -from marconi.tests.system.common import config -from marconi.tests.system.common import http - - -CFG = config.Config() - - -def get_keystone_token(): - """Gets Keystone Auth token.""" - req_json = { - 'auth': { - 'passwordCredentials': { - 'username': CFG.username, - 'password': CFG.password - }, - }, - } - - header = '{"Host": "identity.api.rackspacecloud.com",' - header += '"Content-Type": "application/json","Accept":"application/json"}' - url = CFG.auth_url - - response = http.post(url=url, header=header, body=req_json) - response_body = json.loads(response.text) - - auth_token = response_body['access']['token']['id'] - - return auth_token - - -def get_auth_token(): - """Returns a valid auth token if auth is turned on.""" - if CFG.auth_enabled: - auth_token = get_keystone_token() - else: - auth_token = 'notrealtoken' - - return auth_token - - -def create_marconi_headers(): - """Returns headers to be used for all Marconi requests.""" - auth_token = get_auth_token() - - headers = ('{"Host": "$host","User-Agent": "$user_agent","Date":"DATE",' - '"Accept": "application/json","Accept-Encoding": "gzip",' - '"X-Project-ID": "$project_id",' - '"X-Auth-Token": "$token","Client-ID": "$uuid"}') - headers = string.Template(headers) - - return headers.substitute(host=CFG.host, user_agent=CFG.user_agent, - project_id=CFG.project_id, - token=auth_token, uuid=CFG.uuid) - - -def invalid_auth_token_header(): - """Returns a header with invalid auth token.""" - headers = '{"Host":"$host","User-Agent":"$user_agent","Date":"DATE",' - headers += '"Accept": "application/json","Accept-Encoding": "gzip",' - headers += '"X-Project-ID": "$project_id",' - headers += '"X-Auth-Token": "InvalidToken"}' - headers = string.Template(headers) - - return headers.substitute(host=CFG.host, - project_id=CFG.project_id, - user_agent=CFG.user_agent) - - -def plain_text_in_header(): - """Returns headers to be used for all Marconi requests.""" - auth_token = get_auth_token() - - headers = '{"Host":"$host","User-Agent":"$user_agent","Date":"DATE",' - headers += '"Accept": "text/plain","Accept-Encoding": "gzip",' - headers += '"X-Project-ID": "$project_id",' - headers += '"X-Auth-Token": "$token","Client-ID": "$uuid"}' - headers = string.Template(headers) - - return headers.substitute(host=CFG.host, user_agent=CFG.user_agent, - project_id=CFG.project_id, - token=auth_token, uuid=CFG.uuid) - - -def asterisk_in_header(): - """Returns headers to be used for all Marconi requests.""" - auth_token = get_auth_token() - - headers = '{"Host":"$host","User-Agent":"$user_agent","Date":"DATE",' - headers += '"Accept": "*/*","Accept-Encoding": "gzip",' - headers += '"X-Project-ID": "$project_id",' - headers += '"X-Auth-Token": "$token"}' - headers = string.Template(headers) - - return headers.substitute(host=CFG.host, user_agent=CFG.user_agent, - project_id=CFG.project_id, token=auth_token) - - -def get_headers(input_header): - """Creates http request headers. - - 1. If header value is specified in the test_data.csv, that will be used. - 2. Headers can also be substituted in the Robot test case definition - file (*_tests.txt) - 3. If 1. & 2. is not true --> - Replaces the header data with generic Marconi headers. - """ - if input_header: - header = input_header - else: - header = create_marconi_headers() - - return header - - -def get_custom_body(kwargs): - """Returns a custom request body.""" - req_body = {'data': '[DATA]'} - if 'metadatasize' in kwargs.keys(): - random_data = binascii.b2a_hex(os.urandom(kwargs['metadatasize'])) - req_body['data'] = random_data - - return json.dumps(req_body) - - -def create_url_from_appender(appender): - """Returns complete url using the appender (with a a preceding '/').""" - next_url = str(CFG.base_server + appender) - return(next_url) - - -def get_url_from_location(header): - """returns : the complete url referring to the location.""" - location = header['location'] - url = create_url_from_appender(location) - return url - - -def verify_delete(url, header): - """Verifies the DELETE was successful, with a GET on the deleted item.""" - test_result_flag = False - - getmsg = http.get(url, header) - if getmsg.status_code == 404: - test_result_flag = True - else: - print('GET after DELETE failed') - print('URL') - print(url) - print('headers') - print(header) - print('Response Body') - print(getmsg.text) - print('GET Code {}'.format(getmsg.status_code)) - - return test_result_flag - - -class TestUtils(testtools.TestCase): - - def assertIsSubset(self, required_values, actual_values): - form = 'Missing Header(s) - {}' - self.assertTrue(required_values.issubset(actual_values), - msg=form.format((required_values - actual_values))) diff --git a/marconi/tests/system/etc/__init__.py b/marconi/tests/system/etc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/marconi/tests/system/messages/__init__.py b/marconi/tests/system/messages/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/marconi/tests/system/messages/msgfnlib.py b/marconi/tests/system/messages/msgfnlib.py deleted file mode 100644 index 2743184..0000000 --- a/marconi/tests/system/messages/msgfnlib.py +++ /dev/null @@ -1,210 +0,0 @@ -# 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 __future__ import print_function -import json -import random -import string - -from marconi.tests.system.common import config -from marconi.tests.system.common import functionlib -from marconi.tests.system.common import http - - -def generate_dict(dict_length): - """Returns dictionary of specified length. Key:Value is random data. - - :param dict_length: length of the dictionary - """ - dict = {} - while len(dict) < dict_length: - key = generate_random_string() - value = generate_random_string() - dict[key] = value - return dict - - -def generate_random_string(length=10): - """Returns an ASCII string of specified length.""" - chars = string.ascii_letters + string.digits - return ''.join(random.choice(chars) for i in range(length)) - - -def single_message_body(**kwargs): - """Returns message body for one message . - - The ttl will be a random value (60 <= TTL <= 1209600). - The message body will be random dict. - :param **kwargs: can be {messagesize: x} , where x is message size - :param **kwargs: can be {ttl: x} , where x is ttl in seconds - """ - valid_ttl = random.randint(60, 1209600) - - if 'messagesize' in kwargs.keys(): - body = generate_dict(kwargs['messagesize']) - else: - body = generate_dict(2) - - if 'ttl' in kwargs.keys(): - ttl = kwargs['ttl'] - else: - ttl = valid_ttl - - message_body = {'ttl': ttl, 'body': body} - return message_body - - -def get_message_body(**kwargs): - """Returns request body for post message tests. - - :param **kwargs: can be {messagecount: x} , where x is the # of messages. - """ - message_count = kwargs['messagecount'] - multiple_message_body = [] - for i in range(message_count): - message_body = single_message_body(**kwargs) - multiple_message_body.append(message_body) - return multiple_message_body - - -def create_url(*msg_id_list): - """Creates url list for retrieving messages with message id.""" - cfg = config.Config() - url = [(cfg.base_url + msg_id) for msg_id in msg_id_list] - return url - - -def verify_msg_length(count=10, *msg_list): - """Verifies the number of messages returned. - - :param count: limit specified in the GET url. - :param *msg_list : list of message returned in the GET. - """ - test_result_flag = False - msg_body = json.loads(msg_list[0]) - msg_list = msg_body['messages'] - msg_count = len(msg_list) - if (msg_count <= count): - test_result_flag = True - else: - return test_result_flag - return test_result_flag - - -def get_href(*msg_list): - """Extracts href. - - :param *msg_list: list of messages returned by the server. - """ - msg_body = json.loads(msg_list[0]) - link = msg_body['links'] - href = link[0]['href'] - return href - - -def verify_post_msg(msg_headers, posted_body): - """Verifies the response of POST Message(s). - - Retrieves the posted Message(s) & validates the message metadata. - :param msg_headers: headers returned for post message request. - :param posted_body: message metadata(s) in the post message request. - """ - test_result_flag = False - - location = msg_headers['location'] - url = functionlib.create_url_from_appender(location) - header = functionlib.create_marconi_headers() - - getmsg = http.get(url, header) - if getmsg.status_code == 200: - test_result_flag = True - else: - print('Failed to GET {}'.format(url)) - print('Request Header') - print(header) - print('Response Headers') - print(getmsg.headers) - print('Response Body') - print(getmsg.text) - return test_result_flag - - -def get_next_msgset(responsetext): - """Follows the href path & GETs the next batch of messages recursively.""" - test_result_flag = False - - href = get_href(responsetext) - url = functionlib.create_url_from_appender(href) - header = functionlib.create_marconi_headers() - - getmsg = http.get(url, header) - if getmsg.status_code == 200: - return get_next_msgset(getmsg.text) - elif getmsg.status_code == 204: - test_result_flag = True - return test_result_flag - else: - test_result_flag = False - print('Failed to GET {}'.format(url)) - print(getmsg.text) - assert test_result_flag, 'HTTP code {}'.format(getmsg.status_code) - - -def verify_get_msgs(count, *getresponse): - """Verifies GET message & does a recursive GET if needed. - - :param count: limit value specified in the get message request. - :param *getresponse: [headers, body] returned for get message request. - """ - test_result_flag = False - - body = getresponse[1] - - msglengthflag = verify_msg_length(count, body) - if msglengthflag: - test_result_flag = get_next_msgset(body) - else: - print('Messages returned exceed requested number of messages') - test_result_flag = False - - return test_result_flag - - -def delete_msg(*postresponse): - """Post DELETE message & verifies that a subsequent GET returns 404. - - :param *postresponse: [headers, body] returned for post message request. - """ - test_result_flag = False - headers = str(postresponse[0]) - headers = headers.replace("'", '"') - headers = json.loads(headers) - location = headers['location'] - url = functionlib.create_url_from_appender(location) - header = functionlib.create_marconi_headers() - deletemsg = http.delete(url, header) - if deletemsg.status_code == 204: - test_result_flag = functionlib.verify_delete(url, header) - else: - print('DELETE message failed') - print('URL') - print(url) - print('headers') - print(header) - print('Response Body') - print(deletemsg.text) - print('DELETE Code {}'.format(deletemsg.status_code)) - - return test_result_flag diff --git a/marconi/tests/system/messages/test_messages.py b/marconi/tests/system/messages/test_messages.py deleted file mode 100644 index b9ccfe4..0000000 --- a/marconi/tests/system/messages/test_messages.py +++ /dev/null @@ -1,304 +0,0 @@ -# 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.tests.system.common import config -from marconi.tests.system.common import functionlib -from marconi.tests.system.common import http -from marconi.tests.system.messages import msgfnlib - -import ddt - - -@ddt.ddt -class TestMessages(functionlib.TestUtils): - - """Tests for Messages.""" - - def setUp(self): - super(TestMessages, self).setUp() - self.cfg = config.Config() - self.header = functionlib.create_marconi_headers() - - self.headers_response_with_body = set(['location', - 'content-type']) - - def test_000_message_setup(self): - """Create Queue for Message Tests.""" - url = self.cfg.base_url + '/queues/messagetestqueue' - result = http.put(url, self.header) - - self.assertEqual(result.status_code, 201) - - test_000_message_setup.tags = ['smoke', 'positive'] - - def test_001_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 = msgfnlib.get_message_body(messagecount=1) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - - result = http.post(url, self.header, 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.base_server + href - - result = http.get(url, self.header) - 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.cfg.base_url + '/queues/messagetestqueue/claims' - doc = '{"ttl": 300, "grace": 100}' - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - url = self.cfg.base_url + '/queues/messagetestqueue/messages' \ - '?include_claimed=true&echo=true' - result = http.get(url, self.header) - 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 - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - result = http.get(url, self.header) - self.assertEqual(result.status_code, 204) - - test_001_message_single_insert.tags = ['smoke', 'positive'] - - def test_002_message_bulk_insert(self): - """Bulk Insert Messages into the Queue.""" - message_count = 10 - doc = msgfnlib.get_message_body(messagecount=message_count) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - #GET on posted messages - location = result.headers['location'] - url = self.cfg.base_server + location - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - - #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_002_message_bulk_insert.tags = ['smoke', 'positive'] - - def test_003_message_get_no_params(self): - """Get Messages with no params.""" - default_msg_count = 10 - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - - test_result_flag = msgfnlib.verify_get_msgs(default_msg_count, - result.headers, - result.text) - self.assertEqual(test_result_flag, True) - - test_003_message_get_no_params.tags = ['smoke', 'positive'] - - def test_004_message_get_limit_5(self): - """Get Messages with no params.""" - msg_count = 5 - url = self.cfg.base_url + '/queues/messagetestqueue/messages?limit=5' - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - - test_result_flag = msgfnlib.verify_get_msgs(msg_count, - result.headers, - result.text) - self.assertEqual(test_result_flag, True) - - test_004_message_get_limit_5.tags = ['smoke', 'positive'] - - def test_005_message_delete(self): - """Delete Message.""" - doc = msgfnlib.get_message_body(messagecount=1) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - result = http.post(url, self.header, doc) - - self.assertEqual(result.status_code, 201) - - #Delete posted message - href = result.json()['resources'][0] - url = self.cfg.base_server + href - - result = http.delete(url, self.header) - self.assertEqual(result.status_code, 204) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 404) - - test_005_message_delete.tags = ['smoke', 'positive'] - - def test_006_message_bulk_delete(self): - """Bulk Delete Messages.""" - doc = msgfnlib.get_message_body(messagecount=10) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - result = http.post(url, self.header, doc) - - self.assertEqual(result.status_code, 201) - - #Delete posted messages - location = result.headers['Location'] - url = self.cfg.base_server + location - - result = http.delete(url, self.header) - self.assertEqual(result.status_code, 204) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 204) - - test_006_message_bulk_delete.tags = ['smoke', 'positive'] - - def test_007_message_delete_nonexisting(self): - """Delete non-existing Messages.""" - url = self.cfg.base_url + '/queues/messagetestqueue/messages' \ - '/non-existing' - result = http.delete(url, self.header) - - self.assertEqual(result.status_code, 204) - - test_007_message_delete_nonexisting.tags = ['negative'] - - def test_008_message_partial_delete(self): - """Delete Messages will be partially successful.""" - doc = msgfnlib.get_message_body(messagecount=3) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - result = http.post(url, self.header, doc) - - self.assertEqual(result.status_code, 201) - - #Delete posted message - location = result.headers['Location'] - url = self.cfg.base_server + location - url += ',nonexisting' - result = http.delete(url, self.header) - self.assertEqual(result.status_code, 204) - - test_008_message_partial_delete.tags = ['negative'] - - def test_009_message_partial_get(self): - """Get Messages will be partially successful.""" - doc = msgfnlib.get_message_body(messagecount=3) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - result = http.post(url, self.header, doc) - - self.assertEqual(result.status_code, 201) - - #Get posted message and a nonexisting message - location = result.headers['Location'] - url = self.cfg.base_server + location - url += ',nonexisting' - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - - test_009_message_partial_get.tags = ['negative'] - - def test_010_message_bulk_insert_60(self): - """Insert more than max allowed messages. - - Marconi allows a maximum of 50 message per POST. - """ - doc = msgfnlib.get_message_body(messagecount=60) - url = self.cfg.base_url + '/queues/messagetestqueue/messages' - - result = http.post(url, self.header, doc) - self.assertEqual(result.status_code, 400) - - test_010_message_bulk_insert_60.tags = ['negative'] - - @ddt.data(10000000000000000000, -100, 0, 30, -10000000000000000000) - def test_011_message_get_invalid_limit(self, limit): - """Get Messages with invalid value for limit. - - Allowed values for limit are 0 < limit <= 20(configurable). - """ - url = self.cfg.base_url + '/queues/messagetestqueue/messages?limit=' \ - + str(limit) - result = http.get(url, self.header) - self.assertEqual(result.status_code, 400) - - test_011_message_get_invalid_limit.tags = ['negative'] - - def test_012_message_bulk_delete(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.cfg.base_url + '/queues/messagetestqueue/messages?ids=' \ - + ','.join(str(i) for i in - range(self.cfg.message_paging_uplimit + 1)) - result = http.delete(url, self.header) - - self.assertEqual(result.status_code, 400) - - test_012_message_bulk_delete.tags = ['negative'] - - def test_013_message_bulk_get(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.cfg.base_url + '/queues/messagetestqueue/messages?ids=' \ - + ','.join(str(i) for i in - range(self.cfg.message_paging_uplimit + 1)) - result = http.get(url, self.header) - - self.assertEqual(result.status_code, 400) - - test_013_message_bulk_get.tags = ['negative'] - - def test_014_get_messages_malformed_marker(self): - """Get messages with non-existing marker.""" - url = self.cfg.base_url + '/queues/messagetestqueue/messages' \ - '?marker=invalid' - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 204) - - test_014_get_messages_malformed_marker.tags = ['negative'] - - def test_999_message_teardown(self): - url = self.cfg.base_url + '/queues/messagetestqueue' - http.delete(url, self.header) - - test_999_message_teardown.tags = ['smoke', 'positive'] diff --git a/marconi/tests/system/queue/__init__.py b/marconi/tests/system/queue/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/marconi/tests/system/queue/queuefnlib.py b/marconi/tests/system/queue/queuefnlib.py deleted file mode 100644 index 56681df..0000000 --- a/marconi/tests/system/queue/queuefnlib.py +++ /dev/null @@ -1,146 +0,0 @@ -# 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 __future__ import print_function -import binascii -import json -import os - -from marconi.tests.system.common import functionlib -from marconi.tests.system.common import http - - -def verify_queue_stats(*get_response): - """Verifies GET queue/stats response. - - Verification Steps: - 1. stats json body has the keys - action & messages. - 2. messages json has the keys - claimed & free. - 3. claimed & free key values are int. - :param *getresponse: [headers, body] returned for get queue stats. - """ - - test_result_flag = True - headers = get_response[0] - body = json.loads(get_response[1]) - - keys_in_body = body.keys() - keys_in_body.sort() - - if (keys_in_body == ['messages']): - stats = body['messages'] - keys_in_stats = stats.keys() - keys_in_stats.sort() - if (keys_in_stats == ['claimed', 'free', 'total']): - try: - int(stats['claimed']) - int(stats['free']) - int(stats['total']) - except ValueError: - test_result_flag = False - else: - test_result_flag = False - else: - test_result_flag = False - - if test_result_flag: - return test_result_flag - else: - print(headers) - print(body) - assert test_result_flag, 'Get Request stats failed' - - -def get_queue_name(namelength=65): - """Returns a queuename of specified length. - - By default, a name longer than Marconi allows - currently 64 char. - :param namelength: length of the queue name. - """ - - appender = '/queues/' + binascii.b2a_hex(os.urandom(namelength)) - return appender - - -def verify_list_queues(*list_queue_response): - """Verifies the response of list queues. - - :param *list_queue_response: [header, body] returned for list queue. - """ - test_result_flag = True - response_body = json.loads(list_queue_response[1]) - links = response_body['links'] - href = links[0]['href'] - detail_enabled = 'detailed=true' in href - - queue_list = response_body['queues'] - test_result_flags = [verify_listed(queue, detail_enabled) - for queue in queue_list] - - if False in test_result_flags: - test_result_flag = False - print('List Queue API response: {}'.format(response_body)) - return test_result_flag - - if links[0]['rel'] == 'next': - test_result_flag = list_queues(href) - - return test_result_flag - - -def verify_listed(queue, detail_enabled): - """Verifies the listed queues. - - :param queue: queue returned in the list queues response. - :param detail_enabled: indicates if queue contains metadata - """ - test_result_flag = True - - keys = queue.keys() - keys.sort() - - if detail_enabled: - expected_keys = ['href', 'metadata', 'name'] - else: - expected_keys = ['href', 'name'] - - if keys == expected_keys: - return test_result_flag - else: - print('list_queue response does not match expected response') - print(queue) - test_result_flag = False - - return test_result_flag - - -def list_queues(href): - """Lists queue using the href value. - - :param href: href returned by a previous list queue request. - """ - test_result_flag = False - - url = functionlib.create_url_from_appender(href) - header = functionlib.create_marconi_headers() - - list_queue_response = http.get(url, header) - if list_queue_response.status_code == 200: - headers = list_queue_response.headers - text = list_queue_response.text - test_result_flag = verify_list_queues(headers, text) - elif list_queue_response.status_code == 204: - test_result_flag = True - return test_result_flag diff --git a/marconi/tests/system/queue/test_queue.py b/marconi/tests/system/queue/test_queue.py deleted file mode 100644 index 2c46192..0000000 --- a/marconi/tests/system/queue/test_queue.py +++ /dev/null @@ -1,376 +0,0 @@ -# -*- coding: utf-8 -*- -# 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.tests.system.common import config -from marconi.tests.system.common import functionlib -from marconi.tests.system.common import http -from marconi.tests.system.queue import queuefnlib - -import ddt -import json - - -@ddt.ddt -class TestQueue(functionlib.TestUtils): - """Tests for queue.""" - - def setUp(self): - super(TestQueue, self).setUp() - self.cfg = config.Config() - self.header = functionlib.create_marconi_headers() - - self.headers_response_empty = set(['location']) - self.headers_response_with_body = set(['content-location', - 'content-type']) - - def test_001_insert_queue(self): - """Create Queue.""" - url = self.cfg.base_url + '/queues/qtestqueue' - - result = http.put(url, self.header) - self.assertEqual(result.status_code, 201) - - response_headers = set(result.headers.keys()) - self.assertIsSubset(self.headers_response_empty, response_headers) - - url = self.cfg.base_url + '/queues/qtestqueue/metadata' - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - self.assertEqual(result.json(), {}) - - http.delete(url, self.header) - - test_001_insert_queue.tags = ['smoke', 'positive', 'create_queue'] - - def test_002_insert_queue_case_insensitive(self): - """Insert Queue with same name, different case.""" - url = self.cfg.base_url + '/queues/QteStquEue' - - result = http.put(url, self.header) - self.assertEqual(result.status_code, 201) - - http.delete(url, self.header) - - test_002_insert_queue_case_insensitive.tags = ['positive'] - - def test_003_insert_queue_empty_json(self): - """Update Queue with empty json.""" - url = self.cfg.base_url + '/queues/emptyjson' - doc = '{}' - - result = http.put(url, self.header, doc) - self.assertEqual(result.status_code, 201) - - http.delete(url, self.header) - - test_003_insert_queue_empty_json.tags = ['smoke', 'positive'] - - def test_004_insert_queue_invalid_authtoken(self): - """Insert Queue with invalid authtoken.""" - url = self.cfg.base_url + '/queues/invalidauthtoken' - header = functionlib.invalid_auth_token_header() - - result = http.put(url, header) - self.assertEqual(result.status_code, 401) - - test_004_insert_queue_invalid_authtoken.tags = ['negative'] - - def test_006_insert_queue_header_plaintext(self): - """Insert Queue with 'Accept': 'plain/text'.""" - url = self.cfg.base_url + '/queues/plaintextheader' - header = functionlib.plain_text_in_header() - - result = http.put(url, header) - self.assertEqual(result.status_code, 406) - - http.delete(url, self.header) - - test_006_insert_queue_header_plaintext.tags = ['negative'] - - def test_007_insert_queue_header_asterisk(self): - """Insert Queue with 'Accept': '*/*'.""" - url = self.cfg.base_url + '/queues/asteriskinheader' - header = functionlib.asterisk_in_header() - - result = http.put(url, header) - self.assertEqual(result.status_code, 201) - - http.delete(url, self.header) - - test_007_insert_queue_header_asterisk.tags = ['positive'] - - def test_008_insert_queue_nonASCII_name(self): - """Insert Queue with non ASCII name.""" - url = self.cfg.base_url + '/queues/汉字漢字' - doc = '{"queue": "non ASCII name"}' - - result = http.put(url, self.header, doc) - self.assertEqual(result.status_code, 400) - - test_008_insert_queue_nonASCII_name.tags = ['negative'] - - def test_009_insert_queue_long_queuename(self): - """Insert queue with name > 64 bytes.""" - url = self.cfg.base_url + queuefnlib.get_queue_name() - result = http.put(url, self.header) - - self.assertEqual(result.status_code, 400) - - test_009_insert_queue_long_queuename.tags = ['negative'] - - def test_010_insert_queue_hyphenated_queuename(self): - """Insert queue with hyphen in name.""" - url = self.cfg.base_url + '/queues/hyphen-name' - result = http.put(url, self.header) - - self.assertEqual(result.status_code, 201) - - http.delete(url, self.header) - - test_010_insert_queue_hyphenated_queuename.tags = ['positive'] - - def test_011_insert_queue_invalid_char(self): - """Insert queue with invalid characters in name.""" - url = self.cfg.base_url + '/queues/@$@^qw)' - result = http.put(url, self.header) - - self.assertEqual(result.status_code, 400) - - test_011_insert_queue_invalid_char.tags = ['negative'] - - def test_012_insert_queue_with_metadata(self): - """Insert queue with a non-empty request body.""" - url = self.cfg.base_url + '/queues/hasmetadata' - doc = '{"queue": "Has Metadata"}' - result = http.put(url, self.header, doc) - - self.assertEqual(result.status_code, 201) - - url = self.cfg.base_url + '/queues/hasmetadata/metadata' - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - self.assertEqual(result.json(), {}) - - url = self.cfg.base_url + '/queues/hasmetadata' - http.delete(url, self.header) - - test_012_insert_queue_with_metadata.tags = ['negative'] - - def test_013_queue_metadata_invalid_authtoken(self): - """Update Queue with invalid authtoken.""" - url = self.cfg.base_url + '/queues/qtestqueue/metadata' - header = functionlib.invalid_auth_token_header() - doc = '{"queue": "invalid auth token"}' - - result = http.put(url, header, doc) - self.assertEqual(result.status_code, 401) - - test_013_queue_metadata_invalid_authtoken.tags = ['negative'] - - def test_014_queue_metadata_toplevel_underscore(self): - """Insert Queue with underscore in toplevel field.""" - url = self.cfg.base_url + '/queues/toplevel' - result = http.put(url, self.header) - - url = self.cfg.base_url + '/queues/toplevel/metadata' - doc = '{"_queue": "Top Level field with _"}' - - result = http.put(url, self.header, doc) - self.assertEqual(result.status_code, 204) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - self.assertEqual(result.json(), json.loads(doc)) - - url = self.cfg.base_url + '/queues/toplevel' - http.delete(url, self.header) - - test_014_queue_metadata_toplevel_underscore.tags = ['negative'] - - def test_015_queue_insert_nonASCII_metadata(self): - """Insert Queue with non ASCII name.""" - url = self.cfg.base_url + '/queues/nonASCIImetadata' - result = http.put(url, self.header) - - url = self.cfg.base_url + '/queues/nonASCIImetadata/metadata' - doc = '{"汉字": "non ASCII metadata"}' - result = http.put(url, self.header, doc) - - self.assertEqual(result.status_code, 204) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - - url = self.cfg.base_url + '/queues/nonASCIImetadata' - result = http.delete(url, self.header) - - test_015_queue_insert_nonASCII_metadata.tags = ['negative'] - - def test_016_queue_insert_metadata_size65535(self): - """Updates Queue with metadata_size = 65535.""" - url = self.cfg.base_url + '/queues/qtestqueue' - result = http.put(url, self.header) - - url = self.cfg.base_url + '/queues/qtestqueue/metadata' - doc = functionlib.get_custom_body({"metadatasize": 65535}) - result = http.put(url, self.header, doc) - - self.assertEqual(result.status_code, 204) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - self.assertEqual(result.json(), json.loads(doc)) - - test_016_queue_insert_metadata_size65535.tags = ['positive'] - - def test_017_queue_insert_metadata_size65536(self): - """Updates Queue with metadata_size = 65536.""" - url = self.cfg.base_url + '/queues/qtestqueue/metadata' - doc = functionlib.get_custom_body({"metadatasize": 65536}) - result = http.put(url, self.header, doc) - - self.assertEqual(result.status_code, 204) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 200) - self.assertEqual(result.json(), json.loads(doc)) - - test_017_queue_insert_metadata_size65536.tags = ['positive'] - - def test_018_queue_insert_metadata_size65537(self): - """Updates Queue with metadata_size = 65537.""" - url = self.cfg.base_url + '/queues/qtestqueue/metadata' - doc = functionlib.get_custom_body({"metadatasize": 65537}) - result = http.put(url, self.header, doc) - - self.assertEqual(result.status_code, 400) - - test_018_queue_insert_metadata_size65537.tags = ['negative'] - - def test_019_queue_insert_metadata_invalidchar(self): - """Update Queues with invalid char in metadata.""" - url = self.cfg.base_url + '/queues/qtestqueue/metadata' - doc = '{"queue": "#$%^&Apple"}' - result = http.put(url, self.header, doc) - - self.assertEqual(result.status_code, 204) - - url = self.cfg.base_url + '/queues/qtestqueue' - - test_019_queue_insert_metadata_invalidchar.tags = ['negative'] - - def test_020_queue_stats(self): - """Insert queue with name > 64 bytes.""" - url = self.cfg.base_url + '/queues/qtestqueue/stats' - result = http.get(url, self.header) - - self.assertEqual(result.status_code, 200) - - test_result_flag = queuefnlib.verify_queue_stats(result.headers, - result.text) - self.assertEqual(test_result_flag, True) - - test_020_queue_stats.tags = ['smoke', 'positive'] - - def test_021_queue_list(self): - """List Queues.""" - url = self.cfg.base_url + '/queues' - result = http.get(url, self.header) - - self.assertEqual(result.status_code, 200) - - test_result_flag = queuefnlib.verify_list_queues(result.headers, - result.text) - self.assertEqual(test_result_flag, True) - - test_021_queue_list.tags = ['smoke', 'positive'] - - def test_022_queue_list_detailed(self): - """List Queues with detailed = True.""" - url = self.cfg.base_url + '/queues?detailed=True' - result = http.get(url, self.header) - - self.assertEqual(result.status_code, 200) - - test_result_flag = queuefnlib.verify_list_queues(result.headers, - result.text) - self.assertEqual(test_result_flag, True) - - test_022_queue_list_detailed.tags = ['smoke', 'positive'] - - @ddt.data(0, -1, 30) - def test_023_queue_list_invalid_limit(self, limit): - """List Queues with a limit value that is not allowed.""" - url = self.cfg.base_url + '/queues?limit=' + str(limit) - result = http.get(url, self.header) - - self.assertEqual(result.status_code, 400) - - test_023_queue_list_invalid_limit.tags = ['negative'] - - def test_024_check_health(self): - """Test health endpoint.""" - url = self.cfg.base_url + '/health' - result = http.get(url, self.header) - - self.assertEqual(result.status_code, 204) - - test_024_check_health.tags = ['positive'] - - def test_025_check_queue_exists(self): - """Checks if queue exists.""" - url = self.cfg.base_url + '/queues/qtestqueue' - http.put(url, self.header) - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 204) - - result = http.head(url, self.header) - self.assertEqual(result.status_code, 204) - - test_025_check_queue_exists.tags = ['positive'] - - def test_026_check_queue_exists(self): - """Checks non-existing queue.""" - url = self.cfg.base_url + '/queues/nonexistingqueue' - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 404) - - result = http.head(url, self.header) - self.assertEqual(result.status_code, 404) - - test_026_check_queue_exists.tags = ['negative'] - - def test_026_get_queue_malformed_marker(self): - """Checks non-existing queue.""" - url = self.cfg.base_url + '/queues/qtestqueue?marker=invalid' - - result = http.get(url, self.header) - self.assertEqual(result.status_code, 204) - - test_026_get_queue_malformed_marker.tags = ['negative'] - - def test_999_delete_queue(self): - """Delete Queue. - - Deletes Queue & performs GET to confirm 404. - """ - url = self.cfg.base_url + '/queues/qtestqueue' - - result = http.delete(url, self.header) - self.assertEqual(result.status_code, 204) - - test_999_delete_queue.tags = ['smoke']