test_resource.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.web.resource}.
  5. """
  6. from twisted.trial.unittest import TestCase
  7. from twisted.web.error import UnsupportedMethod
  8. from twisted.web.resource import (
  9. NOT_FOUND, FORBIDDEN, Resource, ErrorPage, NoResource, ForbiddenResource,
  10. getChildForRequest)
  11. from twisted.web.http_headers import Headers
  12. from twisted.web.test.requesthelper import DummyRequest
  13. class ErrorPageTests(TestCase):
  14. """
  15. Tests for L{ErrorPage}, L{NoResource}, and L{ForbiddenResource}.
  16. """
  17. errorPage = ErrorPage
  18. noResource = NoResource
  19. forbiddenResource = ForbiddenResource
  20. def test_getChild(self):
  21. """
  22. The C{getChild} method of L{ErrorPage} returns the L{ErrorPage} it is
  23. called on.
  24. """
  25. page = self.errorPage(321, "foo", "bar")
  26. self.assertIdentical(page.getChild(b"name", object()), page)
  27. def _pageRenderingTest(self, page, code, brief, detail):
  28. request = DummyRequest([b''])
  29. template = (
  30. u"\n"
  31. u"<html>\n"
  32. u" <head><title>%s - %s</title></head>\n"
  33. u" <body>\n"
  34. u" <h1>%s</h1>\n"
  35. u" <p>%s</p>\n"
  36. u" </body>\n"
  37. u"</html>\n")
  38. expected = template % (code, brief, brief, detail)
  39. self.assertEqual(
  40. page.render(request), expected.encode('utf-8'))
  41. self.assertEqual(request.responseCode, code)
  42. self.assertEqual(
  43. request.responseHeaders,
  44. Headers({b'content-type': [b'text/html; charset=utf-8']}))
  45. def test_errorPageRendering(self):
  46. """
  47. L{ErrorPage.render} returns a C{bytes} describing the error defined by
  48. the response code and message passed to L{ErrorPage.__init__}. It also
  49. uses that response code to set the response code on the L{Request}
  50. passed in.
  51. """
  52. code = 321
  53. brief = "brief description text"
  54. detail = "much longer text might go here"
  55. page = self.errorPage(code, brief, detail)
  56. self._pageRenderingTest(page, code, brief, detail)
  57. def test_noResourceRendering(self):
  58. """
  59. L{NoResource} sets the HTTP I{NOT FOUND} code.
  60. """
  61. detail = "long message"
  62. page = self.noResource(detail)
  63. self._pageRenderingTest(page, NOT_FOUND, "No Such Resource", detail)
  64. def test_forbiddenResourceRendering(self):
  65. """
  66. L{ForbiddenResource} sets the HTTP I{FORBIDDEN} code.
  67. """
  68. detail = "longer message"
  69. page = self.forbiddenResource(detail)
  70. self._pageRenderingTest(page, FORBIDDEN, "Forbidden Resource", detail)
  71. class DynamicChild(Resource):
  72. """
  73. A L{Resource} to be created on the fly by L{DynamicChildren}.
  74. """
  75. def __init__(self, path, request):
  76. Resource.__init__(self)
  77. self.path = path
  78. self.request = request
  79. class DynamicChildren(Resource):
  80. """
  81. A L{Resource} with dynamic children.
  82. """
  83. def getChild(self, path, request):
  84. return DynamicChild(path, request)
  85. class BytesReturnedRenderable(Resource):
  86. """
  87. A L{Resource} with minimal capabilities to render a response.
  88. """
  89. def __init__(self, response):
  90. """
  91. @param response: A C{bytes} object giving the value to return from
  92. C{render_GET}.
  93. """
  94. Resource.__init__(self)
  95. self._response = response
  96. def render_GET(self, request):
  97. """
  98. Render a response to a I{GET} request by returning a short byte string
  99. to be written by the server.
  100. """
  101. return self._response
  102. class ImplicitAllowedMethods(Resource):
  103. """
  104. A L{Resource} which implicitly defines its allowed methods by defining
  105. renderers to handle them.
  106. """
  107. def render_GET(self, request):
  108. pass
  109. def render_PUT(self, request):
  110. pass
  111. class ResourceTests(TestCase):
  112. """
  113. Tests for L{Resource}.
  114. """
  115. def test_staticChildren(self):
  116. """
  117. L{Resource.putChild} adds a I{static} child to the resource. That child
  118. is returned from any call to L{Resource.getChildWithDefault} for the
  119. child's path.
  120. """
  121. resource = Resource()
  122. child = Resource()
  123. sibling = Resource()
  124. resource.putChild(b"foo", child)
  125. resource.putChild(b"bar", sibling)
  126. self.assertIdentical(
  127. child, resource.getChildWithDefault(b"foo", DummyRequest([])))
  128. def test_dynamicChildren(self):
  129. """
  130. L{Resource.getChildWithDefault} delegates to L{Resource.getChild} when
  131. the requested path is not associated with any static child.
  132. """
  133. path = b"foo"
  134. request = DummyRequest([])
  135. resource = DynamicChildren()
  136. child = resource.getChildWithDefault(path, request)
  137. self.assertIsInstance(child, DynamicChild)
  138. self.assertEqual(child.path, path)
  139. self.assertIdentical(child.request, request)
  140. def test_defaultHEAD(self):
  141. """
  142. When not otherwise overridden, L{Resource.render} treats a I{HEAD}
  143. request as if it were a I{GET} request.
  144. """
  145. expected = b"insert response here"
  146. request = DummyRequest([])
  147. request.method = b'HEAD'
  148. resource = BytesReturnedRenderable(expected)
  149. self.assertEqual(expected, resource.render(request))
  150. def test_explicitAllowedMethods(self):
  151. """
  152. The L{UnsupportedMethod} raised by L{Resource.render} for an unsupported
  153. request method has a C{allowedMethods} attribute set to the value of the
  154. C{allowedMethods} attribute of the L{Resource}, if it has one.
  155. """
  156. expected = [b'GET', b'HEAD', b'PUT']
  157. resource = Resource()
  158. resource.allowedMethods = expected
  159. request = DummyRequest([])
  160. request.method = b'FICTIONAL'
  161. exc = self.assertRaises(UnsupportedMethod, resource.render, request)
  162. self.assertEqual(set(expected), set(exc.allowedMethods))
  163. def test_implicitAllowedMethods(self):
  164. """
  165. The L{UnsupportedMethod} raised by L{Resource.render} for an unsupported
  166. request method has a C{allowedMethods} attribute set to a list of the
  167. methods supported by the L{Resource}, as determined by the
  168. I{render_}-prefixed methods which it defines, if C{allowedMethods} is
  169. not explicitly defined by the L{Resource}.
  170. """
  171. expected = set([b'GET', b'HEAD', b'PUT'])
  172. resource = ImplicitAllowedMethods()
  173. request = DummyRequest([])
  174. request.method = b'FICTIONAL'
  175. exc = self.assertRaises(UnsupportedMethod, resource.render, request)
  176. self.assertEqual(expected, set(exc.allowedMethods))
  177. class GetChildForRequestTests(TestCase):
  178. """
  179. Tests for L{getChildForRequest}.
  180. """
  181. def test_exhaustedPostPath(self):
  182. """
  183. L{getChildForRequest} returns whatever resource has been reached by the
  184. time the request's C{postpath} is empty.
  185. """
  186. request = DummyRequest([])
  187. resource = Resource()
  188. result = getChildForRequest(resource, request)
  189. self.assertIdentical(resource, result)
  190. def test_leafResource(self):
  191. """
  192. L{getChildForRequest} returns the first resource it encounters with a
  193. C{isLeaf} attribute set to C{True}.
  194. """
  195. request = DummyRequest([b"foo", b"bar"])
  196. resource = Resource()
  197. resource.isLeaf = True
  198. result = getChildForRequest(resource, request)
  199. self.assertIdentical(resource, result)
  200. def test_postPathToPrePath(self):
  201. """
  202. As path segments from the request are traversed, they are taken from
  203. C{postpath} and put into C{prepath}.
  204. """
  205. request = DummyRequest([b"foo", b"bar"])
  206. root = Resource()
  207. child = Resource()
  208. child.isLeaf = True
  209. root.putChild(b"foo", child)
  210. self.assertIdentical(child, getChildForRequest(root, request))
  211. self.assertEqual(request.prepath, [b"foo"])
  212. self.assertEqual(request.postpath, [b"bar"])