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