comparison sat_pubsub/test/test_backend.py @ 232:923281d4c5bc

renamed idavoll directory to sat_pubsub
author Goffi <goffi@goffi.org>
date Thu, 17 May 2012 12:48:14 +0200
parents idavoll/test/test_backend.py@0eafdced5f24
children 564ae55219e1
comparison
equal deleted inserted replaced
231:d99047cd90f9 232:923281d4c5bc
1 # Copyright (c) 2003-2010 Ralph Meijer
2 # See LICENSE for details.
3
4 """
5 Tests for L{idavoll.backend}.
6 """
7
8 from zope.interface import implements
9 from zope.interface.verify import verifyObject
10
11 from twisted.internet import defer
12 from twisted.trial import unittest
13 from twisted.words.protocols.jabber import jid
14 from twisted.words.protocols.jabber.error import StanzaError
15
16 from wokkel import iwokkel, pubsub
17
18 from idavoll import backend, error, iidavoll
19
20 OWNER = jid.JID('owner@example.com')
21 OWNER_FULL = jid.JID('owner@example.com/home')
22 SERVICE = jid.JID('test.example.org')
23 NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
24
25 class BackendTest(unittest.TestCase):
26
27 def test_interfaceIBackend(self):
28 self.assertTrue(verifyObject(iidavoll.IBackendService,
29 backend.BackendService(None)))
30
31
32 def test_deleteNode(self):
33 class TestNode:
34 nodeIdentifier = 'to-be-deleted'
35 def getAffiliation(self, entity):
36 if entity.userhostJID() == OWNER:
37 return defer.succeed('owner')
38
39 class TestStorage:
40 def __init__(self):
41 self.deleteCalled = []
42
43 def getNode(self, nodeIdentifier):
44 return defer.succeed(TestNode())
45
46 def deleteNode(self, nodeIdentifier):
47 if nodeIdentifier in ['to-be-deleted']:
48 self.deleteCalled.append(nodeIdentifier)
49 return defer.succeed(None)
50 else:
51 return defer.fail(error.NodeNotFound())
52
53 def preDelete(data):
54 self.assertFalse(self.storage.deleteCalled)
55 preDeleteCalled.append(data)
56 return defer.succeed(None)
57
58 def cb(result):
59 self.assertEquals(1, len(preDeleteCalled))
60 data = preDeleteCalled[-1]
61 self.assertEquals('to-be-deleted', data['nodeIdentifier'])
62 self.assertTrue(self.storage.deleteCalled)
63
64 self.storage = TestStorage()
65 self.backend = backend.BackendService(self.storage)
66
67 preDeleteCalled = []
68
69 self.backend.registerPreDelete(preDelete)
70 d = self.backend.deleteNode('to-be-deleted', OWNER_FULL)
71 d.addCallback(cb)
72 return d
73
74
75 def test_deleteNodeRedirect(self):
76 uri = 'xmpp:%s?;node=test2' % (SERVICE.full(),)
77
78 class TestNode:
79 nodeIdentifier = 'to-be-deleted'
80 def getAffiliation(self, entity):
81 if entity.userhostJID() == OWNER:
82 return defer.succeed('owner')
83
84 class TestStorage:
85 def __init__(self):
86 self.deleteCalled = []
87
88 def getNode(self, nodeIdentifier):
89 return defer.succeed(TestNode())
90
91 def deleteNode(self, nodeIdentifier):
92 if nodeIdentifier in ['to-be-deleted']:
93 self.deleteCalled.append(nodeIdentifier)
94 return defer.succeed(None)
95 else:
96 return defer.fail(error.NodeNotFound())
97
98 def preDelete(data):
99 self.assertFalse(self.storage.deleteCalled)
100 preDeleteCalled.append(data)
101 return defer.succeed(None)
102
103 def cb(result):
104 self.assertEquals(1, len(preDeleteCalled))
105 data = preDeleteCalled[-1]
106 self.assertEquals('to-be-deleted', data['nodeIdentifier'])
107 self.assertEquals(uri, data['redirectURI'])
108 self.assertTrue(self.storage.deleteCalled)
109
110 self.storage = TestStorage()
111 self.backend = backend.BackendService(self.storage)
112
113 preDeleteCalled = []
114
115 self.backend.registerPreDelete(preDelete)
116 d = self.backend.deleteNode('to-be-deleted', OWNER, redirectURI=uri)
117 d.addCallback(cb)
118 return d
119
120
121 def test_createNodeNoID(self):
122 """
123 Test creation of a node without a given node identifier.
124 """
125 class TestStorage:
126 def getDefaultConfiguration(self, nodeType):
127 return {}
128
129 def createNode(self, nodeIdentifier, requestor, config):
130 self.nodeIdentifier = nodeIdentifier
131 return defer.succeed(None)
132
133 self.storage = TestStorage()
134 self.backend = backend.BackendService(self.storage)
135 self.storage.backend = self.backend
136
137 def checkID(nodeIdentifier):
138 self.assertNotIdentical(None, nodeIdentifier)
139 self.assertIdentical(self.storage.nodeIdentifier, nodeIdentifier)
140
141 d = self.backend.createNode(None, OWNER_FULL)
142 d.addCallback(checkID)
143 return d
144
145 class NodeStore:
146 """
147 I just store nodes to pose as an L{IStorage} implementation.
148 """
149 def __init__(self, nodes):
150 self.nodes = nodes
151
152 def getNode(self, nodeIdentifier):
153 try:
154 return defer.succeed(self.nodes[nodeIdentifier])
155 except KeyError:
156 return defer.fail(error.NodeNotFound())
157
158
159 def test_getNotifications(self):
160 """
161 Ensure subscribers show up in the notification list.
162 """
163 item = pubsub.Item()
164 sub = pubsub.Subscription('test', OWNER, 'subscribed')
165
166 class TestNode:
167 def getSubscriptions(self, state=None):
168 return [sub]
169
170 def cb(result):
171 self.assertEquals(1, len(result))
172 subscriber, subscriptions, items = result[-1]
173
174 self.assertEquals(OWNER, subscriber)
175 self.assertEquals(set([sub]), subscriptions)
176 self.assertEquals([item], items)
177
178 self.storage = self.NodeStore({'test': TestNode()})
179 self.backend = backend.BackendService(self.storage)
180 d = self.backend.getNotifications('test', [item])
181 d.addCallback(cb)
182 return d
183
184 def test_getNotificationsRoot(self):
185 """
186 Ensure subscribers to the root node show up in the notification list
187 for leaf nodes.
188
189 This assumes a flat node relationship model with exactly one collection
190 node: the root node. Each leaf node is automatically a child node
191 of the root node.
192 """
193 item = pubsub.Item()
194 subRoot = pubsub.Subscription('', OWNER, 'subscribed')
195
196 class TestNode:
197 def getSubscriptions(self, state=None):
198 return []
199
200 class TestRootNode:
201 def getSubscriptions(self, state=None):
202 return [subRoot]
203
204 def cb(result):
205 self.assertEquals(1, len(result))
206 subscriber, subscriptions, items = result[-1]
207 self.assertEquals(OWNER, subscriber)
208 self.assertEquals(set([subRoot]), subscriptions)
209 self.assertEquals([item], items)
210
211 self.storage = self.NodeStore({'test': TestNode(),
212 '': TestRootNode()})
213 self.backend = backend.BackendService(self.storage)
214 d = self.backend.getNotifications('test', [item])
215 d.addCallback(cb)
216 return d
217
218
219 def test_getNotificationsMultipleNodes(self):
220 """
221 Ensure that entities that subscribe to a leaf node as well as the
222 root node get exactly one notification.
223 """
224 item = pubsub.Item()
225 sub = pubsub.Subscription('test', OWNER, 'subscribed')
226 subRoot = pubsub.Subscription('', OWNER, 'subscribed')
227
228 class TestNode:
229 def getSubscriptions(self, state=None):
230 return [sub]
231
232 class TestRootNode:
233 def getSubscriptions(self, state=None):
234 return [subRoot]
235
236 def cb(result):
237 self.assertEquals(1, len(result))
238 subscriber, subscriptions, items = result[-1]
239
240 self.assertEquals(OWNER, subscriber)
241 self.assertEquals(set([sub, subRoot]), subscriptions)
242 self.assertEquals([item], items)
243
244 self.storage = self.NodeStore({'test': TestNode(),
245 '': TestRootNode()})
246 self.backend = backend.BackendService(self.storage)
247 d = self.backend.getNotifications('test', [item])
248 d.addCallback(cb)
249 return d
250
251
252 def test_getDefaultConfiguration(self):
253 """
254 L{backend.BackendService.getDefaultConfiguration} should return
255 a deferred that fires a dictionary with configuration values.
256 """
257
258 class TestStorage:
259 def getDefaultConfiguration(self, nodeType):
260 return {
261 "pubsub#persist_items": True,
262 "pubsub#deliver_payloads": True}
263
264 def cb(options):
265 self.assertIn("pubsub#persist_items", options)
266 self.assertEqual(True, options["pubsub#persist_items"])
267
268 self.backend = backend.BackendService(TestStorage())
269 d = self.backend.getDefaultConfiguration('leaf')
270 d.addCallback(cb)
271 return d
272
273
274 def test_getNodeConfiguration(self):
275 class testNode:
276 nodeIdentifier = 'node'
277 def getConfiguration(self):
278 return {'pubsub#deliver_payloads': True,
279 'pubsub#persist_items': False}
280
281 class testStorage:
282 def getNode(self, nodeIdentifier):
283 return defer.succeed(testNode())
284
285 def cb(options):
286 self.assertIn("pubsub#deliver_payloads", options)
287 self.assertEqual(True, options["pubsub#deliver_payloads"])
288 self.assertIn("pubsub#persist_items", options)
289 self.assertEqual(False, options["pubsub#persist_items"])
290
291 self.storage = testStorage()
292 self.backend = backend.BackendService(self.storage)
293 self.storage.backend = self.backend
294
295 d = self.backend.getNodeConfiguration('node')
296 d.addCallback(cb)
297 return d
298
299
300 def test_setNodeConfiguration(self):
301 class testNode:
302 nodeIdentifier = 'node'
303 def getAffiliation(self, entity):
304 if entity.userhostJID() == OWNER:
305 return defer.succeed('owner')
306 def setConfiguration(self, options):
307 self.options = options
308
309 class testStorage:
310 def __init__(self):
311 self.nodes = {'node': testNode()}
312 def getNode(self, nodeIdentifier):
313 return defer.succeed(self.nodes[nodeIdentifier])
314
315 def checkOptions(node):
316 options = node.options
317 self.assertIn("pubsub#deliver_payloads", options)
318 self.assertEqual(True, options["pubsub#deliver_payloads"])
319 self.assertIn("pubsub#persist_items", options)
320 self.assertEqual(False, options["pubsub#persist_items"])
321
322 def cb(result):
323 d = self.storage.getNode('node')
324 d.addCallback(checkOptions)
325 return d
326
327 self.storage = testStorage()
328 self.backend = backend.BackendService(self.storage)
329 self.storage.backend = self.backend
330
331 options = {'pubsub#deliver_payloads': True,
332 'pubsub#persist_items': False}
333
334 d = self.backend.setNodeConfiguration('node', options, OWNER_FULL)
335 d.addCallback(cb)
336 return d
337
338
339 def test_publishNoID(self):
340 """
341 Test publish request with an item without a node identifier.
342 """
343 class TestNode:
344 nodeType = 'leaf'
345 nodeIdentifier = 'node'
346 def getAffiliation(self, entity):
347 if entity.userhostJID() == OWNER:
348 return defer.succeed('owner')
349 def getConfiguration(self):
350 return {'pubsub#deliver_payloads': True,
351 'pubsub#persist_items': False}
352
353 class TestStorage:
354 def getNode(self, nodeIdentifier):
355 return defer.succeed(TestNode())
356
357 def checkID(notification):
358 self.assertNotIdentical(None, notification['items'][0]['id'])
359
360 self.storage = TestStorage()
361 self.backend = backend.BackendService(self.storage)
362 self.storage.backend = self.backend
363
364 self.backend.registerNotifier(checkID)
365
366 items = [pubsub.Item()]
367 d = self.backend.publish('node', items, OWNER_FULL)
368 return d
369
370
371 def test_notifyOnSubscription(self):
372 """
373 Test notification of last published item on subscription.
374 """
375 ITEM = "<item xmlns='%s' id='1'/>" % NS_PUBSUB
376
377 class TestNode:
378 implements(iidavoll.ILeafNode)
379 nodeIdentifier = 'node'
380 nodeType = 'leaf'
381 def getAffiliation(self, entity):
382 if entity is OWNER:
383 return defer.succeed('owner')
384 def getConfiguration(self):
385 return {'pubsub#deliver_payloads': True,
386 'pubsub#persist_items': False,
387 'pubsub#send_last_published_item': 'on_sub'}
388 def getItems(self, maxItems):
389 return [ITEM]
390 def addSubscription(self, subscriber, state, options):
391 self.subscription = pubsub.Subscription('node', subscriber,
392 state, options)
393 return defer.succeed(None)
394 def getSubscription(self, subscriber):
395 return defer.succeed(self.subscription)
396
397 class TestStorage:
398 def getNode(self, nodeIdentifier):
399 return defer.succeed(TestNode())
400
401 def cb(data):
402 self.assertEquals('node', data['nodeIdentifier'])
403 self.assertEquals([ITEM], data['items'])
404 self.assertEquals(OWNER, data['subscription'].subscriber)
405
406 self.storage = TestStorage()
407 self.backend = backend.BackendService(self.storage)
408 self.storage.backend = self.backend
409
410 d1 = defer.Deferred()
411 d1.addCallback(cb)
412 self.backend.registerNotifier(d1.callback)
413 d2 = self.backend.subscribe('node', OWNER, OWNER_FULL)
414 return defer.gatherResults([d1, d2])
415
416 test_notifyOnSubscription.timeout = 2
417
418
419
420 class BaseTestBackend(object):
421 """
422 Base class for backend stubs.
423 """
424
425 def supportsPublisherAffiliation(self):
426 return True
427
428
429 def supportsOutcastAffiliation(self):
430 return True
431
432
433 def supportsPersistentItems(self):
434 return True
435
436
437 def supportsInstantNodes(self):
438 return True
439
440
441 def registerNotifier(self, observerfn, *args, **kwargs):
442 return
443
444
445 def registerPreDelete(self, preDeleteFn):
446 return
447
448
449
450 class PubSubResourceFromBackendTest(unittest.TestCase):
451
452 def test_interface(self):
453 resource = backend.PubSubResourceFromBackend(BaseTestBackend())
454 self.assertTrue(verifyObject(iwokkel.IPubSubResource, resource))
455
456
457 def test_preDelete(self):
458 """
459 Test pre-delete sending out notifications to subscribers.
460 """
461
462 class TestBackend(BaseTestBackend):
463 preDeleteFn = None
464
465 def registerPreDelete(self, preDeleteFn):
466 self.preDeleteFn = preDeleteFn
467
468 def getSubscribers(self, nodeIdentifier):
469 return defer.succeed([OWNER])
470
471 def notifyDelete(service, nodeIdentifier, subscribers,
472 redirectURI=None):
473 self.assertEqual(SERVICE, service)
474 self.assertEqual('test', nodeIdentifier)
475 self.assertEqual([OWNER], subscribers)
476 self.assertIdentical(None, redirectURI)
477 d1.callback(None)
478
479 d1 = defer.Deferred()
480 resource = backend.PubSubResourceFromBackend(TestBackend())
481 resource.serviceJID = SERVICE
482 resource.pubsubService = pubsub.PubSubService()
483 resource.pubsubService.notifyDelete = notifyDelete
484 self.assertTrue(verifyObject(iwokkel.IPubSubResource, resource))
485 self.assertNotIdentical(None, resource.backend.preDeleteFn)
486 data = {'nodeIdentifier': 'test'}
487 d2 = resource.backend.preDeleteFn(data)
488 return defer.DeferredList([d1, d2], fireOnOneErrback=1)
489
490
491 def test_preDeleteRedirect(self):
492 """
493 Test pre-delete sending out notifications to subscribers.
494 """
495
496 uri = 'xmpp:%s?;node=test2' % (SERVICE.full(),)
497
498 class TestBackend(BaseTestBackend):
499 preDeleteFn = None
500
501 def registerPreDelete(self, preDeleteFn):
502 self.preDeleteFn = preDeleteFn
503
504 def getSubscribers(self, nodeIdentifier):
505 return defer.succeed([OWNER])
506
507 def notifyDelete(service, nodeIdentifier, subscribers,
508 redirectURI=None):
509 self.assertEqual(SERVICE, service)
510 self.assertEqual('test', nodeIdentifier)
511 self.assertEqual([OWNER], subscribers)
512 self.assertEqual(uri, redirectURI)
513 d1.callback(None)
514
515 d1 = defer.Deferred()
516 resource = backend.PubSubResourceFromBackend(TestBackend())
517 resource.serviceJID = SERVICE
518 resource.pubsubService = pubsub.PubSubService()
519 resource.pubsubService.notifyDelete = notifyDelete
520 self.assertTrue(verifyObject(iwokkel.IPubSubResource, resource))
521 self.assertNotIdentical(None, resource.backend.preDeleteFn)
522 data = {'nodeIdentifier': 'test',
523 'redirectURI': uri}
524 d2 = resource.backend.preDeleteFn(data)
525 return defer.DeferredList([d1, d2], fireOnOneErrback=1)
526
527
528 def test_unsubscribeNotSubscribed(self):
529 """
530 Test unsubscription request when not subscribed.
531 """
532
533 class TestBackend(BaseTestBackend):
534 def unsubscribe(self, nodeIdentifier, subscriber, requestor):
535 return defer.fail(error.NotSubscribed())
536
537 def cb(e):
538 self.assertEquals('unexpected-request', e.condition)
539
540 resource = backend.PubSubResourceFromBackend(TestBackend())
541 request = pubsub.PubSubRequest()
542 request.sender = OWNER
543 request.recipient = SERVICE
544 request.nodeIdentifier = 'test'
545 request.subscriber = OWNER
546 d = resource.unsubscribe(request)
547 self.assertFailure(d, StanzaError)
548 d.addCallback(cb)
549 return d
550
551
552 def test_getInfo(self):
553 """
554 Test retrieving node information.
555 """
556
557 class TestBackend(BaseTestBackend):
558 def getNodeType(self, nodeIdentifier):
559 return defer.succeed('leaf')
560
561 def getNodeMetaData(self, nodeIdentifier):
562 return defer.succeed({'pubsub#persist_items': True})
563
564 def cb(info):
565 self.assertIn('type', info)
566 self.assertEquals('leaf', info['type'])
567 self.assertIn('meta-data', info)
568 self.assertEquals({'pubsub#persist_items': True}, info['meta-data'])
569
570 resource = backend.PubSubResourceFromBackend(TestBackend())
571 d = resource.getInfo(OWNER, SERVICE, 'test')
572 d.addCallback(cb)
573 return d
574
575
576 def test_getConfigurationOptions(self):
577 class TestBackend(BaseTestBackend):
578 nodeOptions = {
579 "pubsub#persist_items":
580 {"type": "boolean",
581 "label": "Persist items to storage"},
582 "pubsub#deliver_payloads":
583 {"type": "boolean",
584 "label": "Deliver payloads with event notifications"}
585 }
586
587 resource = backend.PubSubResourceFromBackend(TestBackend())
588 options = resource.getConfigurationOptions()
589 self.assertIn("pubsub#persist_items", options)
590
591
592 def test_default(self):
593 class TestBackend(BaseTestBackend):
594 def getDefaultConfiguration(self, nodeType):
595 options = {"pubsub#persist_items": True,
596 "pubsub#deliver_payloads": True,
597 "pubsub#send_last_published_item": 'on_sub',
598 }
599 return defer.succeed(options)
600
601 def cb(options):
602 self.assertEquals(True, options["pubsub#persist_items"])
603
604 resource = backend.PubSubResourceFromBackend(TestBackend())
605 request = pubsub.PubSubRequest()
606 request.sender = OWNER
607 request.recipient = SERVICE
608 request.nodeType = 'leaf'
609 d = resource.default(request)
610 d.addCallback(cb)
611 return d