"""Tests for micronian.webapp http://www.decafbad.com mailto:l.m.orchard@pobox.com Share and Enjoy """ # TODO: More testing / implementation of XPath-based access # TODO: Assert the need for content-type: text/xml in most methods # TODO: Test out page name and node ID sanitizing import sys, unittest, os.path from md5 import md5 from StringIO import StringIO from random import random from lxml import etree from wsgi.stub import WSGIStub from micronian.page import Page, NS from micronian.store.filesystem import FilesystemStore from micronian.webapp import WebApp class TestCase(unittest.TestCase): def __init__(self, name): unittest.TestCase.__init__(self, name) def setUp(self): # Do this in __init__ to exercise run_once=False? store_root = "data/test" self.store = FilesystemStore(store_root) self.app = WebApp(self.store) self.uri = "http://localhost/wiki" self.stub = WSGIStub(self.app) self.stub.SCRIPT_NAME = "/wiki" self.init_src = """

This is a test

Mauris libero. Suspendisse magna urna, nonummy suscipit, mattis quis, molestie non, erat. Duis eu risus. Vivamus nec pede. Cras sit amet dui. Ut nec arcu sit amet dui bibendum volutpat.

""" % NS['xhtml'] def assertHeaders(self, expect_status, expect_headers, body=None): """Assert a status, set of headers, and response body.""" stub, uri = self.stub, self.uri self.assertEqual(expect_status, stub.status, "Status %s expected, %s / %s found." % \ (expect_status, stub.status, stub.body) ) self.assertEqual(expect_headers, stub.response_headers, "Headers %s expected, %s found." % \ (expect_headers, stub.response_headers) ) def testCreateApp(self): """Try creating a stub with the web app.""" stub = WSGIStub(WebApp(self.store)) def testCreateNoName(self): """Try creating a page without a name.""" stub, uri = self.stub, self.uri # Fire off the PUT request stub.run(method="PUT", uri=uri) # Double check the response headers. expect_status = '405 Method Not Allowed' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}error' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '405', 'Expected status="405", found status="%s".' % \ out_root.get('status')) def testCreateNewBlank(self): """Try creating a new blank page.""" stub, uri = self.stub, self.uri # Fire off the PUT request page_name = 'TestPage%s' % (int(random()*100000)) stub.run( method="PUT", uri='%s/%s' % (uri, page_name) ) # Double check the response headers. expect_status = '201 Created' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Expect to see the new page in the store self.assert_(page_name in self.store.list(), "Expected to find new page in store list.") # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}created' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '201', 'Expected status="201", found status="%s".' % \ out_root.get('status')) def testCreateNewWithContent(self): """Try creating a new page with initial content.""" stub, uri = self.stub, self.uri # Fire off the PUT request page_name = 'TestPage%s' % (int(random()*100000)) stub.run( method="PUT", uri='%s/%s' % (uri, page_name), stdin=StringIO(self.init_src) ) # Double check the response status and headers. expect_status = '201 Created' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Expect to see the new page in the store self.assert_(page_name in self.store.list(), "Expected to find new page in store list.") # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}created' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '201', 'Expected status="201", found status="%s".' % \ out_root.get('status')) # Double check the stored page. stored = self.store.fetch(page_name) div = stored.doc.xpath('//xhtml:body/xhtml:div', NS)[0] self.assert_(div[0].tag == '{%s}h1' % NS['xhtml'], "First child of page's first div should be

") self.assert_(div[0].text == 'This is a test', "Text of div's first child should be 'This is a test'") self.assert_(div[1].tag == '{%s}p' % NS['xhtml'], "First child of page's first div should be

") def testCreateNewWithInvalidContent(self): """Try creating a new page with invalid content.""" stub, uri = self.stub, self.uri # Fire off the PUT request page_name = 'TestPage%s' % (int(random()*100000)) stub.run( method="PUT", uri='%s/%s' % (uri, page_name), stdin=StringIO(" broken, no?") ) # Double check the response status and headers. expect_status = '400 Bad Request' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}error' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '400', 'Expected status="400", found status="%s".' % \ out_root.get('status')) def testUpdate(self): """Try updating a page's content.""" stub, uri = self.stub, self.uri # Create an initial page. page_name = 'PageToBeUpdated' page = Page(page_name) page.replace("

This should go away.

", '/xhtml:div[1]') self.store.store(page_name, page) # Fire off the PUT request stub.run( method="PUT", uri='%s/%s' % (uri, page_name), stdin=StringIO(self.init_src) ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}updated' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '200', 'Expected status="200", found status="%s".' % \ out_root.get('status')) # Double check the stored page. stored = self.store.fetch(page_name) div = stored.doc.xpath('//xhtml:body/xhtml:div', NS)[0] self.assert_(div[0].tag != '{%s}h2' % NS['xhtml'], "First child of page's first div should not be

") self.assert_(div[0].text != 'This should go away.', "Text of first child didn't change.") def testUpdateSubnodes(self): stub, uri = self.stub, self.uri # Create an initial page. page_name = 'PageToBeSubUpdated' page = Page(page_name) page.append("""

This is the first paragraph.

This is the second paragraph.

This is the third paragraph.

This is the fourth paragraph.

""" % NS['xhtml']) self.store.store(page_name, page) # Update the third paragraph by ID shortcut third_para = \ page.doc.xpath('//xhtml:div[2]/xhtml:p[3]', NS)[0] third_para_id = third_para.get('id') src1 = "

New third paragraph goes here.

" stub.run( method="PUT", uri="%s/%s$%s" % (uri, page_name, third_para_id), stdin=StringIO(src1) ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}updated' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '200', 'Expected status="200", found status="%s".' % \ out_root.get('status')) # Update the second paragraph via path. src2 = "

New second paragraph goes here.

" stub.run( method="PUT", uri="%s/%s/xhtml:div[2]/xhtml:p[2]" % (uri, page_name), stdin=StringIO(src2) ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}updated' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '200', 'Expected status="200", found status="%s".' % \ out_root.get('status')) # Double check the stored page. stored = self.store.fetch(page_name) paras = stored.doc.xpath\ ('//xhtml:body/xhtml:div[2]/xhtml:p', NS) self.assertEqual(paras[1].text, "New second paragraph goes here.") self.assertEqual(paras[2].text, "New third paragraph goes here.") def testDeleteNoName(self): """Try creating a page without a name.""" stub, uri = self.stub, self.uri # Fire off the PUT request stub.run(method="DELETE", uri=uri) # Double check the response headers. expect_status = '405 Method Not Allowed' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}error' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '405', 'Expected status="405", found status="%s".' % \ out_root.get('status')) def testDelete(self): stub, uri = self.stub, self.uri # Create an initial page. page_name = 'PageToBeDeleted' page = Page(page_name) page.replace("

This should go away.

", '/xhtml:div[1]') self.store.store(page_name, page) # Fire off the DELETE request stub.run( method="DELETE", uri='%s/%s' % (uri, page_name), stdin=StringIO(self.init_src) ) # Double check the response status and headers. expect_status = '410 Gone' expect_headers = [ ('Content-Type', 'text/xml'), ] self.assertHeaders(expect_status, expect_headers) # Expect to see the new page missing from the store self.assert_(page_name not in self.store.list(), "Expected to find new page not in store list.") def testDeleteSubnode(self): stub, uri = self.stub, self.uri # Create an initial page. page_name = 'TestPage%s' % (int(random()*100000)) page = Page(page_name) page.append("""

This is the first paragraph.

This is the second paragraph.

This is the third paragraph.

This is the fourth paragraph.

""" % NS['xhtml']) self.store.store(page_name, page) # Update the third paragraph by ID shortcut third_para = \ page.doc.xpath('//xhtml:div[2]/xhtml:p[3]', NS)[0] third_para_id = third_para.get('id') # Fire off the GET request stub.run( method="DELETE", uri='%s/%s$%s' % (uri, page_name, third_para_id), ) # Double check the response status and headers. expect_status = '410 Gone' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Double check the third paragraph is missing page = self.store.fetch(page_name) def do_lookup(x): page.get("//*[@id='%s']" % third_para_id) self.assertRaises(KeyError, do_lookup, "Lookup on third paragraph key should throw KeyError.") # Check that there are only 3 of the 4 paras left. paras = page.doc.xpath('//xhtml:div[2]/xhtml:p', NS) self.assertEqual(len(paras), 3, 'Should have found 3 paras, found %s.' % len(paras)) def testGet(self): """Try getting a page from the store.""" stub, uri = self.stub, self.uri # Create an initial page. page_name = 'TestPage%s' % (int(random()*100000)) page = Page(page_name) page.append("""

This is the first paragraph.

This is the second paragraph.

This is the third paragraph.

This is the fourth paragraph.

""" % NS['xhtml']) self.store.store(page_name, page) # Fire off the GET request stub.run( method="GET", uri='%s/%s' % (uri, page_name), ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Double check the contents of the page fetched fetched_page = Page(page_name, stub.body) self.assertEqual(page.toString(), fetched_page.toString(), "Stored page and fetched page don't match.") def testGetSubnode(self): """Try getting a page subnode from the store.""" stub, uri = self.stub, self.uri # Create an initial page. page_name = 'TestPage%s' % (int(random()*100000)) page = Page(page_name) page.append("""

This is the first paragraph.

This is the second paragraph.

This is the third paragraph.

This is the fourth paragraph.

""" % NS['xhtml']) self.store.store(page_name, page) # Update the third paragraph by ID shortcut third_para = \ page.doc.xpath('//xhtml:div[2]/xhtml:p[3]', NS)[0] third_para_id = third_para.get('id') # Fire off the GET request stub.run( method="GET", uri='%s/%s$%s' % (uri, page_name, third_para_id), ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Double check the third paragraph tag and text fetched_ele = etree.parse(StringIO(stub.body)).getroot() self.assertEqual(third_para.tag, fetched_ele.tag, "Third para (%s) and fetched ele (%s) tag mismatch." % \ (third_para.tag, fetched_ele.tag) ) self.assertEqual(third_para.text, fetched_ele.text, "Third para (%s) and fetched ele (%s) text mismatch." % \ (third_para.text, fetched_ele.text) ) def testGetIndex(self): """Try adding some pages and look for them in the index.""" stub, uri = self.stub, self.uri # Create a few pages. page_names = [] for x in range(5): page_name = 'TestPage%s' % (int(random()*100000)) page = Page(page_name) self.store.store(page_name, page) page_names.append(page_name) # Fire off the GET request. stub.run(method="GET", uri=uri) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml') ] self.assertHeaders(expect_status, expect_headers) # Parse the page index sent in response doc = etree.parse(StringIO(stub.body)) page_lis = doc.xpath('//xhtml:li', NS) # The index should be an XHTML list. self.assert_(len(page_lis) > 0, "Should have found some list items.") # Match up all the found pages with the created pages. for page_li in page_lis: page_a = page_li[0] if page_a.get('href') in page_names: page_names.remove(page_a.get('href')) # All of the added pages should have been found self.assertEqual(len(page_names), 0, "Should've found all of the added pages in the index,"+\ " %s left." % (len(page_names))) def testInsertInvalidSubnode(self): stub, uri = self.stub, self.uri # Create an initial page. page_name = 'PageForInsertion' page = Page(page_name) self.store.store(page_name, page) # Fire off the POST request stub.run( method="POST", uri='%s/%s' % (uri, page_name), environ={ 'CONTENT_TYPE' : 'text/xml' }, stdin=StringIO("""<&&&broken><<

This is a new block.

""" % NS['xhtml']) ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}appended' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '200', 'Expected status="200", found status="%s".' % \ out_root.get('status')) # Double check the stored page. stored = self.store.fetch(page_name) div = stored.doc.xpath('//xhtml:body/xhtml:div[2]', NS)[0] self.assert_(div[0].tag == '{%s}p' % NS['xhtml'], "Should have found a second div paragraph.") self.assert_(div[0].text == 'This is a new block.', "Should have found correct text in paragraph.") def testInsertBeforeSubnode(self): stub, uri = self.stub, self.uri # Create an initial page. page_name = 'PageForInsertion' page = Page(page_name) page.append("""

This should come last.

""" % NS['xhtml']) self.store.store(page_name, page) # Find an ID for the first DIV on the page. second_div = \ page.doc.xpath('//xhtml:div[2]', NS)[0] second_div_id = second_div.get('id') # Fire off the POST request stub.run( method="POST", uri='%s/%s$%s' % (uri, page_name, second_div_id), environ={ 'CONTENT_TYPE' : 'text/xml' }, stdin=StringIO("""

This should be the second DIV.

""" % NS['xhtml']) ) # Double check the response status and headers. expect_status = '200 OK' expect_headers = [ ('Content-Type', 'text/xml'), # TODO: Location should include the new element's ID ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}inserted' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '200', 'Expected status="200", found status="%s".' % \ out_root.get('status')) # Double check the stored page. stored = self.store.fetch(page_name) div1 = stored.doc.xpath('//xhtml:body/xhtml:div[2]', NS)[0] self.assert_(div1[0].tag == '{%s}p' % NS['xhtml'], "Should have found a second div paragraph.") self.assert_(div1[0].text == 'This should be the second DIV.', "Should have found correct text in paragraph.") div2 = stored.doc.xpath('//xhtml:body/xhtml:div[3]', NS)[0] self.assert_(div2[0].tag == '{%s}p' % NS['xhtml'], "Should have found a second div paragraph.") self.assert_(div2[0].text == 'This should come last.', "Should have found correct text in paragraph.") def testSafariHTTPMethodHack(self): """Try a PUT request proxied as a PUT request.""" # TODO: Try out some other non-POST requests. stub, uri = self.stub, self.uri # Fire off the PUT request page_name = 'TestPage%s' % (int(random()*100000)) stub.run( method="POST", uri='%s/%s?method=PUT' % (uri, page_name) ) # Double check the response headers. expect_status = '201 Created' expect_headers = [ ('Content-Type', 'text/xml'), ('Location', '/wiki/%s' % page_name) ] self.assertHeaders(expect_status, expect_headers) # Expect to see the new page in the store self.assert_(page_name in self.store.list(), "Expected to find new page in store list.") # Double check the XML in the response body. out_root = etree.parse(StringIO(stub.body)).getroot() expect_root = '{%s}created' % NS['micronian'] self.assertEqual(out_root.tag, expect_root, 'Expected %s, found %s' % (expect_root, out_root.tag)) self.assertEqual(out_root.get('status'), '201', 'Expected status="201", found status="%s".' % \ out_root.get('status')) if __name__ == '__main__': unittest.main()