source: wokkel/test/test_component.py @ 41:72fa7b817767

Last change on this file since 41:72fa7b817767 was 41:72fa7b817767, checked in by Ralph Meijer <ralphm@…>, 14 years ago

Compatibility fixes for Twisted 8.0 and 8.1.

File size: 12.6 KB
Line 
1# Copyright (c) 2003-2008 Ralph Meijer
2# See LICENSE for details.
3
4"""
5Tests for L{wokkel.component}
6"""
7
8from zope.interface.verify import verifyObject
9
10from twisted.internet import defer
11from twisted.python import failure
12from twisted.trial import unittest
13from twisted.words.protocols.jabber import ijabber, xmlstream
14from twisted.words.protocols.jabber.jid import JID
15from twisted.words.xish import domish
16
17try:
18    from twisted.words.protocols.jabber.ijabber import IXMPPHandlerCollection
19    from twisted.words.protocols.jabber.xmlstream import XMPPHandler
20except ImportError:
21    from wokkel.subprotocols import IXMPPHandlerCollection, XMPPHandler
22
23from wokkel import component
24from wokkel.generic import XmlPipe
25
26class InternalComponentTest(unittest.TestCase):
27    """
28    Tests for L{component.InternalComponent}.
29    """
30
31    def setUp(self):
32        self.router = component.Router()
33        self.component = component.InternalComponent(self.router, 'component')
34
35
36    def test_interface(self):
37        """
38        L{component.InternalComponent} implements
39        L{IXMPPHandlerCollection}.
40        """
41        verifyObject(IXMPPHandlerCollection, self.component)
42
43
44    def test_startService(self):
45        """
46        Starting the service creates a new route and hooks up handlers.
47        """
48
49        events = []
50
51        class TestHandler(XMPPHandler):
52
53            def connectionInitialized(self):
54                fn = lambda obj: events.append(obj)
55                self.xmlstream.addObserver('//event/test', fn)
56
57        TestHandler().setHandlerParent(self.component)
58
59        self.assertFalse(self.component.running)
60
61        self.component.startService()
62
63        self.assertTrue(self.component.running)
64        self.assertIn('component', self.router.routes)
65
66        self.assertEquals([], events)
67        self.component.xmlstream.dispatch(None, '//event/test')
68        self.assertEquals([None], events)
69
70
71    def test_stopService(self):
72        """
73        Stopping the service removes the route and disconnects handlers.
74        """
75
76        events = []
77
78        class TestHandler(XMPPHandler):
79
80            def connectionLost(self, reason):
81                events.append(reason)
82
83        TestHandler().setHandlerParent(self.component)
84
85        self.component.startService()
86        self.component.stopService()
87
88        self.assertFalse(self.component.running)
89        self.assertEquals(1, len(events))
90        self.assertNotIn('component', self.router.routes)
91
92
93    def test_addHandler(self):
94        """
95        Adding a handler connects it to the stream.
96        """
97        events = []
98
99        class TestHandler(XMPPHandler):
100
101            def connectionInitialized(self):
102                fn = lambda obj: events.append(obj)
103                self.xmlstream.addObserver('//event/test', fn)
104
105        self.component.startService()
106        self.component.xmlstream.dispatch(None, '//event/test')
107        self.assertEquals([], events)
108
109        TestHandler().setHandlerParent(self.component)
110        self.component.xmlstream.dispatch(None, '//event/test')
111        self.assertEquals([None], events)
112
113
114    def test_send(self):
115        """
116        A message sent from the component ends up at the router.
117        """
118        events = []
119        fn = lambda obj: events.append(obj)
120        message = domish.Element((None, 'message'))
121
122        self.router.route = fn
123        self.component.startService()
124        self.component.send(message)
125
126        self.assertEquals([message], events)
127
128
129
130class RouterTest(unittest.TestCase):
131    """
132    Tests for L{component.Router}.
133    """
134
135    def test_addRoute(self):
136        """
137        Test route registration and routing on incoming stanzas.
138        """
139        router = component.Router()
140        routed = []
141        router.route = lambda element: routed.append(element)
142
143        pipe = XmlPipe()
144        router.addRoute('example.org', pipe.sink)
145        self.assertEquals(1, len(router.routes))
146        self.assertEquals(pipe.sink, router.routes['example.org'])
147
148        element = domish.Element(('testns', 'test'))
149        pipe.source.send(element)
150        self.assertEquals([element], routed)
151
152
153    def test_route(self):
154        """
155        Test routing of a message.
156        """
157        component1 = XmlPipe()
158        component2 = XmlPipe()
159        router = component.Router()
160        router.addRoute('component1.example.org', component1.sink)
161        router.addRoute('component2.example.org', component2.sink)
162
163        outgoing = []
164        component2.source.addObserver('/*',
165                                      lambda element: outgoing.append(element))
166        stanza = domish.Element((None, 'presence'))
167        stanza['from'] = 'component1.example.org'
168        stanza['to'] = 'component2.example.org'
169        component1.source.send(stanza)
170        self.assertEquals([stanza], outgoing)
171
172
173    def test_routeDefault(self):
174        """
175        Test routing of a message using the default route.
176
177        The default route is the one with C{None} as its key in the
178        routing table. It is taken when there is no more specific route
179        in the routing table that matches the stanza's destination.
180        """
181        component1 = XmlPipe()
182        s2s = XmlPipe()
183        router = component.Router()
184        router.addRoute('component1.example.org', component1.sink)
185        router.addRoute(None, s2s.sink)
186
187        outgoing = []
188        s2s.source.addObserver('/*', lambda element: outgoing.append(element))
189        stanza = domish.Element((None, 'presence'))
190        stanza['from'] = 'component1.example.org'
191        stanza['to'] = 'example.com'
192        component1.source.send(stanza)
193        self.assertEquals([stanza], outgoing)
194
195
196
197class ListenComponentAuthenticatorTest(unittest.TestCase):
198    """
199    Tests for L{component.ListenComponentAuthenticator}.
200    """
201
202    def setUp(self):
203        self.output = []
204        authenticator = component.ListenComponentAuthenticator('secret')
205        self.xmlstream = xmlstream.XmlStream(authenticator)
206        self.xmlstream.send = self.output.append
207
208
209    def loseConnection(self):
210        """
211        Stub loseConnection because we are a transport.
212        """
213        self.xmlstream.connectionLost("no reason")
214
215
216    def test_streamStarted(self):
217        """
218        The received stream header should set several attributes.
219        """
220        observers = []
221
222        def addOnetimeObserver(event, observerfn):
223            observers.append((event, observerfn))
224
225        xs = self.xmlstream
226        xs.addOnetimeObserver = addOnetimeObserver
227
228        xs.makeConnection(self)
229        self.assertIdentical(None, xs.sid)
230        self.assertFalse(xs._headerSent)
231
232        xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
233                         "xmlns:stream='http://etherx.jabber.org/streams' "
234                         "to='component.example.org'>")
235        self.assertEqual((0, 0), xs.version)
236        self.assertNotIdentical(None, xs.sid)
237        self.assertTrue(xs._headerSent)
238        self.assertEquals(('/*', xs.authenticator.onElement), observers[-1])
239
240
241    def test_streamStartedWrongNamespace(self):
242        """
243        The received stream header should have a correct namespace.
244        """
245        streamErrors = []
246
247        xs = self.xmlstream
248        xs.sendStreamError = streamErrors.append
249        xs.makeConnection(self)
250        xs.dataReceived("<stream:stream xmlns='jabber:client' "
251                         "xmlns:stream='http://etherx.jabber.org/streams' "
252                         "to='component.example.org'>")
253        self.assertEquals(1, len(streamErrors))
254        self.assertEquals('invalid-namespace', streamErrors[-1].condition)
255
256
257    def test_streamStartedNoTo(self):
258        """
259        The received stream header should have a 'to' attribute.
260        """
261        streamErrors = []
262
263        xs = self.xmlstream
264        xs.sendStreamError = streamErrors.append
265        xs.makeConnection(self)
266        xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
267                         "xmlns:stream='http://etherx.jabber.org/streams'>")
268        self.assertEquals(1, len(streamErrors))
269        self.assertEquals('improper-addressing', streamErrors[-1].condition)
270
271
272    def test_onElement(self):
273        """
274        We expect a handshake element with a hash.
275        """
276        handshakes = []
277
278        xs = self.xmlstream
279        xs.authenticator.onHandshake = handshakes.append
280
281        handshake = domish.Element(('jabber:component:accept', 'handshake'))
282        handshake.addContent('1234')
283        xs.authenticator.onElement(handshake)
284        self.assertEqual('1234', handshakes[-1])
285
286    def test_onElementNotHandshake(self):
287        """
288        Reject elements that are not handshakes
289        """
290        handshakes = []
291        streamErrors = []
292
293        xs = self.xmlstream
294        xs.authenticator.onHandshake = handshakes.append
295        xs.sendStreamError = streamErrors.append
296
297        element = domish.Element(('jabber:component:accept', 'message'))
298        xs.authenticator.onElement(element)
299        self.assertFalse(handshakes)
300        self.assertEquals('not-authorized', streamErrors[-1].condition)
301
302
303    def test_onHandshake(self):
304        """
305        Receiving a handshake matching the secret authenticates the stream.
306        """
307        authd = []
308
309        def authenticated(xs):
310            authd.append(xs)
311
312        xs = self.xmlstream
313        xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
314        xs.sid = '1234'
315        theHash = '32532c0f7dbf1253c095b18b18e36d38d94c1256'
316        xs.authenticator.onHandshake(theHash)
317        self.assertEqual('<handshake/>', self.output[-1])
318        self.assertEquals(1, len(authd))
319
320
321    def test_onHandshakeWrongHash(self):
322        """
323        Receiving a bad handshake should yield a stream error.
324        """
325        streamErrors = []
326        authd = []
327
328        def authenticated(xs):
329            authd.append(xs)
330
331        xs = self.xmlstream
332        xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
333        xs.sendStreamError = streamErrors.append
334
335        xs.sid = '1234'
336        theHash = '1234'
337        xs.authenticator.onHandshake(theHash)
338        self.assertEquals('not-authorized', streamErrors[-1].condition)
339        self.assertEquals(0, len(authd))
340
341
342
343class XMPPComponentServerFactoryTest(unittest.TestCase):
344    """
345    Tests for L{component.XMPPComponentServerFactory}.
346    """
347
348    def setUp(self):
349        self.router = component.Router()
350        self.factory = component.XMPPComponentServerFactory(self.router,
351                                                            'secret')
352        self.xmlstream = self.factory.buildProtocol(None)
353        self.xmlstream.thisEntity = JID('component.example.org')
354
355
356    def test_makeConnection(self):
357        """
358        A new connection increases the stream serial count. No logs by default.
359        """
360        self.xmlstream.dispatch(self.xmlstream,
361                                xmlstream.STREAM_CONNECTED_EVENT)
362        self.assertEqual(0, self.xmlstream.serial)
363        self.assertEqual(1, self.factory.serial)
364        self.assertIdentical(None, self.xmlstream.rawDataInFn)
365        self.assertIdentical(None, self.xmlstream.rawDataOutFn)
366
367
368    def test_makeConnectionLogTraffic(self):
369        """
370        Setting logTraffic should set up raw data loggers.
371        """
372        self.factory.logTraffic = True
373        self.xmlstream.dispatch(self.xmlstream,
374                                xmlstream.STREAM_CONNECTED_EVENT)
375        self.assertNotIdentical(None, self.xmlstream.rawDataInFn)
376        self.assertNotIdentical(None, self.xmlstream.rawDataOutFn)
377
378
379    def test_onError(self):
380        """
381        An observer for stream errors should trigger onError to log it.
382        """
383        self.xmlstream.dispatch(self.xmlstream,
384                                xmlstream.STREAM_CONNECTED_EVENT)
385
386        class TestError(Exception):
387            pass
388
389        reason = failure.Failure(TestError())
390        self.xmlstream.dispatch(reason, xmlstream.STREAM_ERROR_EVENT)
391        self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
392
393
394    def test_connectionInitialized(self):
395        """
396        Make sure a new stream is added to the routing table.
397        """
398        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
399        self.assertIn('component.example.org', self.router.routes)
400        self.assertIdentical(self.xmlstream,
401                             self.router.routes['component.example.org'])
402
403
404    def test_connectionLost(self):
405        """
406        Make sure a stream is removed from the routing table on disconnect.
407        """
408        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
409        self.xmlstream.dispatch(None, xmlstream.STREAM_END_EVENT)
410        self.assertNotIn('component.example.org', self.router.routes)
Note: See TracBrowser for help on using the repository browser.