source: wokkel/test/test_component.py @ 205:e861fc4596ed

Last change on this file since 205:e861fc4596ed was 205:e861fc4596ed, checked in by Ralph Meijer <ralphm@…>, 4 years ago

imported patch py3-component.patch

  • Property exe set to *
File size: 16.5 KB
Line 
1# Copyright (c) Ralph Meijer.
2# See LICENSE for details.
3
4"""
5Tests for L{wokkel.component}.
6"""
7
8from __future__ import division, absolute_import
9
10from zope.interface.verify import verifyObject
11
12from twisted.internet.base import BaseConnector
13from twisted.internet.error import ConnectionRefusedError
14from twisted.internet.task import Clock
15from twisted.python import failure
16from twisted.trial import unittest
17from twisted.words.protocols.jabber import xmlstream
18from twisted.words.protocols.jabber.ijabber import IXMPPHandlerCollection
19from twisted.words.protocols.jabber.jid import JID
20from twisted.words.protocols.jabber.xmlstream import XMPPHandler
21from twisted.words.xish import domish
22
23from wokkel import component
24from wokkel.generic import XmlPipe
25
26class FakeConnector(BaseConnector):
27    """
28    Fake connector that counts connection attempts.
29    """
30    connects = 0
31
32    def connect(self):
33        self.connects += 1
34        BaseConnector.connect(self)
35
36
37    def _makeTransport(self):
38        return None
39
40
41
42class TestableComponent(component.Component):
43    """
44    Testable component.
45
46    This component provides the created factory with a L{Clock}
47    instead of the regular reactor and uses L{FakeConnector} for testing
48    connects and reconnects.
49    """
50
51    def __init__(self, *args, **kwargs):
52        component.Component.__init__(self, *args, **kwargs)
53        self.factory.clock = Clock()
54
55
56    def _getConnection(self):
57        c = FakeConnector(self.factory, None, None)
58        c.connect()
59        return c
60
61
62
63class ComponentTest(unittest.TestCase):
64    """
65    Tests for L{component.Component}.
66    """
67    def test_startServiceReconnectAfterFailure(self):
68        """
69        When the first connection attempt fails, retry.
70        """
71        comp = TestableComponent('example.org', 5347,
72                                 'test.example.org', 'secret')
73
74        # Starting the service initiates a connection attempt.
75        comp.startService()
76        connector = comp._connection
77        self.assertEqual(1, connector.connects)
78
79        # Fail the connection.
80        connector.connectionFailed(ConnectionRefusedError())
81
82        # After a back-off delay, a new connection is attempted.
83        comp.factory.clock.advance(5)
84        self.assertEqual(2, connector.connects)
85
86
87    def test_stopServiceNoReconnect(self):
88        """
89        When the service is stopped, no reconnect is attempted.
90        """
91        comp = TestableComponent('example.org', 5347,
92                                 'test.example.org', 'secret')
93
94        # Starting the service initiates a connection attempt.
95        comp.startService()
96        connector = comp._connection
97
98        # Fail the connection.
99        connector.connectionFailed(ConnectionRefusedError())
100
101        # If the service is stopped before the back-off delay expires,
102        # no new connection is attempted.
103        comp.factory.clock.advance(1)
104        comp.stopService()
105        comp.factory.clock.advance(4)
106        self.assertEqual(1, connector.connects)
107
108
109
110class InternalComponentTest(unittest.TestCase):
111    """
112    Tests for L{component.InternalComponent}.
113    """
114
115    def setUp(self):
116        self.router = component.Router()
117        self.component = component.InternalComponent(self.router, 'component')
118
119
120    def test_interface(self):
121        """
122        L{component.InternalComponent} implements
123        L{IXMPPHandlerCollection}.
124        """
125        verifyObject(IXMPPHandlerCollection, self.component)
126
127
128    def test_startServiceRunning(self):
129        """
130        Starting the service makes it running.
131        """
132        self.assertFalse(self.component.running)
133        self.component.startService()
134        self.assertTrue(self.component.running)
135
136
137    def test_startServiceAddRoute(self):
138        """
139        Starting the service creates a new route.
140        """
141        self.component.startService()
142        self.assertIn('component', self.router.routes)
143
144
145    def test_startServiceNoDomain(self):
146        self.component = component.InternalComponent(self.router)
147        self.component.startService()
148
149
150    def test_startServiceAddMultipleRoutes(self):
151        """
152        Starting the service creates a new route.
153        """
154        self.component.domains.add('component2')
155        self.component.startService()
156        self.assertIn('component', self.router.routes)
157        self.assertIn('component2', self.router.routes)
158
159
160    def test_startServiceHandlerDispatch(self):
161        """
162        Starting the service hooks up handlers.
163        """
164        events = []
165
166        class TestHandler(XMPPHandler):
167
168            def connectionInitialized(self):
169                fn = lambda obj: events.append(obj)
170                self.xmlstream.addObserver('//event/test', fn)
171
172        TestHandler().setHandlerParent(self.component)
173
174        self.component.startService()
175        self.assertEquals([], events)
176        self.component.xmlstream.dispatch(None, '//event/test')
177        self.assertEquals([None], events)
178
179
180    def test_stopServiceNotRunning(self):
181        """
182        Stopping the service makes it not running.
183        """
184        self.component.startService()
185        self.component.stopService()
186        self.assertFalse(self.component.running)
187
188
189    def test_stopServiceRemoveRoute(self):
190        """
191        Stopping the service removes routes.
192        """
193        self.component.startService()
194        self.component.stopService()
195        self.assertNotIn('component', self.router.routes)
196
197
198    def test_stopServiceNoDomain(self):
199        self.component = component.InternalComponent(self.router)
200        self.component.startService()
201        self.component.stopService()
202
203
204    def test_startServiceRemoveMultipleRoutes(self):
205        """
206        Starting the service creates a new route.
207        """
208        self.component.domains.add('component2')
209        self.component.startService()
210        self.component.stopService()
211        self.assertNotIn('component', self.router.routes)
212        self.assertNotIn('component2', self.router.routes)
213
214
215    def test_stopServiceHandlerDispatch(self):
216        """
217        Stopping the service disconnects handlers.
218        """
219        events = []
220
221        class TestHandler(XMPPHandler):
222
223            def connectionLost(self, reason):
224                events.append(reason)
225
226        TestHandler().setHandlerParent(self.component)
227
228        self.component.startService()
229        self.component.stopService()
230        self.assertEquals(1, len(events))
231
232
233    def test_addHandler(self):
234        """
235        Adding a handler connects it to the stream.
236        """
237        events = []
238
239        class TestHandler(XMPPHandler):
240
241            def connectionInitialized(self):
242                fn = lambda obj: events.append(obj)
243                self.xmlstream.addObserver('//event/test', fn)
244
245        self.component.startService()
246        self.component.xmlstream.dispatch(None, '//event/test')
247        self.assertEquals([], events)
248
249        TestHandler().setHandlerParent(self.component)
250        self.component.xmlstream.dispatch(None, '//event/test')
251        self.assertEquals([None], events)
252
253
254    def test_send(self):
255        """
256        A message sent from the component ends up at the router.
257        """
258        events = []
259        fn = lambda obj: events.append(obj)
260        message = domish.Element((None, 'message'))
261
262        self.router.route = fn
263        self.component.startService()
264        self.component.send(message)
265
266        self.assertEquals([message], events)
267
268
269
270class RouterTest(unittest.TestCase):
271    """
272    Tests for L{component.Router}.
273    """
274
275    def test_addRoute(self):
276        """
277        Test route registration and routing on incoming stanzas.
278        """
279        router = component.Router()
280        routed = []
281        router.route = lambda element: routed.append(element)
282
283        pipe = XmlPipe()
284        router.addRoute('example.org', pipe.sink)
285        self.assertEquals(1, len(router.routes))
286        self.assertEquals(pipe.sink, router.routes['example.org'])
287
288        element = domish.Element(('testns', 'test'))
289        pipe.source.send(element)
290        self.assertEquals([element], routed)
291
292
293    def test_route(self):
294        """
295        Test routing of a message.
296        """
297        component1 = XmlPipe()
298        component2 = XmlPipe()
299        router = component.Router()
300        router.addRoute('component1.example.org', component1.sink)
301        router.addRoute('component2.example.org', component2.sink)
302
303        outgoing = []
304        component2.source.addObserver('/*',
305                                      lambda element: outgoing.append(element))
306        stanza = domish.Element((None, 'presence'))
307        stanza['from'] = 'component1.example.org'
308        stanza['to'] = 'component2.example.org'
309        component1.source.send(stanza)
310        self.assertEquals([stanza], outgoing)
311
312
313    def test_routeDefault(self):
314        """
315        Test routing of a message using the default route.
316
317        The default route is the one with L{None} as its key in the
318        routing table. It is taken when there is no more specific route
319        in the routing table that matches the stanza's destination.
320        """
321        component1 = XmlPipe()
322        s2s = XmlPipe()
323        router = component.Router()
324        router.addRoute('component1.example.org', component1.sink)
325        router.addRoute(None, s2s.sink)
326
327        outgoing = []
328        s2s.source.addObserver('/*', lambda element: outgoing.append(element))
329        stanza = domish.Element((None, 'presence'))
330        stanza['from'] = 'component1.example.org'
331        stanza['to'] = 'example.com'
332        component1.source.send(stanza)
333        self.assertEquals([stanza], outgoing)
334
335
336
337class ListenComponentAuthenticatorTest(unittest.TestCase):
338    """
339    Tests for L{component.ListenComponentAuthenticator}.
340    """
341
342    def setUp(self):
343        self.output = []
344        authenticator = component.ListenComponentAuthenticator('secret')
345        self.xmlstream = xmlstream.XmlStream(authenticator)
346        self.xmlstream.send = self.output.append
347
348
349    def loseConnection(self):
350        """
351        Stub loseConnection because we are a transport.
352        """
353        self.xmlstream.connectionLost("no reason")
354
355
356    def test_streamStarted(self):
357        """
358        The received stream header should set several attributes.
359        """
360        observers = []
361
362        def addOnetimeObserver(event, observerfn):
363            observers.append((event, observerfn))
364
365        xs = self.xmlstream
366        xs.addOnetimeObserver = addOnetimeObserver
367
368        xs.makeConnection(self)
369        self.assertIdentical(None, xs.sid)
370        self.assertFalse(xs._headerSent)
371
372        xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
373                         "xmlns:stream='http://etherx.jabber.org/streams' "
374                         "to='component.example.org'>")
375        self.assertEqual((0, 0), xs.version)
376        self.assertNotIdentical(None, xs.sid)
377        self.assertTrue(xs._headerSent)
378        self.assertEquals(('/*', xs.authenticator.onElement), observers[-1])
379
380
381    def test_streamStartedWrongNamespace(self):
382        """
383        The received stream header should have a correct namespace.
384        """
385        streamErrors = []
386
387        xs = self.xmlstream
388        xs.sendStreamError = streamErrors.append
389        xs.makeConnection(self)
390        xs.dataReceived("<stream:stream xmlns='jabber:client' "
391                         "xmlns:stream='http://etherx.jabber.org/streams' "
392                         "to='component.example.org'>")
393        self.assertEquals(1, len(streamErrors))
394        self.assertEquals('invalid-namespace', streamErrors[-1].condition)
395
396
397    def test_streamStartedNoTo(self):
398        """
399        The received stream header should have a 'to' attribute.
400        """
401        streamErrors = []
402
403        xs = self.xmlstream
404        xs.sendStreamError = streamErrors.append
405        xs.makeConnection(self)
406        xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
407                         "xmlns:stream='http://etherx.jabber.org/streams'>")
408        self.assertEquals(1, len(streamErrors))
409        self.assertEquals('improper-addressing', streamErrors[-1].condition)
410
411
412    def test_onElement(self):
413        """
414        We expect a handshake element with a hash.
415        """
416        handshakes = []
417
418        xs = self.xmlstream
419        xs.authenticator.onHandshake = handshakes.append
420
421        handshake = domish.Element(('jabber:component:accept', 'handshake'))
422        handshake.addContent('1234')
423        xs.authenticator.onElement(handshake)
424        self.assertEqual('1234', handshakes[-1])
425
426    def test_onElementNotHandshake(self):
427        """
428        Reject elements that are not handshakes
429        """
430        handshakes = []
431        streamErrors = []
432
433        xs = self.xmlstream
434        xs.authenticator.onHandshake = handshakes.append
435        xs.sendStreamError = streamErrors.append
436
437        element = domish.Element(('jabber:component:accept', 'message'))
438        xs.authenticator.onElement(element)
439        self.assertFalse(handshakes)
440        self.assertEquals('not-authorized', streamErrors[-1].condition)
441
442
443    def test_onHandshake(self):
444        """
445        Receiving a handshake matching the secret authenticates the stream.
446        """
447        authd = []
448
449        def authenticated(xs):
450            authd.append(xs)
451
452        xs = self.xmlstream
453        xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
454        xs.sid = u'1234'
455        theHash = '32532c0f7dbf1253c095b18b18e36d38d94c1256'
456        xs.authenticator.onHandshake(theHash)
457        self.assertEqual('<handshake/>', self.output[-1])
458        self.assertEquals(1, len(authd))
459
460
461    def test_onHandshakeWrongHash(self):
462        """
463        Receiving a bad handshake should yield a stream error.
464        """
465        streamErrors = []
466        authd = []
467
468        def authenticated(xs):
469            authd.append(xs)
470
471        xs = self.xmlstream
472        xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
473        xs.sendStreamError = streamErrors.append
474
475        xs.sid = u'1234'
476        theHash = '1234'
477        xs.authenticator.onHandshake(theHash)
478        self.assertEquals('not-authorized', streamErrors[-1].condition)
479        self.assertEquals(0, len(authd))
480
481
482
483class XMPPComponentServerFactoryTest(unittest.TestCase):
484    """
485    Tests for L{component.XMPPComponentServerFactory}.
486    """
487
488    def setUp(self):
489        self.router = component.Router()
490        self.factory = component.XMPPComponentServerFactory(self.router,
491                                                            'secret')
492        self.xmlstream = self.factory.buildProtocol(None)
493        self.xmlstream.thisEntity = JID('component.example.org')
494
495
496    def test_makeConnection(self):
497        """
498        A new connection increases the stream serial count. No logs by default.
499        """
500        self.xmlstream.dispatch(self.xmlstream,
501                                xmlstream.STREAM_CONNECTED_EVENT)
502        self.assertEqual(0, self.xmlstream.serial)
503        self.assertEqual(1, self.factory.serial)
504        self.assertIdentical(None, self.xmlstream.rawDataInFn)
505        self.assertIdentical(None, self.xmlstream.rawDataOutFn)
506
507
508    def test_makeConnectionLogTraffic(self):
509        """
510        Setting logTraffic should set up raw data loggers.
511        """
512        self.factory.logTraffic = True
513        self.xmlstream.dispatch(self.xmlstream,
514                                xmlstream.STREAM_CONNECTED_EVENT)
515        self.assertNotIdentical(None, self.xmlstream.rawDataInFn)
516        self.assertNotIdentical(None, self.xmlstream.rawDataOutFn)
517
518
519    def test_onError(self):
520        """
521        An observer for stream errors should trigger onError to log it.
522        """
523        self.xmlstream.dispatch(self.xmlstream,
524                                xmlstream.STREAM_CONNECTED_EVENT)
525
526        class TestError(Exception):
527            pass
528
529        reason = failure.Failure(TestError())
530        self.xmlstream.dispatch(reason, xmlstream.STREAM_ERROR_EVENT)
531        self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
532
533
534    def test_connectionInitialized(self):
535        """
536        Make sure a new stream is added to the routing table.
537        """
538        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
539        self.assertIn('component.example.org', self.router.routes)
540        self.assertIdentical(self.xmlstream,
541                             self.router.routes['component.example.org'])
542
543
544    def test_connectionLost(self):
545        """
546        Make sure a stream is removed from the routing table on disconnect.
547        """
548        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
549        self.xmlstream.dispatch(None, xmlstream.STREAM_END_EVENT)
550        self.assertNotIn('component.example.org', self.router.routes)
Note: See TracBrowser for help on using the repository browser.