diff --git a/CHANGES.md b/CHANGES.md index 7d8abaabb..ac5fe2e48 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,11 @@ Changelog - TBD +0.8.1 (2014/09/04) +------------------ + +- Fixed a bug requesting new item numbers from the server. + 0.8 (2014/08/28) ---------------- diff --git a/doorstop/cli/test/__init__.py b/doorstop/cli/test/__init__.py index e5a8b97f3..acd73a3f3 100644 --- a/doorstop/cli/test/__init__.py +++ b/doorstop/cli/test/__init__.py @@ -33,7 +33,9 @@ def setUp(self): settings.CACHE_ITEMS, settings.CACHE_PATHS, settings.WARN_ALL, - settings.ERROR_ALL) + settings.ERROR_ALL, + settings.SERVER_HOST, + settings.SERVER_PORT) def tearDown(self): (settings.REFORMAT, @@ -49,4 +51,6 @@ def tearDown(self): settings.CACHE_ITEMS, settings.CACHE_PATHS, settings.WARN_ALL, - settings.ERROR_ALL) = self.backup + settings.ERROR_ALL, + settings.SERVER_HOST, + settings.SERVER_PORT) = self.backup diff --git a/doorstop/server/main.py b/doorstop/server/main.py index 5069c1e53..7a71dfb06 100644 --- a/doorstop/server/main.py +++ b/doorstop/server/main.py @@ -18,6 +18,7 @@ log = common.logger(__name__) +app = utilities.StripPathMiddleware(bottle.app()) tree = None # set in `run`, read in the route functions numbers = defaultdict(int) # cache of next document numbers @@ -68,7 +69,8 @@ def run(args, cwd, _): if args.launch: url = utilities.build_url(host=host, port=port) webbrowser.open(url) - bottle.run(host=host, port=port, debug=args.debug, reloader=args.debug) + bottle.run(app=app, host=host, port=port, + debug=args.debug, reloader=args.debug) @get('/') @@ -159,7 +161,7 @@ def get_attr(prefix, uid, name): def post_numbers(prefix): """Create the next number in a document.""" document = tree.find_document(prefix) - number = max(document.next, numbers[prefix]) + number = max(document.next_number, numbers[prefix]) numbers[prefix] = number + 1 if utilities.json_response(request): data = {'next': number} diff --git a/doorstop/server/test/__init__.py b/doorstop/server/test/__init__.py index dfa98c2f1..8cce51fdc 100644 --- a/doorstop/server/test/__init__.py +++ b/doorstop/server/test/__init__.py @@ -1,4 +1,4 @@ -"""Package for the doorstop.web tests.""" +"""Package for the doorstop.server tests.""" ENV = 'TEST_INTEGRATION' # environment variable to enable integration tests REASON = "'{0}' variable not set".format(ENV) diff --git a/doorstop/server/test/test_all.py b/doorstop/server/test/test_all.py index 1195aea0b..a59dad218 100644 --- a/doorstop/server/test/test_all.py +++ b/doorstop/server/test/test_all.py @@ -1 +1,46 @@ -"""Integration tests for the doorstop.web package.""" +"""Integration tests for the doorstop.server package.""" + +import os +import time +import unittest +from unittest.mock import patch +from multiprocessing import Process +import logging + +from doorstop import server +from doorstop.server import main + +from doorstop.server.test import ENV, REASON + + +@unittest.skipUnless(os.getenv(ENV), REASON) +@patch('doorstop.settings.SERVER_HOST', 'localhost') +class TestServer(unittest.TestCase): + + """Integration tests for the client/server feature.""" + + @classmethod + def setUpClass(cls): + cls.process = Process(target=main.main, kwargs={'args': []}) + cls.process.start() + logging.info("delaying for the server to initialize...") + time.sleep(3) + assert cls.process.is_alive() + + @classmethod + def tearDownClass(cls): + cls.process.terminate() + logging.info("delaying for the server to shutdown...") + time.sleep(1) + + def test_check(self): # pylint: disable=R0201 + """Verify the server can be checked.""" + server.check() + + def test_get_next_number(self): + """Verify the next number can be requested from the server.""" + number1 = server.get_next_number('req') + number2 = server.get_next_number('req') + self.assertIsNot(None, number1) + self.assertIsNot(None, number2) + self.assertLess(number1, number2) diff --git a/doorstop/server/test/test_client.py b/doorstop/server/test/test_client.py index 5d1ac6b5a..8c6c3cfe5 100644 --- a/doorstop/server/test/test_client.py +++ b/doorstop/server/test/test_client.py @@ -1,4 +1,4 @@ -"""Unit tests for the doorstop.web.client package.""" +"""Unit tests for the doorstop.server.client module.""" import unittest from unittest.mock import patch, Mock @@ -12,7 +12,7 @@ @patch('doorstop.settings.SERVER_PORT', 8080) class TestModule(unittest.TestCase): - """Unit tests for the doorstop.web.client module.""" + """Unit tests for the doorstop.server.client module.""" @patch('doorstop.settings.SERVER_HOST', '1.2.3.4') def test_exists(self): diff --git a/doorstop/server/test/test_server.py b/doorstop/server/test/test_server.py index 24e2bfa9a..0db901d6d 100644 --- a/doorstop/server/test/test_server.py +++ b/doorstop/server/test/test_server.py @@ -1,4 +1,4 @@ -"""Unit tests for the doorstop.web.server package.""" +"""Unit tests for the doorstop.server.main module.""" import unittest from unittest.mock import patch, Mock, MagicMock @@ -22,7 +22,7 @@ class BaseTestCase(unittest.TestCase): mock_document = MagicMock() mock_document.__iter__.return_value = [mock_item, mock_item2] mock_document.prefix = 'PREFIX' - mock_document.next = 42 + mock_document.next_number = 42 mock_document.find_item = Mock(return_value=mock_item) mock_document2 = Mock() @@ -40,7 +40,7 @@ def setUp(self): class TestModule(BaseTestCase): - """Unit tests for the doorstop.web.server module.""" + """Unit tests for the doorstop.server.main module.""" @patch('doorstop.server.main.build') @patch('bottle.run') @@ -64,7 +64,7 @@ def test_main_debug(self, mock_run, mock_open, mock_build): class TestRoutesHTML(BaseTestCase): - """Unit tests for the doorstop.web.server module HTML responses.""" + """Unit tests for the doorstop.server.main module HTML responses.""" def test_get_index(self): """Verify `/` works (HTML).""" @@ -122,7 +122,7 @@ def test_post_numbers(self): @patch('doorstop.server.utilities.json_response', Mock(return_value=True)) class TestRoutesJSON(BaseTestCase): - """Unit tests for the doorstop.web.server module JSON responses.""" + """Unit tests for the doorstop.server.main module JSON responses.""" def test_get_documents(self): """Verify `/documents` works (JSON).""" diff --git a/doorstop/server/utilities.py b/doorstop/server/utilities.py index e0c2a6c4a..e6d8e065c 100644 --- a/doorstop/server/utilities.py +++ b/doorstop/server/utilities.py @@ -1,12 +1,28 @@ -"""Shared functions for the `doorstop.web` package.""" +"""Shared functions for the `doorstop.server` package.""" +from doorstop import common from doorstop import settings +log = common.logger(__name__) + + +class StripPathMiddleware(object): # pylint: disable=R0903 + + """WSGI middleware that strips trailing slashes from all URLs.""" + + def __init__(self, app): + self.app = app + + def __call__(self, e, h): # pragma: no cover (integration test) + e['PATH_INFO'] = e['PATH_INFO'].rstrip('/') + return self.app(e, h) + def build_url(host=None, port=None, path=None): """Build the server's URL with optional path.""" host = host or settings.SERVER_HOST port = port or settings.SERVER_PORT + log.debug("building URL: {} + {} + {}".format(host, port, path)) if not host: return None url = 'http://{}'.format(host)