changeset 225:ebb12ff21cb2

Updated unit tests to be able to run.
author Ludovic Chabant <ludovic@chabant.com>
date Wed, 12 Mar 2014 23:02:40 -0700
parents d45450a0256a
children 23e310dd1ff7
files tests/__init__.py tests/mock.py tests/test_db.py tests/test_mock.py tests/test_page.py tests/test_resolver.py wikked/db/base.py wikked/db/sql.py wikked/formatter.py wikked/indexer/base.py wikked/page.py wikked/scm/base.py wikked/wiki.py
diffstat 13 files changed, 364 insertions(+), 349 deletions(-) [+]
line wrap: on
line diff
--- a/tests/__init__.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/tests/__init__.py	Wed Mar 12 23:02:40 2014 -0700
@@ -1,53 +1,81 @@
 import os
 import os.path
+import urllib
 import shutil
 import unittest
 from wikked.wiki import Wiki
+from wikked.db.sql import SQLDatabase
 from mock import MockWikiParameters, MockFileSystem
 
 
+class MockWikiParametersWithStructure(MockWikiParameters):
+    def __init__(self, structure, root=None):
+        super(MockWikiParametersWithStructure, self).__init__(root)
+        self.structure = structure
+
+    def fs_factory(self):
+        return MockFileSystem(self.root, self.config, self.structure)
+
+
 class WikkedTest(unittest.TestCase):
     def setUp(self):
         # Directory you can use for temporary files.
-        self.root = os.path.join(
+        self.test_data_dir = os.path.join(
             os.path.dirname(os.path.dirname(__file__)),
             'test_data')
 
     def tearDown(self):
-        if hasattr(self, 'root') and os.path.isdir(self.root):
-            shutil.rmtree(self.root)
+        if hasattr(self, 'wiki') and self.wiki is not None:
+            self.wiki.db.close(False, None)
+
+        if os.path.isdir(self.test_data_dir):
+            shutil.rmtree(self.test_data_dir)
 
-    def getWiki(self, **kwargs):
-        parameters = self.getParameters()
+    def _getParameters(self, root=None):
+        return MockWikiParameters(root)
+
+    def _getWiki(self, parameters=None, **kwargs):
+        parameters = parameters or self._getParameters()
         for key in kwargs:
             setattr(parameters, key, kwargs[key])
-        wiki = Wiki(parameters)
+        self.wiki = Wiki(parameters)
+        self._onWikiCreated(self.wiki)
+        return self.wiki
+
+    def _getStartedWiki(self, **kwargs):
+        wiki = self._getWiki(**kwargs)
+        wiki.start()
+        self._onWikiStarted(wiki)
         return wiki
 
-    def getStartedWiki(self, **kwargs):
-        wiki = self.getWiki(**kwargs)
-        wiki.start()
+    def _getWikiFromStructure(self, structure, root='/'):
+        params = self._getParameters(root)
+        params.fs_factory = lambda: MockFileSystem(
+                params.root, params.config, structure)
+        params.db_factory = lambda: SQLDatabase(params.config)
+        params.config_text = "[wiki]\ndatabase_url = sqlite://\n"
+        wiki = self._getStartedWiki(parameters=params)
         return wiki
 
-    def getParameters(self):
-        return MockWikiParameters()
+    def _onWikiCreated(self, wiki):
+        pass
 
-    def _getWikiFromStructure(self, structure):
-        wiki = self.getWiki(use_db=False, fs_factory=lambda cfg: MockFileSystem(structure))
-        wiki.start()
-        return wiki
+    def _onWikiStarted(self, wiki):
+        wiki.reset()
 
 
 def format_link(title, url, missing=False, mod=None):
     res = '<a class=\"wiki-link'
     if missing:
         res += ' missing'
+    url = urllib.quote(url)
     res += '\" data-wiki-url=\"' + url + '\"'
     if mod:
         res += ' data-wiki-mod=\"' + mod + '\"'
     res += '>' + title + '</a>'
     return res
 
+
 def format_include(url, args=None, mod=None):
     res = '<div class=\"wiki-include\" data-wiki-url=\"' + url + '\"'
     if mod:
--- a/tests/mock.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/tests/mock.py	Wed Mar 12 23:02:40 2014 -0700
@@ -1,194 +1,164 @@
-import re
+import os
 import os.path
 import types
 import codecs
 import logging
 import StringIO
-from wikked.page import Page
-from wikked.fs import PageInfo, PageNotFoundError
-from wikked.db import Database
-from wikked.indexer import WikiIndex
-from wikked.scm import SourceControl
-from wikked.utils import title_to_url
+from collections import deque
+from contextlib import closing
+from ConfigParser import SafeConfigParser
+from wikked.fs import FileSystem
+from wikked.db.base import Database
+from wikked.indexer.base import WikiIndex
+from wikked.scm.base import SourceControl
+from wikked.wiki import WikiParameters, passthrough_formatter
 
 
-class MockWikiParameters(object):
-    def __init__(self):
-        self.formatters = {
-            self._passthrough: ['txt', 'html']
-        }
-
-        self.config_text = ""
-        self.special_filenames = []
-        self.use_db = False
-
-        self.logger_factory = lambda: logging.getLogger('wikked.tests')
-        self.page_factory = lambda wiki, url: MockPage(wiki, url)
-        self.config_factory = lambda: StringIO.StringIO(self.config_text)
-        self.fs_factory = lambda cfg: MockFileSystem()
-        self.index_factory = lambda cfg: MockWikiIndex()
-        self.db_factory = lambda cfg: MockDatabase()
-        self.scm_factory = lambda cfg: MockSourceControl()
-
-    def getSpecialFilenames(self):
-        return self.special_filenames
-
-    def _passthrough(self, text):
-        return text
-
-
-class MockPage(Page):
-    def __init__(self, wiki, url):
-        Page.__init__(self, wiki, url)
+logger = logging.getLogger(__name__)
 
 
-class MockDatabase(Database):
-    def __init__(self, content=None, logger=None):
-        Database.__init__(self, logger)
-        self.content = content
-        self.conn = None
-        self._open_count = 0
+class MockWikiParameters(WikiParameters):
+    def __init__(self, root=None):
+        super(MockWikiParameters, self).__init__(root)
+        self.config_text = ""
+        self.mock_fs = None
+        self.mock_index = None
+        self.mock_db = None
+        self.mock_scm = None
 
-    def initDb(self):
-        pass
+    def fs_factory(self):
+        if self.mock_fs is False:
+            return super(MockWikiParameters, self).fs_factory()
+        return self.mock_fs or MockFileSystem(self.root, self.config)
 
-    def open(self):
-        self._open_count += 1
-        self.conn = 'MOCK_CONNECTION'
+    def index_factory(self):
+        if self.mock_index is False:
+            return super(MockWikiParameters, self).index_factory()
+        return self.mock_index or MockWikiIndex()
+
+    def db_factory(self):
+        if self.mock_db is False:
+            return super(MockWikiParameters, self).db_factory()
+        return self.mock_db or MockDatabase()
 
-    def close(self):
-        self._open_count -= 1
-        if self._open_count < 0:
-            raise Exception(
-                "The database was closed more times than it was open.")
-        elif self._open_count == 0:
-            self.conn = None
+    def scm_factory(self, for_init=False):
+        if self.mock_scm is False:
+            return super(MockWikiParameters, self).scm_factory(for_init)
+        return self.mock_scm or MockSourceControl()
 
-    def reset(self, pages):
-        pass
-
-    def update(self, pages):
-        pass
+    def getFormatters(self):
+        formatters = {
+            passthrough_formatter: ['txt', 'html']
+        }
+        return formatters
 
-    def getPageUrls(self, subdir=None):
-        return []
+    def getPageUpdater(self):
+        return lambda wiki, url: wiki.update(url, cache_ext_data=True)
 
-    def getPages(self, subdir=None):
-        return []
+    def _loadConfig(self):
+        default_config_path = os.path.join(
+            os.path.dirname(__file__), '..', 'wikked', 'resources', 'defaults.cfg')
 
-    def getPage(self, url):
-        return None
+        config = SafeConfigParser()
+        config.readfp(open(default_config_path))
+        config.set('wiki', 'root', '/fake/root')
+        if self.config_text:
+            with closing(StringIO.StringIO(self.config_text)) as conf:
+                config.readfp(conf)
 
-    def pageExists(self, url):
-        return False
-
-    def getLinksTo(self, url):
-        return []
+        return config
 
 
-class MockFileSystem():
-    def __init__(self, structure=None, logger=None):
+def mock_os_walk(root_dir, root_node):
+    queue = deque()
+    queue.appendleft((root_dir, root_node))
+    while len(queue) > 0:
+        cur_dir, cur_node = queue.pop()
+
+        dirnames = []
+        filenames = []
+        for name, child in cur_node.iteritems():
+            if isinstance(child, dict):
+                dirnames.append(name)
+            else:
+                filenames.append(name)
+        yield cur_dir, dirnames, filenames
+        for name in dirnames:
+            fullname = os.path.join(cur_dir, name)
+            queue.appendleft((fullname, cur_node[name]))
+
+
+class MockFileSystem(FileSystem):
+    def __init__(self, root, config, structure=None):
+        super(MockFileSystem, self).__init__(root, config)
         if not structure:
-            structure = []
-        self.structure = structure
-        self.logger = logger
-        self.excluded = []
+            self.structure = {}
+        else:
+            self.structure = MockFileSystem.flat_to_nested(structure)
 
     def getPageInfos(self, subdir=None):
-        node = self._getNode(subdir)
-        if node is None:
-            raise PageNotFoundError()
-        for n in self._getChildren(node):
-            yield self._getPageInfo(n)
+        def tmp_walk(path):
+            node = self._getNode(path)
+            return mock_os_walk(path, node)
 
-    def getPageInfo(self, path):
-        node = self._getNode(path)
-        if node is None:
-            raise PageNotFoundError()
-        return self._getPageInfo(node)
+        orig_walk = os.walk
+        os.walk = tmp_walk
+        try:
+            gen = super(MockFileSystem, self).getPageInfos(subdir)
+            return list(gen)
+        finally:
+            os.walk = orig_walk
 
-    def getPage(self, url):
-        path = self._getPath(url, True)
-        node = self._getNode(path)
-        if node is None:
-            raise PageNotFoundError()
-        return self._getPageInfo(node, True)
-
-    def setPage(self, path, content):
+    def setPage(self, url, content):
         raise NotImplementedError()
 
-    def pageExists(self, url):
-        try:
-            self._getPath(url, True)
-            return True
-        except PageNotFoundError:
-            return False
-
-    def getPhysicalNamespacePath(self, url):
-        raise NotImplementedError()
-
-    def _getPageInfo(self, node, with_content=False):
-        path_split = os.path.splitext(node['path'])
-        url = title_to_url(path_split[0])
-        info = PageInfo(url, node['path'])
-        if with_content:
-            info.content = node['content']
-        return info
+    def _getPageInfo(self, path):
+        pi = super(MockFileSystem, self)._getPageInfo(path)
+        node = self._getNode(path)
+        if node is not None:
+            pi._content = node
+        else:
+            raise Exception("Can't find node: %s" % path)
+        return pi
 
     def _getNode(self, path):
         node = self.structure
-        if path:
+        path = path.lstrip('/')
+        if path != '':
             for n in path.split('/'):
                 if n not in node:
                     return None
                 node = node[n]
-        else:
-            path = ''
-        if isinstance(node, types.StringTypes):
-            return {'type': 'file', 'path': path, 'content': node}
-        return {'type': 'dir', 'path': path, 'content': node}
+        return node
+
+    def _getPhysicalPath(self, url, is_file=True, make_new=False):
+        def tmp_walk(path):
+            node = self._getNode(path)
+            return mock_os_walk(path, node)
+
+        orig_walk = os.walk
+        os.walk = tmp_walk
+        try:
+            return super(MockFileSystem, self)._getPhysicalPath(url, is_file,
+                                                                make_new)
+        finally:
+            os.walk = orig_walk
 
-    def _getChildren(self, node):
-        if node['type'] != 'dir':
-            raise Exception("'%s' is not a directory." % node['path'])
-        for name in node['content']:
-            child_path = os.path.join(node['path'], name)
-            child = node['content'][name]
-            if isinstance(child, types.StringTypes):
-                yield {
-                    'type': 'file',
-                    'path': child_path,
-                    'content': child
-                    }
-            else:
-                for c in self._getChildren({
-                    'type': 'dir',
-                    'path': child_path,
-                    'content': child
-                    }):
-                    yield c
+    @staticmethod
+    def flat_to_nested(flat):
+        nested = {}
+        for k, v in flat.iteritems():
+            bits = k.lstrip('/').split('/')
+            cur = nested
+            for i, b in enumerate(bits):
+                if i < len(bits) - 1:
+                    if b not in cur:
+                        cur[b] = {}
+                    cur = cur[b]
+                else:
+                    cur[b] = v
+        return nested
 
-    def _getPath(self, url, is_file):
-        path = ''
-        current = self.structure
-        parts = unicode(url).lower().split('/')
-        for i, part in enumerate(parts):
-            for name in current:
-                name_slug = title_to_url(name)
-                if is_file and i == len(parts) - 1:
-                    if re.match(r"%s\.[a-z]+" % re.escape(part), name_slug):
-                        current = current[name]
-                        path = os.path.join(path, name)
-                        break
-                else:
-                    if name_slug == part:
-                        current = current[name]
-                        path = os.path.join(path, name)
-                        break
-            else:
-                # Failed to find a part of the URL.
-                raise PageNotFoundError("No such page: " + url)
-        return path
 
     @staticmethod
     def save_structure(path, structure):
@@ -203,18 +173,37 @@
                 MockFileSystem.save_structure(node_path, structure[node])
 
 
-class MockWikiIndex(WikiIndex):
-    def __init__(self, logger=None):
-        WikiIndex.__init__(self, logger)
+class MockDatabase(Database):
+    def __init__(self, content=None):
+        super(MockDatabase, self).__init__()
+        self.content = content
+
+    def getPageUrls(self, subdir=None, uncached_only=False):
+        return []
+
+    def getPages(self, subdir=None, meta_query=None, uncached_only=False,
+                 fields=None):
+        return []
+
+    def isCacheValid(self, page):
+        return False
 
-    def initIndex(self):
-        pass
+    def pageExists(self, url=None, path=None):
+        return False
+
+    def getLinksTo(self, url):
+        return []
 
-    def reset(self, pages):
-        pass
+    def _getPageByUrl(self, url, fields):
+        return None
 
-    def update(self, pages):
-        pass
+    def _getPageByPath(self, path, fields):
+        return None
+
+
+class MockWikiIndex(WikiIndex):
+    def __init__(self):
+        super(MockWikiIndex, self).__init__()
 
     def search(self, query):
         # url, title, content_highlights
@@ -222,11 +211,8 @@
 
 
 class MockSourceControl(SourceControl):
-    def __init__(self, logger=None):
-        SourceControl.__init__(self, logger)
-
-    def initRepo(self):
-        pass
+    def __init__(self):
+        super(MockSourceControl, self).__init__()
 
     def getSpecialFilenames(self):
         return []
@@ -242,9 +228,3 @@
 
     def diff(self, path, rev1, rev2):
         raise NotImplementedError()
-
-    def commit(self, paths, op_meta):
-        raise NotImplementedError()
-
-    def revert(self, paths=None):
-        raise NotImplementedError()
--- a/tests/test_db.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/tests/test_db.py	Wed Mar 12 23:02:40 2014 -0700
@@ -1,47 +1,17 @@
-import os.path
 from tests import WikkedTest
-from mock import MockFileSystem
-from wikked.fs import FileSystem
-from wikked.db import SQLiteDatabase
 
 
 class DatabaseTest(WikkedTest):
-    def tearDown(self):
-        if hasattr(self, 'wiki') and self.wiki:
-            self.wiki.db.close()
-        WikkedTest.tearDown(self)
-
     def testEmpty(self):
-        self.wiki = self._getWikiFromStructure({})
-        self.assertEqual([], list(self.wiki.getPageUrls()))
+        wiki = self._getWikiFromStructure({})
+        self.assertEqual([], list(wiki.getPageUrls()))
 
     def testOnePage(self):
-        self.wiki = self._getWikiFromStructure({
-            'foo.txt': 'A test page.'
+        wiki = self._getWikiFromStructure({
+            '/foo.txt': 'A test page.'
             })
-        self.assertEqual(['foo'], list(self.wiki.getPageUrls()))
-        page = self.wiki.getPage('foo')
-        self.assertEqual('foo', page.url)
-        self.assertEqual(os.path.join(self.root, 'foo.txt'), page.path)
+        self.assertEqual(['/foo'], list(wiki.getPageUrls()))
+        page = wiki.getPage('/foo')
+        self.assertEqual('/foo', page.url)
+        self.assertEqual('/foo.txt', page.path)
         self.assertEqual('A test page.', page.raw_text)
-
-    def _getWikiFromStructure(self, structure):
-        MockFileSystem.save_structure(self.root, structure)
-        wiki = self.getWiki(
-            db_factory=self._dbFactory,
-            fs_factory=self._fsFactory
-            )
-
-        # Open the DB before we do anything so that it will be closed
-        # only on `tearDown` (memory DBs are discarded when the
-        # connection is closed.
-        wiki.db.open()
-
-        wiki.start()
-        return wiki
-
-    def _fsFactory(self, config):
-        return FileSystem(self.root)
-
-    def _dbFactory(self, config):
-        return SQLiteDatabase(':memory:')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_mock.py	Wed Mar 12 23:02:40 2014 -0700
@@ -0,0 +1,14 @@
+import pytest
+from .mock import MockFileSystem
+
+
+@pytest.mark.parametrize('flat, expected', [
+    ({}, {}),
+    ({'/foo.txt': 'Bar'}, {'foo.txt': 'Bar'}),
+    ({'/tmp/foo.txt': 'Bar'}, {'tmp': {'foo.txt': 'Bar'}}),
+    (
+        {'/tmp/foo.txt': 'Bar', '/tmp/bar': 'Foo'},
+        {'tmp': {'foo.txt': 'Bar', 'bar': 'Foo'}})])
+def test_flat_to_nested(flat, expected):
+    actual = MockFileSystem.flat_to_nested(flat)
+    assert actual == expected
--- a/tests/test_page.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/tests/test_page.py	Wed Mar 12 23:02:40 2014 -0700
@@ -1,97 +1,110 @@
 from tests import WikkedTest, format_link
-from wikked.page import Page
 
 
 class PageTest(WikkedTest):
+    def _onWikiStarted(self, wiki):
+        wiki.reset()
+
+    def _getParameters(self, root=None):
+        params = WikkedTest._getParameters(self, root)
+        return params
+
     def testSimplePage(self):
-        self.wiki = self._getWikiFromStructure({
-            'foo.txt': 'A test page.'
+        wiki = self._getWikiFromStructure({
+            '/foo.txt': 'A test page.'
             })
-        page = Page(self.wiki, 'foo')
-        self.assertEqual('foo', page.url)
-        self.assertEqual('foo.txt', page.path)
+        page = wiki.getPage('/foo')
+        self.assertEqual('/foo', page.url)
+        self.assertEqual('/foo.txt', page.path)
         self.assertEqual('foo', page.filename)
         self.assertEqual('txt', page.extension)
         self.assertEqual('A test page.', page.raw_text)
-        self.assertEqual('A test page.', page._getFormattedText())
+        self.assertEqual('A test page.', page.getFormattedText())
         self.assertEqual('foo', page.title)
         self.assertEqual('A test page.', page.text)
-        self.assertEqual({}, page._getLocalMeta())
-        self.assertEqual([], page._getLocalLinks())
+        self.assertEqual({}, page.getLocalMeta())
+        self.assertEqual([], page.getLocalLinks())
 
     def testPageMeta(self):
-        self.wiki = self._getWikiFromStructure({
-            'foo.txt': "A page with simple meta.\n{{bar: baz}}\n{{is_test: }}"
+        wiki = self._getWikiFromStructure({
+            '/foo.txt': "A page with simple meta.\n{{bar: baz}}\n{{is_test: }}"
             })
-        page = Page(self.wiki, 'foo')
-        self.assertEqual('foo', page.url)
+        page = wiki.getPage('/foo')
+        self.assertEqual('/foo', page.url)
         self.assertEqual("A page with simple meta.\n{{bar: baz}}\n{{is_test: }}", page.raw_text)
-        self.assertEqual('A page with simple meta.\n\n', page._getFormattedText())
+        self.assertEqual('A page with simple meta.\n\n', page.getFormattedText())
         self.assertEqual('foo', page.title)
         self.assertEqual('A page with simple meta.\n', page.text)
-        self.assertEqual({'bar': ['baz'], 'is_test': True}, page._getLocalMeta())
-        self.assertEqual([], page._getLocalLinks())
+        self.assertEqual({'bar': ['baz'], 'is_test': [True]}, page.getLocalMeta())
+        self.assertEqual([], page.getLocalLinks())
 
     def testPageTitleMeta(self):
-        self.wiki = self._getWikiFromStructure({
-            'test_title.txt': "A page with a custom title.\n{{title: TEST-TITLE}}"
+        wiki = self._getWikiFromStructure({
+            '/test_title.txt': "A page with a custom title.\n{{title: TEST-TITLE}}"
             })
-        page = Page(self.wiki, 'test_title')
-        self.assertEqual('test_title', page.url)
+        page = wiki.getPage('/test_title')
+        self.assertEqual('/test_title', page.url)
         self.assertEqual("A page with a custom title.\n{{title: TEST-TITLE}}", page.raw_text)
-        self.assertEqual('A page with a custom title.\n', page._getFormattedText())
+        self.assertEqual('A page with a custom title.\n', page.getFormattedText())
         self.assertEqual('TEST-TITLE', page.title)
         self.assertEqual('A page with a custom title.', page.text)
-        self.assertEqual({'title': ['TEST-TITLE']}, page._getLocalMeta())
-        self.assertEqual([], page._getLocalLinks())
+        self.assertEqual({'title': ['TEST-TITLE']}, page.getLocalMeta())
+        self.assertEqual([], page.getLocalLinks())
 
     def testPageOutLinks(self):
-        self.wiki = self._getWikiFromStructure({
-            'test_links.txt': "Follow a link to the [[Sandbox]]. Or to [[this page|Other Sandbox]].",
-            'sandbox.txt': "This is just a placeholder."
+        wiki = self._getWikiFromStructure({
+            '/TestLinks.txt': "Follow a link to the [[Sandbox]]. Or to [[this page|Other Sandbox]].",
+            '/Sandbox.txt': "This is just a placeholder."
             })
-        self.assertTrue(self.wiki.pageExists('sandbox', from_db=False))
-        page = Page(self.wiki, 'test_links')
-        self.assertEqual('test_links', page.url)
+        self.assertTrue(wiki.pageExists('/Sandbox'))
+        page = wiki.getPage('/TestLinks')
+        self.assertEqual('/TestLinks', page.url)
         self.assertEqual("Follow a link to the [[Sandbox]]. Or to [[this page|Other Sandbox]].", page.raw_text)
         self.assertEqual(
                 "Follow a link to the %s. Or to %s." % (
-                    format_link('Sandbox', 'sandbox'),
-                    format_link('this page', 'other-sandbox', True)),
+                    format_link('Sandbox', '/Sandbox'),
+                    format_link('this page', '/Other Sandbox', missing=True)),
                 page.text)
-        self.assertEqual(set(['sandbox', 'other-sandbox']), set(page._getLocalLinks()))
+        self.assertEqual(set(['Sandbox', 'Other Sandbox']), set(page.getLocalLinks()))
+        self.assertEqual(set(['/Sandbox', '/Other Sandbox']), set(page.links))
 
     def testPageRelativeOutLinks(self):
-        self.wiki = self._getWikiFromStructure({
-            'first.txt': "Go to [[First Sibling]].",
-            'first-sibling.txt': "Go back to [[First]], or to [[sub_dir/Second]].",
-            'sub_dir': {
-                'second.txt': "Go back to [[../First]], or to [[Second Sibling]].",
-                'second-sibling.txt': "Go back to [[Second]]."
-                }
+        wiki = self._getWikiFromStructure({
+            '/First.txt': "Go to [[First Sibling]].",
+            '/First Sibling.txt': "Go back to [[First]], or to [[sub_dir/Second]].",
+            '/sub_dir/Second.txt': "Go back to [[../First]], or to [[Second Sibling]].",
+            '/sub_dir/Second Sibling.txt': "Go back to [[Second]]."
             })
-        first = Page(self.wiki, 'first')
-        self.assertEqual(['first-sibling'], first._getLocalLinks())
-        first2 = Page(self.wiki, 'first-sibling')
-        self.assertEqual(['first', 'sub_dir/second'], first2._getLocalLinks())
-        second = Page(self.wiki, 'sub_dir/second')
-        self.assertEqual(['first', 'sub_dir/second-sibling'], second._getLocalLinks())
-        second2 = Page(self.wiki, 'sub_dir/second-sibling')
-        self.assertEqual(['sub_dir/second'], second2._getLocalLinks())
+        print str(list(wiki.getPageUrls()))
+        first = wiki.getPage('/First')
+        self.assertEqual(['/First Sibling'], first.links)
+        first2 = wiki.getPage('/First Sibling')
+        self.assertEqual(['/First', '/sub_dir/Second'], first2.links)
+        second = wiki.getPage('/sub_dir/Second')
+        self.assertEqual(['/First', '/sub_dir/Second Sibling'], second.links)
+        second2 = wiki.getPage('/sub_dir/Second Sibling')
+        self.assertEqual(['/sub_dir/Second'], second2.links)
 
     def testGenericUrl(self):
-        self.wiki = self._getWikiFromStructure({
-            'foo.txt': "URL: [[url:/blah/boo/image.png]]"
+        wiki = self._getWikiFromStructure({
+            '/foo.txt': "URL: [[blah|url:/blah/boo/raw.txt]]"
             })
-        foo = Page(self.wiki, 'foo')
-        self.assertEqual("URL: /files/blah/boo/image.png", foo._getFormattedText())
+        foo = wiki.getPage('/foo')
+        self.assertEqual("URL: <a class=\"wiki-asset\" href=\"/files/blah/boo/raw.txt\">blah</a>", foo.getFormattedText())
+
+    def testImageUrl(self):
+        wiki = self._getWikiFromStructure({
+            '/foo.txt': "URL: [[blah|url:/blah/boo/image.png]]"
+            })
+        foo = wiki.getPage('/foo')
+        self.assertEqual("URL: <img src=\"/files/blah/boo/image.png\" alt=\"blah\"></img>", foo.getFormattedText())
 
     def testUrlTemplateFunctions(self):
-        self.wiki =self._getWikiFromStructure({
-            'foo.txt': "Here is {{read_url(__page.url, 'FOO')}}!"
+        wiki =self._getWikiFromStructure({
+            '/foo.txt': "Here is {{read_url(__page.url, 'FOO')}}!"
             })
-        foo = Page(self.wiki, 'foo')
+        foo = wiki.getPage('/foo')
         self.assertEqual(
-            'Here is <a class="wiki-link" data-wiki-url="foo">FOO</a>!',
+            'Here is <a class="wiki-link" data-wiki-url="/foo">FOO</a>!',
             foo.text
             )
--- a/tests/test_resolver.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/tests/test_resolver.py	Wed Mar 12 23:02:40 2014 -0700
@@ -1,87 +1,90 @@
 from tests import WikkedTest, format_link, format_include
-from wikked.page import Page
 
 
 class ResolverTest(WikkedTest):
     def testPageInclude(self):
-        self.wiki = self._getWikiFromStructure({
-            'Foo.txt': "A test page.\n{{include: trans-desc}}\n",
-            'Trans Desc.txt': "BLAH\n"
+        wiki = self._getWikiFromStructure({
+            '/foo.txt': "A test page.\n{{include: trans-desc}}\n",
+            '/trans-desc.txt': "BLAH\n"
             })
-        foo = Page(self.wiki, 'foo')
-        self.assertEqual({'include': ['trans-desc']}, foo._getLocalMeta())
+        foo = wiki.getPage('/foo')
+        self.assertEqual({'include': ['trans-desc']}, foo.getLocalMeta())
         self.assertEqual(
                 "A test page.\n%s" % format_include('trans-desc'),
-                foo._getFormattedText())
+                foo.getFormattedText())
         self.assertEqual("A test page.\nBLAH", foo.text)
 
     def testPageIncludeWithMeta(self):
-        self.wiki = self._getWikiFromStructure({
-            'Foo.txt': "A test page.\n{{include: trans-desc}}\n",
-            'Trans Desc.txt': "BLAH: [[Somewhere]]\n{{bar: 42}}\n{{__secret: love}}\n{{+given: hope}}"
+        wiki = self._getWikiFromStructure({
+            'foo.txt': "A test page.\n{{include: trans-desc}}\n",
+            'trans-desc.txt': "BLAH: [[Somewhere]]\n{{bar: 42}}\n{{__secret: love}}\n{{+given: hope}}"
             })
-        foo = Page(self.wiki, 'foo')
-        self.assertEqual([], foo._getLocalLinks())
-        self.assertEqual({'include': ['trans-desc']}, foo._getLocalMeta())
+        foo = wiki.getPage('/foo')
+        self.assertEqual([], foo.getLocalLinks())
+        self.assertEqual({'include': ['trans-desc']}, foo.getLocalMeta())
         self.assertEqual(
                 "A test page.\n%s" % format_include('trans-desc'),
-                foo._getFormattedText())
+                foo.getFormattedText())
         self.assertEqual(
-                "A test page.\nBLAH: %s\n\n" % format_link('Somewhere', 'somewhere', True),
+                "A test page.\nBLAH: %s\n\n" % format_link('Somewhere', '/Somewhere', True),
                 foo.text)
-        self.assertEqual(['somewhere'], foo.links)
-        self.assertEqual({'bar': ['42'], 'given': ['hope'], 'include': ['trans-desc']}, foo.meta)
+        self.assertEqual(['/Somewhere'], foo.links)
+        self.assertEqual({'bar': ['42'], 'given': ['hope'], 'include': ['trans-desc']}, foo.getMeta())
 
     def testPageIncludeWithNamedTemplating(self):
-        self.wiki = self._getWikiFromStructure({
-            'Foo.txt': "A test page.\n{{include: greeting|name=Dave|what=drink}}\n",
-            'Greeting.txt': "Hello {{name}}, would you like a {{what}}?"
+        wiki = self._getWikiFromStructure({
+            'foo.txt': "A test page.\n{{include: greeting|name=Dave|what=drink}}\n",
+            'greeting.txt': "Hello {{name}}, would you like a {{what}}?"
             })
-        foo = Page(self.wiki, 'foo')
+        foo = wiki.getPage('/foo')
         self.assertEqual(
-            "A test page.\n%s" % format_include('greeting', 'name=Dave|what=drink'),
-            foo._getFormattedText())
+            "A test page.\n%s" % format_include(
+                'greeting',
+                '<div class="wiki-param" data-name="name">Dave</div><div class="wiki-param" data-name="what">drink</div>'),
+            foo.getFormattedText())
         self.assertEqual("A test page.\nHello Dave, would you like a drink?", foo.text)
 
     def testPageIncludeWithNumberedTemplating(self):
-        self.wiki = self._getWikiFromStructure({
-            'Foo.txt': "A test page.\n{{include: greeting|Dave|Roger|Tom}}\n",
-            'Greeting.txt': "Hello {{__args[0]}}, {{__args[1]}} and {{__args[2]}}."
+        wiki = self._getWikiFromStructure({
+            'foo.txt': "A test page.\n{{include: greeting|Dave|Roger|Tom}}\n",
+            'greeting.txt': "Hello {{__args[0]}}, {{__args[1]}} and {{__args[2]}}."
             })
-        foo = Page(self.wiki, 'foo')
+        foo = wiki.getPage('/foo')
         self.assertEqual(
-            "A test page.\n%s" % format_include('greeting', 'Dave|Roger|Tom'),
-            foo._getFormattedText())
+            "A test page.\n%s" % format_include(
+                'greeting',
+                '<div class="wiki-param" data-name="">Dave</div><div class="wiki-param" data-name="">Roger</div><div class="wiki-param" data-name="">Tom</div>'),
+            foo.getFormattedText())
         self.assertEqual("A test page.\nHello Dave, Roger and Tom.", foo.text)
 
     def testIncludeWithPageReferenceTemplating(self):
-        self.wiki =self._getWikiFromStructure({
-            'SelfRef.txt': "Here is {{read_url(__page.url, __page.title)}}!",
-            'Foo.txt': "Hello here.\n{{include: selfref}}\n"
+        wiki =self._getWikiFromStructure({
+            'selfref.txt': "Here is {{read_url(__page.url, __page.title)}}!",
+            'foo.txt': "Hello here.\n{{include: selfref}}\n"
             })
-        foo = Page(self.wiki, 'foo')
+        foo = wiki.getPage('/foo')
         self.assertEqual(
-            'Hello here.\nHere is <a class="wiki-link" data-wiki-url="foo">Foo</a>!',
+            'Hello here.\nHere is <a class="wiki-link" data-wiki-url="/foo">foo</a>!',
             foo.text
             )
 
     def testGivenOnlyInclude(self):
-        self.wiki = self._getWikiFromStructure({
+        wiki = self._getWikiFromStructure({
             'Base.txt': "The base page.\n{{include: Template 1}}",
             'Template 1.txt': "TEMPLATE!\n{{+include: Template 2}}",
             'Template 2.txt': "MORE TEMPLATE!"
             })
-        tpl1 = Page(self.wiki, 'template-1')
+        tpl1 = wiki.getPage('/Template 1')
         self.assertEqual(
-                "TEMPLATE!\n%s" % format_include('template-2', mod='+'),
-                tpl1._getFormattedText())
+                "TEMPLATE!\n%s" % format_include('Template 2', mod='+'),
+                tpl1.getFormattedText())
         self.assertEqual("TEMPLATE!\n", tpl1.text)
-        base = Page(self.wiki, 'base')
+        base = wiki.getPage('/Base')
         self.assertEqual("The base page.\nTEMPLATE!\nMORE TEMPLATE!", base.text)
 
     def testDoublePageIncludeWithMeta(self):
         return
-        self.wiki = self._getWikiFromStructure({
+        wiki = self._getWikiFromStructure({
             'Base.txt': "The base page.\n{{include: Template 1}}",
             'Wrong.txt': "{{include: Template 2}}",
             'Template 1.txt': "{{foo: bar}}\n{{+category: blah}}\n{{+include: Template 2}}\n{{__secret1: ssh}}",
@@ -89,27 +92,27 @@
             'Query 1.txt': "{{query: category=yolo}}",
             'Query 2.txt': "{{query: category=blah}}"
             })
-        base = Page(self.wiki, 'base')
+        base = wiki.getPage('/Base')
         self.assertEqual({
             'foo': ['bar'],
             'category': ['blah', 'yolo']
-            }, base.meta)
-        tpl1 = Page(self.wiki, 'template-1')
+            }, base.getMeta())
+        tpl1 = wiki.getPage('/Template 1')
         self.assertEqual({
             'foo': ['bar'],
             '+category': ['blah'],
-            '+include': ['template-2'],
+            '+include': ['Template 1'],
             '__secret': ['ssh']
-            }, tpl1.meta)
+            }, tpl1.getMeta())
         self.assertEqual(
-                "\n\n%s\n\n" % format_include('template-2'),
+                "\n\n%s\n\n" % format_include('/Template 2'),
                 tpl1.text)
-        q1 = Page(self.wiki, 'query-1')
+        q1 = wiki.getPage('query-1')
         self.assertEqual(
-                "<ul>\n<li>%s</li>\n<li>%s</li>\n</ul>" % (format_link('Base', 'base'), format_link('Wrong', 'wrong')),
+                "<ul>\n<li>%s</li>\n<li>%s</li>\n</ul>" % (format_link('Base', '/Base'), format_link('Wrong', '/Wrong')),
                 q1.text)
-        q2 = Page(self.wiki, 'query-2')
+        q2 = wiki.getPage('query-2')
         self.assertEqual(
-                "<ul>\n<li>%s</li>\n</ul>" % format_link('Base', 'base'),
+                "<ul>\n<li>%s</li>\n</ul>" % format_link('Base', '/Base'),
                 q2.text)
 
--- a/wikked/db/base.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/db/base.py	Wed Mar 12 23:02:40 2014 -0700
@@ -16,14 +16,14 @@
     def postInit(self):
         pass
 
-    def close(self):
-        raise NotImplementedError()
+    def close(self, commit, exception):
+        pass
 
     def reset(self, page_infos, page_factory):
-        raise NotImplementedError()
+        pass
 
     def update(self, page_infos, page_factory, force=False):
-        raise NotImplementedError()
+        pass
 
     def getPageUrls(self, subdir=None, uncached_only=False):
         raise NotImplementedError()
@@ -48,7 +48,7 @@
         return page
 
     def cachePage(self, page):
-        raise NotImplementedError()
+        pass
 
     def isCacheValid(self, page):
         raise NotImplementedError()
--- a/wikked/db/sql.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/db/sql.py	Wed Mar 12 23:02:40 2014 -0700
@@ -152,7 +152,8 @@
         return self._session
 
     def _needsSchemaUpdate(self):
-        if self.engine_url == 'sqlite:///:memory:':
+        if (self.engine_url == 'sqlite://' or
+                self.engine_url == 'sqlite:///:memory:'):
             # Always create the schema for a memory database.
             return True
 
--- a/wikked/formatter.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/formatter.py	Wed Mar 12 23:02:40 2014 -0700
@@ -193,11 +193,17 @@
                 mod_attr, '|'.join(processed_args))
 
     def _formatUrlLink(self, ctx, endpoint, value, display):
+        img_exts = ['.jpg', '.jpeg', '.png', '.gif']
+        base, ext = os.path.splitext(value)
         if value.startswith('/'):
             abs_url = '/files' + value
         else:
             abs_url = os.path.join('/files', ctx.urldir, value)
             abs_url = os.path.normpath(abs_url).replace('\\', '/')
+
+        if ext in img_exts:
+            return '<img src="%s" alt="%s"></img>' % (abs_url, display)
+
         return '<a class="wiki-asset" href="%s">%s</a>' % (abs_url, display)
 
     def _formatMetaLink(self, ctx, endpoint, value, display):
--- a/wikked/indexer/base.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/indexer/base.py	Wed Mar 12 23:02:40 2014 -0700
@@ -12,7 +12,7 @@
         pass
 
     def start(self, wiki):
-        raise NotImplementedError()
+        pass
 
     def init(self, wiki):
         pass
@@ -21,10 +21,10 @@
         pass
 
     def reset(self, pages):
-        raise NotImplementedError()
+        pass
 
     def update(self, pages):
-        raise NotImplementedError()
+        pass
 
     def search(self, query):
         raise NotImplementedError()
--- a/wikked/page.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/page.py	Wed Mar 12 23:02:40 2014 -0700
@@ -66,7 +66,7 @@
     def filename(self):
         if self._data.path is None:
             raise Exception("The 'path' field was not loaded.")
-        basename = os.path.basename(self._data.filename)
+        basename = os.path.basename(self._data.path)
         return os.path.splitext(basename)[0]
 
     @property
--- a/wikked/scm/base.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/scm/base.py	Wed Mar 12 23:02:40 2014 -0700
@@ -41,10 +41,10 @@
         raise NotImplementedError()
 
     def commit(self, paths, op_meta):
-        raise NotImplementedError()
+        pass
 
     def revert(self, paths=None):
-        raise NotImplementedError()
+        pass
 
 
 class Author(object):
--- a/wikked/wiki.py	Mon Mar 10 16:47:21 2014 -0700
+++ b/wikked/wiki.py	Wed Mar 12 23:02:40 2014 -0700
@@ -41,7 +41,7 @@
     @property
     def config(self):
         if self._config is None:
-            self._loadConfig()
+            self._config = self._loadConfig()
         return self._config
 
     def fs_factory(self):
@@ -136,7 +136,7 @@
             else:
                 logger.debug("Setting up simple updater.")
                 self._page_updater = lambda wiki, url: wiki.update(
-                    url, cache_ext_data=False)
+                    url, cache_ext_data=True)
         return self._page_updater
 
     def tryAddFormatter(self, formatters, module_name, module_func,
@@ -160,7 +160,7 @@
         config.readfp(open(default_config_path))
         config.set('wiki', 'root', self.root)
         config.read([config_path, local_config_path])
-        self._config = config
+        return config
 
 
 class EndpointInfo(object):