~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/test/test_xmlrpc.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_xmlrpc -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Tests for  XML-RPC support in L{twisted.web.xmlrpc}.
 
7
"""
 
8
 
 
9
import xmlrpclib
 
10
 
 
11
from twisted.trial import unittest
 
12
from twisted.web import xmlrpc
 
13
from twisted.web.xmlrpc import XMLRPC, addIntrospection, _QueryFactory
 
14
from twisted.web import server, static, client, error, http
 
15
from twisted.internet import reactor, defer
 
16
from twisted.internet.error import ConnectionDone
 
17
from twisted.python import failure
 
18
 
 
19
 
 
20
class TestRuntimeError(RuntimeError):
 
21
    pass
 
22
 
 
23
class TestValueError(ValueError):
 
24
    pass
 
25
 
 
26
 
 
27
 
 
28
class Test(XMLRPC):
 
29
 
 
30
    # If you add xmlrpc_ methods to this class, go change test_listMethods
 
31
    # below.
 
32
 
 
33
    FAILURE = 666
 
34
    NOT_FOUND = 23
 
35
    SESSION_EXPIRED = 42
 
36
 
 
37
    def xmlrpc_echo(self, arg):
 
38
        return arg
 
39
 
 
40
    # the doc string is part of the test
 
41
    def xmlrpc_add(self, a, b):
 
42
        """
 
43
        This function add two numbers.
 
44
        """
 
45
        return a + b
 
46
 
 
47
    xmlrpc_add.signature = [['int', 'int', 'int'],
 
48
                            ['double', 'double', 'double']]
 
49
 
 
50
    # the doc string is part of the test
 
51
    def xmlrpc_pair(self, string, num):
 
52
        """
 
53
        This function puts the two arguments in an array.
 
54
        """
 
55
        return [string, num]
 
56
 
 
57
    xmlrpc_pair.signature = [['array', 'string', 'int']]
 
58
 
 
59
    # the doc string is part of the test
 
60
    def xmlrpc_defer(self, x):
 
61
        """Help for defer."""
 
62
        return defer.succeed(x)
 
63
 
 
64
    def xmlrpc_deferFail(self):
 
65
        return defer.fail(TestValueError())
 
66
 
 
67
    # don't add a doc string, it's part of the test
 
68
    def xmlrpc_fail(self):
 
69
        raise TestRuntimeError
 
70
 
 
71
    def xmlrpc_fault(self):
 
72
        return xmlrpc.Fault(12, "hello")
 
73
 
 
74
    def xmlrpc_deferFault(self):
 
75
        return defer.fail(xmlrpc.Fault(17, "hi"))
 
76
 
 
77
    def xmlrpc_complex(self):
 
78
        return {"a": ["b", "c", 12, []], "D": "foo"}
 
79
 
 
80
    def xmlrpc_dict(self, map, key):
 
81
        return map[key]
 
82
    xmlrpc_dict.help = 'Help for dict.'
 
83
 
 
84
    def _getFunction(self, functionPath):
 
85
        try:
 
86
            return XMLRPC._getFunction(self, functionPath)
 
87
        except xmlrpc.NoSuchFunction:
 
88
            if functionPath.startswith("SESSION"):
 
89
                raise xmlrpc.Fault(self.SESSION_EXPIRED,
 
90
                                   "Session non-existant/expired.")
 
91
            else:
 
92
                raise
 
93
 
 
94
 
 
95
class TestAuthHeader(Test):
 
96
    """
 
97
    This is used to get the header info so that we can test
 
98
    authentication.
 
99
    """
 
100
    def __init__(self):
 
101
        Test.__init__(self)
 
102
        self.request = None
 
103
 
 
104
    def render(self, request):
 
105
        self.request = request
 
106
        return Test.render(self, request)
 
107
 
 
108
    def xmlrpc_authinfo(self):
 
109
        return self.request.getUser(), self.request.getPassword()
 
110
 
 
111
 
 
112
class TestQueryProtocol(xmlrpc.QueryProtocol):
 
113
    """
 
114
    QueryProtocol for tests that saves headers received inside the factory.
 
115
    """
 
116
    def handleHeader(self, key, val):
 
117
        self.factory.headers[key.lower()] = val
 
118
 
 
119
 
 
120
class TestQueryFactory(xmlrpc._QueryFactory):
 
121
    """
 
122
    QueryFactory using L{TestQueryProtocol} for saving headers.
 
123
    """
 
124
    protocol = TestQueryProtocol
 
125
 
 
126
    def __init__(self, *args, **kwargs):
 
127
        self.headers = {}
 
128
        xmlrpc._QueryFactory.__init__(self, *args, **kwargs)
 
129
 
 
130
 
 
131
class XMLRPCTestCase(unittest.TestCase):
 
132
 
 
133
    def setUp(self):
 
134
        self.p = reactor.listenTCP(0, server.Site(Test()),
 
135
                                   interface="127.0.0.1")
 
136
        self.port = self.p.getHost().port
 
137
        self.factories = []
 
138
 
 
139
    def tearDown(self):
 
140
        self.factories = []
 
141
        return self.p.stopListening()
 
142
 
 
143
    def queryFactory(self, *args, **kwargs):
 
144
        """
 
145
        Specific queryFactory for proxy that uses our custom
 
146
        L{TestQueryFactory}, and save factories.
 
147
        """
 
148
        factory = TestQueryFactory(*args, **kwargs)
 
149
        self.factories.append(factory)
 
150
        return factory
 
151
 
 
152
    def proxy(self):
 
153
        p = xmlrpc.Proxy("http://127.0.0.1:%d/" % self.port)
 
154
        p.queryFactory = self.queryFactory
 
155
        return p
 
156
 
 
157
    def test_results(self):
 
158
        inputOutput = [
 
159
            ("add", (2, 3), 5),
 
160
            ("defer", ("a",), "a"),
 
161
            ("dict", ({"a": 1}, "a"), 1),
 
162
            ("pair", ("a", 1), ["a", 1]),
 
163
            ("complex", (), {"a": ["b", "c", 12, []], "D": "foo"})]
 
164
 
 
165
        dl = []
 
166
        for meth, args, outp in inputOutput:
 
167
            d = self.proxy().callRemote(meth, *args)
 
168
            d.addCallback(self.assertEquals, outp)
 
169
            dl.append(d)
 
170
        return defer.DeferredList(dl, fireOnOneErrback=True)
 
171
 
 
172
    def test_errors(self):
 
173
        """
 
174
        Verify that for each way a method exposed via XML-RPC can fail, the
 
175
        correct 'Content-type' header is set in the response and that the
 
176
        client-side Deferred is errbacked with an appropriate C{Fault}
 
177
        instance.
 
178
        """
 
179
        dl = []
 
180
        for code, methodName in [(666, "fail"), (666, "deferFail"),
 
181
                                 (12, "fault"), (23, "noSuchMethod"),
 
182
                                 (17, "deferFault"), (42, "SESSION_TEST")]:
 
183
            d = self.proxy().callRemote(methodName)
 
184
            d = self.assertFailure(d, xmlrpc.Fault)
 
185
            d.addCallback(lambda exc, code=code:
 
186
                self.assertEquals(exc.faultCode, code))
 
187
            dl.append(d)
 
188
        d = defer.DeferredList(dl, fireOnOneErrback=True)
 
189
        def cb(ign):
 
190
            for factory in self.factories:
 
191
                self.assertEquals(factory.headers['content-type'],
 
192
                                  'text/xml')
 
193
            self.flushLoggedErrors(TestRuntimeError, TestValueError)
 
194
        d.addCallback(cb)
 
195
        return d
 
196
 
 
197
    def test_errorGet(self):
 
198
        """
 
199
        A classic GET on the xml server should return a NOT_ALLOWED.
 
200
        """
 
201
        d = client.getPage("http://127.0.0.1:%d/" % (self.port,))
 
202
        d = self.assertFailure(d, error.Error)
 
203
        d.addCallback(
 
204
            lambda exc: self.assertEquals(int(exc.args[0]), http.NOT_ALLOWED))
 
205
        return d
 
206
 
 
207
    def test_errorXMLContent(self):
 
208
        """
 
209
        Test that an invalid XML input returns an L{xmlrpc.Fault}.
 
210
        """
 
211
        d = client.getPage("http://127.0.0.1:%d/" % (self.port,),
 
212
                           method="POST", postdata="foo")
 
213
        def cb(result):
 
214
            self.assertRaises(xmlrpc.Fault, xmlrpclib.loads, result)
 
215
        d.addCallback(cb)
 
216
        return d
 
217
 
 
218
 
 
219
    def test_datetimeRoundtrip(self):
 
220
        """
 
221
        If an L{xmlrpclib.DateTime} is passed as an argument to an XML-RPC
 
222
        call and then returned by the server unmodified, the result should
 
223
        be equal to the original object.
 
224
        """
 
225
        when = xmlrpclib.DateTime()
 
226
        d = self.proxy().callRemote("echo", when)
 
227
        d.addCallback(self.assertEqual, when)
 
228
        return d
 
229
 
 
230
 
 
231
    def test_doubleEncodingError(self):
 
232
        """
 
233
        If it is not possible to encode a response to the request (for example,
 
234
        because L{xmlrpclib.dumps} raises an exception when encoding a
 
235
        L{Fault}) the exception which prevents the response from being
 
236
        generated is logged and the request object is finished anyway.
 
237
        """
 
238
        d = self.proxy().callRemote("echo", "")
 
239
 
 
240
        # *Now* break xmlrpclib.dumps.  Hopefully the client already used it.
 
241
        def fakeDumps(*args, **kwargs):
 
242
            raise RuntimeError("Cannot encode anything at all!")
 
243
        self.patch(xmlrpclib, 'dumps', fakeDumps)
 
244
 
 
245
        # It doesn't matter how it fails, so long as it does.  Also, it happens
 
246
        # to fail with an implementation detail exception right now, not
 
247
        # something suitable as part of a public interface.
 
248
        d = self.assertFailure(d, Exception)
 
249
 
 
250
        def cbFailed(ignored):
 
251
            # The fakeDumps exception should have been logged.
 
252
            self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
 
253
        d.addCallback(cbFailed)
 
254
        return d
 
255
 
 
256
 
 
257
 
 
258
class XMLRPCTestCase2(XMLRPCTestCase):
 
259
    """
 
260
    Test with proxy that doesn't add a slash.
 
261
    """
 
262
 
 
263
    def proxy(self):
 
264
        p = xmlrpc.Proxy("http://127.0.0.1:%d" % self.port)
 
265
        p.queryFactory = self.queryFactory
 
266
        return p
 
267
 
 
268
 
 
269
 
 
270
class XMLRPCAllowNoneTestCase(unittest.TestCase):
 
271
    """
 
272
    Test with allowNone set to True.
 
273
 
 
274
    These are not meant to be exhaustive serialization tests, since
 
275
    L{xmlrpclib} does all of the actual serialization work.  They are just
 
276
    meant to exercise a few codepaths to make sure we are calling into
 
277
    xmlrpclib correctly.
 
278
    """
 
279
 
 
280
    def setUp(self):
 
281
        self.p = reactor.listenTCP(
 
282
            0, server.Site(Test(allowNone=True)), interface="127.0.0.1")
 
283
        self.port = self.p.getHost().port
 
284
 
 
285
 
 
286
    def tearDown(self):
 
287
        return self.p.stopListening()
 
288
 
 
289
 
 
290
    def proxy(self):
 
291
        return xmlrpc.Proxy("http://127.0.0.1:%d" % (self.port,),
 
292
                            allowNone=True)
 
293
 
 
294
 
 
295
    def test_deferredNone(self):
 
296
        """
 
297
        Test that passing a C{None} as an argument to a remote method and
 
298
        returning a L{Deferred} which fires with C{None} properly passes
 
299
        </nil> over the network if allowNone is set to True.
 
300
        """
 
301
        d = self.proxy().callRemote('defer', None)
 
302
        d.addCallback(self.assertEquals, None)
 
303
        return d
 
304
 
 
305
 
 
306
    def test_dictWithNoneValue(self):
 
307
        """
 
308
        Test that return a C{dict} with C{None} as a value works properly.
 
309
        """
 
310
        d = self.proxy().callRemote('defer', {'a': None})
 
311
        d.addCallback(self.assertEquals, {'a': None})
 
312
        return d
 
313
 
 
314
 
 
315
 
 
316
class XMLRPCTestAuthenticated(XMLRPCTestCase):
 
317
    """
 
318
    Test with authenticated proxy. We run this with the same inout/ouput as
 
319
    above.
 
320
    """
 
321
    user = "username"
 
322
    password = "asecret"
 
323
 
 
324
    def setUp(self):
 
325
        self.p = reactor.listenTCP(0, server.Site(TestAuthHeader()),
 
326
                                   interface="127.0.0.1")
 
327
        self.port = self.p.getHost().port
 
328
        self.factories = []
 
329
 
 
330
 
 
331
    def test_authInfoInURL(self):
 
332
        p = xmlrpc.Proxy("http://%s:%s@127.0.0.1:%d/" % (
 
333
            self.user, self.password, self.port))
 
334
        d = p.callRemote("authinfo")
 
335
        d.addCallback(self.assertEquals, [self.user, self.password])
 
336
        return d
 
337
 
 
338
 
 
339
    def test_explicitAuthInfo(self):
 
340
        p = xmlrpc.Proxy("http://127.0.0.1:%d/" % (
 
341
            self.port,), self.user, self.password)
 
342
        d = p.callRemote("authinfo")
 
343
        d.addCallback(self.assertEquals, [self.user, self.password])
 
344
        return d
 
345
 
 
346
 
 
347
    def test_explicitAuthInfoOverride(self):
 
348
        p = xmlrpc.Proxy("http://wrong:info@127.0.0.1:%d/" % (
 
349
                self.port,), self.user, self.password)
 
350
        d = p.callRemote("authinfo")
 
351
        d.addCallback(self.assertEquals, [self.user, self.password])
 
352
        return d
 
353
 
 
354
 
 
355
class XMLRPCTestIntrospection(XMLRPCTestCase):
 
356
 
 
357
    def setUp(self):
 
358
        xmlrpc = Test()
 
359
        addIntrospection(xmlrpc)
 
360
        self.p = reactor.listenTCP(0, server.Site(xmlrpc),interface="127.0.0.1")
 
361
        self.port = self.p.getHost().port
 
362
        self.factories = []
 
363
 
 
364
    def test_listMethods(self):
 
365
 
 
366
        def cbMethods(meths):
 
367
            meths.sort()
 
368
            self.assertEqual(
 
369
                meths,
 
370
                ['add', 'complex', 'defer', 'deferFail',
 
371
                 'deferFault', 'dict', 'echo', 'fail', 'fault',
 
372
                 'pair', 'system.listMethods',
 
373
                 'system.methodHelp',
 
374
                 'system.methodSignature'])
 
375
 
 
376
        d = self.proxy().callRemote("system.listMethods")
 
377
        d.addCallback(cbMethods)
 
378
        return d
 
379
 
 
380
    def test_methodHelp(self):
 
381
        inputOutputs = [
 
382
            ("defer", "Help for defer."),
 
383
            ("fail", ""),
 
384
            ("dict", "Help for dict.")]
 
385
 
 
386
        dl = []
 
387
        for meth, expected in inputOutputs:
 
388
            d = self.proxy().callRemote("system.methodHelp", meth)
 
389
            d.addCallback(self.assertEquals, expected)
 
390
            dl.append(d)
 
391
        return defer.DeferredList(dl, fireOnOneErrback=True)
 
392
 
 
393
    def test_methodSignature(self):
 
394
        inputOutputs = [
 
395
            ("defer", ""),
 
396
            ("add", [['int', 'int', 'int'],
 
397
                     ['double', 'double', 'double']]),
 
398
            ("pair", [['array', 'string', 'int']])]
 
399
 
 
400
        dl = []
 
401
        for meth, expected in inputOutputs:
 
402
            d = self.proxy().callRemote("system.methodSignature", meth)
 
403
            d.addCallback(self.assertEquals, expected)
 
404
            dl.append(d)
 
405
        return defer.DeferredList(dl, fireOnOneErrback=True)
 
406
 
 
407
 
 
408
class XMLRPCClientErrorHandling(unittest.TestCase):
 
409
    """
 
410
    Test error handling on the xmlrpc client.
 
411
    """
 
412
    def setUp(self):
 
413
        self.resource = static.Data(
 
414
            "This text is not a valid XML-RPC response.",
 
415
            "text/plain")
 
416
        self.resource.isLeaf = True
 
417
        self.port = reactor.listenTCP(0, server.Site(self.resource),
 
418
                                                     interface='127.0.0.1')
 
419
 
 
420
    def tearDown(self):
 
421
        return self.port.stopListening()
 
422
 
 
423
    def test_erroneousResponse(self):
 
424
        """
 
425
        Test that calling the xmlrpc client on a static http server raises
 
426
        an exception.
 
427
        """
 
428
        proxy = xmlrpc.Proxy("http://127.0.0.1:%d/" %
 
429
                             (self.port.getHost().port,))
 
430
        return self.assertFailure(proxy.callRemote("someMethod"), Exception)
 
431
 
 
432
 
 
433
 
 
434
class TestQueryFactoryParseResponse(unittest.TestCase):
 
435
    """
 
436
    Test the behaviour of L{_QueryFactory.parseResponse}.
 
437
    """
 
438
 
 
439
    def setUp(self):
 
440
        # The _QueryFactory that we are testing. We don't care about any
 
441
        # of the constructor parameters.
 
442
        self.queryFactory = _QueryFactory(
 
443
            path=None, host=None, method='POST', user=None, password=None,
 
444
            allowNone=False, args=())
 
445
        # An XML-RPC response that will parse without raising an error.
 
446
        self.goodContents = xmlrpclib.dumps(('',))
 
447
        # An 'XML-RPC response' that will raise a parsing error.
 
448
        self.badContents = 'invalid xml'
 
449
        # A dummy 'reason' to pass to clientConnectionLost. We don't care
 
450
        # what it is.
 
451
        self.reason = failure.Failure(ConnectionDone())
 
452
 
 
453
 
 
454
    def test_parseResponseCallbackSafety(self):
 
455
        """
 
456
        We can safely call L{_QueryFactory.clientConnectionLost} as a callback
 
457
        of L{_QueryFactory.parseResponse}.
 
458
        """
 
459
        d = self.queryFactory.deferred
 
460
        # The failure mode is that this callback raises an AlreadyCalled
 
461
        # error. We have to add it now so that it gets called synchronously
 
462
        # and triggers the race condition.
 
463
        d.addCallback(self.queryFactory.clientConnectionLost, self.reason)
 
464
        self.queryFactory.parseResponse(self.goodContents)
 
465
        return d
 
466
 
 
467
 
 
468
    def test_parseResponseErrbackSafety(self):
 
469
        """
 
470
        We can safely call L{_QueryFactory.clientConnectionLost} as an errback
 
471
        of L{_QueryFactory.parseResponse}.
 
472
        """
 
473
        d = self.queryFactory.deferred
 
474
        # The failure mode is that this callback raises an AlreadyCalled
 
475
        # error. We have to add it now so that it gets called synchronously
 
476
        # and triggers the race condition.
 
477
        d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
 
478
        self.queryFactory.parseResponse(self.badContents)
 
479
        return d
 
480
 
 
481
 
 
482
    def test_badStatusErrbackSafety(self):
 
483
        """
 
484
        We can safely call L{_QueryFactory.clientConnectionLost} as an errback
 
485
        of L{_QueryFactory.badStatus}.
 
486
        """
 
487
        d = self.queryFactory.deferred
 
488
        # The failure mode is that this callback raises an AlreadyCalled
 
489
        # error. We have to add it now so that it gets called synchronously
 
490
        # and triggers the race condition.
 
491
        d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
 
492
        self.queryFactory.badStatus('status', 'message')
 
493
        return d
 
494
 
 
495
    def test_parseResponseWithoutData(self):
 
496
        """
 
497
        Some server can send a response without any data:
 
498
        L{_QueryFactory.parseResponse} should catch the error and call the
 
499
        result errback.
 
500
        """
 
501
        content = """
 
502
<methodResponse>
 
503
 <params>
 
504
  <param>
 
505
  </param>
 
506
 </params>
 
507
</methodResponse>"""
 
508
        d = self.queryFactory.deferred
 
509
        self.queryFactory.parseResponse(content)
 
510
        return self.assertFailure(d, IndexError)