Add System Tests

This patch adds the System level testing framework &
the initial set of tests for Marconi.This will allow
developers to run the system tests (regression suite for
Marconi system level tests), against local Marconi server.

Refer tests/system/README.rst file for details on
how to add new tests and run the existing tests.

Change-Id: I2f47e03091acc260293b19edad69ff5c83888ec3
Implements: blueprint system-tests
This commit is contained in:
Malini Kamalambal 2013-05-02 13:55:28 -04:00
parent a556f392c5
commit 5477b646cc
27 changed files with 1501 additions and 1 deletions

2
.gitignore vendored
View File

@ -9,8 +9,10 @@ build
cover
venv
.venv
output.xml
*.sublime-workspace
*.sqlite
*.html
.*.swp
.DS_Store
.testrepository

View File

@ -1 +1,31 @@
Marconi - Queue and Notification service for OpenStack
**Marconi - Queue and Notification service for OpenStack**
*Steps to run Marconi server locally with MongoDB*
1. `Install mongodb`_ locally
2. Start your local MongoDB instance::
mongod
3. Clone the Marconi repo::
git clone https://github.com/stackforge/marconi.git
4. cd to your local copy of the repo
5. Copy the Marconi config files to the directory ~/.marconi::
cp -r marconi/etc/*.conf-sample ~/.marconi
6. Update the [drivers:storage:mongodb] section in ~/.marconi/marconi.conf
6a. Comment out the uri pointing to replicaset::
;uri = mongodb://db1.example.net,db2.example.net:2500/?replicaSet=test&ssl=true&w=majority
6b. Add a new line with uri pointing to the local mongoDB instance::
uri = mongodb://localhost
7. Run the following command::
python setup.py develop
8. Start the marconi server::
marconi-server
.. _`Install mongodb` : http://docs.mongodb.org/manual/installation/

View File

@ -0,0 +1,63 @@
**Marconi System Tests**
The System tests treat Marconi as a black box.
The API calls are made similar to how an user would make them.
Unlike unit tests, the system tests do not use mock endpoints.
**Running the System Tests**
1. Setup the Marconi server, to run the tests against.
Refer to the Marconi `README`_ on how to run Marconi locally.
(If you are running the tests against an existing server, skip this step.)
2. System tests require the `requests`_ & `robot`_ packages. Run the following to install them ::
pip install -r tools/system-test-requires
3. cd to the marconi/tests/system directory
4. Copy etc/system-tests.conf-sample to one of the following locations::
~/.marconi/system-tests.conf
/etc/marconi/system-tests.conf
5. Update the config file to point to the Marconi server you want to run the tests against
6. If keystone auth is enabled, update system-tests.conf with the credentials.
7. To run tests use the pybot commands,
Run all test suites ::
pybot marconi/tests/system/queue/queue_tests.txt marconi/tests/system/messages/messages_tests.txt marconi/tests/system/claim/claim_tests.txt
Run a specific test suite ::
pybot marconi/tests/system/queue/queue_tests.txt
pybot marconi/tests/system/messages/messages_tests.txt
pybot marconi/tests/system/claim/claim_tests.txt
pybot will generate report.html & log.html after the test run is complete.
**To Add new tests**
1. Add test case definition to the robot test case file (queue/queue_tests.txt, messages/messages_tests.txt, claim/claim_tests.txt)
See `here`_ for more details on writing test cases.
2. Add test data to the test_data.csv in the same directory as the test case file you updated above (eg. queue/test_data.csv)
3. Add any validation logic you might need, to one of the following:
* corresponing \*fnlib.py (eg. queue/queuefnlib.py)
* common/functionlib.py (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
.. _robot : https://pypi.python.org/pypi/robotframework
.. _here : http://robotframework.googlecode.com/hg/doc/userguide/RobotFrameworkUserGuide.html?r=2.7.7#creating-test-cases

View File

View File

View File

@ -0,0 +1,66 @@
| *Setting* | *Value* |
| Documentation | Marconi - Queue Test Suite |
| Library | ../common/http.py |
| Library | ../common/functionlib.py |
| Library | ../messages/msgfnlib.py |
| Library | Collections |
| Library | claimfnlib.py |
| Variables | getdata.py |
| Force Tags | CLAIM |
| Suite Setup | executetests | ${API_TEST_DATA[6]} | # Test Suite Setup - Creates a Queue
| Suite Teardown | executetests | ${API_TEST_DATA[8]} | # Test Suite Teardown - Deletes the queue created by setup
| *Test Case* | *Action* | *Argument* | *Argument* | *Argument* | # Comment
| 0:SUITE SETUP | [DOCUMENTATION] | | Post 500 messages | | #SUITE SETUP - POST MULTIPLE MESSAGES
| | [Tags] | INSERT_MESSAGE | | | #(Robot allows only one keyword in setup)
| | ${reqparam}= | Create Dictionary | messagecount | ${50} | # Specify count of messages to be posted
| | | ... | ttl | ${300} | # Specify count of messages to be posted
| | ${msgbody}= | dummygetmessagebody | ${reqparam} | | # Gets the message body to post
| | Set To Dictionary | ${API_TEST_DATA[7]} | body | ${msgbody} | # Set the POST body
| | :FOR | ${index} | IN RANGE | 10 | # Loop to post 50 messages * 10 times
| | | executetests | ${API_TEST_DATA[7]} | | # postresponse = [httpheaders,httpresponsebod]
| 1:CLAIM 2 MESSAGES | [DOCUMENTATION] | Claim messages | | #TEST CASE 1 - CLAIM 2 MESSAGES
| | ... | with limit = 2 | |
| | @{postresponse}= | executetests | ${API_TEST_DATA[0]} | # Post a claim; postresponse = [httpheaders, httpresponsebody]
| | verifyclaimmsg | ${2} | @{postresponse} |
| 2:CLAIM 5 MESSAGES | [DOCUMENTATION] | Claim messages | | #TEST CASE 2 - CLAIM 5 MESSAGES
| | ... | with limit = 5 | |
| | @{postresponse}= | executetests | ${API_TEST_DATA[1]} | # Post a claim; postresponse = [httpheaders, httpresponsebody]
| | verifyclaimmsg | ${5} | @{postresponse} |
| 3:CLAIM MESSAGES | [DOCUMENTATION] | Claim messages | | #TEST CASE 3 - CLAIM DEFAULT # OF MESSAGES
| | ... | with no params | | (currently 10)
| | @{postresponse}= | executetests | ${API_TEST_DATA[2]} | # postresponse = [httpheaders, httpresponsebody]
| | verifyclaimmsg | ${10} | @{postresponse} |
| 4:CLAIM 15 MESSAGE | [DOCUMENTATION] | Claim messages | | #TEST CASE 4 - CLAIM 15 MESSAGES
| | ... | with limit = 15 | |
| | @{postresponse}= | executetests | ${API_TEST_DATA[3]} | # Post a claim; postresponse = [httpheaders, httpresponsebody]
| | verifyclaimmsg | ${15} | @{postresponse} |
| 5:CLAIM 55 MESSAGE | [DOCUMENTATION] | Claim messages | | #TEST CASE 5 - CLAIM 55 MESSAGES
| | ... | with limit = 55 | |
| | @{postresponse}= | executetests | ${API_TEST_DATA[4]} | # Post a claim; postresponse = [httpheaders, httpresponsebody]
| | verifyclaimmsg | ${50} | @{postresponse} | # MAXIMUM MESSAGES RETURNED IS CURRENTLY 50
| 6: PATCH CLAIM | [DOCUMENTATION] | Patch a claim | | # TEST CASE 6 - UPDATE CLAIM
| | @{postresponse}= | executetests | ${API_TEST_DATA[5]} | # Post a claim; postresponse = [httpheaders, httpresponsebody]
| | patchclaim | @{postresponse} | | # Patch the above claim
| 7: DELETE MESSAGE | [DOCUMENTATION] | Delete message | | # TEST CASE 7 - DELETE A CLAIMED MESSAGE
| | ... | with claim id | |
| | @{postresponse}= | executetests | ${API_TEST_DATA[5]} | # Post a claim
| | deleteclaimedmsgs | @{postresponse} | | # Delete messages returned in the above claim
| 8: PATCH EXPIRED CLAIM | [DOCUMENTATION] | Patch expired claim | | # TEST CASE 8 - UPDATE EXPIRED CLAIM
| | @{postresponse}= | executetests | ${API_TEST_DATA[9]} | # Post a claim with TTL= 1 sec
| | Sleep | 3s | |
| | patchclaim | @{postresponse} | | # Patch the above claim
| 9: DELETE MESSAGE ON EXPIRED CLAIM | [DOCUMENTATION] | Delete message | # TEST CASE 9 - DELETE MESSAGE ON AN EXPIRED CLAIM
| | ... | on expired claim |
| | @{postresponse}= | executetests | ${API_TEST_DATA[10]} | # Post a claim with TTL= 1 sec
| | Sleep | 3s | |
| | deleteclaimedmsgs | @{postresponse} | | # Delete message returned in the above claim
| 10: RELEASE CLAIM | [DOCUMENTATION] | Release claim | | # TEST CASE 10 - RELEASE CLAIM
| | @{postresponse}= | executetests | ${API_TEST_DATA[11]} | # Post a claim with TTL= 1 sec
| | releaseclaim | @{postresponse} | | # Patch the above claim
| 11: GET MESSAGE FROM EXPIRED CLAIM | [DOCUMENTATION] | Get message | # TEST CASE 11 - GET MESSAGE FROM EXPIRED CLAIM
| | ... | from expired claim |
| | @{postresponse}= | executetests | ${API_TEST_DATA[12]} | # Post a claim with TTL= 1 sec
| | Sleep | 3s | |
| | getclaimedmsgs | @{postresponse} | | # Delete message returned in the above claim

View File

@ -0,0 +1,239 @@
# 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 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 : response returned for the claim request
"""
msg_length_flag = False
headers = claim_response[0]
body = claim_response[1]
msg_length_flag = verify_claim_msglength(count, body)
if msg_length_flag:
query_claim(headers, body)
else:
assert msg_length_flag, "More msgs returned than specified in limit"
def verify_claim_msglength(count, *body):
"""Validates that number of messages returned is <= limit specified."""
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.
"""
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 test_result_flag:
return test_result_flag
else:
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)
assert test_result_flag, "Query Claim Failed"
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.
"""
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 patch_claim(*claim_response):
"""Patches a claim & verifies the results.
Extracts claim id from the POST response input & updates the claim.
If PATCH claim succeeds, verifies that the claim TTL is extended.
"""
test_result_flag = False
headers = claim_response[0]
location = headers["Location"]
url = functionlib.create_url_from_appender(location)
header = functionlib.create_marconi_headers()
ttl_value = 300
payload = '{ "ttl": ttlvalue }'
payload = payload.replace("ttlvalue", str(ttl_value))
patch_response = http.patch(url, header, body=payload)
if patch_response.status_code == 204:
test_result_flag = verify_patch_claim(url, header, ttl_value)
else:
print "Patch HTTP Response code: {}".format(patch_response.status_code)
print patch_response.headers
print patch_response.text
assert test_result_flag, "Patch Claim Failed"
if not test_result_flag:
assert test_result_flag, "Query claim after the patch failed"
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 text with 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.
"""
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
assert test_result_flag, "Delete Claimed Message Failed"
if not test_result_flag:
assert test_result_flag, "Get message after DELETE did not return 404"
def get_claimed_msgs(*claim_response):
"""Does get on all messages returned in the claim."""
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.
"""
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"

View File

@ -0,0 +1,37 @@
# 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 csv
from marconi.tests.system.common import config
from marconi.tests.system.common import functionlib
cfg = config.Config()
def get_data():
"""Reads the test data from claim/test_data.csv."""
DATA = []
with open('marconi/tests/system/claim/test_data.csv', 'rb') as datafile:
testdata = csv.DictReader(datafile, delimiter='|')
for row in testdata:
DATA.append(row)
for row in DATA:
row['header'] = functionlib.get_headers(row['header'])
row['url'] = row['url'].replace("<BASE_URL>", cfg.base_url)
return DATA
API_TEST_DATA = get_data()

View File

@ -0,0 +1,15 @@
TestID|httpverb|url|header|body|params|expectedRC|expectedResponseBody
1|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=2||{"ttl": 50, "grace": 60}||200|
2|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=5||{"ttl": 50, "grace": 60}||200|
3|POST |<BASE_URL>/queues/claimtestqueue/claims||{"ttl": 50, "grace": 60}||200|
4|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=15||{"ttl": 50, "grace": 60}||200|
5|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=55||{"ttl": 50, "grace": 60}||200|
6|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=4||{"ttl": 50, "grace": 60}||200|
7|PUT |<BASE_URL>/queues/claimtestqueue||{"ttl": 50, "grace": 60}||201|
0|POST |<BASE_URL>/queues/claimtestqueue/messages||||201|
0|DELETE |<BASE_URL>/queues/claimtestqueue||||204|
8|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=2||{"ttl": 1, "grace": 1}||200|
9|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=2||{"ttl": 1, "grace": 1}||200|
10|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=2||{"ttl": 50, "grace": 60}||200|
11|POST |<BASE_URL>/queues/claimtestqueue/claims?limit=2||{"ttl": 1, "grace": 1}||200
11|GET |||||200
Can't render this file because it contains an unexpected character in line 2 and column 60.

View File

View File

@ -0,0 +1,71 @@
# 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 ConfigParser
import os
import uuid
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'
else:
config_path = os.path.expanduser('~/.marconi'
'/system-tests.conf')
self.parser = ConfigParser.SafeConfigParser()
self.parser.read(config_path)
@property
def auth_enabled(self):
return self.parser.getboolean('auth', 'auth_on')
@property
def username(self):
return self.parser.get('auth', 'username')
@property
def password(self):
return self.parser.get('auth', 'password')
@property
def base_server(self):
return self.parser.get('marconi_env', 'marconi_url')
@property
def marconi_version(self):
return self.parser.get('marconi_env', 'marconi_version')
@property
def tenant_id(self):
return self.parser.get('marconi_env', 'tenant_id')
@property
def base_url(self):
return (self.base_server + '/' + self.marconi_version +
'/' + self.tenant_id)
@property
def uuid(self):
return str(uuid.uuid1())
@property
def user_agent(self):
return self.parser.get('header_values', 'useragent')
@property
def host(self):
return self.parser.get('header_values', 'host')

View File

@ -0,0 +1,200 @@
# 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 binascii
import json
import os
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 == "true":
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>",'
headers += '"Accept": "application/json","Accept-Encoding": "gzip",'
headers += '"X-Auth-Token": "<auth_token>","Client-ID": "<UUID>"}'
headers = headers.replace("<auth_token>", auth_token)
headers = headers.replace("<HOST>", cfg.host)
headers = headers.replace("<USER-AGENT>", cfg.user_agent)
headers = headers.replace("<UUID>", cfg.uuid)
return headers
def invalid_auth_token_header():
"""Returns a header with invalid auth token."""
auth_token = get_auth_token()
headers = '{"Host":"<HOST>","User-Agent":"<USER-AGENT>","Date":"<DATE>",'
headers += '"Accept": "application/json","Accept-Encoding": "gzip",'
headers += '"X-Auth-Token": "<auth_token>"}'
headers = headers.replace("<auth_token>", auth_token)
headers = headers.replace("<HOST>", cfg.host)
headers = headers.replace("<USER-AGENT>", cfg.user_agent)
return headers
def missing_header_fields():
"""Returns a header with missing USER_AGENT header."""
auth_token = get_auth_token()
headers = '{"Host": "<HOST>","Date": "<DATE>",'
headers += '"Accept": "application/json","Accept-Encoding": "gzip",'
headers += '"X-Auth-Token": "<auth_token>"}'
headers = headers.replace("<auth_token>", auth_token)
headers = headers.replace("<HOST>", cfg.host)
return headers
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-Auth-Token": "<auth_token>"}'
headers = headers.replace("<auth_token>", auth_token)
headers = headers.replace("<HOST>", cfg.host)
headers = headers.replace("<USER-AGENT>", cfg.user_agent)
return headers
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-Auth-Token": "<auth_token>"}'
headers = headers.replace("<auth_token>", auth_token)
headers = headers.replace("<HOST>", cfg.host)
headers = headers.replace("<USER-AGENT>", cfg.user_agent)
return headers
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_metadata(get_data, posted_body):
"""@todo(malini) - Really verify the metadata."""
test_result_flag = False
get_data = str(get_data)
posted_body = str(posted_body)
print(get_data, type(get_data))
print(posted_body, type(posted_body))
if get_data in posted_body:
print("AYYY")
else:
test_result_flag = False
print("NAYYY")
return test_result_flag
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
assert test_result_flag, "GET Code {}".format(getmsg.status_code)
return test_result_flag

View File

@ -0,0 +1,176 @@
# 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 json
import requests
def get(url, header='', param=''):
"""Does http GET."""
if header:
header = json.loads(header)
try:
response = requests.get(url, headers=header, params=param)
except requests.ConnectionError as detail:
print("ConnectionError: Exception in http.get {}".format(detail))
except requests.HTTPError as detail:
print("HTTPError: Exception in http.get {}".format(detail))
except requests.Timeout as detail:
print("Timeout: Exception in http.get {}".format(detail))
except requests.TooManyRedirects as detail:
print("TooManyRedirects: Exception in http.get {}".format(detail))
return response
def post(url, header='', body='', param=''):
"""Does http POST."""
if header:
header = json.loads(header)
body = str(body)
body = body.replace("'", '"')
try:
response = requests.post(url, headers=header, data=body,
params=param)
except requests.ConnectionError as detail:
print("ConnectionError: Exception in http.post {}".format(detail))
except requests.HTTPError as detail:
print("HTTPError: Exception in http.post {}".format(detail))
except requests.Timeout as detail:
print("Timeout: Exception in http.post {}".format(detail))
except requests.TooManyRedirects as detail:
print("TooManyRedirects: Exception in http.post {}".format(detail))
return response
def put(url, header='', body='', param=''):
"""Does http PUT."""
response = None
if header:
header = json.loads(header)
try:
response = requests.put(url, headers=header, data=body,
params=param)
except requests.ConnectionError as detail:
print("ConnectionError: Exception in http.put {}".format(detail))
except requests.HTTPError as detail:
print("HTTPError: Exception in http.put {}".format(detail))
except requests.Timeout as detail:
print("Timeout: Exception in http.put {}".format(detail))
except requests.TooManyRedirects as detail:
print("TooManyRedirects: Exception in http.put {}".format(detail))
return response
def delete(url, header='', param=''):
"""Does http DELETE."""
response = None
if header:
header = json.loads(header)
try:
response = requests.delete(url, headers=header, params=param)
except requests.ConnectionError as detail:
print("ConnectionError: Exception in http.delete {}".format(detail))
except requests.HTTPError as detail:
print("HTTPError: Exception in http.delete {}".format(detail))
except requests.Timeout as detail:
print("Timeout: Exception in http.delete {}".format(detail))
except requests.TooManyRedirects as detail:
print("TooManyRedirects: Exception in http.delete {}".format(detail))
return response
def patch(url, header='', body='', param=''):
"""Does http PATCH."""
response = None
if header:
header = json.loads(header)
try:
response = requests.patch(url, headers=header, data=body,
params=param)
except requests.ConnectionError as detail:
print("ConnectionError: Exception in http.patch {}".format(detail))
except requests.HTTPError as detail:
print("HTTPError: Exception in http.patch {}".format(detail))
except requests.Timeout as detail:
print("Timeout: Exception in http.patch {}".format(detail))
except requests.TooManyRedirects as detail:
print("TooManyRedirects: Exception in http.patch {}".format(detail))
return response
def executetests(row):
"""Entry Point for all tests.
Executes the tests defined in the *_tests.txt,
using the test data from *_data.csv.
"""
http_verb = row['httpverb'].strip()
url = row['url']
header = row['header']
params = row['params']
body = row['body']
expected_RC = row['expectedRC']
expected_RC = int(expected_RC)
expected_response_body = row['expectedResponseBody']
response = None
if http_verb == 'GET':
response = get(url, header, params)
elif http_verb == 'POST':
response = post(url, header, body, params)
elif http_verb == 'PUT':
response = put(url, header, body, params)
elif http_verb == 'DELETE':
response = delete(url, header, params)
elif http_verb == 'PATCH':
response = patch(url, header, body, params)
if response is not None:
test_result_flag = verify_response(response, expected_RC)
else:
test_result_flag = False
if test_result_flag:
return response.headers, response.text
else:
print http_verb
print url
print header
print body
print "Actual Response: {}".format(response.status_code)
print "Actual Response Headers"
print response.headers
print"Actual Response Body"
print response.text
print"ExpectedRC: {}".format(expected_RC)
print"expectedresponsebody: {}".format(expected_response_body)
assert test_result_flag, "Actual Response does not match the Expected"
def verify_response(response, expected_RC):
"""Compares the http Response code with the expected Response code."""
test_result_flag = True
actual_RC = response.status_code
actual_response_body = response.text
if actual_RC != expected_RC:
test_result_flag = False
print("Unexpected http Response code {}".format(actual_RC))
print "Response Body returned"
print actual_response_body
return test_result_flag

View File

View File

@ -0,0 +1,15 @@
[auth]
auth_on = true
#auth endpoint - url to get the auth token
url = https://identity.xxx.xxxx.com/v2.0/tokens
username = user
password = secret
[marconi_env]
marconi_url = http://166.78.143.130:80
marconi_version = v1
tenant_id = 1
[header_values]
host = marconi.test.com
useragent = systemtests

View File

@ -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 csv
from marconi.tests.system.common import config
from marconi.tests.system.common import functionlib
cfg = config.Config()
def get_data():
"""Gets Test Data from a csv file."""
DATA = []
with open('marconi/tests/system/messages/test_data.csv', 'rb') as datafile:
test_data = csv.DictReader(datafile, delimiter='|')
for row in test_data:
DATA.append(row)
for row in DATA:
row['header'] = functionlib.get_headers(row['header'])
row['url'] = row['url'].replace("<BASE_URL>", cfg.base_url)
return DATA
API_TEST_DATA = get_data()

View File

@ -0,0 +1,53 @@
| *Setting* | *Value* |
| Documentation | Marconi - Message Test Suite |
| Library | ../common/http.py |
| Library | ../common/functionlib.py |
| Library | msgfnlib.py |
| Library | Collections |
| Variables | getdata.py |
| Force Tags | MESSAGES |
| Suite Setup | executetests | ${API_TEST_DATA[0]} | # Test Suite Setup - Creates a Queue
| Suite Teardown | executetests | ${API_TEST_DATA[7]} | # Test Suite Teardown - Deletes the queue created by setup
| *Test Case* | *Action* | *Argument* | *Argument* | *Argument* | # Comment
| 1:POST SINGLE MESSAGE | [DOCUMENTATION] | | Post single message | | #TEST CASE 1 - POST SINGLE MESSAGE
| | [Tags] | INSERT_MESSAGE | | |
| | ${reqparam}= | Create Dictionary | messagecount | ${1} | # test Setup- Specify count of messages to be posted
| | ${msgbody}= | dummygetmessagebody | ${reqparam} | | # test Setup- Get the message body to post
| | Set To Dictionary | ${API_TEST_DATA[1]} | body | ${msgbody} | # test Setup -Set the POST body
| | @{postresponse}= | executetests | ${API_TEST_DATA[1]} | | # postresponse = [httpheaders, httpresponsebody] |
| | verifypostmsg | ${postresponse[0]} | ${API_TEST_DATA[1]["body"]} | | # GET the posted message & verify metadata
| |
| 2:POST MULTIPLE MESSAGES | [DOCUMENTATION] | | Post 50 messages | | #TEST CASE 2 - POST MULTIPLE MESSAGES
| | [Tags] | INSERT_MESSAGE | | |
| | ${reqparam}= | Create Dictionary | messagecount | ${50} | # Specify count of messages to be posted
| | ${msgbody}= | dummygetmessagebody | ${reqparam} | | # Gets the message body to post
| | Set To Dictionary | ${API_TEST_DATA[2]} | body | ${msgbody} | # Set the POST body
| | @{postresponse}= | executetests | ${API_TEST_DATA[2]} | | # postresponse = [httpheaders, httpresponsebody]
#| | verifypostmsg | ${postresponse[0]} | ${API_TEST_DATA[2]["body"]} | | # GET the posted messages & verify metadata
| |
| 3:GET MESSAGES-no params | [DOCUMENTATION] | Get message with no params | | # TEST CASE 3 - GET MESSAGE WITH NO PARAMS
| | @{getresponse}= | executetests | ${API_TEST_DATA[3]} |
| | verifygetmsgs | ${10} | @{getresponse} | # Verifies that number of messages returned is <= 10
| |
| 4:GET MESSAGES-limit=5 | [DOCUMENTATION] | Get message with limit = 5 | | # TEST CASE 4 - GET MESSAGE WITH limit = 5
| | @{getresponse}= | executetests | ${API_TEST_DATA[4]} |
| | verifygetmsgs | ${5} | @{getresponse} | # Verifies that number of messages returned is <= 5
| 5:GET MESSAGES-echo=False | [DOCUMENTATION] | Get message with echo=False | | # TEST CASE 5 - GET MESSAGE WITH echo = False
| | @{getresponse}= | executetests | ${API_TEST_DATA[5]} |
| | verifygetmsgs | ${10} | @{getresponse} | # Verifies that number of messages returned is <= 10
| 6:DELETE MESSAGE | [DOCUMENTATION] | | Delete message | | #TEST CASE 6 - DELETE MESSAGE
| | [Tags] | INSERT_MESSAGE | | |
| | ${reqparam}= | Create Dictionary | messagecount | ${1} | # test Setup- Specify count of messages to be posted
| | ${msgbody}= | dummygetmessagebody | ${reqparam} | | # test Setup- Get the message body to post
| | Set To Dictionary | ${API_TEST_DATA[1]} | body | ${msgbody} | # test Setup -Set the POST body
| | @{postresponse}= | executetests | ${API_TEST_DATA[1]} | | # postresponse = [httpheaders, httpresponsebody] |
| | deletemsg | ${postresponse[0]} | | | # GET the posted message & verify metadata
| 7:POST 60 MESSAGES | [DOCUMENTATION] | POST > MAX NUMBER OF MESSAGES | | | #TEST CASE 7 - POST > 50 MESSAGES
| | ... | ALLOWED PER POST (currently 50) | | |
| | [Tags] | INSERT_MESSAGE | | |
| | ${reqparam}= | Create Dictionary | messagecount | ${60} | # test Setup- Specify count of messages to be posted
| | ${msgbody}= | dummygetmessagebody | ${reqparam} | | # test Setup- Get the message body to post
| | Set To Dictionary | ${API_TEST_DATA[6]} | body | ${msgbody} | # test Setup -Set the POST body
| | @{postresponse}= | executetests | ${API_TEST_DATA[6]} | | # postresponse = [httpheaders, httpresponsebody] |
| | verifypostmsg | ${postresponse[0]} | ${API_TEST_DATA[6]["body"]} | | # GET the posted message & verify metadata

View File

@ -0,0 +1,201 @@
# 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 with_statement
import json
import random
from marconi.tests.system.common import config
from marconi.tests.system.common import functionlib
from marconi.tests.system.common import http
cfg = config.Config()
def generate_dict(dict_length):
"""Returns dictionary of specified length. Key:Value is random data."""
with open('/usr/share/dict/words', 'rt') as f:
words = f.readlines()
words = [w.rstrip() for w in words]
dict = {}
while len(dict) < dict_length:
key, value = random.sample(words, 2)
dict.update({key: value})
return dict
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 = []
i = 0
while i < message_count:
message_body = single_message_body(**kwargs)
multiple_message_body.append(message_body)
i = i + 1
return multiple_message_body
def dummyget_message_body(dict):
"""Dummy function since Robot framework does not support **kwargs."""
dict = get_message_body(**dict)
return dict
def create_url(base_url=cfg.base_url, *msg_id_list):
"""Creates url list for retrieving messages with message id."""
url = [(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."""
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.
"""
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 = functionlib.verify_metadata(getmsg.text,
posted_body)
else:
print("Failed to GET {}".format(url))
print("Request Header")
print header
print("Response Headers")
print getmsg.headers
print("Response Body")
print getmsg.text
assert test_result_flag, "HTTP code {}".format(getmsg.status_code)
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."""
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
if not test_result_flag:
assert test_result_flag, "Recursive Get Messages Failed"
def delete_msg(*postresponse):
"""Post DELETE message & verifies that a subsequent GET returns 404."""
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
assert test_result_flag, "DELETE Code {}".format(deletemsg.status_code)

View File

@ -0,0 +1,9 @@
TestID|httpverb|url|header|body|params|expectedRC|expectedResponseBody
0|PUT |<BASE_URL>/queues/msgtestqueue||{"messages":{"ttl": 86400}}||201|
1|POST |<BASE_URL>/queues/msgtestqueue/messages||||201|
2|POST |<BASE_URL>/queues/msgtestqueue/messages||||201|
3|GET |<BASE_URL>/queues/msgtestqueue/messages?echo=true||||200|
4|GET |<BASE_URL>/queues/msgtestqueue/messages?limit=5&echo=true||||200|
5|GET |<BASE_URL>/queues/msgtestqueue/messages?limit=5&echo=true||||200|
6|POST |<BASE_URL>/queues/msgtestqueue/messages||{"messages":{"ttl": 86400}}||201|
0|DELETE |<BASE_URL>/queues/msgtestqueue||||204|
Can't render this file because it contains an unexpected character in line 2 and column 44.

View File

View File

@ -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 csv
from marconi.tests.system.common import config
from marconi.tests.system.common import functionlib
cfg = config.Config()
def get_data():
"""Gets Test Data from a csv file."""
DATA = []
with open('marconi/tests/system/queue/test_data.csv', 'rb') as datafile:
test_data = csv.DictReader(datafile, delimiter='|')
for row in test_data:
DATA.append(row)
for row in DATA:
row['header'] = functionlib.get_headers(row['header'])
row['url'] = row['url'].replace("<BASE_URL>", cfg.base_url)
return DATA
API_TEST_DATA = get_data()

View File

@ -0,0 +1,106 @@
| *Setting* | *Value* |
| Documentation | Marconi - Queue Test Suite |
| Library | ../common/http.py |
| Library | queuefnlib.py |
| Library | ../common/functionlib.py |
| Library | Collections |
| Variables | getdata.py |
| Force Tags | QUEUE |
| *Test Case* | *Action* | *Argument* | *Argument* |
| 1: PUT QUEUE | [DOCUMENTATION] | Creates, gets & verifies | |
| | ... | Queue | |
| | @{putresponse}= | executetests | ${API_TEST_DATA[0]} |
| | ${url}= | geturlfromlocation | ${putresponse[0]} |
| | ${getresponse}= | executetests | ${API_TEST_DATA[1]} |
| | verifymetadata | ${API_TEST_DATA[0]["body"]} | ${getresponse} |
| 2: PUT QUEUE | [DOCUMENTATION] | Verifies that queue name | |
| | ... | are NOT case sensitive | |
| | ${putresponse}= | executetests | ${API_TEST_DATA[2]} |
| | ${getresponse}= | executetests | ${API_TEST_DATA[3]} |
| | verifymetadata | ${API_TEST_DATA[2]["body"]} | ${getresponse} |
| 3: UPDATE QUEUE | [DOCUMENTATION] | Updates an existing queue | |
| | ${putresponse}= | executetests | ${API_TEST_DATA[4]} |
| | ${getresponse}= | executetests | ${API_TEST_DATA[5]} |
| | verifymetadata | ${API_TEST_DATA[4]["body"]} | ${getresponse} |
| 4: PUT QUEUE | [DOCUMENTATION] | Create Queue with no request | |
| | ... | body | |
| | executetests | ${API_TEST_DATA[6]} | |
| 5: PUT QUEUE | [DOCUMENTATION] | Create Queue with invalid | |
| | ... | Authtoken | |
| | ${header}= | invalidauthtokenheader | |
| | Set To Dictionary | ${API_TEST_DATA[7]} | header | ${header} | # test Setup -Set the POST header
| | executetests | ${API_TEST_DATA[7]} | |
| 6: PUT QUEUE | [DOCUMENTATION] | Create Queue with missing | |
| | ... | header field USERAGENT | |
| | ${header}= | missingheaderfields | |
| | Set To Dictionary | ${API_TEST_DATA[8]} | header | ${header} | # test Setup -Set the POST header
| | executetests | ${API_TEST_DATA[8]} | |
| 7: PUT QUEUE | [DOCUMENTATION] | Verifies metadata toplevel | |
| | ... | field do not start with _ | |
| | executetests | ${API_TEST_DATA[9]} | |
| 8: PUT QUEUE | [DOCUMENTATION] | Header has Accept value that | |
| | ... | is not "application/json" | |
| | ${header}= | plaintextinheader | |
| | Set To Dictionary | ${API_TEST_DATA[10]} | header | ${header} | # test Setup -Set the POST header
| | executetests | ${API_TEST_DATA[10]} | |
| 9: PUT QUEUE | [DOCUMENTATION] | Header has Accept value that | |
| | ... | is "\*/\*" | |
| | ${header}= | asteriskinheader | |
| | Set To Dictionary | ${API_TEST_DATA[11]} | header | ${header} | # test Setup -Set the POST header
| | executetests | ${API_TEST_DATA[11]} | |
| 10: PUT QUEUE | [DOCUMENTATION] | Create queue with Non ASCII | |
| | ... | characters in name | |
| | executetests | ${API_TEST_DATA[12]} | |
| 11: PUT QUEUE | [DOCUMENTATION] | Create queue with Non ASCII | |
| | ... | characters in body | |
| | executetests | ${API_TEST_DATA[13]} | |
| 12: PUT QUEUE | [DOCUMENTATION] | Create queue with metadata | |
| | ... | size = 4KB | |
| | ${reqdata}= | Create Dictionary | metadatasize | ${4096} |
| | ${body}= | getcustombody | ${reqdata} |
| | Set To Dictionary | ${API_TEST_DATA[14]} | body | ${body} | # test Setup -Set the POST body
| | executetests | ${API_TEST_DATA[14]} | |
| 13: PUT QUEUE | [DOCUMENTATION] | Create queue with metadata | |
| | ... | size = 4KB + 1 | |
| | ${reqdata}= | Create Dictionary | metadatasize | ${4097} |
| | ${body}= | getcustombody | ${reqdata} |
| | Set To Dictionary | ${API_TEST_DATA[15]} | body | ${body} | # test Setup -Set the POST body
| | executetests | ${API_TEST_DATA[15]} | |
| 14: PUT QUEUE | [DOCUMENTATION] | Create queue with metadata | |
| | ... | size = 4KB - 1 | |
| | ${reqdata}= | Create Dictionary | metadatasize | ${4095} |
| | ${body}= | getcustombody | ${reqdata} |
| | Set To Dictionary | ${API_TEST_DATA[16]} | body | ${body} | # test Setup -Set the POST body
| | executetests | ${API_TEST_DATA[16]} | |
| 15: PUT QUEUE | [DOCUMENTATION] | Create queue with name | |
| | ... | longer than 64 char | |
| | ${url}= | getqueuename | |
| | Set To Dictionary | ${API_TEST_DATA[17]} | url | ${url} | # test Setup -Set the PUT url
| | executetests | ${API_TEST_DATA[17]} | |
| 16: GET QUEUE STATS | [DOCUMENTATION] | Get Queue Stats | |
| | @{getresponse}= | executetests | ${API_TEST_DATA[18]} |
| | verifyqueuestats | @{getresponse} | |
| 17: LIST QUEUES | [DOCUMENTATION] | List queues with no params | |
| | @{listqueues}= | executetests | ${API_TEST_DATA[19]} |
| | verifylistqueues | @{listqueues} | |
| 18: LIST QUEUES DETAILED | [DOCUMENTATION] | List queues ?detailed=true | |
| | @{listqueues}= | executetests | ${API_TEST_DATA[20]} |
| | verifylistqueues | @{listqueues} | |
| 19: DELETE QUEUE | [DOCUMENTATION] | Delete a queue | |
| | executetests | ${API_TEST_DATA[21]} | |
| 20: PUT QUEUE | [DOCUMENTATION] | Creates a queue with same | |
| | ... | name as deleted | |
| | @{putresponse}= | executetests | ${API_TEST_DATA[22]} |
| | ${url}= | geturlfromlocation | ${putresponse[0]} |
| | ${getresponse}= | executetests | ${API_TEST_DATA[23]} |
| | verifymetadata | ${API_TEST_DATA[22]["body"]} | ${getresponse} |
| 21: PUT QUEUE | [DOCUMENTATION] | Create Queue with invalid | |
| | ... | char in metadata | |
| | executetests | ${API_TEST_DATA[24]} | |
| 22: DELETE QUEUE | [DOCUMENTATION] | Delete a queue | |
| | executetests | ${API_TEST_DATA[25]} | |
| 23: GET QUEUE - 404 | [DOCUMENTATION] | Get non existing queue | |
| | executetests | ${API_TEST_DATA[26]} | |

View File

@ -0,0 +1,110 @@
# 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 binascii
import json
import os
from marconi.tests.system.common import functionlib
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.
"""
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 == ["actions", "messages"]):
stats = body["messages"]
keys_in_stats = stats.keys()
keys_in_stats.sort()
if (keys_in_stats == ["claimed", "free"]):
try:
int(stats["claimed"])
int(stats["free"])
except Exception:
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.
"""
appender = "/queues/" + binascii.b2a_hex(os.urandom(namelength))
url = functionlib.create_url_from_appender(appender)
return url
def verify_list_queues(*list_queue_response):
"""Verifies the response of list queues."""
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)
assert test_result_flag, 'List Queue failed'
def verify_listed(queue, detail_enabled):
'''Verifies the listed queues.'''
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

View File

@ -0,0 +1,28 @@
TestID|httpverb|url|header|body|params|expectedRC|expectedResponseBody
1|PUT |<BASE_URL>/queues/qtestqueue||{"messages":{"ttl": 86400}}||201|
1|GET |<BASE_URL>/queues/qtestqueue||||200|{"ttl": 86400}
2|PUT |<BASE_URL>/queues/qtestqueue||{"messages": {"ttl": 86400}}||204|
2|GET |<BASE_URL>/queues/qtestqueue||||200|{"ttl": 86400}
3|PUT |<BASE_URL>/queues/qtestqueue||{"messages": {"ttl": 86400}}||204|
3|GET |<BASE_URL>/queues/qtestqueue||||200|{"ttl": 86400}
4|PUT |<BASE_URL>/queues/qtestqueue||||400|{"title": "Bad request","description": "Missing queue metadata."}
5|PUT |<BASE_URL>/queues/qtestqueue||{"messages": {"ttl": 86400}}||401|
6|PUT |<BASE_URL>/queues/qtestqueue||{"messages": {"ttl": 86400}}||400|
7|PUT |<BASE_URL>/queues/qtestqueue||{"_TOPLEVEL": {"ttl": 86400}}||400|
8|PUT |<BASE_URL>/queues/qtestqueue||{"TOPLEVEL": {"ttl": 86400}}||406|
9|PUT |<BASE_URL>/queues/qtestqueue||{"TOPLEVEL": {"ttl": 86400}}||200|
10|PUT |<BASE_URL>/queues/汉字/漢字||{"messages": {"ttl": 86400}}||201|
11|PUT |<BASE_URL>/queues/qtestqueue||{"汉字": {"ttl": 86400}}||201|
12|PUT |<BASE_URL>/queues/qtestqueue||||201|
13|PUT |<BASE_URL>/queues/qtestqueue||||400|
14|PUT |<BASE_URL>/queues/qtestqueue||||204|
15|PUT |||{"messages":{"ttl": 86400}}||400|
16|GET |<BASE_URL>/queues/qtestqueue/stats||||200|
17|GET |<BASE_URL>/queues||||200|
18|GET |<BASE_URL>/queues?detailed=true||||200|
19|DELETE |<BASE_URL>/queues/qtestqueue||||204|
20|PUT |<BASE_URL>/queues/qtestqueue||{"messages":{"ttl": 86400}}||201|
20|GET |<BASE_URL>/queues/qtestqueue||||200|{"ttl": 86400}
21|PUT |<BASE_URL>/queues/qtestqueue||{"mess~&%^":{"ttl": 86400}}||400|
22|DELETE |<BASE_URL>/queues/qtestqueue||||204|
23|GET |<BASE_URL>/queues/nonexistingqueue||||404|
Can't render this file because it contains an unexpected character in line 2 and column 43.

View File

@ -1,6 +1,7 @@
[nosetests]
where=marconi/tests
verbosity=2
exclude=system/*
with-doctest = true

View File

@ -0,0 +1,2 @@
robotframework
requests