test_xmlrpc.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. # -*- test-case-name: twisted.web.test.test_xmlrpc -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Tests for XML-RPC support in L{twisted.web.xmlrpc}.
  6. """
  7. from __future__ import division, absolute_import
  8. from twisted.python.compat import nativeString, networkString, NativeStringIO
  9. from io import BytesIO
  10. import datetime
  11. from twisted.trial import unittest
  12. from twisted.web import xmlrpc
  13. from twisted.web.xmlrpc import XMLRPC, payloadTemplate, addIntrospection
  14. from twisted.web.xmlrpc import _QueryFactory, withRequest, xmlrpclib
  15. from twisted.web import server, client, http, static
  16. from twisted.internet import reactor, defer
  17. from twisted.internet.error import ConnectionDone
  18. from twisted.python import failure
  19. from twisted.python.reflect import namedModule
  20. from twisted.test.proto_helpers import MemoryReactor
  21. from twisted.web.test.test_web import DummyRequest
  22. try:
  23. namedModule('twisted.internet.ssl')
  24. except ImportError:
  25. sslSkip = "OpenSSL not present"
  26. else:
  27. sslSkip = None
  28. class AsyncXMLRPCTests(unittest.TestCase):
  29. """
  30. Tests for L{XMLRPC}'s support of Deferreds.
  31. """
  32. def setUp(self):
  33. self.request = DummyRequest([''])
  34. self.request.method = 'POST'
  35. self.request.content = NativeStringIO(
  36. payloadTemplate % ('async', xmlrpclib.dumps(())))
  37. result = self.result = defer.Deferred()
  38. class AsyncResource(XMLRPC):
  39. def xmlrpc_async(self):
  40. return result
  41. self.resource = AsyncResource()
  42. def test_deferredResponse(self):
  43. """
  44. If an L{XMLRPC} C{xmlrpc_*} method returns a L{defer.Deferred}, the
  45. response to the request is the result of that L{defer.Deferred}.
  46. """
  47. self.resource.render(self.request)
  48. self.assertEqual(self.request.written, [])
  49. self.result.callback("result")
  50. resp = xmlrpclib.loads(b"".join(self.request.written))
  51. self.assertEqual(resp, (('result',), None))
  52. self.assertEqual(self.request.finished, 1)
  53. def test_interruptedDeferredResponse(self):
  54. """
  55. While waiting for the L{Deferred} returned by an L{XMLRPC} C{xmlrpc_*}
  56. method to fire, the connection the request was issued over may close.
  57. If this happens, neither C{write} nor C{finish} is called on the
  58. request.
  59. """
  60. self.resource.render(self.request)
  61. self.request.processingFailed(
  62. failure.Failure(ConnectionDone("Simulated")))
  63. self.result.callback("result")
  64. self.assertEqual(self.request.written, [])
  65. self.assertEqual(self.request.finished, 0)
  66. class TestRuntimeError(RuntimeError):
  67. pass
  68. class TestValueError(ValueError):
  69. pass
  70. class Test(XMLRPC):
  71. # If you add xmlrpc_ methods to this class, go change test_listMethods
  72. # below.
  73. FAILURE = 666
  74. NOT_FOUND = 23
  75. SESSION_EXPIRED = 42
  76. def xmlrpc_echo(self, arg):
  77. return arg
  78. # the doc string is part of the test
  79. def xmlrpc_add(self, a, b):
  80. """
  81. This function add two numbers.
  82. """
  83. return a + b
  84. xmlrpc_add.signature = [['int', 'int', 'int'],
  85. ['double', 'double', 'double']]
  86. # the doc string is part of the test
  87. def xmlrpc_pair(self, string, num):
  88. """
  89. This function puts the two arguments in an array.
  90. """
  91. return [string, num]
  92. xmlrpc_pair.signature = [['array', 'string', 'int']]
  93. # the doc string is part of the test
  94. def xmlrpc_defer(self, x):
  95. """Help for defer."""
  96. return defer.succeed(x)
  97. def xmlrpc_deferFail(self):
  98. return defer.fail(TestValueError())
  99. # don't add a doc string, it's part of the test
  100. def xmlrpc_fail(self):
  101. raise TestRuntimeError
  102. def xmlrpc_fault(self):
  103. return xmlrpc.Fault(12, "hello")
  104. def xmlrpc_deferFault(self):
  105. return defer.fail(xmlrpc.Fault(17, "hi"))
  106. def xmlrpc_snowman(self, payload):
  107. """
  108. Used to test that we can pass Unicode.
  109. """
  110. snowman = u"\u2603"
  111. if snowman != payload:
  112. return xmlrpc.Fault(13, "Payload not unicode snowman")
  113. return snowman
  114. def xmlrpc_complex(self):
  115. return {"a": ["b", "c", 12, []], "D": "foo"}
  116. def xmlrpc_dict(self, map, key):
  117. return map[key]
  118. xmlrpc_dict.help = 'Help for dict.'
  119. @withRequest
  120. def xmlrpc_withRequest(self, request, other):
  121. """
  122. A method decorated with L{withRequest} which can be called by
  123. a test to verify that the request object really is passed as
  124. an argument.
  125. """
  126. return (
  127. # as a proof that request is a request
  128. request.method +
  129. # plus proof other arguments are still passed along
  130. ' ' + other)
  131. def lookupProcedure(self, procedurePath):
  132. try:
  133. return XMLRPC.lookupProcedure(self, procedurePath)
  134. except xmlrpc.NoSuchFunction:
  135. if procedurePath.startswith("SESSION"):
  136. raise xmlrpc.Fault(self.SESSION_EXPIRED,
  137. "Session non-existent/expired.")
  138. else:
  139. raise
  140. class TestLookupProcedure(XMLRPC):
  141. """
  142. This is a resource which customizes procedure lookup to be used by the tests
  143. of support for this customization.
  144. """
  145. def echo(self, x):
  146. return x
  147. def lookupProcedure(self, procedureName):
  148. """
  149. Lookup a procedure from a fixed set of choices, either I{echo} or
  150. I{system.listeMethods}.
  151. """
  152. if procedureName == 'echo':
  153. return self.echo
  154. raise xmlrpc.NoSuchFunction(
  155. self.NOT_FOUND, 'procedure %s not found' % (procedureName,))
  156. class TestListProcedures(XMLRPC):
  157. """
  158. This is a resource which customizes procedure enumeration to be used by the
  159. tests of support for this customization.
  160. """
  161. def listProcedures(self):
  162. """
  163. Return a list of a single method this resource will claim to support.
  164. """
  165. return ['foo']
  166. class TestAuthHeader(Test):
  167. """
  168. This is used to get the header info so that we can test
  169. authentication.
  170. """
  171. def __init__(self):
  172. Test.__init__(self)
  173. self.request = None
  174. def render(self, request):
  175. self.request = request
  176. return Test.render(self, request)
  177. def xmlrpc_authinfo(self):
  178. return self.request.getUser(), self.request.getPassword()
  179. class TestQueryProtocol(xmlrpc.QueryProtocol):
  180. """
  181. QueryProtocol for tests that saves headers received and sent,
  182. inside the factory.
  183. """
  184. def connectionMade(self):
  185. self.factory.transport = self.transport
  186. xmlrpc.QueryProtocol.connectionMade(self)
  187. def handleHeader(self, key, val):
  188. self.factory.headers[key.lower()] = val
  189. def sendHeader(self, key, val):
  190. """
  191. Keep sent headers so we can inspect them later.
  192. """
  193. self.factory.sent_headers[key.lower()] = val
  194. xmlrpc.QueryProtocol.sendHeader(self, key, val)
  195. class TestQueryFactory(xmlrpc._QueryFactory):
  196. """
  197. QueryFactory using L{TestQueryProtocol} for saving headers.
  198. """
  199. protocol = TestQueryProtocol
  200. def __init__(self, *args, **kwargs):
  201. self.headers = {}
  202. self.sent_headers = {}
  203. xmlrpc._QueryFactory.__init__(self, *args, **kwargs)
  204. class TestQueryFactoryCancel(xmlrpc._QueryFactory):
  205. """
  206. QueryFactory that saves a reference to the
  207. L{twisted.internet.interfaces.IConnector} to test connection lost.
  208. """
  209. def startedConnecting(self, connector):
  210. self.connector = connector
  211. class XMLRPCTests(unittest.TestCase):
  212. def setUp(self):
  213. self.p = reactor.listenTCP(0, server.Site(Test()),
  214. interface="127.0.0.1")
  215. self.port = self.p.getHost().port
  216. self.factories = []
  217. def tearDown(self):
  218. self.factories = []
  219. return self.p.stopListening()
  220. def queryFactory(self, *args, **kwargs):
  221. """
  222. Specific queryFactory for proxy that uses our custom
  223. L{TestQueryFactory}, and save factories.
  224. """
  225. factory = TestQueryFactory(*args, **kwargs)
  226. self.factories.append(factory)
  227. return factory
  228. def proxy(self, factory=None):
  229. """
  230. Return a new xmlrpc.Proxy for the test site created in
  231. setUp(), using the given factory as the queryFactory, or
  232. self.queryFactory if no factory is provided.
  233. """
  234. p = xmlrpc.Proxy(networkString("http://127.0.0.1:%d/" % self.port))
  235. if factory is None:
  236. p.queryFactory = self.queryFactory
  237. else:
  238. p.queryFactory = factory
  239. return p
  240. def test_results(self):
  241. inputOutput = [
  242. ("add", (2, 3), 5),
  243. ("defer", ("a",), "a"),
  244. ("dict", ({"a": 1}, "a"), 1),
  245. ("pair", ("a", 1), ["a", 1]),
  246. ("snowman", (u"\u2603"), u"\u2603"),
  247. ("complex", (), {"a": ["b", "c", 12, []], "D": "foo"})]
  248. dl = []
  249. for meth, args, outp in inputOutput:
  250. d = self.proxy().callRemote(meth, *args)
  251. d.addCallback(self.assertEqual, outp)
  252. dl.append(d)
  253. return defer.DeferredList(dl, fireOnOneErrback=True)
  254. def test_headers(self):
  255. """
  256. Verify that headers sent from the client side and the ones we
  257. get back from the server side are correct.
  258. """
  259. d = self.proxy().callRemote("snowman", u"\u2603")
  260. def check_server_headers(ing):
  261. self.assertEqual(
  262. self.factories[0].headers[b'content-type'],
  263. b'text/xml; charset=utf-8')
  264. self.assertEqual(
  265. self.factories[0].headers[b'content-length'], b'129')
  266. def check_client_headers(ign):
  267. self.assertEqual(
  268. self.factories[0].sent_headers[b'user-agent'],
  269. b'Twisted/XMLRPClib')
  270. self.assertEqual(
  271. self.factories[0].sent_headers[b'content-type'],
  272. b'text/xml; charset=utf-8')
  273. self.assertEqual(
  274. self.factories[0].sent_headers[b'content-length'], b'155')
  275. d.addCallback(check_server_headers)
  276. d.addCallback(check_client_headers)
  277. return d
  278. def test_errors(self):
  279. """
  280. Verify that for each way a method exposed via XML-RPC can fail, the
  281. correct 'Content-type' header is set in the response and that the
  282. client-side Deferred is errbacked with an appropriate C{Fault}
  283. instance.
  284. """
  285. dl = []
  286. for code, methodName in [(666, "fail"), (666, "deferFail"),
  287. (12, "fault"), (23, "noSuchMethod"),
  288. (17, "deferFault"), (42, "SESSION_TEST")]:
  289. d = self.proxy().callRemote(methodName)
  290. d = self.assertFailure(d, xmlrpc.Fault)
  291. d.addCallback(lambda exc, code=code:
  292. self.assertEqual(exc.faultCode, code))
  293. dl.append(d)
  294. d = defer.DeferredList(dl, fireOnOneErrback=True)
  295. def cb(ign):
  296. for factory in self.factories:
  297. self.assertEqual(factory.headers[b'content-type'],
  298. b'text/xml; charset=utf-8')
  299. self.flushLoggedErrors(TestRuntimeError, TestValueError)
  300. d.addCallback(cb)
  301. return d
  302. def test_cancel(self):
  303. """
  304. A deferred from the Proxy can be cancelled, disconnecting
  305. the L{twisted.internet.interfaces.IConnector}.
  306. """
  307. def factory(*args, **kw):
  308. factory.f = TestQueryFactoryCancel(*args, **kw)
  309. return factory.f
  310. d = self.proxy(factory).callRemote('add', 2, 3)
  311. self.assertNotEqual(factory.f.connector.state, "disconnected")
  312. d.cancel()
  313. self.assertEqual(factory.f.connector.state, "disconnected")
  314. d = self.assertFailure(d, defer.CancelledError)
  315. return d
  316. def test_errorGet(self):
  317. """
  318. A classic GET on the xml server should return a NOT_ALLOWED.
  319. """
  320. agent = client.Agent(reactor)
  321. d = agent.request(b"GET", networkString("http://127.0.0.1:%d/" % (self.port,)))
  322. def checkResponse(response):
  323. self.assertEqual(response.code, http.NOT_ALLOWED)
  324. d.addCallback(checkResponse)
  325. return d
  326. def test_errorXMLContent(self):
  327. """
  328. Test that an invalid XML input returns an L{xmlrpc.Fault}.
  329. """
  330. agent = client.Agent(reactor)
  331. d = agent.request(
  332. uri=networkString("http://127.0.0.1:%d/" % (self.port,)),
  333. method=b"POST",
  334. bodyProducer=client.FileBodyProducer(BytesIO(b"foo")))
  335. d.addCallback(client.readBody)
  336. def cb(result):
  337. self.assertRaises(xmlrpc.Fault, xmlrpclib.loads, result)
  338. d.addCallback(cb)
  339. return d
  340. def test_datetimeRoundtrip(self):
  341. """
  342. If an L{xmlrpclib.DateTime} is passed as an argument to an XML-RPC
  343. call and then returned by the server unmodified, the result should
  344. be equal to the original object.
  345. """
  346. when = xmlrpclib.DateTime()
  347. d = self.proxy().callRemote("echo", when)
  348. d.addCallback(self.assertEqual, when)
  349. return d
  350. def test_doubleEncodingError(self):
  351. """
  352. If it is not possible to encode a response to the request (for example,
  353. because L{xmlrpclib.dumps} raises an exception when encoding a
  354. L{Fault}) the exception which prevents the response from being
  355. generated is logged and the request object is finished anyway.
  356. """
  357. d = self.proxy().callRemote("echo", "")
  358. # *Now* break xmlrpclib.dumps. Hopefully the client already used it.
  359. def fakeDumps(*args, **kwargs):
  360. raise RuntimeError("Cannot encode anything at all!")
  361. self.patch(xmlrpclib, 'dumps', fakeDumps)
  362. # It doesn't matter how it fails, so long as it does. Also, it happens
  363. # to fail with an implementation detail exception right now, not
  364. # something suitable as part of a public interface.
  365. d = self.assertFailure(d, Exception)
  366. def cbFailed(ignored):
  367. # The fakeDumps exception should have been logged.
  368. self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
  369. d.addCallback(cbFailed)
  370. return d
  371. def test_closeConnectionAfterRequest(self):
  372. """
  373. The connection to the web server is closed when the request is done.
  374. """
  375. d = self.proxy().callRemote('echo', '')
  376. def responseDone(ignored):
  377. [factory] = self.factories
  378. self.assertFalse(factory.transport.connected)
  379. self.assertTrue(factory.transport.disconnected)
  380. return d.addCallback(responseDone)
  381. def test_tcpTimeout(self):
  382. """
  383. For I{HTTP} URIs, L{xmlrpc.Proxy.callRemote} passes the value it
  384. received for the C{connectTimeout} parameter as the C{timeout} argument
  385. to the underlying connectTCP call.
  386. """
  387. reactor = MemoryReactor()
  388. proxy = xmlrpc.Proxy(b"http://127.0.0.1:69", connectTimeout=2.0,
  389. reactor=reactor)
  390. proxy.callRemote("someMethod")
  391. self.assertEqual(reactor.tcpClients[0][3], 2.0)
  392. def test_sslTimeout(self):
  393. """
  394. For I{HTTPS} URIs, L{xmlrpc.Proxy.callRemote} passes the value it
  395. received for the C{connectTimeout} parameter as the C{timeout} argument
  396. to the underlying connectSSL call.
  397. """
  398. reactor = MemoryReactor()
  399. proxy = xmlrpc.Proxy(b"https://127.0.0.1:69", connectTimeout=3.0,
  400. reactor=reactor)
  401. proxy.callRemote("someMethod")
  402. self.assertEqual(reactor.sslClients[0][4], 3.0)
  403. test_sslTimeout.skip = sslSkip
  404. class XMLRPCProxyWithoutSlashTests(XMLRPCTests):
  405. """
  406. Test with proxy that doesn't add a slash.
  407. """
  408. def proxy(self, factory=None):
  409. p = xmlrpc.Proxy(networkString("http://127.0.0.1:%d" % self.port))
  410. if factory is None:
  411. p.queryFactory = self.queryFactory
  412. else:
  413. p.queryFactory = factory
  414. return p
  415. class XMLRPCPublicLookupProcedureTests(unittest.TestCase):
  416. """
  417. Tests for L{XMLRPC}'s support of subclasses which override
  418. C{lookupProcedure} and C{listProcedures}.
  419. """
  420. def createServer(self, resource):
  421. self.p = reactor.listenTCP(
  422. 0, server.Site(resource), interface="127.0.0.1")
  423. self.addCleanup(self.p.stopListening)
  424. self.port = self.p.getHost().port
  425. self.proxy = xmlrpc.Proxy(
  426. networkString('http://127.0.0.1:%d' % self.port))
  427. def test_lookupProcedure(self):
  428. """
  429. A subclass of L{XMLRPC} can override C{lookupProcedure} to find
  430. procedures that are not defined using a C{xmlrpc_}-prefixed method name.
  431. """
  432. self.createServer(TestLookupProcedure())
  433. what = "hello"
  434. d = self.proxy.callRemote("echo", what)
  435. d.addCallback(self.assertEqual, what)
  436. return d
  437. def test_errors(self):
  438. """
  439. A subclass of L{XMLRPC} can override C{lookupProcedure} to raise
  440. L{NoSuchFunction} to indicate that a requested method is not available
  441. to be called, signalling a fault to the XML-RPC client.
  442. """
  443. self.createServer(TestLookupProcedure())
  444. d = self.proxy.callRemote("xxxx", "hello")
  445. d = self.assertFailure(d, xmlrpc.Fault)
  446. return d
  447. def test_listMethods(self):
  448. """
  449. A subclass of L{XMLRPC} can override C{listProcedures} to define
  450. Overriding listProcedures should prevent introspection from being
  451. broken.
  452. """
  453. resource = TestListProcedures()
  454. addIntrospection(resource)
  455. self.createServer(resource)
  456. d = self.proxy.callRemote("system.listMethods")
  457. def listed(procedures):
  458. # The list will also include other introspection procedures added by
  459. # addIntrospection. We just want to see "foo" from our customized
  460. # listProcedures.
  461. self.assertIn('foo', procedures)
  462. d.addCallback(listed)
  463. return d
  464. class SerializationConfigMixin:
  465. """
  466. Mixin which defines a couple tests which should pass when a particular flag
  467. is passed to L{XMLRPC}.
  468. These are not meant to be exhaustive serialization tests, since L{xmlrpclib}
  469. does all of the actual serialization work. They are just meant to exercise
  470. a few codepaths to make sure we are calling into xmlrpclib correctly.
  471. @ivar flagName: A C{str} giving the name of the flag which must be passed to
  472. L{XMLRPC} to allow the tests to pass. Subclasses should set this.
  473. @ivar value: A value which the specified flag will allow the serialization
  474. of. Subclasses should set this.
  475. """
  476. def setUp(self):
  477. """
  478. Create a new XML-RPC server with C{allowNone} set to C{True}.
  479. """
  480. kwargs = {self.flagName: True}
  481. self.p = reactor.listenTCP(
  482. 0, server.Site(Test(**kwargs)), interface="127.0.0.1")
  483. self.addCleanup(self.p.stopListening)
  484. self.port = self.p.getHost().port
  485. self.proxy = xmlrpc.Proxy(
  486. networkString("http://127.0.0.1:%d/" % (self.port,)), **kwargs)
  487. def test_roundtripValue(self):
  488. """
  489. C{self.value} can be round-tripped over an XMLRPC method call/response.
  490. """
  491. d = self.proxy.callRemote('defer', self.value)
  492. d.addCallback(self.assertEqual, self.value)
  493. return d
  494. def test_roundtripNestedValue(self):
  495. """
  496. A C{dict} which contains C{self.value} can be round-tripped over an
  497. XMLRPC method call/response.
  498. """
  499. d = self.proxy.callRemote('defer', {'a': self.value})
  500. d.addCallback(self.assertEqual, {'a': self.value})
  501. return d
  502. class XMLRPCAllowNoneTests(SerializationConfigMixin, unittest.TestCase):
  503. """
  504. Tests for passing L{None} when the C{allowNone} flag is set.
  505. """
  506. flagName = "allowNone"
  507. value = None
  508. class XMLRPCUseDateTimeTests(SerializationConfigMixin, unittest.TestCase):
  509. """
  510. Tests for passing a C{datetime.datetime} instance when the C{useDateTime}
  511. flag is set.
  512. """
  513. flagName = "useDateTime"
  514. value = datetime.datetime(2000, 12, 28, 3, 45, 59)
  515. class XMLRPCAuthenticatedTests(XMLRPCTests):
  516. """
  517. Test with authenticated proxy. We run this with the same input/output as
  518. above.
  519. """
  520. user = b"username"
  521. password = b"asecret"
  522. def setUp(self):
  523. self.p = reactor.listenTCP(0, server.Site(TestAuthHeader()),
  524. interface="127.0.0.1")
  525. self.port = self.p.getHost().port
  526. self.factories = []
  527. def test_authInfoInURL(self):
  528. url = "http://%s:%s@127.0.0.1:%d/" % (
  529. nativeString(self.user), nativeString(self.password), self.port)
  530. p = xmlrpc.Proxy(networkString(url))
  531. d = p.callRemote("authinfo")
  532. d.addCallback(self.assertEqual, [self.user, self.password])
  533. return d
  534. def test_explicitAuthInfo(self):
  535. p = xmlrpc.Proxy(networkString("http://127.0.0.1:%d/" % (
  536. self.port,)), self.user, self.password)
  537. d = p.callRemote("authinfo")
  538. d.addCallback(self.assertEqual, [self.user, self.password])
  539. return d
  540. def test_longPassword(self):
  541. """
  542. C{QueryProtocol} uses the C{base64.b64encode} function to encode user
  543. name and password in the I{Authorization} header, so that it doesn't
  544. embed new lines when using long inputs.
  545. """
  546. longPassword = self.password * 40
  547. p = xmlrpc.Proxy(networkString("http://127.0.0.1:%d/" % (
  548. self.port,)), self.user, longPassword)
  549. d = p.callRemote("authinfo")
  550. d.addCallback(self.assertEqual, [self.user, longPassword])
  551. return d
  552. def test_explicitAuthInfoOverride(self):
  553. p = xmlrpc.Proxy(networkString("http://wrong:info@127.0.0.1:%d/" % (
  554. self.port,)), self.user, self.password)
  555. d = p.callRemote("authinfo")
  556. d.addCallback(self.assertEqual, [self.user, self.password])
  557. return d
  558. class XMLRPCIntrospectionTests(XMLRPCTests):
  559. def setUp(self):
  560. xmlrpc = Test()
  561. addIntrospection(xmlrpc)
  562. self.p = reactor.listenTCP(0, server.Site(xmlrpc),interface="127.0.0.1")
  563. self.port = self.p.getHost().port
  564. self.factories = []
  565. def test_listMethods(self):
  566. def cbMethods(meths):
  567. meths.sort()
  568. self.assertEqual(
  569. meths,
  570. ['add', 'complex', 'defer', 'deferFail',
  571. 'deferFault', 'dict', 'echo', 'fail', 'fault',
  572. 'pair', 'snowman', 'system.listMethods',
  573. 'system.methodHelp',
  574. 'system.methodSignature', 'withRequest'])
  575. d = self.proxy().callRemote("system.listMethods")
  576. d.addCallback(cbMethods)
  577. return d
  578. def test_methodHelp(self):
  579. inputOutputs = [
  580. ("defer", "Help for defer."),
  581. ("fail", ""),
  582. ("dict", "Help for dict.")]
  583. dl = []
  584. for meth, expected in inputOutputs:
  585. d = self.proxy().callRemote("system.methodHelp", meth)
  586. d.addCallback(self.assertEqual, expected)
  587. dl.append(d)
  588. return defer.DeferredList(dl, fireOnOneErrback=True)
  589. def test_methodSignature(self):
  590. inputOutputs = [
  591. ("defer", ""),
  592. ("add", [['int', 'int', 'int'],
  593. ['double', 'double', 'double']]),
  594. ("pair", [['array', 'string', 'int']])]
  595. dl = []
  596. for meth, expected in inputOutputs:
  597. d = self.proxy().callRemote("system.methodSignature", meth)
  598. d.addCallback(self.assertEqual, expected)
  599. dl.append(d)
  600. return defer.DeferredList(dl, fireOnOneErrback=True)
  601. class XMLRPCClientErrorHandlingTests(unittest.TestCase):
  602. """
  603. Test error handling on the xmlrpc client.
  604. """
  605. def setUp(self):
  606. self.resource = static.Data(
  607. b"This text is not a valid XML-RPC response.",
  608. b"text/plain")
  609. self.resource.isLeaf = True
  610. self.port = reactor.listenTCP(0, server.Site(self.resource),
  611. interface='127.0.0.1')
  612. def tearDown(self):
  613. return self.port.stopListening()
  614. def test_erroneousResponse(self):
  615. """
  616. Test that calling the xmlrpc client on a static http server raises
  617. an exception.
  618. """
  619. proxy = xmlrpc.Proxy(networkString("http://127.0.0.1:%d/" %
  620. (self.port.getHost().port,)))
  621. return self.assertFailure(proxy.callRemote("someMethod"), ValueError)
  622. class QueryFactoryParseResponseTests(unittest.TestCase):
  623. """
  624. Test the behaviour of L{_QueryFactory.parseResponse}.
  625. """
  626. def setUp(self):
  627. # The _QueryFactory that we are testing. We don't care about any
  628. # of the constructor parameters.
  629. self.queryFactory = _QueryFactory(
  630. path=None, host=None, method='POST', user=None, password=None,
  631. allowNone=False, args=())
  632. # An XML-RPC response that will parse without raising an error.
  633. self.goodContents = xmlrpclib.dumps(('',))
  634. # An 'XML-RPC response' that will raise a parsing error.
  635. self.badContents = 'invalid xml'
  636. # A dummy 'reason' to pass to clientConnectionLost. We don't care
  637. # what it is.
  638. self.reason = failure.Failure(ConnectionDone())
  639. def test_parseResponseCallbackSafety(self):
  640. """
  641. We can safely call L{_QueryFactory.clientConnectionLost} as a callback
  642. of L{_QueryFactory.parseResponse}.
  643. """
  644. d = self.queryFactory.deferred
  645. # The failure mode is that this callback raises an AlreadyCalled
  646. # error. We have to add it now so that it gets called synchronously
  647. # and triggers the race condition.
  648. d.addCallback(self.queryFactory.clientConnectionLost, self.reason)
  649. self.queryFactory.parseResponse(self.goodContents)
  650. return d
  651. def test_parseResponseErrbackSafety(self):
  652. """
  653. We can safely call L{_QueryFactory.clientConnectionLost} as an errback
  654. of L{_QueryFactory.parseResponse}.
  655. """
  656. d = self.queryFactory.deferred
  657. # The failure mode is that this callback raises an AlreadyCalled
  658. # error. We have to add it now so that it gets called synchronously
  659. # and triggers the race condition.
  660. d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
  661. self.queryFactory.parseResponse(self.badContents)
  662. return d
  663. def test_badStatusErrbackSafety(self):
  664. """
  665. We can safely call L{_QueryFactory.clientConnectionLost} as an errback
  666. of L{_QueryFactory.badStatus}.
  667. """
  668. d = self.queryFactory.deferred
  669. # The failure mode is that this callback raises an AlreadyCalled
  670. # error. We have to add it now so that it gets called synchronously
  671. # and triggers the race condition.
  672. d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
  673. self.queryFactory.badStatus('status', 'message')
  674. return d
  675. def test_parseResponseWithoutData(self):
  676. """
  677. Some server can send a response without any data:
  678. L{_QueryFactory.parseResponse} should catch the error and call the
  679. result errback.
  680. """
  681. content = """
  682. <methodResponse>
  683. <params>
  684. <param>
  685. </param>
  686. </params>
  687. </methodResponse>"""
  688. d = self.queryFactory.deferred
  689. self.queryFactory.parseResponse(content)
  690. return self.assertFailure(d, IndexError)
  691. class XMLRPCWithRequestTests(unittest.TestCase):
  692. def setUp(self):
  693. self.resource = Test()
  694. def test_withRequest(self):
  695. """
  696. When an XML-RPC method is called and the implementation is
  697. decorated with L{withRequest}, the request object is passed as
  698. the first argument.
  699. """
  700. request = DummyRequest('/RPC2')
  701. request.method = "POST"
  702. request.content = NativeStringIO(xmlrpclib.dumps(
  703. ("foo",), 'withRequest'))
  704. def valid(n, request):
  705. data = xmlrpclib.loads(request.written[0])
  706. self.assertEqual(data, (('POST foo',), None))
  707. d = request.notifyFinish().addCallback(valid, request)
  708. self.resource.render_POST(request)
  709. return d