# -*- test-case-name: twisted.web.test.test_xmlrpc -*- # # Copyright (c) 2001-2004 Twisted Matrix Laboratories. # See LICENSE for details. # """Test XML-RPC support.""" import xmlrpclib from twisted.web2 import xmlrpc from twisted.web2.xmlrpc import XMLRPC, addIntrospection from twisted.internet import defer from twisted.web2.test.test_server import BaseCase class TestRuntimeError(RuntimeError): """ Fake RuntimeError for testing purposes. """ class TestValueError(ValueError): """ Fake ValueError for testing purposes. """ class XMLRPCTestResource(XMLRPC): """ This is the XML-RPC "server" against which the tests will be run. """ FAILURE = 666 NOT_FOUND = 23 SESSION_EXPIRED = 42 addSlash = True # cause it's at the root # the doc string is part of the test def xmlrpc_add(self, request, a, b): """This function add two numbers.""" return a + b xmlrpc_add.signature = [['int', 'int', 'int'], ['double', 'double', 'double']] # the doc string is part of the test def xmlrpc_pair(self, request, string, num): """This function puts the two arguments in an array.""" return [string, num] xmlrpc_pair.signature = [['array', 'string', 'int']] # the doc string is part of the test def xmlrpc_defer(self, request, x): """Help for defer.""" return defer.succeed(x) def xmlrpc_deferFail(self, request): return defer.fail(TestValueError()) # don't add a doc string, it's part of the test def xmlrpc_fail(self, request): raise TestRuntimeError def xmlrpc_fault(self, request): return xmlrpc.Fault(12, "hello") def xmlrpc_deferFault(self, request): return defer.fail(xmlrpc.Fault(17, "hi")) def xmlrpc_complex(self, request): return {"a": ["b", "c", 12, []], "D": "foo"} def xmlrpc_dict(self, request, map, key): return map[key] def getFunction(self, functionPath): try: return XMLRPC.getFunction(self, functionPath) except xmlrpc.NoSuchFunction: if functionPath.startswith("SESSION"): raise xmlrpc.Fault(self.SESSION_EXPIRED, "Session non-existant/expired.") else: raise xmlrpc_dict.help = 'Help for dict.' class XMLRPCServerBase(BaseCase): """ The parent class of the XML-RPC test classes. """ method = 'POST' version = (1, 1) def setUp(self): self.root = XMLRPCTestResource() self.xml = ("\n\n" + "%s\n") class XMLRPCServerGETTest(XMLRPCServerBase): """ Attempt access to the RPC resources as regular HTTP resource. """ def setUp(self): super(XMLRPCServerGETTest, self).setUp() self.method = 'GET' self.errorRPC = ('XML-RPC responder' + '

XML-RPC responder

POST your XML-RPC ' + 'here.') self.errorHTTP = ('404 Not Found' + '

Not Found

The resource http://host/add ' + 'cannot be found.') def test_rootGET(self): """ Test a simple GET against the XML-RPC server. """ return self.assertResponse( (self.root, 'http://host/'), (200, {}, self.errorRPC)) def test_childGET(self): """ Try to access an XML-RPC method as a regular resource via GET. """ return self.assertResponse( (self.root, 'http://host/add'), (404, {}, self.errorHTTP)) class XMLRPCServerPOSTTest(XMLRPCServerBase): """ Tests for standard XML-RPC usage. """ def test_RPCMethods(self): """ Make RPC calls of the defined methods, checking for the expected results. """ inputOutput = [ ("add", (2, 3), 5), ("defer", ("a",), "a"), ("dict", ({"a": 1}, "a"), 1), ("pair", ("a", 1), ["a", 1]), ("complex", (), {"a": ["b", "c", 12, []], "D": "foo"})] dl = [] for meth, args, outp in inputOutput: postdata = xmlrpclib.dumps(args, meth) respdata = xmlrpclib.dumps((outp,)) reqdata = (self.root, 'http://host/', {}, None, None, '', postdata) d = self.assertResponse(reqdata, (200, {}, self.xml % respdata)) dl.append(d) return defer.DeferredList(dl, fireOnOneErrback=True) def test_RPCFaults(self): """ Ensure that RPC faults are properly processed. """ dl = [] codeMethod = [ (12, "fault", 'hello'), (23, "noSuchMethod", 'function noSuchMethod not found'), (17, "deferFault", 'hi'), (42, "SESSION_TEST", 'Session non-existant/expired.')] for code, meth, fault in codeMethod: postdata = xmlrpclib.dumps((), meth) respdata = xmlrpclib.dumps(xmlrpc.Fault(code, fault)) reqdata = (self.root, 'http://host/', {}, None, None, '', postdata) d = self.assertResponse(reqdata, (200, {}, respdata)) dl.append(d) d = defer.DeferredList(dl, fireOnOneErrback=True) return d def test_RPCFailures(self): """ Ensure that failures behave as expected. """ dl = [] codeMethod = [ (666, "fail"), (666, "deferFail")] for code, meth in codeMethod: postdata = xmlrpclib.dumps((), meth) respdata = xmlrpclib.dumps(xmlrpc.Fault(code, 'error')) reqdata = (self.root, 'http://host/', {}, None, None, '', postdata) d = self.assertResponse(reqdata, (200, {}, respdata)) d.addCallback(self.flushLoggedErrors, TestRuntimeError, TestValueError) dl.append(d) d = defer.DeferredList(dl, fireOnOneErrback=True) return d class XMLRPCTestIntrospection(XMLRPCServerBase): def setUp(self): """ Introspection requires additional setup, most importantly, adding introspection to the root object. """ super(XMLRPCTestIntrospection, self).setUp() addIntrospection(self.root) self.methodList = ['add', 'complex', 'defer', 'deferFail', 'deferFault', 'dict', 'fail', 'fault', 'pair', 'system.listMethods', 'system.methodHelp', 'system.methodSignature'] def test_listMethods(self): """ Check that the introspection method "listMethods" returns all the methods we defined in the XML-RPC server. """ def cbMethods(meths): meths.sort() self.failUnlessEqual( meths, ) postdata = xmlrpclib.dumps((), 'system.listMethods') respdata = xmlrpclib.dumps((self.methodList,)) reqdata = (self.root, 'http://host/', {}, None, None, '', postdata) return self.assertResponse(reqdata, (200, {}, self.xml % respdata)) def test_methodHelp(self): """ Check the RPC methods for docstrings or .help attributes. """ inputOutput = [ ("defer", "Help for defer."), ("fail", ""), ("dict", "Help for dict.")] dl = [] for meth, outp in inputOutput: postdata = xmlrpclib.dumps((meth,), 'system.methodHelp') respdata = xmlrpclib.dumps((outp,)) reqdata = (self.root, 'http://host/', {}, None, None, '', postdata) d = self.assertResponse(reqdata, (200, {}, self.xml % respdata)) dl.append(d) return defer.DeferredList(dl, fireOnOneErrback=True) def test_methodSignature(self): """ Check that the RPC methods whose signatures have been set via the .signature attribute (on the method) are returned as expected. """ inputOutput = [ ("defer", ""), ("add", [['int', 'int', 'int'], ['double', 'double', 'double']]), ("pair", [['array', 'string', 'int']])] dl = [] for meth, outp in inputOutput: postdata = xmlrpclib.dumps((meth,), 'system.methodSignature') respdata = xmlrpclib.dumps((outp,)) reqdata = (self.root, 'http://host/', {}, None, None, '', postdata) d = self.assertResponse(reqdata, (200, {}, self.xml % respdata)) dl.append(d) return defer.DeferredList(dl, fireOnOneErrback=True)