source: wokkel/test/test_component.py @ 96:8e6130587088

Last change on this file since 96:8e6130587088 was 96:8e6130587088, checked in by Ralph Meijer <ralphm@…>, 9 years ago

Remove copyright dates from individual source files, only update LICENSE.

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