comparison sat_pubsub/test/test_gateway.py @ 273:6ba0d6def7f5

Use twisted.web instead of web2, initial tests.
author Ralph Meijer <ralphm@ik.nu>
date Sun, 20 Jan 2013 13:38:41 +0100
parents d55620ceafed
children 6641ea7990ee
comparison
equal deleted inserted replaced
272:558a43366c9f 273:6ba0d6def7f5
57 57
58 Note that some tests are functional tests that require a running idavoll 58 Note that some tests are functional tests that require a running idavoll
59 service. 59 service.
60 """ 60 """
61 61
62 from StringIO import StringIO
63
64 import simplejson
65
62 from twisted.internet import defer 66 from twisted.internet import defer
63 from twisted.trial import unittest 67 from twisted.trial import unittest
64 from twisted.web import error 68 from twisted.web import error, http, http_headers, server
69 from twisted.web.test import requesthelper
65 from twisted.words.xish import domish 70 from twisted.words.xish import domish
71 from twisted.words.protocols.jabber.jid import JID
66 72
67 from sat_pubsub import gateway 73 from sat_pubsub import gateway
74 from sat_pubsub.backend import BackendService
75 from sat_pubsub.memory_storage import Storage
68 76
69 AGENT = "Idavoll Test Script" 77 AGENT = "Idavoll Test Script"
70 NS_ATOM = "http://www.w3.org/2005/Atom" 78 NS_ATOM = "http://www.w3.org/2005/Atom"
71 79
72 TEST_ENTRY = domish.Element((NS_ATOM, 'entry')) 80 TEST_ENTRY = domish.Element((NS_ATOM, 'entry'))
75 TEST_ENTRY.addElement("title", content="Atom-Powered Robots Run Amok") 83 TEST_ENTRY.addElement("title", content="Atom-Powered Robots Run Amok")
76 TEST_ENTRY.addElement("author").addElement("name", content="John Doe") 84 TEST_ENTRY.addElement("author").addElement("name", content="John Doe")
77 TEST_ENTRY.addElement("content", content="Some text.") 85 TEST_ENTRY.addElement("content", content="Some text.")
78 86
79 baseURI = "http://localhost:8086/" 87 baseURI = "http://localhost:8086/"
80 componentJID = "pubsub" 88 component = "pubsub"
89 componentJID = JID(component)
90 ownerJID = JID('owner@example.org')
91
92 def _render(resource, request):
93 result = resource.render(request)
94 if isinstance(result, str):
95 request.write(result)
96 request.finish()
97 return defer.succeed(None)
98 elif result is server.NOT_DONE_YET:
99 if request.finished:
100 return defer.succeed(None)
101 else:
102 return request.notifyFinish()
103 else:
104 raise ValueError("Unexpected return value: %r" % (result,))
105
106
107 class DummyRequest(requesthelper.DummyRequest):
108
109 def __init__(self, *args, **kwargs):
110 requesthelper.DummyRequest.__init__(self, *args, **kwargs)
111 self.requestHeaders = http_headers.Headers()
112
113
114
115 class GetServiceAndNodeTest(unittest.TestCase):
116 """
117 Tests for {gateway.getServiceAndNode}.
118 """
119
120 def test_basic(self):
121 """
122 getServiceAndNode parses an XMPP URI with node parameter.
123 """
124 uri = b'xmpp:pubsub.example.org?;node=test'
125 service, nodeIdentifier = gateway.getServiceAndNode(uri)
126 self.assertEqual(JID(u'pubsub.example.org'), service)
127 self.assertEqual(u'test', nodeIdentifier)
128
129
130 def test_schemeEmpty(self):
131 """
132 If the URI scheme is empty, an exception is raised.
133 """
134 uri = b'pubsub.example.org'
135 self.assertRaises(gateway.XMPPURIParseError,
136 gateway.getServiceAndNode, uri)
137
138
139 def test_schemeNotXMPP(self):
140 """
141 If the URI scheme is not 'xmpp', an exception is raised.
142 """
143 uri = b'mailto:test@example.org'
144 self.assertRaises(gateway.XMPPURIParseError,
145 gateway.getServiceAndNode, uri)
146
147
148 def test_authorityPresent(self):
149 """
150 If the URI has an authority component, an exception is raised.
151 """
152 uri = b'xmpp://pubsub.example.org/'
153 self.assertRaises(gateway.XMPPURIParseError,
154 gateway.getServiceAndNode, uri)
155
156
157 def test_queryEmpty(self):
158 """
159 If there is no query component, the nodeIdentifier is empty.
160 """
161 uri = b'xmpp:pubsub.example.org'
162 service, nodeIdentifier = gateway.getServiceAndNode(uri)
163
164 self.assertEqual(JID(u'pubsub.example.org'), service)
165 self.assertEqual(u'', nodeIdentifier)
166
167
168 def test_jidInvalid(self):
169 """
170 If the JID from the path component is invalid, an exception is raised.
171 """
172 uri = b'xmpp:@@pubsub.example.org?;node=test'
173 self.assertRaises(gateway.XMPPURIParseError,
174 gateway.getServiceAndNode, uri)
175
176
177 def test_pathEmpty(self):
178 """
179 If there is no path component, an exception is raised.
180 """
181 uri = b'xmpp:?node=test'
182 self.assertRaises(gateway.XMPPURIParseError,
183 gateway.getServiceAndNode, uri)
184
185
186 def test_nodeAbsent(self):
187 """
188 If the node parameter is missing, the nodeIdentifier is empty.
189 """
190 uri = b'xmpp:pubsub.example.org?'
191 service, nodeIdentifier = gateway.getServiceAndNode(uri)
192
193 self.assertEqual(JID(u'pubsub.example.org'), service)
194 self.assertEqual(u'', nodeIdentifier)
195
196
197
198 class GetXMPPURITest(unittest.TestCase):
199 """
200 Tests for L{gateway.getXMPPURITest}.
201 """
202
203 def test_basic(self):
204 uri = gateway.getXMPPURI(JID(u'pubsub.example.org'), u'test')
205 self.assertEqual('xmpp:pubsub.example.org?;node=test', uri)
206
207
208 class CreateResourceTest(unittest.TestCase):
209 """
210 Tests for L{gateway.CreateResource}.
211 """
212
213 def setUp(self):
214 self.backend = BackendService(Storage())
215 self.resource = gateway.CreateResource(self.backend, componentJID,
216 ownerJID)
217
218
219 def test_get(self):
220 """
221 The method GET is not supported.
222 """
223 request = DummyRequest([b''])
224 self.assertRaises(error.UnsupportedMethod,
225 _render, self.resource, request)
226
227
228 def test_post(self):
229 """
230 Upon a POST, a new node is created and the URI returned.
231 """
232 request = DummyRequest([b''])
233 request.method = 'POST'
234
235 def gotNodes(nodeIdentifiers, uri):
236 service, nodeIdentifier = gateway.getServiceAndNode(uri)
237 self.assertIn(nodeIdentifier, nodeIdentifiers)
238
239 def rendered(result):
240 self.assertEqual('application/json',
241 request.outgoingHeaders['content-type'])
242 payload = simplejson.loads(b''.join(request.written))
243 self.assertIn('uri', payload)
244 d = self.backend.getNodes()
245 d.addCallback(gotNodes, payload['uri'])
246 return d
247
248 d = _render(self.resource, request)
249 d.addCallback(rendered)
250 return d
251
252
253
254 class DeleteResourceTest(unittest.TestCase):
255 """
256 Tests for L{gateway.DeleteResource}.
257 """
258
259 def setUp(self):
260 self.backend = BackendService(Storage())
261 self.resource = gateway.DeleteResource(self.backend, componentJID,
262 ownerJID)
263
264
265 def test_get(self):
266 """
267 The method GET is not supported.
268 """
269 request = DummyRequest([b''])
270 self.assertRaises(error.UnsupportedMethod,
271 _render, self.resource, request)
272
273
274 def test_post(self):
275 """
276 Upon a POST, a new node is created and the URI returned.
277 """
278 request = DummyRequest([b''])
279 request.method = b'POST'
280
281 def rendered(result):
282 self.assertEqual(http.NO_CONTENT, request.responseCode)
283
284 def nodeCreated(nodeIdentifier):
285 uri = gateway.getXMPPURI(componentJID, nodeIdentifier)
286 request.args[b'uri'] = [uri]
287 request.content = StringIO(b'')
288
289 return _render(self.resource, request)
290
291 d = self.backend.createNode(u'test', ownerJID)
292 d.addCallback(nodeCreated)
293 d.addCallback(rendered)
294 return d
295
296
297 def test_postWithRedirect(self):
298 """
299 Upon a POST, a new node is created and the URI returned.
300 """
301 request = DummyRequest([b''])
302 request.method = b'POST'
303 otherNodeURI = b'xmpp:pubsub.example.org?node=other'
304
305 def rendered(result):
306 self.assertEqual(http.NO_CONTENT, request.responseCode)
307 self.assertEqual(1, len(deletes))
308 nodeIdentifier, owner, redirectURI = deletes[-1]
309 self.assertEqual(otherNodeURI, redirectURI)
310
311 def nodeCreated(nodeIdentifier):
312 uri = gateway.getXMPPURI(componentJID, nodeIdentifier)
313 request.args[b'uri'] = [uri]
314 payload = {b'redirect_uri': otherNodeURI}
315 body = simplejson.dumps(payload)
316 request.content = StringIO(body)
317 return _render(self.resource, request)
318
319 def deleteNode(nodeIdentifier, owner, redirectURI):
320 deletes.append((nodeIdentifier, owner, redirectURI))
321 return defer.succeed(nodeIdentifier)
322
323 deletes = []
324 self.patch(self.backend, 'deleteNode', deleteNode)
325 d = self.backend.createNode(u'test', ownerJID)
326 d.addCallback(nodeCreated)
327 d.addCallback(rendered)
328 return d
329
330
331 def test_postUnknownNode(self):
332 """
333 If the node to be deleted is unknown, 404 Not Found is returned.
334 """
335 request = DummyRequest([b''])
336 request.method = b'POST'
337
338 def rendered(result):
339 self.assertEqual(http.NOT_FOUND, request.responseCode)
340
341 uri = gateway.getXMPPURI(componentJID, u'unknown')
342 request.args[b'uri'] = [uri]
343 request.content = StringIO(b'')
344
345 d = _render(self.resource, request)
346 d.addCallback(rendered)
347 return d
348
349
350 def test_postURIMissing(self):
351 """
352 If no URI is passed, 400 Bad Request is returned.
353 """
354 request = DummyRequest([b''])
355 request.method = b'POST'
356
357 def rendered(result):
358 self.assertEqual(http.BAD_REQUEST, request.responseCode)
359
360 request.content = StringIO(b'')
361
362 d = _render(self.resource, request)
363 d.addCallback(rendered)
364 return d
365
366
367 class CallbackResourceTest(unittest.TestCase):
368 """
369 Tests for L{gateway.CallbackResource}.
370 """
371
372 def setUp(self):
373 self.callbackEvents = []
374 self.resource = gateway.CallbackResource(self._callback)
375
376
377 def _callback(self, payload, headers):
378 self.callbackEvents.append((payload, headers))
379
380
381 def test_get(self):
382 """
383 The method GET is not supported.
384 """
385 request = DummyRequest([b''])
386 self.assertRaises(error.UnsupportedMethod,
387 _render, self.resource, request)
388
389
390 def test_post(self):
391 """
392 The body posted is passed to the callback.
393 """
394 request = DummyRequest([b''])
395 request.method = 'POST'
396 request.content = StringIO(b'<root><child/></root>')
397
398 def rendered(result):
399 self.assertEqual(1, len(self.callbackEvents))
400 payload, headers = self.callbackEvents[-1]
401 self.assertEqual('root', payload.name)
402
403 self.assertEqual(http.NO_CONTENT, request.responseCode)
404 self.assertFalse(b''.join(request.written))
405
406 d = _render(self.resource, request)
407 d.addCallback(rendered)
408 return d
409
410
411 def test_postEvent(self):
412 """
413 If the Event header is set, the payload is empty and the header passed.
414 """
415 request = DummyRequest([b''])
416 request.method = 'POST'
417 request.requestHeaders.addRawHeader(b'Event', b'DELETE')
418 request.content = StringIO(b'')
419
420 def rendered(result):
421 self.assertEqual(1, len(self.callbackEvents))
422 payload, headers = self.callbackEvents[-1]
423 self.assertIdentical(None, payload)
424 self.assertEqual(['DELETE'], headers.getRawHeaders(b'Event'))
425 self.assertFalse(b''.join(request.written))
426
427 d = _render(self.resource, request)
428 d.addCallback(rendered)
429 return d
430
431
81 432
82 class GatewayTest(unittest.TestCase): 433 class GatewayTest(unittest.TestCase):
83 timeout = 2 434 timeout = 2
84 435
85 def setUp(self): 436 def setUp(self):
141 492
142 def test_publishNonExisting(self): 493 def test_publishNonExisting(self):
143 def cb(err): 494 def cb(err):
144 self.assertEqual('404', err.status) 495 self.assertEqual('404', err.status)
145 496
146 d = self.client.publish(TEST_ENTRY, 'xmpp:%s?node=test' % componentJID) 497 d = self.client.publish(TEST_ENTRY, 'xmpp:%s?node=test' % component)
147 self.assertFailure(d, error.Error) 498 self.assertFailure(d, error.Error)
148 d.addCallback(cb) 499 d.addCallback(cb)
149 return d 500 return d
150 501
151 def test_delete(self): 502 def test_delete(self):
159 return d 510 return d
160 511
161 def test_deleteWithRedirect(self): 512 def test_deleteWithRedirect(self):
162 def cb(response): 513 def cb(response):
163 xmppURI = response['uri'] 514 xmppURI = response['uri']
164 redirectURI = 'xmpp:%s?node=test' % componentJID 515 redirectURI = 'xmpp:%s?node=test' % component
165 d = self.client.delete(xmppURI, redirectURI) 516 d = self.client.delete(xmppURI, redirectURI)
166 return d 517 return d
167 518
168 d = self.client.create() 519 d = self.client.create()
169 d.addCallback(cb) 520 d.addCallback(cb)
196 d.addCallback(cb) 547 d.addCallback(cb)
197 d.addCallback(cb2) 548 d.addCallback(cb2)
198 return defer.gatherResults([d, self.client.deferred]) 549 return defer.gatherResults([d, self.client.deferred])
199 550
200 def test_deleteNotificationWithRedirect(self): 551 def test_deleteNotificationWithRedirect(self):
201 redirectURI = 'xmpp:%s?node=test' % componentJID 552 redirectURI = 'xmpp:%s?node=test' % component
202 553
203 def onNotification(data, headers): 554 def onNotification(data, headers):
204 try: 555 try:
205 self.assertTrue(headers.hasHeader('Event')) 556 self.assertTrue(headers.hasHeader('Event'))
206 self.assertEquals(['DELETED'], headers.getRawHeaders('Event')) 557 self.assertEquals(['DELETED'], headers.getRawHeaders('Event'))
383 734
384 def test_subscribeNonExisting(self): 735 def test_subscribeNonExisting(self):
385 def cb(err): 736 def cb(err):
386 self.assertEqual('403', err.status) 737 self.assertEqual('403', err.status)
387 738
388 d = self.client.subscribe('xmpp:%s?node=test' % componentJID) 739 d = self.client.subscribe('xmpp:%s?node=test' % component)
389 self.assertFailure(d, error.Error) 740 self.assertFailure(d, error.Error)
390 d.addCallback(cb) 741 d.addCallback(cb)
391 return d 742 return d
392 743
393 744
394 def test_subscribeRootGetNotification(self): 745 def test_subscribeRootGetNotification(self):
746
747 def clean(rootNode):
748 return self.client.unsubscribe(rootNode)
395 749
396 def onNotification(data, headers): 750 def onNotification(data, headers):
397 self.client.deferred.callback(None) 751 self.client.deferred.callback(None)
398 752
399 def cb(response): 753 def cb(response):
400 xmppURI = response['uri'] 754 xmppURI = response['uri']
401 jid, nodeIdentifier = gateway.getServiceAndNode(xmppURI) 755 jid, nodeIdentifier = gateway.getServiceAndNode(xmppURI)
402 rootNode = gateway.getXMPPURI(jid, '') 756 rootNode = gateway.getXMPPURI(jid, '')
403 757
404 d = self.client.subscribe(rootNode) 758 d = self.client.subscribe(rootNode)
759 d.addCallback(lambda _: self.addCleanup(clean, rootNode))
405 d.addCallback(lambda _: xmppURI) 760 d.addCallback(lambda _: xmppURI)
406 return d 761 return d
407 762
408 def cb2(xmppURI): 763 def cb2(xmppURI):
409 return self.client.publish(TEST_ENTRY, xmppURI) 764 return self.client.publish(TEST_ENTRY, xmppURI)
419 774
420 def test_unsubscribeNonExisting(self): 775 def test_unsubscribeNonExisting(self):
421 def cb(err): 776 def cb(err):
422 self.assertEqual('403', err.status) 777 self.assertEqual('403', err.status)
423 778
424 d = self.client.unsubscribe('xmpp:%s?node=test' % componentJID) 779 d = self.client.unsubscribe('xmpp:%s?node=test' % component)
425 self.assertFailure(d, error.Error) 780 self.assertFailure(d, error.Error)
426 d.addCallback(cb) 781 d.addCallback(cb)
427 return d 782 return d
428 783
429 784