OLD | NEW |
| (Empty) |
1 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
2 # See LICENSE for details. | |
3 | |
4 | |
5 from twisted.trial import unittest | |
6 from twisted.web import server, resource, microdom, domhelpers | |
7 from twisted.web import http | |
8 from twisted.web.test import test_web | |
9 from twisted.internet import reactor, defer, address | |
10 | |
11 from twisted.web.woven import template, model, view, controller, widgets, input,
page, guard | |
12 | |
13 outputNum = 0 | |
14 | |
15 # Reusable test harness | |
16 | |
17 class WovenTC(unittest.TestCase): | |
18 modelFactory = lambda self: None | |
19 resourceFactory = None | |
20 def setUp(self): | |
21 self.m = self.modelFactory() | |
22 self.t = self.resourceFactory(self.m) | |
23 self.r = test_web.DummyRequest(['']) | |
24 self.r.prepath = [''] | |
25 self.prerender() | |
26 self.t.render(self.r) | |
27 | |
28 self.channel = "a fake channel" | |
29 self.output = ''.join(self.r.written) | |
30 assert self.output, "No output was generated by the test." | |
31 global outputNum | |
32 open("wovenTestOutput%s.html" % (outputNum + 1), 'w').write(self.output) | |
33 outputNum += 1 | |
34 self.d = microdom.parseString(self.output) | |
35 | |
36 def prerender(self): | |
37 pass | |
38 | |
39 # Test 1 | |
40 # Test that replacing nodes with a string works properly | |
41 | |
42 | |
43 class SimpleTemplate(template.DOMTemplate): | |
44 template = """<http> | |
45 <head> | |
46 <title id="title"><span view="getTitle">Hello</span></title> | |
47 </head> | |
48 <body> | |
49 <h3 id="hello"><span view="getHello">Hi</span></h3> | |
50 </body> | |
51 </http>""" | |
52 | |
53 def factory_getTitle(self, request, node): | |
54 return "Title" | |
55 | |
56 def factory_getHello(self, request, node): | |
57 return "Hello" | |
58 | |
59 | |
60 class DOMTemplateTest(WovenTC): | |
61 resourceFactory = SimpleTemplate | |
62 def testSimpleRender(self): | |
63 titleNode = self.d.getElementById("title") | |
64 helloNode = self.d.getElementById("hello") | |
65 | |
66 self.assert_(domhelpers.gatherTextNodes(titleNode) == 'Title') | |
67 self.assert_(domhelpers.gatherTextNodes(helloNode) == 'Hello') | |
68 | |
69 | |
70 # Test 2 | |
71 # Test just like the first, but with Text widgets | |
72 | |
73 class TemplateWithWidgets(SimpleTemplate): | |
74 def wcfactory_getTitle(self, request, node): | |
75 return widgets.Text("Title") | |
76 | |
77 def wcfactory_getHello(self, request, node): | |
78 return widgets.Text("Hello") | |
79 | |
80 | |
81 class TWWTest(DOMTemplateTest): | |
82 resourceFactory = TemplateWithWidgets | |
83 | |
84 | |
85 # Test 3 | |
86 # Test a fancier widget, and controllers handling submitted input | |
87 | |
88 | |
89 class MDemo(model.AttributeModel): | |
90 foo = "Hello world" | |
91 color = 'blue' | |
92 | |
93 | |
94 class FancyBox(widgets.Widget): | |
95 def setUp(self, request, node, data): | |
96 self['style'] = 'margin: 1em; padding: 1em; background-color: %s' % data | |
97 | |
98 | |
99 class VDemo(view.View): | |
100 template = """<html> | |
101 | |
102 <div id="box" model="color" view="FancyBox"></div> | |
103 | |
104 <form action=""> | |
105 Type a color and hit submit: | |
106 <input type="text" controller="change" model="color" name="color" /> | |
107 <input type="submit" /> | |
108 </form> | |
109 | |
110 </html> | |
111 """ | |
112 def wvfactory_FancyBox(self, request, node, model): | |
113 return FancyBox(model) | |
114 | |
115 def renderFailure(self, failure, request): | |
116 return failure | |
117 | |
118 | |
119 class ChangeColor(input.Anything): | |
120 def commit(self, request, node, data): | |
121 session = request.getSession() | |
122 session.color = data | |
123 self.model.setData(request, data) | |
124 self.model.notify({'request': request}) | |
125 | |
126 | |
127 class CDemo(controller.Controller): | |
128 def setUp(self, request): | |
129 session = request.getSession() | |
130 self.model.color = getattr(session, 'color', self.model.color) | |
131 | |
132 def wcfactory_change(self, request, node, model): | |
133 return ChangeColor(model) | |
134 | |
135 | |
136 view.registerViewForModel(VDemo, MDemo) | |
137 controller.registerControllerForModel(CDemo, MDemo) | |
138 | |
139 | |
140 class ControllerTest(WovenTC): | |
141 modelFactory = MDemo | |
142 resourceFactory = CDemo | |
143 | |
144 def prerender(self): | |
145 self.r.addArg('color', 'red') | |
146 | |
147 def testControllerOutput(self): | |
148 boxNode = self.d.getElementById("box") | |
149 assert boxNode, "Test %s failed" % outputNum | |
150 style = boxNode.getAttribute("style") | |
151 styles = style.split(";") | |
152 sDict = {} | |
153 for item in styles: | |
154 key, value = item.split(":") | |
155 key = key.strip() | |
156 value = value.strip() | |
157 sDict[key] = value | |
158 | |
159 # print sDict | |
160 assert sDict['background-color'] == 'red' | |
161 | |
162 | |
163 # Test 4 | |
164 # Test a list, a list widget, and Deferred data handling | |
165 | |
166 identityList = ['asdf', 'foo', 'fredf', 'bob'] | |
167 | |
168 class MIdentityList(model.AttributeModel): | |
169 def __init__(self): | |
170 model.Model.__init__(self) | |
171 self.identityList = defer.Deferred() | |
172 self.identityList.callback(identityList) | |
173 | |
174 | |
175 class VIdentityList(view.View): | |
176 template = """<html> | |
177 <ul id="list" view="identityList" model="identityList"> | |
178 <li listItemOf="identityList" view="text"> | |
179 Stuff. | |
180 </li> | |
181 </ul> | |
182 </html>""" | |
183 | |
184 def wvfactory_identityList(self, request, node, model): | |
185 return widgets.List(model) | |
186 | |
187 def wvfactory_text(self, request, node, model): | |
188 return widgets.Text(model) | |
189 | |
190 def renderFailure(self, failure, request): | |
191 return failure | |
192 | |
193 | |
194 class CIdentityList(controller.Controller): | |
195 pass | |
196 | |
197 | |
198 view.registerViewForModel(VIdentityList, MIdentityList) | |
199 controller.registerControllerForModel(CIdentityList, MIdentityList) | |
200 | |
201 | |
202 class ListDeferredTest(WovenTC): | |
203 modelFactory = MIdentityList | |
204 resourceFactory = CIdentityList | |
205 | |
206 def testOutput(self): | |
207 listNode = self.d.getElementById("list") | |
208 assert listNode, "Test %s failed; there was no element with the id 'list
' in the output" % outputNum | |
209 liNodes = domhelpers.getElementsByTagName(listNode, 'li') | |
210 assert len(liNodes) == len(identityList), "Test %s failed; the number of
'li' nodes did not match the list size" % outputNum | |
211 | |
212 | |
213 # Test 5 | |
214 # Test nested lists | |
215 | |
216 class LLModel(model.AttributeModel): | |
217 data = [['foo', 'bar', 'baz'], | |
218 ['gum', 'shoe'], | |
219 ['ggg', 'hhh', 'iii'] | |
220 ] | |
221 | |
222 | |
223 class LLView(view.View): | |
224 template = """<html> | |
225 <ul id="first" view="List" model="data"> | |
226 <li pattern="listItem" view="DefaultWidget"> | |
227 <ol view="List"> | |
228 <li pattern="listItem" view="Text" /> | |
229 </ol> | |
230 </li> | |
231 </ul> | |
232 </html>""" | |
233 | |
234 def wvfactory_List(self, request, node, model): | |
235 return widgets.List(model) | |
236 | |
237 | |
238 class NestedListTest(WovenTC): | |
239 modelFactory = LLModel | |
240 resourceFactory = LLView | |
241 | |
242 def testOutput(self): | |
243 listNode = self.d.getElementById("first") | |
244 assert listNode, "Test %s failed" % outputNum | |
245 liNodes = filter(lambda x: hasattr(x, 'tagName') and x.tagName == 'li',
listNode.childNodes) | |
246 # print len(liNodes), len(self.m.data), liNodes, self.m.data | |
247 assert len(liNodes) == len(self.m.data), "Test %s failed" % outputNum | |
248 for i in range(len(liNodes)): | |
249 sublistNode = domhelpers.getElementsByTagName(liNodes[i], 'ol')[0] | |
250 subLiNodes = domhelpers.getElementsByTagName(sublistNode, 'li') | |
251 assert len(self.m.data[i]) == len(subLiNodes) | |
252 | |
253 # Test 6 | |
254 # Test notification when a model is a dict or a list | |
255 | |
256 class MNotifyTest(model.AttributeModel): | |
257 def initialize(self, *args, **kwargs): | |
258 self.root = {"inventory": [], 'log': ""} | |
259 | |
260 | |
261 class VNotifyTest(view.View): | |
262 template = """<html> | |
263 <body> | |
264 <ol id="theList" model="root/inventory" view="List"> | |
265 <li view="someText" pattern="listItem" /> | |
266 </ol> | |
267 | |
268 <form action=""> | |
269 <input model="root" view="DefaultWidget" controller="updateInventory
" name="root" /> | |
270 <input type="submit" /> | |
271 </form> | |
272 </body> | |
273 </html>""" | |
274 | |
275 def wvfactory_someText(self, request, node, m): | |
276 return widgets.Text(m) | |
277 | |
278 class InventoryUpdater(input.Anything): | |
279 def commit(self, request, node, data): | |
280 invmodel = self.model.getSubmodel(request, "inventory") | |
281 log = self.model.getSubmodel(request, "log") | |
282 inv = invmodel.getData(request) | |
283 inv.append(data) # just add a string to the list | |
284 log.setData(request, log.getData(request) + ("%s added to servers\n" % d
ata)) | |
285 invmodel.setData(request, inv) | |
286 invmodel.notify({'request': request}) | |
287 | |
288 | |
289 class CNotifyTest(controller.Controller): | |
290 def wcfactory_updateInventory(self, request, node, model): | |
291 return InventoryUpdater(model) | |
292 | |
293 | |
294 view.registerViewForModel(VNotifyTest, MNotifyTest) | |
295 controller.registerControllerForModel(CNotifyTest, MNotifyTest) | |
296 | |
297 class NotifyTest(WovenTC): | |
298 modelFactory = MNotifyTest | |
299 resourceFactory = CNotifyTest | |
300 | |
301 def prerender(self): | |
302 self.r.addArg('root', 'test') | |
303 | |
304 def testComplexNotification(self): | |
305 listNode = self.d.getElementById("theList") | |
306 self.assert_(listNode, "Test %s failed" % outputNum) | |
307 liNodes = domhelpers.getElementsByTagName(listNode, 'li') | |
308 self.assert_(liNodes, | |
309 "DOM was not updated by notifying Widgets. Test %s" % outputNum) | |
310 text = domhelpers.gatherTextNodes(liNodes[0]) | |
311 self.assert_(text.strip() == "test", | |
312 "Wrong output: %s. Test %s" % (text, outputNum)) | |
313 | |
314 view.registerViewForModel(LLView, LLModel) | |
315 | |
316 #### Test 7 | |
317 # Test model path syntax | |
318 # model="/" should get you the root object | |
319 # model="." should get you the current object | |
320 # model=".." should get you the parent model object | |
321 | |
322 | |
323 # xxx sanity check for now; just make sure it doesn't raise anything | |
324 | |
325 class ModelPathTest(WovenTC): | |
326 modelFactory = lambda self: ['hello', ['hi', 'there'], | |
327 'hi', ['asdf', ['qwer', 'asdf']]] | |
328 resourceFactory = page.Page | |
329 | |
330 def prerender(self): | |
331 self.t.template = """<html> | |
332 <div model="0" view="None"> | |
333 <div model=".." view="Text" /> | |
334 </div> | |
335 | |
336 <div model="0" view="None"> | |
337 <div model="../1/../2/../3" view="Text" /> | |
338 </div> | |
339 | |
340 <div model="0" view="None"> | |
341 <div model="../3/1/./1" view="Text" /> | |
342 </div> | |
343 | |
344 <div model="3/1/0" view="None"> | |
345 <div model="/" view="Text" /> | |
346 </div> | |
347 | |
348 <div model="3/1/0" view="None"> | |
349 <div model="/3" view="Text" /> | |
350 </div> | |
351 | |
352 </html>""" | |
353 | |
354 #### Test 8 | |
355 # Test a large number of widgets | |
356 | |
357 class HugeTest(WovenTC): | |
358 modelFactory = lambda self: ['hello' for x in range(100)] | |
359 resourceFactory = page.Page | |
360 | |
361 def prerender(self): | |
362 self.t.template = """<html> | |
363 <div model="." view="List"> | |
364 <div pattern="listItem" view="Text" /> | |
365 </div> | |
366 </html>""" | |
367 | |
368 def testHugeTest(self): | |
369 pass | |
370 | |
371 | |
372 class ListOfDeferredsTest(WovenTC): | |
373 """Test rendering a model which is a list of Deferreds.""" | |
374 | |
375 modelFactory = lambda self: [defer.succeed("hello"), defer.succeed("world")] | |
376 resourceFactory = page.Page | |
377 | |
378 def prerender(self): | |
379 t = '''<div model="." view="List"> | |
380 <b pattern="listItem" view="Text" /> | |
381 </div>''' | |
382 self.t.template = t | |
383 | |
384 def testResult(self): | |
385 n = self.d.firstChild() | |
386 self.assertEquals(len(n.childNodes), 2) | |
387 for c in n.childNodes: | |
388 self.assertEquals(c.nodeName, "b") | |
389 self.assertEquals(n.firstChild().firstChild().toxml().strip(), "hello") | |
390 self.assertEquals(n.lastChild().firstChild().toxml().strip(), "world") | |
391 | |
392 | |
393 class FakeHTTPChannel: | |
394 # TODO: this should be an interface in twisted.protocols.http... lots of | |
395 # things want to fake out HTTP | |
396 def __init__(self): | |
397 self.transport = self | |
398 self.factory = self | |
399 self.received_cookies = {} | |
400 | |
401 # 'factory' attribute needs this | |
402 def log(self, req): | |
403 pass | |
404 | |
405 # 'channel' of request needs this | |
406 def requestDone(self, req): | |
407 self.req = req | |
408 | |
409 # 'transport' attribute needs this | |
410 def getPeer(self): | |
411 return address.IPv4Address("TCP", "fake", "fake") | |
412 def getHost(self): | |
413 return address.IPv4Address("TCP", "fake", 80) | |
414 | |
415 def write(self, data): | |
416 # print data | |
417 pass | |
418 def writeSequence(self, datas): | |
419 for data in datas: | |
420 self.write(data) | |
421 | |
422 # Utility for testing. | |
423 | |
424 def makeFakeRequest(self, path, **vars): | |
425 req = FakeHTTPRequest(self, queued=0) | |
426 req.received_cookies.update(self.received_cookies) | |
427 req.requestReceived("GET", path, "1.0") | |
428 return req | |
429 | |
430 class FakeHTTPRequest(server.Request): | |
431 def __init__(self, *args, **kw): | |
432 server.Request.__init__(self, *args, **kw) | |
433 self._cookieCache = {} | |
434 from cStringIO import StringIO | |
435 self.content = StringIO() | |
436 self.received_headers['host'] = 'fake.com' | |
437 self.written = StringIO() | |
438 | |
439 def write(self, data): | |
440 self.written.write(data) | |
441 server.Request.write(self, data) | |
442 | |
443 def addCookie(self, k, v, *args,**kw): | |
444 server.Request.addCookie(self,k,v,*args,**kw) | |
445 assert not self._cookieCache.has_key(k), "Should not be setting duplicat
e cookies!" | |
446 self._cookieCache[k] = v | |
447 self.channel.received_cookies[k] = v | |
448 | |
449 def processingFailed(self, fail): | |
450 raise fail | |
451 | |
452 class FakeSite(server.Site): | |
453 def getResourceFor(self, req): | |
454 res = server.Site.getResourceFor(self,req) | |
455 self.caughtRes = res | |
456 return res | |
457 | |
458 from twisted.web import static | |
459 | |
460 class GuardTest(unittest.TestCase): | |
461 def testSessionInit(self): | |
462 sessWrapped = static.Data("you should never see this", "text/plain") | |
463 swChild = static.Data("NO", "text/plain") | |
464 sessWrapped.putChild("yyy",swChild) | |
465 swrap = guard.SessionWrapper(sessWrapped) | |
466 da = static.Data("b","text/plain") | |
467 da.putChild("xxx", swrap) | |
468 st = FakeSite(da) | |
469 chan = FakeHTTPChannel() | |
470 chan.site = st | |
471 | |
472 # first we're going to make sure that the session doesn't get set by | |
473 # accident when browsing without first explicitly initializing the | |
474 # session | |
475 req = FakeHTTPRequest(chan, queued=0) | |
476 req.requestReceived("GET", "/xxx/yyy", "1.0") | |
477 assert len(req._cookieCache.values()) == 0, req._cookieCache.values() | |
478 self.assertEquals(req.getSession(),None) | |
479 | |
480 # now we're going to make sure that the redirect and cookie are properly
set | |
481 req = FakeHTTPRequest(chan, queued=0) | |
482 req.requestReceived("GET", "/xxx/"+guard.INIT_SESSION, "1.0") | |
483 ccv = req._cookieCache.values() | |
484 self.assertEquals(len(ccv),1) | |
485 cookie = ccv[0] | |
486 # redirect set? | |
487 self.failUnless(req.headers.has_key('location')) | |
488 # redirect matches cookie? | |
489 self.assertEquals(req.headers['location'].split('/')[-1], guard.SESSION_
KEY+cookie) | |
490 # URL is correct? | |
491 self.assertEquals(req.headers['location'], | |
492 'http://fake.com/xxx/'+guard.SESSION_KEY+cookie) | |
493 oldreq = req | |
494 | |
495 # now let's try with a request for the session-cookie URL that has a coo
kie set | |
496 url = "/"+(oldreq.headers['location'].split('http://fake.com/',1))[1] | |
497 req = chan.makeFakeRequest(url) | |
498 self.assertEquals(req.headers['location'].split('?')[0], | |
499 'http://fake.com/xxx/') | |
500 for sz in swrap.sessions.values(): | |
501 sz.expire() | |
502 | |
503 | |
504 | |
505 class _TestPage(page.Page): | |
506 template = """ | |
507 <html><body> | |
508 | |
509 <div>First: <span model="title" view="Text"/></div> | |
510 <div>Second: <span model="title" view="Text"/></div> | |
511 <div>Third: <span model="title" view="Text"/></div> | |
512 | |
513 </body></html> | |
514 """ | |
515 | |
516 def wmfactory_title(self, request): | |
517 d = defer.Deferred() | |
518 reactor.callLater(0, d.callback, 'The Result') | |
519 return d | |
520 | |
521 class DeferredModelTestCase(unittest.TestCase): | |
522 def testDeferredModel(self): | |
523 # Test that multiple uses of a deferred model work correctly. | |
524 channel = FakeHTTPChannel() | |
525 channel.site = FakeSite(_TestPage()) | |
526 request = channel.makeFakeRequest('/') | |
527 | |
528 d = request.notifyFinish() | |
529 def check(_): | |
530 dom = microdom.parseXMLString(request.written.getvalue()) | |
531 spanElems = domhelpers.findNodesNamed(dom, 'span') | |
532 for spanElem in spanElems: | |
533 self.failUnlessEqual('The Result', spanElem.childNodes[0].data) | |
534 | |
535 return d.addCallback(check) | |
536 | |
537 | |
538 class MyMacroPage(page.Page): | |
539 template = """\ | |
540 <html macro='foo'> | |
541 <head fill-slot='head'> | |
542 <script> | |
543 <![CDATA[ | |
544 <>'"& | |
545 ]]> | |
546 </script> | |
547 </head> | |
548 </html> | |
549 """ | |
550 def wvfactory_foo(self, request, node, model): | |
551 return widgets.ExpandMacro(model, macroFile = 'cdataxtester.html', | |
552 macroFileDirectory = '.', | |
553 macroName = 'foo' | |
554 ) | |
555 | |
556 class ExpandMacroTestCase(WovenTC): | |
557 resourceFactory = MyMacroPage | |
558 def setUp(self, *args, **kwargs): | |
559 thepage = """\ | |
560 <html> | |
561 <head slot='head' /> | |
562 </html> | |
563 """ | |
564 file('cdatatester.html', 'wb').write(thepage) | |
565 WovenTC.setUp(self, *args, **kwargs) | |
566 def testCDATANotQuoted(self): | |
567 self.failUnless(self.output.find('<>\'"&')>=0) | |
568 | |
569 | |
570 | |
OLD | NEW |