############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Test traversal convenience functions. """ import unittest from zope import interface import zope.component from zope.component.testing import PlacelessSetup from zope.location.traversing \ import LocationPhysicallyLocatable, RootPhysicallyLocatable from zope.location.interfaces import ILocationInfo, IRoot, LocationError from zope.traversing.adapters import Traverser, DefaultTraversable from zope.traversing.interfaces import ITraversable, ITraverser from zope.traversing.testing import contained class C(object): __parent__ = None __name__ = None def __init__(self, name): self.name = name class TestFunctional(PlacelessSetup, unittest.TestCase): def setUp(self): PlacelessSetup.setUp(self) # Build up a wrapper chain root = C('root') interface.directlyProvides(root, IRoot) folder = C('folder') item = C('item') self.root = root # root is not usually wrapped self.folder = contained(folder, self.root, name='folder') self.item = contained(item, self.folder, name='item') self.unwrapped_item = item self.broken_chain_folder = contained(folder, None) self.broken_chain_item = contained( item, self.broken_chain_folder, name='item' ) root.folder = folder folder.item = item self.tr = Traverser(root) zope.component.provideAdapter(Traverser, (None,), ITraverser) zope.component.provideAdapter(DefaultTraversable, (None,), ITraversable) zope.component.provideAdapter(LocationPhysicallyLocatable, (None,), ILocationInfo) zope.component.provideAdapter(RootPhysicallyLocatable, (IRoot,), ILocationInfo) def testTraverse(self): from zope.traversing.api import traverse self.assertEqual( traverse(self.item, '/folder/item'), self.tr.traverse('/folder/item') ) def test_traverse_with_default(self): from zope.traversing.api import traverse self.assertIs( traverse(self.item, '/no/path', self), self ) def testTraverseFromUnwrapped(self): from zope.traversing.api import traverse self.assertRaises( TypeError, traverse, self.unwrapped_item, '/folder/item' ) def testTraverseName(self): from zope.traversing.api import traverseName self.assertEqual( traverseName(self.folder, 'item'), self.tr.traverse('/folder/item') ) self.assertEqual( traverseName(self.item, '.'), self.tr.traverse('/folder/item') ) self.assertEqual( traverseName(self.item, '..'), self.tr.traverse('/folder') ) self.assertEqual( traverseName(self.folder, 'item', default=self), self.tr.traverse('/folder/item') ) self.assertIs( traverseName(self.folder, 'nothing', default=self), self, ) # TODO test that ++names++ and @@names work too def testTraverseNameBadValue(self): from zope.traversing.api import traverseName self.assertRaises( LocationError, traverseName, self.folder, '../root' ) self.assertRaises( LocationError, traverseName, self.folder, '/root' ) self.assertRaises( LocationError, traverseName, self.folder, './item' ) def testTraverseNameUnicode(self): from zope.traversing.api import traverseName from zope.interface import implementer @implementer(ITraversable) class BrokenTraversable(object): def traverse(self, name, furtherPath): getattr(self, u'\u2019', None) # The above actually works on Python 3 raise unittest.SkipTest("Unicode attrs legal on Py3") self.assertRaises( LocationError, traverseName, BrokenTraversable(), '') def testGetName(self): from zope.traversing.api import getName self.assertEqual( getName(self.item), 'item' ) def testGetParent(self): from zope.traversing.api import getParent self.assertEqual( getParent(self.item), self.folder ) def testGetParentFromRoot(self): from zope.traversing.api import getParent self.assertEqual( getParent(self.root), None ) def testGetParentBrokenChain(self): from zope.traversing.api import getParent self.assertRaises( TypeError, getParent, self.broken_chain_folder ) def testGetParentFromUnwrapped(self): from zope.traversing.api import getParent self.assertRaises( TypeError, getParent, self.unwrapped_item ) def testGetParents(self): from zope.traversing.api import getParents self.assertEqual( getParents(self.item), [self.folder, self.root] ) def testGetParentsBrokenChain(self): from zope.traversing.api import getParents self.assertRaises( TypeError, getParents, self.broken_chain_item ) def testGetPath(self): from zope.traversing.api import getPath self.assertEqual( getPath(self.item), u'/folder/item' ) def testGetPathOfRoot(self): from zope.traversing.api import getPath self.assertEqual( getPath(self.root), u'/', ) def testGetNameOfRoot(self): from zope.traversing.api import getName self.assertEqual( getName(self.root), u'', ) def testGetRoot(self): from zope.traversing.api import getRoot self.assertEqual( getRoot(self.item), self.root ) def testCanonicalPath(self): _bad_locations = ( (ValueError, '\xa323'), (ValueError, ''), (ValueError, '//'), (ValueError, '/foo//bar'), # regarding the next two errors: # having a trailing slash on a location is undefined. # we might want to give it a particular meaning for zope3 later # for now, it is an invalid location identifier (ValueError, '/foo/bar/'), (ValueError, 'foo/bar/'), (IndexError, '/a/../..'), (ValueError, '/a//v'), ) # sequence of N-tuples: # (loc_returned_as_string, input, input, ...) # The string and tuple are tested as input as well as being the # specification for output. _good_locations = ( # location returned as string (u'/xx/yy/zz', # arguments to try in addition to the above '/xx/yy/zz', '/xx/./yy/ww/../zz', ), (u'/xx/yy/zz', '/xx/yy/zz', ), (u'/xx', '/xx', ), (u'/', '/', self.root, ), ) from zope.traversing.api import canonicalPath for error_type, value in _bad_locations: self.assertRaises(error_type, canonicalPath, value) for spec in _good_locations: correct_answer = spec[0] for argument in spec: self.assertEqual(canonicalPath(argument), correct_answer, "failure on %s" % argument) def test_normalizePath(self): _bad_locations = ( (ValueError, '//'), (ValueError, '/foo//bar'), (IndexError, '/a/../..'), (IndexError, '/a/./../..'), ) # sequence of N-tuples: # (loc_returned_as_string, input, input, ...) # The string and tuple are tested as input as well as being the # specification for output. _good_locations = ( # location returned as string ('/xx/yy/zz', # arguments to try in addition to the above '/xx/yy/zz', '/xx/./yy/ww/../zz', '/xx/./yy/ww/./../zz', ), ('xx/yy/zz', # arguments to try in addition to the above 'xx/yy/zz', 'xx/./yy/ww/../zz', 'xx/./yy/ww/./../zz', ), ('/xx/yy/zz', '/xx/yy/zz', ), ('/xx', '/xx', ), ('/', '/', ), ) from zope.traversing.api import _normalizePath for error_type, value in _bad_locations: self.assertRaises(error_type, _normalizePath, value) for spec in _good_locations: correct_answer = spec[0] for argument in spec: self.assertEqual(_normalizePath(argument), correct_answer, "failure on %s" % argument) def test_joinPath_slashes(self): from zope.traversing.api import joinPath path = u'/' args = ('/test', 'bla', '/foo', 'bar') self.assertRaises(ValueError, joinPath, path, *args) args = ('/test', 'bla', 'foo/', '/bar') self.assertRaises(ValueError, joinPath, path, *args) def test_joinPath(self): from zope.traversing.api import joinPath path = u'/bla' args = ('foo', 'bar', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'/bla/foo/bar/baz/bone') path = u'bla' args = ('foo', 'bar', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'bla/foo/bar/baz/bone') path = u'bla' args = ('foo', 'bar/baz', 'bone') self.assertEqual(joinPath(path, *args), u'bla/foo/bar/baz/bone') path = u'bla/' args = ('foo', 'bar', 'baz', 'bone') self.assertRaises(ValueError, joinPath, path, *args) def test_joinPath_normalize(self): from zope.traversing.api import joinPath path = u'/bla' args = ('foo', 'bar', '..', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'/bla/foo/baz/bone') path = u'bla' args = ('foo', 'bar', '.', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'bla/foo/bar/baz/bone') path = u'/' args = ('foo', 'bar', '.', 'baz', 'bone') self.assertEqual(joinPath(path, *args), u'/foo/bar/baz/bone') def test_joinPath_empty_args(self): from zope.traversing.api import joinPath path = 'abc' self.assertEqual(joinPath(path), u'abc') class TestStandalone(unittest.TestCase): # Unlike TestFunctional, we don't register gobs of # adapters, making these tests more self-contained def test_getParent_no_location_info(self): from zope.traversing.api import getParent test = self class Context(object): called = False def __conform__(self, iface): self.called = True test.assertEqual(iface, ILocationInfo) raise TypeError() context = Context() with self.assertRaisesRegexp(TypeError, "Not enough context"): getParent(context) self.assertTrue(context.called) context.called = False # Now give it a parent context.__parent__ = self self.assertIs(self, getParent(context)) self.assertTrue(context.called) context.called = False # Now if it's a root, it has no parent interface.alsoProvides(context, IRoot) self.assertIsNone(getParent(context))