source: wokkel/test/test_component.py @ 40:2bcb10fb4da4

Last change on this file since 40:2bcb10fb4da4 was 40:2bcb10fb4da4, checked in by Ralph Meijer <ralphm@…>, 14 years ago

Track changes to Twisted branch for the XMPP router.

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