source: wokkel/test/test_component.py @ 39:a9e354f69018

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

Add Internal Component service that connects to a router in-process.

Author: ralphm.
Fixes #31.

File size: 11.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.RouterService()
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.component.startService()
117        self.router.routes['component'].addObserver('/message', fn)
118        self.component.send(message)
119
120        self.assertEquals([message], events)
121
122
123
124class RouterServiceTest(unittest.TestCase):
125    """
126    Tests for L{component.RouterService}.
127    """
128
129    def test_addRoute(self):
130        """
131        Test route registration and routing on incoming stanzas.
132        """
133        router = component.RouterService()
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.RouterService()
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, 'route'))
161        stanza['from'] = 'component1.example.org'
162        stanza['to'] = 'component2.example.org'
163        stanza.addElement('presence')
164        component1.source.send(stanza)
165        self.assertEquals([stanza], outgoing)
166
167
168    def test_routeDefault(self):
169        """
170        Test routing of a message using the default route.
171
172        The default route is the one with C{None} as its key in the
173        routing table. It is taken when there is no more specific route
174        in the routing table that matches the stanza's destination.
175        """
176        component1 = XmlPipe()
177        s2s = XmlPipe()
178        router = component.RouterService()
179        router.addRoute('component1.example.org', component1.sink)
180        router.addRoute(None, s2s.sink)
181
182        outgoing = []
183        s2s.source.addObserver('/*', lambda element: outgoing.append(element))
184        stanza = domish.Element((None, 'route'))
185        stanza['from'] = 'component1.example.org'
186        stanza['to'] = 'example.com'
187        stanza.addElement('presence')
188        component1.source.send(stanza)
189        self.assertEquals([stanza], outgoing)
190
191
192
193class ListenComponentAuthenticatorTest(unittest.TestCase):
194    """
195    Tests for L{component.ListenComponentAuthenticator}.
196    """
197
198    def setUp(self):
199        self.output = []
200        authenticator = component.ListenComponentAuthenticator('secret')
201        self.xmlstream = xmlstream.XmlStream(authenticator)
202        self.xmlstream.send = self.output.append
203
204
205    def loseConnection(self):
206        """
207        Stub loseConnection because we are a transport.
208        """
209        self.xmlstream.connectionLost("no reason")
210
211
212    def test_streamStarted(self):
213        observers = []
214
215        def addOnetimeObserver(event, observerfn):
216            observers.append((event, observerfn))
217
218        xs = self.xmlstream
219        xs.addOnetimeObserver = addOnetimeObserver
220
221        xs.makeConnection(self)
222        self.assertIdentical(None, xs.sid)
223        self.assertFalse(xs._headerSent)
224
225        xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
226                         "xmlns:stream='http://etherx.jabber.org/streams' "
227                         "to='component.example.org'>")
228        self.assertEqual((0, 0), xs.version)
229        self.assertNotIdentical(None, xs.sid)
230        self.assertTrue(xs._headerSent)
231        self.assertEquals(('/*', xs.authenticator.onElement), observers[-1])
232
233
234    def test_streamStartedWrongNamespace(self):
235        """
236        The received stream header should have a correct namespace.
237        """
238        streamErrors = []
239
240        xs = self.xmlstream
241        xs.sendStreamError = streamErrors.append
242        xs.makeConnection(self)
243        xs.dataReceived("<stream:stream xmlns='jabber:client' "
244                         "xmlns:stream='http://etherx.jabber.org/streams' "
245                         "to='component.example.org'>")
246        self.assertEquals(1, len(streamErrors))
247        self.assertEquals('invalid-namespace', streamErrors[-1].condition)
248
249
250    def test_streamStartedNoTo(self):
251        streamErrors = []
252
253        xs = self.xmlstream
254        xs.sendStreamError = streamErrors.append
255        xs.makeConnection(self)
256        xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
257                         "xmlns:stream='http://etherx.jabber.org/streams'>")
258        self.assertEquals(1, len(streamErrors))
259        self.assertEquals('improper-addressing', streamErrors[-1].condition)
260
261
262    def test_onElement(self):
263        """
264        We expect a handshake element with a hash.
265        """
266        handshakes = []
267
268        xs = self.xmlstream
269        xs.authenticator.onHandshake = handshakes.append
270
271        handshake = domish.Element(('jabber:component:accept', 'handshake'))
272        handshake.addContent('1234')
273        xs.authenticator.onElement(handshake)
274        self.assertEqual('1234', handshakes[-1])
275
276    def test_onHandshake(self):
277        xs = self.xmlstream
278        xs.sid = '1234'
279        theHash = '32532c0f7dbf1253c095b18b18e36d38d94c1256'
280        xs.authenticator.onHandshake(theHash)
281        self.assertEqual('<handshake/>', self.output[-1])
282
283
284    def test_onHandshakeWrongHash(self):
285        streamErrors = []
286        authd = []
287
288        def authenticated(self, xs):
289            authd.append(xs)
290
291        xs = self.xmlstream
292        xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
293        xs.sendStreamError = streamErrors.append
294
295        xs.sid = '1234'
296        theHash = '1234'
297        xs.authenticator.onHandshake(theHash)
298        self.assertEquals('not-authorized', streamErrors[-1].condition)
299        self.assertEquals(0, len(authd))
300
301
302
303class ComponentServerTest(unittest.TestCase):
304    """
305    Tests for L{component.ComponentServer}.
306    """
307
308    def setUp(self):
309        self.router = component.RouterService()
310        self.server = component.ComponentServer(self.router)
311        self.xmlstream = self.server.factory.buildProtocol(None)
312        self.xmlstream.thisEntity = JID('component.example.org')
313
314
315    def test_makeConnection(self):
316        """
317        A new connection increases the stream serial count. No logs by default.
318        """
319        self.xmlstream.dispatch(self.xmlstream,
320                                xmlstream.STREAM_CONNECTED_EVENT)
321        self.assertEqual(0, self.xmlstream.serial)
322        self.assertEqual(1, self.server.serial)
323        self.assertIdentical(None, self.xmlstream.rawDataInFn)
324        self.assertIdentical(None, self.xmlstream.rawDataOutFn)
325
326
327    def test_makeConnectionLogTraffic(self):
328        """
329        Setting logTraffic should set up raw data loggers.
330        """
331        self.server.logTraffic = True
332        self.xmlstream.dispatch(self.xmlstream,
333                                xmlstream.STREAM_CONNECTED_EVENT)
334        self.assertNotIdentical(None, self.xmlstream.rawDataInFn)
335        self.assertNotIdentical(None, self.xmlstream.rawDataOutFn)
336
337
338    def test_onError(self):
339        """
340        An observer for stream errors should trigger onError to log it.
341        """
342        self.xmlstream.dispatch(self.xmlstream,
343                                xmlstream.STREAM_CONNECTED_EVENT)
344
345        class TestError(Exception):
346            pass
347
348        reason = failure.Failure(TestError())
349        self.xmlstream.dispatch(reason, xmlstream.STREAM_ERROR_EVENT)
350        self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
351
352
353    def test_connectionInitialized(self):
354        """
355        Make sure a new stream is added to the routing table.
356        """
357        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
358        self.assertIn('component.example.org', self.router.routes)
359        self.assertIdentical(self.xmlstream,
360                             self.router.routes['component.example.org'])
361
362
363    def test_connectionLost(self):
364        """
365        Make sure a stream is removed from the routing table on disconnect.
366        """
367        self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
368        self.xmlstream.dispatch(None, xmlstream.STREAM_END_EVENT)
369        self.assertNotIn('component.example.org', self.router.routes)
Note: See TracBrowser for help on using the repository browser.