r5 - 19 Jul 2007 - 06:23:59 - MimiYinYou are here: OSAF >  Journal Web  >  QualityAssuranceTeam > AdamChristianNotes > AdamChristianJSONRPCTest

JSON-RPC Test Tool

The goal of this tool is to simplify the process of writing tests for applications that are driven via JSON-RPC over HTTP.
This tool extends the HTTPTest and TestObject? objects which allows it to use the built in debugging, reporting and other analysis functionality already provided.

The class is simple, it inherits from HTTPTest, which inherits from TestObject?. Currently this only includes two methods; one is used for converting strings of JSON-RPC syntax to python dictionaries for manipulation. The other allows you to convert the other direction from python objects to JSON-RPC strings to be executed via HTTP.

All of the files in this document can be found in the scoobyTests directory of the QA Sandbox while they are under development.

Filename: JSONTest.py ---------------------------

from HTTPTest import HTTPTest

class JSONTest(HTTPTest):
    
    #We take in a JSON string, then return a python dictionary equivalent
    def parseJSON(self, jsonString=None):
        
        if jsonString is None:
            jsonString = self.test_response.read()
            
        jsonString = 'jsonDict = ' + jsonString
        exec (jsonString)
        return jsonDict
        
    #Taking a python dictionary and converting it into a JSON call
    def convertToJSON(self, jsonDict):
        return jsonDict.__str__()

    #getEventID allows you to access the ID generated by saveEvent
    def getJsonAttrib(self, spec):
        dict = self.parseJSON(jsonString=self.test_response.read())
        return dict[spec]
        

*The following is an example implementation of this for doing a general functional test on Scooby 0.1, 0.2
*This test creates the user specified in the main but if it is being run more than once, this user needs to be removed or it will error, but still try to exectute the JSON-RPC calls.

Filename: scoobyGeneralTest.py --------------------------

from JSONTest import JSONTest

class generalScoobyTest(JSONTest):
    
    def startRun(self):
        
        self.testStart('Setup Accounts')
        
        #try:
        #    self.appendUser = self.appendDict['username']
        #except KeyError:
        #    self.appendUser = ''
        
        #self.path = '/cosmo'
        
        cmpheaders = self.headerAdd({'Content-Type' : "text/xml; charset=UTF-8"})
        cmpheaders = self.headerAddAuth("root", "cosmo", headers=cmpheaders)
           
        #CMP path
        #cmppath = self.pathBuilder('/cmp/user/scoobyMETest')
        cmppath = '/cosmo/cmp/user/scoobyMETest'
        
        #Create testing account        
        bodycreateaccount = '<?xml version="1.0" encoding="utf-8" ?> \
                                 <user xmlns="http://osafoundation.org/cosmo/CMP"> \
                                 <username>%s</username> \
                                 <password>%s</password> \
                                 <firstName>scoobyMETest</firstName> \
                                 <lastName>Test</lastName> \
                                 <email>scoobyMETest@osafoundation.org</email> \
                                 </user>' % (username, password)
                                 
        #Create account and check status
        self.request('PUT', cmppath, body=bodycreateaccount, headers=cmpheaders)
        self.checkStatus(201)
        
        #Add auth to global headers
        #self.headers = self.headerAddAuth("scoobyMETest", "scoobyMETest")
        
        #Create Calendar on CalDAV server   
        #self.calpath = self.pathBuilder('/home/%s/Scooby/' % (username))
        #self.request('MKCALENDAR', self.calpath, body=None, headers=self.headers)
        #self.checkStatus(201)
        
        self.path = '/scooby'
        
        #We need to acquire the Jsession ID that is provided in the SET-COOKIE header of the response
        my_headers = {'Accept': '*/*', 'Referrer' : 'http://%s:%s/' % (host, port)}
        self.request(method='GET',url=self.pathBuilder('/'), headers = my_headers)
        self.parseJSID() #Parse and store JSID
        my_headers = self.headerAdd({'Cookie' : 'JSESSIONID=%s; username=%s' % (self.parseJSID(), username)})
        self.verifyListInResponse(negative=['error'])
         
        #Authenticate
        my_headers.update({'Accept': '*/*', 'Content-type' : 'application/x-www-form-urlencoded'})
        self.request(method='POST',url=self.pathBuilder('/j_acegi_security_check'), body='j_username=%s&j_password=%s' % (username,password), headers = my_headers)
        self.verifyListInResponse(negative=['error'])    
        
        #JSON-RPC: Get the available system methods
        my_headers.update({'Accept': '*/*', 'Content-type' : 'text/plain'})
        self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 1, "method": "system.listMethods", "params": []}', headers = my_headers)
        self.verifyListInResponse(negative=['error'])
         
        #JSON-RPC: Call to getVersion
        self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 2, "method": "scoobyService.getVersion", "params": []}', headers = my_headers)
        self.verifyListInResponse(negative=['error'])
        
        #JSON-RPC: Call to getEvents
        self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 3, "method": "scoobyService.getEvents", "params": ["Scooby", 1148713200000, 1149490799000]}', headers = my_headers)
        self.verifyListInResponse(negative=['error'])        
        
        #JSON-RPC: Get the calendars
        self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 4, "method": "scoobyService.getCalendars", "params": []}', headers = my_headers)
        self.verifyListInResponse(negative=['error'])
        
        #JSON-RPC: Create a calendar (for scooby it will be scooby, and will fail if scooby already exists)
        self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 5, "method": "scoobyService.createCalendar", "params": ["Scooby", "Scooby"]}', headers = my_headers)
        self.verifyListInResponse(negative=['error'])
        
        #JSON-RPC: Create an event
        self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 6, "method": "scoobyService.saveEvent", "params": ["Scooby", {"id": null, "title": "Welcome to Scooby!", "description": "Welcome to Scooby!", "start": {"year": 2006, "month": 4, "date": 31, "hours": "9", "minutes": "00", "seconds": 0, "timezone": null, "utc": false}, "end": {"year": 2006, "month": 4, "date": 31, "hours": 10, "minutes": 0, "seconds": 0, "timezone": null, "utc": false}, "allDay": false, "pointInTime": false, "anyTime": false, "recurrenceRule": null, "status": null, "masterEvent": false, "instance": false, "javaClass": "org.osaf.scooby.model.Event"}]}', headers = my_headers)
        self.verifyListInResponse(negative=['error'])
        
        #JSON-RPC: Remove the last event created
        #self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 7, "method": "scoobyService.removeEvent", "params": ["Scooby", "%s"]}' % (self.getJsonAttrib("result")), headers = {'Accept': '*/*', 'Content-type' : 'text/plain'})
        #self.verifyListInResponse(negative=['error'])
        
        #JSON-RPC: Remove an event manually
        #self.request(method='POST', url=self.pathBuilder('/JSON-RPC'), body='{"id": 5, "method": "scoobyService.removeEvent", "params": ["Scooby", "1c18b4ae-5e85-42f4-b74a-b7b5c0c2ac30"]}', headers = {'Accept': '*/*', 'Content-type' : 'text/plain'})
        
if __name__ == "__main__":
    
    import sys
    
    #Set the cosmo server, and scooby path
    host = 'localhost'
    port = '8080'
    path = '/scooby'
    
    #Set the target user
    username = 'test2'
    password = 'test2'
    
    #Set your debug
    debug = 4
    
    for arg in sys.argv:
        args = arg.split("=")
        if args[0] == "host":
            host = args[1]
        elif args[0] == "port":
            port = int(args[1])
        elif args[0] == "path":
            path = args[1]
        elif args[0] == "recurring":
            counter = int(args[1])
        elif args[0] == "debug":
            debug = int(args[1])
    
    #Instantiate the test
    generalScoobyTest = generalScoobyTest(host=host, port=port, path=path)
    generalScoobyTest.username = username #Set username
    generalScoobyTest.password = password #Set password
    generalScoobyTest.debug = debug #Set Debug
    generalScoobyTest.fullRun()    #Run the test
      

Additions were made to the file HTTPTest.py in order to support the JSID functionality

Added to HTTPTest: This method parseJSID will return the JSID for the session your currently in, if it doesn't exist it will try to parse it from the Set-Cookie header in the response. If no JSID is set, and the 'Set-Cookie' header doesn't exist then an empty string is returned to avoid breaking python.

def parseJSID(self):
        """
        Method for parsing jsid in responses.
        
        Used for setting jsid cookies and turning on additional logic in self.request() to handle cookies.
        
        Return:
        jsid: str -- jsid string.
        
        Member Assignment:
        self.jsid: str -- jsid string.
        """

        #If the jsid is already set, then we will just return that, if not we will parse it.
        if self.jsid is None:
               
            mystring = self.test_response.getheader('Set-Cookie')
            if mystring == '':
                self.report(False,test='Parse JSID')
                return
            array = mystring.split('=')
            array = array[1].split(';')
            array = array[0]
            self.jsid = array
            return array
           
        else:
            return self.jsid
Addition to HTTPTest __ init__:
 self.jsid = None
        self.jsidIsSet = False   
Other helpful test writing tidbits:

Once can access the response headers via self.test_response at anytime to access the response from the last call to the object.
self.test_response.read(): The text response.
self.test_response.status: The HTTP code
self.test_response.reason: The corresponding meaning
Response headers can be accessed via: self.test_response.getheader('header identifier (example: Set-Cookie) ')
Accessing the results from saveEvent (Other headers can be retrieved by replacing result): self.getJsonAttrib("result")

Output Example (debug Level 4):
AdamsMB17:~/Documents/projects/qa-sandbox/scoobyTests adam$ python scoobyGeneralTest.py 
Starting New Test :: Setup Accounts
 startingTest Setup Accounts
 Request::
<?xml version="1.0" encoding="utf-8" ?>                                  <user xmlns="http://osafoundation.org/cosmo/CMP">                                  <username>test2</username>                                  <password>test2</password>                                  <firstName>scoobyMETest</firstName>                                  <lastName>Test</lastName>                                  <email>scoobyMETest@osafoundation.org</email>                                  </user>
RequestHeaders::
{'Host': 'localhost:8080', 'Content-Type': 'text/xml; charset=UTF-8', 'Authorization': 'Basic cm9vdDpjb3Ntbw==', 'Accept': '*/*'}
Response::
<html><head><title>Apache Tomcat/5.5.9 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 400 - Username does not match request URI</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>Username does not match request URI</u></p><p><b>description</b> <u>The request sent by the client was syntactically incorrect (Username does not match request URI).</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/5.5.9</h3></body></html>
Failure :: Test Status Code Check on 201 :: expected 201 ; received 400
 Request::
None
 RequestHeaders::
{'Referrer': 'http://localhost:8080/', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
j_username=test2&j_password=test2
 RequestHeaders::
{'Content-type': 'application/x-www-form-urlencoded', 'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
{"id": 1, "method": "system.listMethods", "params": []}
RequestHeaders::
{'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Content-type': 'text/plain', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
{"id": 2, "method": "scoobyService.getVersion", "params": []}
RequestHeaders::
{'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Content-type': 'text/plain', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
{"id": 3, "method": "scoobyService.getEvents", "params": ["Scooby", 1148713200000, 1149490799000]}
RequestHeaders::
{'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Content-type': 'text/plain', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
{"id": 4, "method": "scoobyService.getCalendars", "params": []}
RequestHeaders::
{'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Content-type': 'text/plain', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
{"id": 5, "method": "scoobyService.createCalendar", "params": ["Scooby", "Scooby"]}
RequestHeaders::
{'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Content-type': 'text/plain', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Request::
{"id": 6, "method": "scoobyService.saveEvent", "params": ["Scooby", {"id": null, "title": "Welcome to Scooby!", "description": "Welcome to Scooby!", "start": {"year": 2006, "month": 4, "date": 31, "hours": "9", "minutes": "00", "seconds": 0, "timezone": null, "utc": false}, "end": {"year": 2006, "month": 4, "date": 31, "hours": 10, "minutes": 0, "seconds": 0, "timezone": null, "utc": false}, "allDay": false, "pointInTime": false, "anyTime": false, "recurrenceRule": null, "status": null, "masterEvent": false, "instance": false, "javaClass": "org.osaf.scooby.model.Event"}]}
RequestHeaders::
{'Host': 'localhost:8080', 'Cookie': 'JSESSIONID=D1FC6CDD78B29F930F1EE0E9D34FC2B1; username=test2', 'Content-type': 'text/plain', 'Accept': '*/*'}
Response::

Passed :: Test verifyListInResponse :: None,['error']
Failures :: 1
 Passes :: 8
 Total tests run :: 9
 AdamsMB17:~/Documents/projects/qa-sandbox/scoobyTests adam$ 

Edit | WYSIWYG | Attach | Printable | Raw View | Backlinks: Web, All Webs | History: r5 < r4 < r3 < r2 < r1 | More topic actions
 
Open Source Applications Foundation
Except where otherwise noted, this site and its content are licensed by OSAF under an Creative Commons License, Attribution Only 3.0.
See list of page contributors for attributions.