Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / web / test / test_distrib.py
1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
3
4 """
5 Tests for L{twisted.web.distrib}.
6 """
7
8 from os.path import abspath
9 from xml.dom.minidom import parseString
10 try:
11     import pwd
12 except ImportError:
13     pwd = None
14
15 from zope.interface.verify import verifyObject
16
17 from twisted.python import log, filepath
18 from twisted.internet import reactor, defer
19 from twisted.trial import unittest
20 from twisted.spread import pb
21 from twisted.spread.banana import SIZE_LIMIT
22 from twisted.web import http, distrib, client, resource, static, server
23 from twisted.web.test.test_web import DummyRequest
24 from twisted.web.test._util import _render
25 from twisted.test import proto_helpers
26
27
28 class MySite(server.Site):
29     pass
30
31
32 class PBServerFactory(pb.PBServerFactory):
33     """
34     A PB server factory which keeps track of the most recent protocol it
35     created.
36
37     @ivar proto: L{None} or the L{Broker} instance most recently returned
38         from C{buildProtocol}.
39     """
40     proto = None
41
42     def buildProtocol(self, addr):
43         self.proto = pb.PBServerFactory.buildProtocol(self, addr)
44         return self.proto
45
46
47
48 class DistribTest(unittest.TestCase):
49     port1 = None
50     port2 = None
51     sub = None
52     f1 = None
53
54     def tearDown(self):
55         """
56         Clean up all the event sources left behind by either directly by
57         test methods or indirectly via some distrib API.
58         """
59         dl = [defer.Deferred(), defer.Deferred()]
60         if self.f1 is not None and self.f1.proto is not None:
61             self.f1.proto.notifyOnDisconnect(lambda: dl[0].callback(None))
62         else:
63             dl[0].callback(None)
64         if self.sub is not None and self.sub.publisher is not None:
65             self.sub.publisher.broker.notifyOnDisconnect(
66                 lambda: dl[1].callback(None))
67             self.sub.publisher.broker.transport.loseConnection()
68         else:
69             dl[1].callback(None)
70         if self.port1 is not None:
71             dl.append(self.port1.stopListening())
72         if self.port2 is not None:
73             dl.append(self.port2.stopListening())
74         return defer.gatherResults(dl)
75
76
77     def testDistrib(self):
78         # site1 is the publisher
79         r1 = resource.Resource()
80         r1.putChild("there", static.Data("root", "text/plain"))
81         site1 = server.Site(r1)
82         self.f1 = PBServerFactory(distrib.ResourcePublisher(site1))
83         self.port1 = reactor.listenTCP(0, self.f1)
84         self.sub = distrib.ResourceSubscription("127.0.0.1",
85                                                 self.port1.getHost().port)
86         r2 = resource.Resource()
87         r2.putChild("here", self.sub)
88         f2 = MySite(r2)
89         self.port2 = reactor.listenTCP(0, f2)
90         d = client.getPage("http://127.0.0.1:%d/here/there" % \
91                            self.port2.getHost().port)
92         d.addCallback(self.assertEqual, 'root')
93         return d
94
95
96     def _setupDistribServer(self, child):
97         """
98         Set up a resource on a distrib site using L{ResourcePublisher}.
99
100         @param child: The resource to publish using distrib.
101
102         @return: A tuple consisting of the host and port on which to contact
103             the created site.
104         """
105         distribRoot = resource.Resource()
106         distribRoot.putChild("child", child)
107         distribSite = server.Site(distribRoot)
108         self.f1 = distribFactory = PBServerFactory(
109             distrib.ResourcePublisher(distribSite))
110         distribPort = reactor.listenTCP(
111             0, distribFactory, interface="127.0.0.1")
112         self.addCleanup(distribPort.stopListening)
113         addr = distribPort.getHost()
114
115         self.sub = mainRoot = distrib.ResourceSubscription(
116             addr.host, addr.port)
117         mainSite = server.Site(mainRoot)
118         mainPort = reactor.listenTCP(0, mainSite, interface="127.0.0.1")
119         self.addCleanup(mainPort.stopListening)
120         mainAddr = mainPort.getHost()
121
122         return mainPort, mainAddr
123
124
125     def _requestTest(self, child, **kwargs):
126         """
127         Set up a resource on a distrib site using L{ResourcePublisher} and
128         then retrieve it from a L{ResourceSubscription} via an HTTP client.
129
130         @param child: The resource to publish using distrib.
131         @param **kwargs: Extra keyword arguments to pass to L{getPage} when
132             requesting the resource.
133
134         @return: A L{Deferred} which fires with the result of the request.
135         """
136         mainPort, mainAddr = self._setupDistribServer(child)
137         return client.getPage("http://%s:%s/child" % (
138             mainAddr.host, mainAddr.port), **kwargs)
139
140
141     def _requestAgentTest(self, child, **kwargs):
142         """
143         Set up a resource on a distrib site using L{ResourcePublisher} and
144         then retrieve it from a L{ResourceSubscription} via an HTTP client.
145
146         @param child: The resource to publish using distrib.
147         @param **kwargs: Extra keyword arguments to pass to L{Agent.request} when
148             requesting the resource.
149
150         @return: A L{Deferred} which fires with a tuple consisting of a
151             L{twisted.test.proto_helpers.AccumulatingProtocol} containing the
152             body of the response and an L{IResponse} with the response itself.
153         """
154         mainPort, mainAddr = self._setupDistribServer(child)
155
156         d = client.Agent(reactor).request("GET", "http://%s:%s/child" % (
157             mainAddr.host, mainAddr.port), **kwargs)
158
159         def cbCollectBody(response):
160             protocol = proto_helpers.AccumulatingProtocol()
161             response.deliverBody(protocol)
162             d = protocol.closedDeferred = defer.Deferred()
163             d.addCallback(lambda _: (protocol, response))
164             return d
165         d.addCallback(cbCollectBody)
166         return d
167
168
169     def test_requestHeaders(self):
170         """
171         The request headers are available on the request object passed to a
172         distributed resource's C{render} method.
173         """
174         requestHeaders = {}
175
176         class ReportRequestHeaders(resource.Resource):
177             def render(self, request):
178                 requestHeaders.update(dict(
179                     request.requestHeaders.getAllRawHeaders()))
180                 return ""
181
182         request = self._requestTest(
183             ReportRequestHeaders(), headers={'foo': 'bar'})
184         def cbRequested(result):
185             self.assertEqual(requestHeaders['Foo'], ['bar'])
186         request.addCallback(cbRequested)
187         return request
188
189
190     def test_requestResponseCode(self):
191         """
192         The response code can be set by the request object passed to a
193         distributed resource's C{render} method.
194         """
195         class SetResponseCode(resource.Resource):
196             def render(self, request):
197                 request.setResponseCode(200)
198                 return ""
199
200         request = self._requestAgentTest(SetResponseCode())
201         def cbRequested(result):
202             self.assertEqual(result[0].data, "")
203             self.assertEqual(result[1].code, 200)
204             self.assertEqual(result[1].phrase, "OK")
205         request.addCallback(cbRequested)
206         return request
207
208
209     def test_requestResponseCodeMessage(self):
210         """
211         The response code and message can be set by the request object passed to
212         a distributed resource's C{render} method.
213         """
214         class SetResponseCode(resource.Resource):
215             def render(self, request):
216                 request.setResponseCode(200, "some-message")
217                 return ""
218
219         request = self._requestAgentTest(SetResponseCode())
220         def cbRequested(result):
221             self.assertEqual(result[0].data, "")
222             self.assertEqual(result[1].code, 200)
223             self.assertEqual(result[1].phrase, "some-message")
224         request.addCallback(cbRequested)
225         return request
226
227
228     def test_largeWrite(self):
229         """
230         If a string longer than the Banana size limit is passed to the
231         L{distrib.Request} passed to the remote resource, it is broken into
232         smaller strings to be transported over the PB connection.
233         """
234         class LargeWrite(resource.Resource):
235             def render(self, request):
236                 request.write('x' * SIZE_LIMIT + 'y')
237                 request.finish()
238                 return server.NOT_DONE_YET
239
240         request = self._requestTest(LargeWrite())
241         request.addCallback(self.assertEqual, 'x' * SIZE_LIMIT + 'y')
242         return request
243
244
245     def test_largeReturn(self):
246         """
247         Like L{test_largeWrite}, but for the case where C{render} returns a
248         long string rather than explicitly passing it to L{Request.write}.
249         """
250         class LargeReturn(resource.Resource):
251             def render(self, request):
252                 return 'x' * SIZE_LIMIT + 'y'
253
254         request = self._requestTest(LargeReturn())
255         request.addCallback(self.assertEqual, 'x' * SIZE_LIMIT + 'y')
256         return request
257
258
259     def test_connectionLost(self):
260         """
261         If there is an error issuing the request to the remote publisher, an
262         error response is returned.
263         """
264         # Using pb.Root as a publisher will cause request calls to fail with an
265         # error every time.  Just what we want to test.
266         self.f1 = serverFactory = PBServerFactory(pb.Root())
267         self.port1 = serverPort = reactor.listenTCP(0, serverFactory)
268
269         self.sub = subscription = distrib.ResourceSubscription(
270             "127.0.0.1", serverPort.getHost().port)
271         request = DummyRequest([''])
272         d = _render(subscription, request)
273         def cbRendered(ignored):
274             self.assertEqual(request.responseCode, 500)
275             # This is the error we caused the request to fail with.  It should
276             # have been logged.
277             self.assertEqual(len(self.flushLoggedErrors(pb.NoSuchMethod)), 1)
278         d.addCallback(cbRendered)
279         return d
280
281
282
283 class _PasswordDatabase:
284     def __init__(self, users):
285         self._users = users
286
287
288     def getpwall(self):
289         return iter(self._users)
290
291
292     def getpwnam(self, username):
293         for user in self._users:
294             if user[0] == username:
295                 return user
296         raise KeyError()
297
298
299
300 class UserDirectoryTests(unittest.TestCase):
301     """
302     Tests for L{UserDirectory}, a resource for listing all user resources
303     available on a system.
304     """
305     def setUp(self):
306         self.alice = ('alice', 'x', 123, 456, 'Alice,,,', self.mktemp(), '/bin/sh')
307         self.bob = ('bob', 'x', 234, 567, 'Bob,,,', self.mktemp(), '/bin/sh')
308         self.database = _PasswordDatabase([self.alice, self.bob])
309         self.directory = distrib.UserDirectory(self.database)
310
311
312     def test_interface(self):
313         """
314         L{UserDirectory} instances provide L{resource.IResource}.
315         """
316         self.assertTrue(verifyObject(resource.IResource, self.directory))
317
318
319     def _404Test(self, name):
320         """
321         Verify that requesting the C{name} child of C{self.directory} results
322         in a 404 response.
323         """
324         request = DummyRequest([name])
325         result = self.directory.getChild(name, request)
326         d = _render(result, request)
327         def cbRendered(ignored):
328             self.assertEqual(request.responseCode, 404)
329         d.addCallback(cbRendered)
330         return d
331
332
333     def test_getInvalidUser(self):
334         """
335         L{UserDirectory.getChild} returns a resource which renders a 404
336         response when passed a string which does not correspond to any known
337         user.
338         """
339         return self._404Test('carol')
340
341
342     def test_getUserWithoutResource(self):
343         """
344         L{UserDirectory.getChild} returns a resource which renders a 404
345         response when passed a string which corresponds to a known user who has
346         neither a user directory nor a user distrib socket.
347         """
348         return self._404Test('alice')
349
350
351     def test_getPublicHTMLChild(self):
352         """
353         L{UserDirectory.getChild} returns a L{static.File} instance when passed
354         the name of a user with a home directory containing a I{public_html}
355         directory.
356         """
357         home = filepath.FilePath(self.bob[-2])
358         public_html = home.child('public_html')
359         public_html.makedirs()
360         request = DummyRequest(['bob'])
361         result = self.directory.getChild('bob', request)
362         self.assertIsInstance(result, static.File)
363         self.assertEqual(result.path, public_html.path)
364
365
366     def test_getDistribChild(self):
367         """
368         L{UserDirectory.getChild} returns a L{ResourceSubscription} instance
369         when passed the name of a user suffixed with C{".twistd"} who has a
370         home directory containing a I{.twistd-web-pb} socket.
371         """
372         home = filepath.FilePath(self.bob[-2])
373         home.makedirs()
374         web = home.child('.twistd-web-pb')
375         request = DummyRequest(['bob'])
376         result = self.directory.getChild('bob.twistd', request)
377         self.assertIsInstance(result, distrib.ResourceSubscription)
378         self.assertEqual(result.host, 'unix')
379         self.assertEqual(abspath(result.port), web.path)
380
381
382     def test_invalidMethod(self):
383         """
384         L{UserDirectory.render} raises L{UnsupportedMethod} in response to a
385         non-I{GET} request.
386         """
387         request = DummyRequest([''])
388         request.method = 'POST'
389         self.assertRaises(
390             server.UnsupportedMethod, self.directory.render, request)
391
392
393     def test_render(self):
394         """
395         L{UserDirectory} renders a list of links to available user content
396         in response to a I{GET} request.
397         """
398         public_html = filepath.FilePath(self.alice[-2]).child('public_html')
399         public_html.makedirs()
400         web = filepath.FilePath(self.bob[-2])
401         web.makedirs()
402         # This really only works if it's a unix socket, but the implementation
403         # doesn't currently check for that.  It probably should someday, and
404         # then skip users with non-sockets.
405         web.child('.twistd-web-pb').setContent("")
406
407         request = DummyRequest([''])
408         result = _render(self.directory, request)
409         def cbRendered(ignored):
410             document = parseString(''.join(request.written))
411
412             # Each user should have an li with a link to their page.
413             [alice, bob] = document.getElementsByTagName('li')
414             self.assertEqual(alice.firstChild.tagName, 'a')
415             self.assertEqual(alice.firstChild.getAttribute('href'), 'alice/')
416             self.assertEqual(alice.firstChild.firstChild.data, 'Alice (file)')
417             self.assertEqual(bob.firstChild.tagName, 'a')
418             self.assertEqual(bob.firstChild.getAttribute('href'), 'bob.twistd/')
419             self.assertEqual(bob.firstChild.firstChild.data, 'Bob (twistd)')
420
421         result.addCallback(cbRendered)
422         return result
423
424
425     def test_passwordDatabase(self):
426         """
427         If L{UserDirectory} is instantiated with no arguments, it uses the
428         L{pwd} module as its password database.
429         """
430         directory = distrib.UserDirectory()
431         self.assertIdentical(directory._pwd, pwd)
432     if pwd is None:
433         test_passwordDatabase.skip = "pwd module required"
434