Code

PGP support is again working (pyme API has changed significantly) and we
[roundup.git] / test / test_actions.py
index 190fe5daf2e29ff0fc8e3552838879c832aa7f6e..edebe0c4a5d6d256a35dc930f8a4e84671373c4a 100755 (executable)
-from __future__ import nested_scopes\r
-\r
-import unittest\r
-from cgi import FieldStorage, MiniFieldStorage\r
-\r
-from roundup import hyperdb\r
-from roundup.date import Date, Interval\r
-from roundup.cgi.actions import *\r
-from roundup.cgi.exceptions import Redirect, Unauthorised, SeriousError\r
-\r
-class MockNull:\r
-    def __init__(self, **kwargs):\r
-        for key, value in kwargs.items():\r
-            setattr(self, key, value)\r
-\r
-    def __call__(self, *args, **kwargs): return MockNull()\r
-    def __getattr__(self, name):\r
-        # This allows assignments which assume all intermediate steps are Null\r
-        # objects if they don't exist yet.\r
-        #\r
-        # For example (with just 'client' defined):\r
-        #\r
-        # client.db.config.TRACKER_WEB = 'BASE/'\r
-        setattr(self, name, MockNull())\r
-        return getattr(self, name)\r
-\r
-    def __getitem__(self, key): return self\r
-    def __nonzero__(self): return 0\r
-    def __str__(self): return ''\r
-\r
-def true(*args, **kwargs):\r
-    return 1\r
-\r
-class ActionTestCase(unittest.TestCase):\r
-    def setUp(self):\r
-        self.form = FieldStorage()\r
-        self.client = MockNull()\r
-        self.client.form = self.form\r
-\r
-class ShowActionTestCase(ActionTestCase):\r
-    def assertRaisesMessage(self, exception, callable, message, *args,\r
-                            **kwargs):\r
-        """An extension of assertRaises, which also checks the exception\r
-        message. We need this because we rely on exception messages when\r
-        redirecting.\r
-        """\r
-        try:\r
-            callable(*args, **kwargs)\r
-        except exception, msg:\r
-            self.assertEqual(str(msg), message)\r
-        else:\r
-            if hasattr(excClass,'__name__'):\r
-                excName = excClass.__name__\r
-            else:\r
-                excName = str(excClass)\r
-            raise self.failureException, excName\r
-\r
-    def testShowAction(self):\r
-        self.client.db.config.TRACKER_WEB = 'BASE/'\r
-\r
-        action = ShowAction(self.client)\r
-        self.assertRaises(ValueError, action.handle)\r
-\r
-        self.form.value.append(MiniFieldStorage('@type', 'issue'))\r
-        self.assertRaises(SeriousError, action.handle)\r
-\r
-        self.form.value.append(MiniFieldStorage('@number', '1'))\r
-        self.assertRaisesMessage(Redirect, action.handle, 'BASE/issue1')\r
-\r
-    def testShowActionNoType(self):\r
-        action = ShowAction(self.client)\r
-        self.assertRaises(ValueError, action.handle)\r
-        self.form.value.append(MiniFieldStorage('@number', '1'))\r
-        self.assertRaisesMessage(ValueError, action.handle,\r
-            'No type specified')\r
-\r
-class RetireActionTestCase(ActionTestCase):\r
-    def testRetireAction(self):\r
-        self.client.db.security.hasPermission = true\r
-        self.client.ok_message = []\r
-        RetireAction(self.client).handle()\r
-        self.assert_(len(self.client.ok_message) == 1)\r
-\r
-    def testNoPermission(self):\r
-        self.assertRaises(Unauthorised, RetireAction(self.client).execute)\r
-\r
-    def testDontRetireAdminOrAnonymous(self):\r
-        self.client.db.security.hasPermission=true\r
-        # look up the user class\r
-        self.client.classname = 'user'\r
-        # but always look up admin, regardless of nodeid\r
-        self.client.db.user.get = lambda a,b: 'admin'\r
-        self.assertRaises(ValueError, RetireAction(self.client).handle)\r
-        # .. or anonymous\r
-        self.client.db.user.get = lambda a,b: 'anonymous'\r
-        self.assertRaises(ValueError, RetireAction(self.client).handle)\r
-\r
-class SearchActionTestCase(ActionTestCase):\r
-    def setUp(self):\r
-        ActionTestCase.setUp(self)\r
-        self.action = SearchAction(self.client)\r
-\r
-class StandardSearchActionTestCase(SearchActionTestCase):\r
-    def testNoPermission(self):\r
-        self.assertRaises(Unauthorised, self.action.execute)\r
-\r
-    def testQueryName(self):\r
-        self.assertEqual(self.action.getQueryName(), '')\r
-\r
-        self.form.value.append(MiniFieldStorage('@queryname', 'foo'))\r
-        self.assertEqual(self.action.getQueryName(), 'foo')\r
-\r
-class FakeFilterVarsTestCase(SearchActionTestCase):\r
-    def setUp(self):\r
-        SearchActionTestCase.setUp(self)\r
-        self.client.db.classes.getprops = lambda: {'foo': hyperdb.Multilink('foo')}\r
-\r
-    def assertFilterEquals(self, expected):\r
-        self.action.fakeFilterVars()\r
-        self.assertEqual(self.form.getvalue('@filter'), expected)\r
-\r
-    def testEmptyMultilink(self):\r
-        self.form.value.append(MiniFieldStorage('foo', ''))\r
-        self.form.value.append(MiniFieldStorage('foo', ''))\r
-\r
-        self.assertFilterEquals(None)\r
-\r
-    def testNonEmptyMultilink(self):\r
-        self.form.value.append(MiniFieldStorage('foo', ''))\r
-        self.form.value.append(MiniFieldStorage('foo', '1'))\r
-\r
-        self.assertFilterEquals('foo')\r
-\r
-    def testEmptyKey(self):\r
-        self.form.value.append(MiniFieldStorage('foo', ''))\r
-        self.assertFilterEquals(None)\r
-\r
-    def testStandardKey(self):\r
-        self.form.value.append(MiniFieldStorage('foo', '1'))\r
-        self.assertFilterEquals('foo')\r
-\r
-    def testStringKey(self):\r
-        self.client.db.classes.getprops = lambda: {'foo': hyperdb.String()}\r
-        self.form.value.append(MiniFieldStorage('foo', 'hello'))\r
-        self.assertFilterEquals('foo')\r
-\r
-    def testTokenizedStringKey(self):\r
-        self.client.db.classes.getprops = lambda: {'foo': hyperdb.String()}\r
-        self.form.value.append(MiniFieldStorage('foo', 'hello world'))\r
-\r
-        self.assertFilterEquals('foo')\r
-\r
-        # The single value gets replaced with the tokenized list.\r
-        self.assertEqual([x.value for x in self.form['foo']], ['hello', 'world'])\r
-\r
-class CollisionDetectionTestCase(ActionTestCase):\r
-    def setUp(self):\r
-        ActionTestCase.setUp(self)\r
-        self.action = EditItemAction(self.client)\r
-        self.now = Date('.')\r
-        # round off for testing\r
-        self.now.second = int(self.now.second)\r
-\r
-    def testLastUserActivity(self):\r
-        self.assertEqual(self.action.lastUserActivity(), None)\r
-\r
-        self.client.form.value.append(MiniFieldStorage('@lastactivity', str(self.now)))\r
-        self.assertEqual(self.action.lastUserActivity(), self.now)\r
-\r
-    def testLastNodeActivity(self):\r
-        self.action.classname = 'issue'\r
-        self.action.nodeid = '1'\r
-\r
-        def get(nodeid, propname):\r
-            self.assertEqual(nodeid, '1')\r
-            self.assertEqual(propname, 'activity')\r
-            return self.now\r
-        self.client.db.issue.get = get\r
-\r
-        self.assertEqual(self.action.lastNodeActivity(), self.now)\r
-\r
-    def testCollision(self):\r
-        self.failUnless(self.action.detectCollision(self.now, self.now + Interval("1d")))\r
-        self.failIf(self.action.detectCollision(self.now, self.now - Interval("1d")))\r
-        self.failIf(self.action.detectCollision(None, self.now))\r
-\r
-def test_suite():\r
-    suite = unittest.TestSuite()\r
-    suite.addTest(unittest.makeSuite(RetireActionTestCase))\r
-    suite.addTest(unittest.makeSuite(StandardSearchActionTestCase))\r
-    suite.addTest(unittest.makeSuite(FakeFilterVarsTestCase))\r
-    suite.addTest(unittest.makeSuite(ShowActionTestCase))\r
-    suite.addTest(unittest.makeSuite(CollisionDetectionTestCase))\r
-    return suite\r
-\r
-if __name__ == '__main__':\r
-    runner = unittest.TextTestRunner()\r
-    unittest.main(testRunner=runner)\r
+from __future__ import nested_scopes
+
+import unittest
+from cgi import FieldStorage, MiniFieldStorage
+
+from roundup import hyperdb
+from roundup.date import Date, Interval
+from roundup.cgi.actions import *
+from roundup.cgi.exceptions import Redirect, Unauthorised, SeriousError
+
+from mocknull import MockNull
+
+def true(*args, **kwargs):
+    return 1
+
+class ActionTestCase(unittest.TestCase):
+    def setUp(self):
+        self.form = FieldStorage()
+        self.client = MockNull()
+        self.client.form = self.form
+        class TemplatingUtils:
+            pass
+        self.client.instance.interfaces.TemplatingUtils = TemplatingUtils
+
+class ShowActionTestCase(ActionTestCase):
+    def assertRaisesMessage(self, exception, callable, message, *args,
+                            **kwargs):
+        """An extension of assertRaises, which also checks the exception
+        message. We need this because we rely on exception messages when
+        redirecting.
+        """
+        try:
+            callable(*args, **kwargs)
+        except exception, msg:
+            self.assertEqual(str(msg), message)
+        else:
+            if hasattr(exception, '__name__'):
+                excName = exception.__name__
+            else:
+                excName = str(exception)
+            raise self.failureException, excName
+
+    def testShowAction(self):
+        self.client.base = 'BASE/'
+
+        action = ShowAction(self.client)
+        self.assertRaises(ValueError, action.handle)
+
+        self.form.value.append(MiniFieldStorage('@type', 'issue'))
+        self.assertRaises(SeriousError, action.handle)
+
+        self.form.value.append(MiniFieldStorage('@number', '1'))
+        self.assertRaisesMessage(Redirect, action.handle, 'BASE/issue1')
+
+    def testShowActionNoType(self):
+        action = ShowAction(self.client)
+        self.assertRaises(ValueError, action.handle)
+        self.form.value.append(MiniFieldStorage('@number', '1'))
+        self.assertRaisesMessage(ValueError, action.handle,
+            'No type specified')
+
+class RetireActionTestCase(ActionTestCase):
+    def testRetireAction(self):
+        self.client.db.security.hasPermission = true
+        self.client.ok_message = []
+        RetireAction(self.client).handle()
+        self.assert_(len(self.client.ok_message) == 1)
+
+    def testNoPermission(self):
+        self.assertRaises(Unauthorised, RetireAction(self.client).execute)
+
+    def testDontRetireAdminOrAnonymous(self):
+        self.client.db.security.hasPermission=true
+        # look up the user class
+        self.client.classname = 'user'
+        # but always look up admin, regardless of nodeid
+        self.client.db.user.get = lambda a,b: 'admin'
+        self.assertRaises(ValueError, RetireAction(self.client).handle)
+        # .. or anonymous
+        self.client.db.user.get = lambda a,b: 'anonymous'
+        self.assertRaises(ValueError, RetireAction(self.client).handle)
+
+class SearchActionTestCase(ActionTestCase):
+    def setUp(self):
+        ActionTestCase.setUp(self)
+        self.action = SearchAction(self.client)
+
+class StandardSearchActionTestCase(SearchActionTestCase):
+    def testNoPermission(self):
+        self.assertRaises(Unauthorised, self.action.execute)
+
+    def testQueryName(self):
+        self.assertEqual(self.action.getQueryName(), '')
+
+        self.form.value.append(MiniFieldStorage('@queryname', 'foo'))
+        self.assertEqual(self.action.getQueryName(), 'foo')
+
+class FakeFilterVarsTestCase(SearchActionTestCase):
+    def setUp(self):
+        SearchActionTestCase.setUp(self)
+        self.client.db.classes.get_transitive_prop = lambda x: \
+            hyperdb.Multilink('foo')
+
+    def assertFilterEquals(self, expected):
+        self.action.fakeFilterVars()
+        self.assertEqual(self.form.getvalue('@filter'), expected)
+
+    def testEmptyMultilink(self):
+        self.form.value.append(MiniFieldStorage('foo', ''))
+        self.form.value.append(MiniFieldStorage('foo', ''))
+
+        self.assertFilterEquals(None)
+
+    def testNonEmptyMultilink(self):
+        self.form.value.append(MiniFieldStorage('foo', ''))
+        self.form.value.append(MiniFieldStorage('foo', '1'))
+
+        self.assertFilterEquals('foo')
+
+    def testEmptyKey(self):
+        self.form.value.append(MiniFieldStorage('foo', ''))
+        self.assertFilterEquals(None)
+
+    def testStandardKey(self):
+        self.form.value.append(MiniFieldStorage('foo', '1'))
+        self.assertFilterEquals('foo')
+
+    def testStringKey(self):
+        self.client.db.classes.getprops = lambda: {'foo': hyperdb.String()}
+        self.form.value.append(MiniFieldStorage('foo', 'hello'))
+        self.assertFilterEquals('foo')
+
+    def testTokenizedStringKey(self):
+        self.client.db.classes.get_transitive_prop = lambda x: hyperdb.String()
+        self.form.value.append(MiniFieldStorage('foo', 'hello world'))
+
+        self.assertFilterEquals('foo')
+
+        # The single value gets replaced with the tokenized list.
+        self.assertEqual([x.value for x in self.form['foo']],
+            ['hello', 'world'])
+
+class CollisionDetectionTestCase(ActionTestCase):
+    def setUp(self):
+        ActionTestCase.setUp(self)
+        self.action = EditItemAction(self.client)
+        self.now = Date('.')
+        # round off for testing
+        self.now.second = int(self.now.second)
+
+    def testLastUserActivity(self):
+        self.assertEqual(self.action.lastUserActivity(), None)
+
+        self.client.form.value.append(
+            MiniFieldStorage('@lastactivity', str(self.now)))
+        self.assertEqual(self.action.lastUserActivity(), self.now)
+
+    def testLastNodeActivity(self):
+        self.action.classname = 'issue'
+        self.action.nodeid = '1'
+
+        def get(nodeid, propname):
+            self.assertEqual(nodeid, '1')
+            self.assertEqual(propname, 'activity')
+            return self.now
+        self.client.db.issue.get = get
+
+        self.assertEqual(self.action.lastNodeActivity(), self.now)
+
+    def testCollision(self):
+        # fake up an actual change
+        self.action.classname = 'test'
+        self.action.nodeid = '1'
+        self.client.parsePropsFromForm = lambda: ({('test','1'):{1:1}}, [])
+        self.failUnless(self.action.detectCollision(self.now,
+            self.now + Interval("1d")))
+        self.failIf(self.action.detectCollision(self.now,
+            self.now - Interval("1d")))
+        self.failIf(self.action.detectCollision(None, self.now))
+
+class LoginTestCase(ActionTestCase):
+    def setUp(self):
+        ActionTestCase.setUp(self)
+        self.client.error_message = []
+
+        # set the db password to 'right'
+        self.client.db.user.get = lambda a,b: 'right'
+
+        # unless explicitly overridden, we should never get here
+        self.client.opendb = lambda a: self.fail(
+            "Logged in, but we shouldn't be.")
+
+    def assertLoginLeavesMessages(self, messages, username=None, password=None):
+        if username is not None:
+            self.form.value.append(MiniFieldStorage('__login_name', username))
+        if password is not None:
+            self.form.value.append(
+                MiniFieldStorage('__login_password', password))
+
+        LoginAction(self.client).handle()
+        self.assertEqual(self.client.error_message, messages)
+
+    def testNoUsername(self):
+        self.assertLoginLeavesMessages(['Username required'])
+
+    def testInvalidUsername(self):
+        def raiseKeyError(a):
+            raise KeyError
+        self.client.db.user.lookup = raiseKeyError
+        self.assertLoginLeavesMessages(['Invalid login'], 'foo')
+
+    def testInvalidPassword(self):
+        self.assertLoginLeavesMessages(['Invalid login'], 'foo', 'wrong')
+
+    def testNoWebAccess(self):
+        self.assertLoginLeavesMessages(['You do not have permission to login'],
+                                        'foo', 'right')
+
+    def testCorrectLogin(self):
+        self.client.db.security.hasPermission = lambda *args, **kwargs: True
+
+        def opendb(username):
+            self.assertEqual(username, 'foo')
+        self.client.opendb = opendb
+
+        self.assertLoginLeavesMessages([], 'foo', 'right')
+
+class EditItemActionTestCase(ActionTestCase):
+    def setUp(self):
+        ActionTestCase.setUp(self)
+        self.result = []
+        class AppendResult:
+            def __init__(inner_self, name):
+                inner_self.name = name
+            def __call__(inner_self, *args, **kw):
+                self.result.append((inner_self.name, args, kw))
+                if inner_self.name == 'set':
+                    return kw
+                return '17'
+
+        self.client.db.security.hasPermission = true
+        self.client.classname = 'issue'
+        self.client.base = 'http://tracker/'
+        self.client.nodeid = '4711'
+        self.client.template = 'item'
+        self.client.db.classes.create = AppendResult('create')
+        self.client.db.classes.set = AppendResult('set')
+        self.client.db.classes.getprops = lambda: \
+            ({'messages':hyperdb.Multilink('msg')
+             ,'content':hyperdb.String()
+             ,'files':hyperdb.Multilink('file')
+             ,'msg':hyperdb.Link('msg')
+             })
+        self.action = EditItemAction(self.client)
+
+    def testMessageAttach(self):
+        expect = \
+            [ ('create',(),{'content':'t'})
+            , ('set',('4711',), {'messages':['23','42','17']})
+            ]
+        self.client.db.classes.get = lambda a, b:['23','42']
+        self.client.parsePropsFromForm = lambda: \
+            ( {('msg','-1'):{'content':'t'},('issue','4711'):{}}
+            , [('issue','4711','messages',[('msg','-1')])]
+            )
+        try :
+            self.action.handle()
+        except Redirect, msg:
+            pass
+        self.assertEqual(expect, self.result)
+
+    def testFileAttach(self):
+        expect = \
+            [('create',(),{'content':'t','type':'text/plain','name':'t.txt'})
+            ,('set',('4711',),{'files':['23','42','17']})
+            ]
+        self.client.db.classes.get = lambda a, b:['23','42']
+        self.client.parsePropsFromForm = lambda: \
+            ( {('file','-1'):{'content':'t','type':'text/plain','name':'t.txt'}
+              ,('issue','4711'):{}
+              }
+            , [('issue','4711','messages',[('msg','-1')])
+              ,('issue','4711','files',[('file','-1')])
+              ,('msg','-1','files',[('file','-1')])
+              ]
+            )
+        try :
+            self.action.handle()
+        except Redirect, msg:
+            pass
+        self.assertEqual(expect, self.result)
+
+    def testLinkExisting(self):
+        expect = [('set',('4711',),{'messages':['23','42','1']})]
+        self.client.db.classes.get = lambda a, b:['23','42']
+        self.client.parsePropsFromForm = lambda: \
+            ( {('issue','4711'):{},('msg','1'):{}}
+            , [('issue','4711','messages',[('msg','1')])]
+            )
+        try :
+            self.action.handle()
+        except Redirect, msg:
+            pass
+        self.assertEqual(expect, self.result)
+
+    def testLinkNewToExisting(self):
+        expect = [('create',(),{'msg':'1','title':'TEST'})]
+        self.client.db.classes.get = lambda a, b:['23','42']
+        self.client.parsePropsFromForm = lambda: \
+            ( {('issue','-1'):{'title':'TEST'},('msg','1'):{}}
+            , [('issue','-1','msg',[('msg','1')])]
+            )
+        try :
+            self.action.handle()
+        except Redirect, msg:
+            pass
+        self.assertEqual(expect, self.result)
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(RetireActionTestCase))
+    suite.addTest(unittest.makeSuite(StandardSearchActionTestCase))
+    suite.addTest(unittest.makeSuite(FakeFilterVarsTestCase))
+    suite.addTest(unittest.makeSuite(ShowActionTestCase))
+    suite.addTest(unittest.makeSuite(CollisionDetectionTestCase))
+    suite.addTest(unittest.makeSuite(LoginTestCase))
+    suite.addTest(unittest.makeSuite(EditItemActionTestCase))
+    return suite
+
+if __name__ == '__main__':
+    runner = unittest.TextTestRunner()
+    unittest.main(testRunner=runner)
+
+# vim: set et sts=4 sw=4 :