1
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
2
# See LICENSE for details.
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
11
from twisted.web.woven import template, model, view, controller, widgets, input, page, guard
15
# Reusable test harness
17
class WovenTC(unittest.TestCase):
18
modelFactory = lambda self: None
19
resourceFactory = None
21
self.m = self.modelFactory()
22
self.t = self.resourceFactory(self.m)
23
self.r = test_web.DummyRequest([''])
28
self.channel = "a fake channel"
29
self.output = ''.join(self.r.written)
30
assert self.output, "No output was generated by the test."
32
open("wovenTestOutput%s.html" % (outputNum + 1), 'w').write(self.output)
34
self.d = microdom.parseString(self.output)
40
# Test that replacing nodes with a string works properly
43
class SimpleTemplate(template.DOMTemplate):
46
<title id="title"><span view="getTitle">Hello</span></title>
49
<h3 id="hello"><span view="getHello">Hi</span></h3>
53
def factory_getTitle(self, request, node):
56
def factory_getHello(self, request, node):
60
class DOMTemplateTest(WovenTC):
61
resourceFactory = SimpleTemplate
62
def testSimpleRender(self):
63
titleNode = self.d.getElementById("title")
64
helloNode = self.d.getElementById("hello")
66
self.assert_(domhelpers.gatherTextNodes(titleNode) == 'Title')
67
self.assert_(domhelpers.gatherTextNodes(helloNode) == 'Hello')
71
# Test just like the first, but with Text widgets
73
class TemplateWithWidgets(SimpleTemplate):
74
def wcfactory_getTitle(self, request, node):
75
return widgets.Text("Title")
77
def wcfactory_getHello(self, request, node):
78
return widgets.Text("Hello")
81
class TWWTest(DOMTemplateTest):
82
resourceFactory = TemplateWithWidgets
86
# Test a fancier widget, and controllers handling submitted input
89
class MDemo(model.AttributeModel):
94
class FancyBox(widgets.Widget):
95
def setUp(self, request, node, data):
96
self['style'] = 'margin: 1em; padding: 1em; background-color: %s' % data
99
class VDemo(view.View):
102
<div id="box" model="color" view="FancyBox"></div>
105
Type a color and hit submit:
106
<input type="text" controller="change" model="color" name="color" />
107
<input type="submit" />
112
def wvfactory_FancyBox(self, request, node, model):
113
return FancyBox(model)
115
def renderFailure(self, failure, request):
119
class ChangeColor(input.Anything):
120
def commit(self, request, node, data):
121
session = request.getSession()
123
self.model.setData(request, data)
124
self.model.notify({'request': request})
127
class CDemo(controller.Controller):
128
def setUp(self, request):
129
session = request.getSession()
130
self.model.color = getattr(session, 'color', self.model.color)
132
def wcfactory_change(self, request, node, model):
133
return ChangeColor(model)
136
view.registerViewForModel(VDemo, MDemo)
137
controller.registerControllerForModel(CDemo, MDemo)
140
class ControllerTest(WovenTC):
142
resourceFactory = CDemo
145
self.r.addArg('color', 'red')
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(";")
154
key, value = item.split(":")
156
value = value.strip()
160
assert sDict['background-color'] == 'red'
164
# Test a list, a list widget, and Deferred data handling
166
identityList = ['asdf', 'foo', 'fredf', 'bob']
168
class MIdentityList(model.AttributeModel):
170
model.Model.__init__(self)
171
self.identityList = defer.Deferred()
172
self.identityList.callback(identityList)
175
class VIdentityList(view.View):
177
<ul id="list" view="identityList" model="identityList">
178
<li listItemOf="identityList" view="text">
184
def wvfactory_identityList(self, request, node, model):
185
return widgets.List(model)
187
def wvfactory_text(self, request, node, model):
188
return widgets.Text(model)
190
def renderFailure(self, failure, request):
194
class CIdentityList(controller.Controller):
198
view.registerViewForModel(VIdentityList, MIdentityList)
199
controller.registerControllerForModel(CIdentityList, MIdentityList)
202
class ListDeferredTest(WovenTC):
203
modelFactory = MIdentityList
204
resourceFactory = CIdentityList
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
216
class LLModel(model.AttributeModel):
217
data = [['foo', 'bar', 'baz'],
219
['ggg', 'hhh', 'iii']
223
class LLView(view.View):
225
<ul id="first" view="List" model="data">
226
<li pattern="listItem" view="DefaultWidget">
228
<li pattern="listItem" view="Text" />
234
def wvfactory_List(self, request, node, model):
235
return widgets.List(model)
238
class NestedListTest(WovenTC):
239
modelFactory = LLModel
240
resourceFactory = LLView
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)
254
# Test notification when a model is a dict or a list
256
class MNotifyTest(model.AttributeModel):
257
def initialize(self, *args, **kwargs):
258
self.root = {"inventory": [], 'log': ""}
261
class VNotifyTest(view.View):
264
<ol id="theList" model="root/inventory" view="List">
265
<li view="someText" pattern="listItem" />
269
<input model="root" view="DefaultWidget" controller="updateInventory" name="root" />
270
<input type="submit" />
275
def wvfactory_someText(self, request, node, m):
276
return widgets.Text(m)
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" % data))
285
invmodel.setData(request, inv)
286
invmodel.notify({'request': request})
289
class CNotifyTest(controller.Controller):
290
def wcfactory_updateInventory(self, request, node, model):
291
return InventoryUpdater(model)
294
view.registerViewForModel(VNotifyTest, MNotifyTest)
295
controller.registerControllerForModel(CNotifyTest, MNotifyTest)
297
class NotifyTest(WovenTC):
298
modelFactory = MNotifyTest
299
resourceFactory = CNotifyTest
302
self.r.addArg('root', 'test')
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))
314
view.registerViewForModel(LLView, LLModel)
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
323
# xxx sanity check for now; just make sure it doesn't raise anything
325
class ModelPathTest(WovenTC):
326
modelFactory = lambda self: ['hello', ['hi', 'there'],
327
'hi', ['asdf', ['qwer', 'asdf']]]
328
resourceFactory = page.Page
331
self.t.template = """<html>
332
<div model="0" view="None">
333
<div model=".." view="Text" />
336
<div model="0" view="None">
337
<div model="../1/../2/../3" view="Text" />
340
<div model="0" view="None">
341
<div model="../3/1/./1" view="Text" />
344
<div model="3/1/0" view="None">
345
<div model="/" view="Text" />
348
<div model="3/1/0" view="None">
349
<div model="/3" view="Text" />
355
# Test a large number of widgets
357
class HugeTest(WovenTC):
358
modelFactory = lambda self: ['hello' for x in range(100)]
359
resourceFactory = page.Page
362
self.t.template = """<html>
363
<div model="." view="List">
364
<div pattern="listItem" view="Text" />
368
def testHugeTest(self):
372
class ListOfDeferredsTest(WovenTC):
373
"""Test rendering a model which is a list of Deferreds."""
375
modelFactory = lambda self: [defer.succeed("hello"), defer.succeed("world")]
376
resourceFactory = page.Page
379
t = '''<div model="." view="List">
380
<b pattern="listItem" view="Text" />
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")
393
class FakeHTTPChannel:
394
# TODO: this should be an interface in twisted.protocols.http... lots of
395
# things want to fake out HTTP
397
self.transport = self
399
self.received_cookies = {}
401
# 'factory' attribute needs this
405
# 'channel' of request needs this
406
def requestDone(self, req):
409
# 'transport' attribute needs this
411
return address.IPv4Address("TCP", "fake", "fake")
413
return address.IPv4Address("TCP", "fake", 80)
415
def write(self, data):
418
def writeSequence(self, datas):
422
# Utility for testing.
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")
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()
439
def write(self, data):
440
self.written.write(data)
441
server.Request.write(self, data)
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 duplicate cookies!"
446
self._cookieCache[k] = v
447
self.channel.received_cookies[k] = v
449
def processingFailed(self, fail):
452
class FakeSite(server.Site):
453
def getResourceFor(self, req):
454
res = server.Site.getResourceFor(self,req)
458
from twisted.web import static
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)
469
chan = FakeHTTPChannel()
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
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)
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)
487
self.failUnless(req.headers.has_key('location'))
488
# redirect matches cookie?
489
self.assertEquals(req.headers['location'].split('/')[-1], guard.SESSION_KEY+cookie)
491
self.assertEquals(req.headers['location'],
492
'http://fake.com/xxx/'+guard.SESSION_KEY+cookie)
495
# now let's try with a request for the session-cookie URL that has a cookie 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():
505
class _TestPage(page.Page):
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>
516
def wmfactory_title(self, request):
518
reactor.callLater(0, d.callback, 'The Result')
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('/')
528
d = request.notifyFinish()
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)
535
return d.addCallback(check)
538
class MyMacroPage(page.Page):
541
<head fill-slot='head'>
550
def wvfactory_foo(self, request, node, model):
551
return widgets.ExpandMacro(model, macroFile = 'cdataxtester.html',
552
macroFileDirectory = '.',
556
class ExpandMacroTestCase(WovenTC):
557
resourceFactory = MyMacroPage
558
def setUp(self, *args, **kwargs):
564
file('cdatatester.html', 'wb').write(thepage)
565
WovenTC.setUp(self, *args, **kwargs)
566
def testCDATANotQuoted(self):
567
self.failUnless(self.output.find('<>\'"&')>=0)