source: wokkel/test/test_component.py @ 165:76a61f5aa343

Last change on this file since 165:76a61f5aa343 was 165:76a61f5aa343, checked in by Ralph Meijer <ralphm@…>, 10 years ago

Cleanups leading up to Wokkel 0.7.0.

As we now depend on Twisted 10.0.0 or higher, the following classes and
interfaces were deprecated:

This also resolves all Pyflakes warnings, changes links for www.xmpp.org to
xmpp.org and fixes the copyright notice in LICENSE to include 2012.

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