diff 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
line wrap: on
line diff
--- a/sat_pubsub/test/test_gateway.py	Tue Sep 09 08:09:26 2014 +0200
+++ b/sat_pubsub/test/test_gateway.py	Sun Jan 20 13:38:41 2013 +0100
@@ -59,12 +59,20 @@
 service.
 """
 
+from StringIO import StringIO
+
+import simplejson
+
 from twisted.internet import defer
 from twisted.trial import unittest
-from twisted.web import error
+from twisted.web import error, http, http_headers, server
+from twisted.web.test import requesthelper
 from twisted.words.xish import domish
+from twisted.words.protocols.jabber.jid import JID
 
 from sat_pubsub import gateway
+from sat_pubsub.backend import BackendService
+from sat_pubsub.memory_storage import Storage
 
 AGENT = "Idavoll Test Script"
 NS_ATOM = "http://www.w3.org/2005/Atom"
@@ -77,7 +85,350 @@
 TEST_ENTRY.addElement("content", content="Some text.")
 
 baseURI = "http://localhost:8086/"
-componentJID = "pubsub"
+component = "pubsub"
+componentJID = JID(component)
+ownerJID = JID('owner@example.org')
+
+def _render(resource, request):
+    result = resource.render(request)
+    if isinstance(result, str):
+        request.write(result)
+        request.finish()
+        return defer.succeed(None)
+    elif result is server.NOT_DONE_YET:
+        if request.finished:
+            return defer.succeed(None)
+        else:
+            return request.notifyFinish()
+    else:
+        raise ValueError("Unexpected return value: %r" % (result,))
+
+
+class DummyRequest(requesthelper.DummyRequest):
+
+    def __init__(self, *args, **kwargs):
+        requesthelper.DummyRequest.__init__(self, *args, **kwargs)
+        self.requestHeaders = http_headers.Headers()
+
+
+
+class GetServiceAndNodeTest(unittest.TestCase):
+    """
+    Tests for {gateway.getServiceAndNode}.
+    """
+
+    def test_basic(self):
+        """
+        getServiceAndNode parses an XMPP URI with node parameter.
+        """
+        uri = b'xmpp:pubsub.example.org?;node=test'
+        service, nodeIdentifier = gateway.getServiceAndNode(uri)
+        self.assertEqual(JID(u'pubsub.example.org'), service)
+        self.assertEqual(u'test', nodeIdentifier)
+
+
+    def test_schemeEmpty(self):
+        """
+        If the URI scheme is empty, an exception is raised.
+        """
+        uri = b'pubsub.example.org'
+        self.assertRaises(gateway.XMPPURIParseError,
+                          gateway.getServiceAndNode, uri)
+
+
+    def test_schemeNotXMPP(self):
+        """
+        If the URI scheme is not 'xmpp', an exception is raised.
+        """
+        uri = b'mailto:test@example.org'
+        self.assertRaises(gateway.XMPPURIParseError,
+                          gateway.getServiceAndNode, uri)
+
+
+    def test_authorityPresent(self):
+        """
+        If the URI has an authority component, an exception is raised.
+        """
+        uri = b'xmpp://pubsub.example.org/'
+        self.assertRaises(gateway.XMPPURIParseError,
+                          gateway.getServiceAndNode, uri)
+
+
+    def test_queryEmpty(self):
+        """
+        If there is no query component, the nodeIdentifier is empty.
+        """
+        uri = b'xmpp:pubsub.example.org'
+        service, nodeIdentifier = gateway.getServiceAndNode(uri)
+
+        self.assertEqual(JID(u'pubsub.example.org'), service)
+        self.assertEqual(u'', nodeIdentifier)
+
+
+    def test_jidInvalid(self):
+        """
+        If the JID from the path component is invalid, an exception is raised.
+        """
+        uri = b'xmpp:@@pubsub.example.org?;node=test'
+        self.assertRaises(gateway.XMPPURIParseError,
+                          gateway.getServiceAndNode, uri)
+
+
+    def test_pathEmpty(self):
+        """
+        If there is no path component, an exception is raised.
+        """
+        uri = b'xmpp:?node=test'
+        self.assertRaises(gateway.XMPPURIParseError,
+                          gateway.getServiceAndNode, uri)
+
+
+    def test_nodeAbsent(self):
+        """
+        If the node parameter is missing, the nodeIdentifier is empty.
+        """
+        uri = b'xmpp:pubsub.example.org?'
+        service, nodeIdentifier = gateway.getServiceAndNode(uri)
+
+        self.assertEqual(JID(u'pubsub.example.org'), service)
+        self.assertEqual(u'', nodeIdentifier)
+
+
+
+class GetXMPPURITest(unittest.TestCase):
+    """
+    Tests for L{gateway.getXMPPURITest}.
+    """
+
+    def test_basic(self):
+        uri = gateway.getXMPPURI(JID(u'pubsub.example.org'), u'test')
+        self.assertEqual('xmpp:pubsub.example.org?;node=test', uri)
+
+
+class CreateResourceTest(unittest.TestCase):
+    """
+    Tests for L{gateway.CreateResource}.
+    """
+
+    def setUp(self):
+        self.backend = BackendService(Storage())
+        self.resource = gateway.CreateResource(self.backend, componentJID,
+                                               ownerJID)
+
+
+    def test_get(self):
+        """
+        The method GET is not supported.
+        """
+        request = DummyRequest([b''])
+        self.assertRaises(error.UnsupportedMethod,
+                          _render, self.resource, request)
+
+
+    def test_post(self):
+        """
+        Upon a POST, a new node is created and the URI returned.
+        """
+        request = DummyRequest([b''])
+        request.method = 'POST'
+
+        def gotNodes(nodeIdentifiers, uri):
+            service, nodeIdentifier = gateway.getServiceAndNode(uri)
+            self.assertIn(nodeIdentifier, nodeIdentifiers)
+
+        def rendered(result):
+            self.assertEqual('application/json',
+                             request.outgoingHeaders['content-type'])
+            payload = simplejson.loads(b''.join(request.written))
+            self.assertIn('uri', payload)
+            d = self.backend.getNodes()
+            d.addCallback(gotNodes, payload['uri'])
+            return d
+
+        d = _render(self.resource, request)
+        d.addCallback(rendered)
+        return d
+
+
+
+class DeleteResourceTest(unittest.TestCase):
+    """
+    Tests for L{gateway.DeleteResource}.
+    """
+
+    def setUp(self):
+        self.backend = BackendService(Storage())
+        self.resource = gateway.DeleteResource(self.backend, componentJID,
+                                               ownerJID)
+
+
+    def test_get(self):
+        """
+        The method GET is not supported.
+        """
+        request = DummyRequest([b''])
+        self.assertRaises(error.UnsupportedMethod,
+                          _render, self.resource, request)
+
+
+    def test_post(self):
+        """
+        Upon a POST, a new node is created and the URI returned.
+        """
+        request = DummyRequest([b''])
+        request.method = b'POST'
+
+        def rendered(result):
+            self.assertEqual(http.NO_CONTENT, request.responseCode)
+
+        def nodeCreated(nodeIdentifier):
+            uri = gateway.getXMPPURI(componentJID, nodeIdentifier)
+            request.args[b'uri'] = [uri]
+            request.content = StringIO(b'')
+
+            return _render(self.resource, request)
+
+        d = self.backend.createNode(u'test', ownerJID)
+        d.addCallback(nodeCreated)
+        d.addCallback(rendered)
+        return d
+
+
+    def test_postWithRedirect(self):
+        """
+        Upon a POST, a new node is created and the URI returned.
+        """
+        request = DummyRequest([b''])
+        request.method = b'POST'
+        otherNodeURI = b'xmpp:pubsub.example.org?node=other'
+
+        def rendered(result):
+            self.assertEqual(http.NO_CONTENT, request.responseCode)
+            self.assertEqual(1, len(deletes))
+            nodeIdentifier, owner, redirectURI = deletes[-1]
+            self.assertEqual(otherNodeURI, redirectURI)
+
+        def nodeCreated(nodeIdentifier):
+            uri = gateway.getXMPPURI(componentJID, nodeIdentifier)
+            request.args[b'uri'] = [uri]
+            payload = {b'redirect_uri': otherNodeURI}
+            body = simplejson.dumps(payload)
+            request.content = StringIO(body)
+            return _render(self.resource, request)
+
+        def deleteNode(nodeIdentifier, owner, redirectURI):
+            deletes.append((nodeIdentifier, owner, redirectURI))
+            return defer.succeed(nodeIdentifier)
+
+        deletes = []
+        self.patch(self.backend, 'deleteNode', deleteNode)
+        d = self.backend.createNode(u'test', ownerJID)
+        d.addCallback(nodeCreated)
+        d.addCallback(rendered)
+        return d
+
+
+    def test_postUnknownNode(self):
+        """
+        If the node to be deleted is unknown, 404 Not Found is returned.
+        """
+        request = DummyRequest([b''])
+        request.method = b'POST'
+
+        def rendered(result):
+            self.assertEqual(http.NOT_FOUND, request.responseCode)
+
+        uri = gateway.getXMPPURI(componentJID, u'unknown')
+        request.args[b'uri'] = [uri]
+        request.content = StringIO(b'')
+
+        d = _render(self.resource, request)
+        d.addCallback(rendered)
+        return d
+
+
+    def test_postURIMissing(self):
+        """
+        If no URI is passed, 400 Bad Request is returned.
+        """
+        request = DummyRequest([b''])
+        request.method = b'POST'
+
+        def rendered(result):
+            self.assertEqual(http.BAD_REQUEST, request.responseCode)
+
+        request.content = StringIO(b'')
+
+        d = _render(self.resource, request)
+        d.addCallback(rendered)
+        return d
+
+
+class CallbackResourceTest(unittest.TestCase):
+    """
+    Tests for L{gateway.CallbackResource}.
+    """
+
+    def setUp(self):
+        self.callbackEvents = []
+        self.resource = gateway.CallbackResource(self._callback)
+
+
+    def _callback(self, payload, headers):
+        self.callbackEvents.append((payload, headers))
+
+
+    def test_get(self):
+        """
+        The method GET is not supported.
+        """
+        request = DummyRequest([b''])
+        self.assertRaises(error.UnsupportedMethod,
+                          _render, self.resource, request)
+
+
+    def test_post(self):
+        """
+        The body posted is passed to the callback.
+        """
+        request = DummyRequest([b''])
+        request.method = 'POST'
+        request.content = StringIO(b'<root><child/></root>')
+
+        def rendered(result):
+            self.assertEqual(1, len(self.callbackEvents))
+            payload, headers = self.callbackEvents[-1]
+            self.assertEqual('root', payload.name)
+
+            self.assertEqual(http.NO_CONTENT, request.responseCode)
+            self.assertFalse(b''.join(request.written))
+
+        d = _render(self.resource, request)
+        d.addCallback(rendered)
+        return d
+
+
+    def test_postEvent(self):
+        """
+        If the Event header is set, the payload is empty and the header passed.
+        """
+        request = DummyRequest([b''])
+        request.method = 'POST'
+        request.requestHeaders.addRawHeader(b'Event', b'DELETE')
+        request.content = StringIO(b'')
+
+        def rendered(result):
+            self.assertEqual(1, len(self.callbackEvents))
+            payload, headers = self.callbackEvents[-1]
+            self.assertIdentical(None, payload)
+            self.assertEqual(['DELETE'], headers.getRawHeaders(b'Event'))
+            self.assertFalse(b''.join(request.written))
+
+        d = _render(self.resource, request)
+        d.addCallback(rendered)
+        return d
+
+
 
 class GatewayTest(unittest.TestCase):
     timeout = 2
@@ -143,7 +494,7 @@
         def cb(err):
             self.assertEqual('404', err.status)
 
-        d = self.client.publish(TEST_ENTRY, 'xmpp:%s?node=test' % componentJID)
+        d = self.client.publish(TEST_ENTRY, 'xmpp:%s?node=test' % component)
         self.assertFailure(d, error.Error)
         d.addCallback(cb)
         return d
@@ -161,7 +512,7 @@
     def test_deleteWithRedirect(self):
         def cb(response):
             xmppURI = response['uri']
-            redirectURI = 'xmpp:%s?node=test' % componentJID
+            redirectURI = 'xmpp:%s?node=test' % component
             d = self.client.delete(xmppURI, redirectURI)
             return d
 
@@ -198,7 +549,7 @@
         return defer.gatherResults([d, self.client.deferred])
 
     def test_deleteNotificationWithRedirect(self):
-        redirectURI = 'xmpp:%s?node=test' % componentJID
+        redirectURI = 'xmpp:%s?node=test' % component
 
         def onNotification(data, headers):
             try:
@@ -385,7 +736,7 @@
         def cb(err):
             self.assertEqual('403', err.status)
 
-        d = self.client.subscribe('xmpp:%s?node=test' % componentJID)
+        d = self.client.subscribe('xmpp:%s?node=test' % component)
         self.assertFailure(d, error.Error)
         d.addCallback(cb)
         return d
@@ -393,6 +744,9 @@
 
     def test_subscribeRootGetNotification(self):
 
+        def clean(rootNode):
+            return self.client.unsubscribe(rootNode)
+
         def onNotification(data, headers):
             self.client.deferred.callback(None)
 
@@ -402,6 +756,7 @@
             rootNode = gateway.getXMPPURI(jid, '')
 
             d = self.client.subscribe(rootNode)
+            d.addCallback(lambda _: self.addCleanup(clean, rootNode))
             d.addCallback(lambda _: xmppURI)
             return d
 
@@ -421,7 +776,7 @@
         def cb(err):
             self.assertEqual('403', err.status)
 
-        d = self.client.unsubscribe('xmpp:%s?node=test' % componentJID)
+        d = self.client.unsubscribe('xmpp:%s?node=test' % component)
         self.assertFailure(d, error.Error)
         d.addCallback(cb)
         return d