From 5cc629b7ae20d94068691884a13b0eb41ffb881d 2013-10-17 21:09:10
From: Zachary Sailer <zachsailer@gmail.com>
Date: 2013-10-17 21:09:10
Subject: [PATCH] Added notebooks API tests.

---

diff --git a/IPython/html/services/contents/tests/test_contentmanger.py b/IPython/html/services/contents/tests/test_contentmanger.py
index 3174893..1550cbd 100644
--- a/IPython/html/services/contents/tests/test_contentmanger.py
+++ b/IPython/html/services/contents/tests/test_contentmanger.py
@@ -109,7 +109,9 @@ class TestContentManager(TestCase):
             # and subdirectory.
             contents = cm.list_contents('/')
             contents1 = cm.list_contents('fold1')
-            self.assertEqual(type(contents), type(list()))
-            self.assertEqual(type(contents[0]), type(dict()))
+            assert isinstance(contents, list)
+            assert isinstance(contents[0], dict)
+            assert contents[0].has_key('path')
+            assert contents[0].has_key('path')
             self.assertEqual(contents[0]['path'], '/')
             self.assertEqual(contents1[0]['path'], 'fold1')
diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
index c0f0d19..d6b9e6c 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -94,17 +94,16 @@ class FileNotebookManager(NotebookManager):
     def change_notebook(self, data, notebook_name, notebook_path='/'):
         """Changes notebook"""
         changes = data.keys()
-        response = 200
         for change in changes:
             full_path = self.get_os_path(notebook_name, notebook_path)
             if change == "name":
                 new_path = self.get_os_path(data['name'], notebook_path)
-                if not os.path.isfile(new_path):
+                try:
                     os.rename(full_path,
                         self.get_os_path(data['name'], notebook_path))
                     notebook_name = data['name']
-                else:
-                    response = 409
+                except OSError as e:
+                    raise web.HTTPError(409, u'Notebook name already exists.')
             if change == "path":
                 new_path = self.get_os_path(data['name'], data['path'])
                 stutil.move(full_path, new_path)
@@ -112,7 +111,7 @@ class FileNotebookManager(NotebookManager):
             if change == "content":
                 self.save_notebook(data, notebook_name, notebook_path)
         model = self.notebook_model(notebook_name, notebook_path)
-        return model, response
+        return model
 
     def notebook_exists(self, name, path):
         """Returns a True if the notebook exists. Else, returns False.
@@ -131,31 +130,6 @@ class FileNotebookManager(NotebookManager):
         path = self.get_os_path(name, path)
         return os.path.isfile(path)
 
-    def get_os_path(self, fname, path='/'):
-        """Given a notebook name and a server URL path, return its file system
-        path.
-        
-        Parameters
-        ----------
-        fname : string
-            The name of a notebook file with the .ipynb extension
-        path : string
-            The relative URL path (with '/' as separator) to the named
-            notebook.
-            
-        Returns
-        -------
-        path : string
-            A file system path that combines notebook_dir (location where
-            server started), the relative path, and the filename with the
-            current operating system's url.
-        """
-        parts = path.split('/')
-        parts = [p for p in parts if p != ''] # remove duplicate splits
-        parts += [fname]
-        path = os.path.join(self.notebook_dir, *parts)
-        return path
-
     def read_notebook_object_from_path(self, path):
         """read a notebook object from a path"""
         info = os.stat(path)
diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py
index 29fc4c2..2c4f86d 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/notebooks/handlers.py
@@ -36,7 +36,7 @@ class NotebookRootHandler(IPythonHandler):
         """get returns a list of notebooks from the location
         where the server was started."""
         nbm = self.notebook_manager
-        notebooks = nbm.list_notebooks("")
+        notebooks = nbm.list_notebooks("/")
         self.finish(jsonapi.dumps(notebooks))
 
     @web.authenticated
@@ -53,18 +53,9 @@ class NotebookRootHandler(IPythonHandler):
             fname = nbm.new_notebook(notebook_path='/')
         self.set_header('Location', nbm.notebook_dir + fname)
         model = nbm.notebook_model(fname)
-        self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, notebook_name))
+        self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, fname))
         self.finish(jsonapi.dumps(model))
 
-
-class NotebookRootRedirect(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        """get redirects to not include trailing backslash"""
-        self.redirect("/api/notebooks")
-
-
 class NotebookHandler(IPythonHandler):
 
     SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST','DELETE')
@@ -86,7 +77,7 @@ class NotebookHandler(IPythonHandler):
             # get and return notebook representation
             format = self.get_argument('format', default='json')
             download = self.get_argument('download', default='False')
-            model = nbm.notebook_model(name,path)
+            model = nbm.notebook_model(name, path)
             last_mod, representation, name = nbm.get_notebook(name, path, format)
             self.set_header('Last-Modified', last_mod)
             
@@ -109,8 +100,7 @@ class NotebookHandler(IPythonHandler):
         nbm = self.notebook_manager
         name, path = nbm.named_notebook_path(notebook_path)
         data = jsonapi.loads(self.request.body)
-        model, response = nbm.change_notebook(data, name, path)
-        self.set_status(response)
+        model = nbm.change_notebook(data, name, path)
         self.finish(jsonapi.dumps(model))
 
     @web.authenticated
@@ -217,7 +207,7 @@ default_handlers = [
         ModifyNotebookCheckpointsHandler),
     (r"api/notebooks/%s/" % _notebook_path_regex, NotebookHandler), 
     (r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
-    (r"api/notebooks/", NotebookRootRedirect),
+    (r"api/notebooks/", NotebookRootHandler),
     (r"api/notebooks", NotebookRootHandler),
 ]
 
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py
index be33166..d610aad 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/notebooks/nbmanager.py
@@ -71,6 +71,32 @@ class NotebookManager(LoggingConfigurable):
             name = None
             path = "/".join(names) + '/'
         return name, path
+        
+    def get_os_path(self, fname=None, path='/'):
+        """Given a notebook name and a server URL path, return its file system
+        path.
+
+        Parameters
+        ----------
+        fname : string
+            The name of a notebook file with the .ipynb extension
+        path : string
+            The relative URL path (with '/' as separator) to the named
+            notebook.
+
+        Returns
+        -------
+        path : string
+            A file system path that combines notebook_dir (location where
+            server started), the relative path, and the filename with the
+            current operating system's url.
+        """
+        parts = path.split('/')
+        parts = [p for p in parts if p != ''] # remove duplicate splits
+        if fname is not None:
+            parts += [fname]
+        path = os.path.join(self.notebook_dir, *parts)
+        return path
 
     def url_encode(self, path):
         """Returns the path with all special characters URL encoded"""
@@ -135,7 +161,7 @@ class NotebookManager(LoggingConfigurable):
         model = {"name": notebook_name, 
                     "path": notebook_path,
                     "last_modified (UTC)": last_modified.ctime()}
-        if content == True:
+        if content is True:
             model['content'] = contents
         return model
 
@@ -196,7 +222,7 @@ class NotebookManager(LoggingConfigurable):
             nb.metadata.name = name
         self.write_notebook_object(nb, name, notebook_path, new_name)
 
-    def write_notebook_object(self, nb, notebook_name=None, notebook_path='/', new_name=None):
+    def write_notebook_object(self, nb, notebook_name='/', notebook_path='/', new_name=None):
         """Write a notebook object and return its notebook_name.
 
         If notebook_name is None, this method should create a new notebook_name.
diff --git a/IPython/html/services/sessions/tests/test_api.py b/IPython/html/services/sessions/tests/test_api.py
new file mode 100644
index 0000000..5ccaaa7
--- /dev/null
+++ b/IPython/html/services/sessions/tests/test_api.py
@@ -0,0 +1,32 @@
+"""Test the sessions service API."""
+
+
+import os
+import sys
+import json
+import urllib
+
+import requests
+
+from IPython.html.tests.launchnotebook import NotebookTestBase
+
+'''
+class SessionAPITest(NotebookTestBase):
+    """Test the sessions web service API"""
+
+    def base_url(self):
+        return super(SessionAPITest,self).base_url() + 'api/sessions'
+
+    def test_no_sessions(self):
+        """Make sure there are no sessions running at the start"""
+        url = self.base_url()
+        r = requests.get(url)
+        self.assertEqual(r.json(), [])
+
+    def test_start_session(self):
+        url = self.base_url()
+        param = urllib.urlencode({'notebook_path': 'test.ipynb'})
+        r = requests.post(url, params=param)
+        print r
+        #self.assertNotEqual(r.json(), [])
+'''
\ No newline at end of file
diff --git a/IPython/html/services/tests/__init__.py b/IPython/html/services/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/IPython/html/services/tests/__init__.py
diff --git a/IPython/html/services/tests/test_services_api.py b/IPython/html/services/tests/test_services_api.py
new file mode 100644
index 0000000..ddc1e40
--- /dev/null
+++ b/IPython/html/services/tests/test_services_api.py
@@ -0,0 +1,124 @@
+"""Test the all of the services API."""
+
+
+import os
+import sys
+import json
+import urllib
+from zmq.utils import jsonapi
+
+import requests
+
+from IPython.html.tests.launchnotebook import NotebookTestBase
+
+class APITest(NotebookTestBase):
+    """Test the kernels web service API"""
+
+    def base_url(self):
+        return super(APITest,self).base_url()
+
+    def notebooks_url(self):
+        return self.base_url() + 'api/notebooks'
+
+    def kernels_url(self):
+        return self.base_url() + 'api/kernels'
+
+    def sessions_url(self):
+        return self.base_url() + 'api/sessions'
+
+    def contents_url(self):
+        return self.contents_url() + 'api/contents'
+
+    def mknb(self, name='', path='/'):
+        url = self.notebooks_url() + path
+        return url, requests.post(url)
+
+    def delnb(self, name, path='/'):
+        url = self.notebooks_url() + path + name
+        r = requests.delete(url)
+        return r.status_code
+
+    def test_no_notebooks(self):
+        url = self.notebooks_url()
+        r = requests.get(url)
+        self.assertEqual(r.json(), [])
+
+    def test_root_notebook_handler(self):
+        # POST a notebook and test the dict thats returned.
+        url, nb = self.mknb()
+        data = nb.json()
+        assert isinstance(data, dict)
+        assert data.has_key("name")
+        assert data.has_key("path")
+        self.assertEqual(data['name'], u'Untitled0.ipynb')
+        self.assertEqual(data['path'], u'/')
+
+        # GET list of notebooks in directory.
+        r = requests.get(url)
+        assert isinstance(r.json(), list)
+        assert isinstance(r.json()[0], dict)
+
+        # GET with a notebook name.
+        url = self.notebooks_url() + '/Untitled0.ipynb'
+        r = requests.get(url)
+        assert isinstance(data, dict)
+        self.assertEqual(r.json(), data)
+
+        # PATCH (rename) request.
+        new_name = {'name':'test.ipynb'}
+        r = requests.patch(url, data=jsonapi.dumps(new_name))
+        data = r.json()
+        assert isinstance(data, dict)
+
+        # make sure the patch worked.
+        new_url = self.notebooks_url() + '/test.ipynb'
+        r = requests.get(new_url)
+        assert isinstance(r.json(), dict)
+        self.assertEqual(r.json(), data)
+
+        # GET bad (old) notebook name.
+        r = requests.get(url)
+        self.assertEqual(r.status_code, 404)
+
+        # POST notebooks to folders one and two levels down.
+        os.makedirs(os.path.join(self.notebook_dir.name, 'foo'))
+        os.makedirs(os.path.join(self.notebook_dir.name, 'foo','bar'))
+        url, nb = self.mknb(path='/foo/')
+        url2, nb2 = self.mknb(path='/foo/bar/')
+        data = nb.json()
+        data2 = nb2.json()
+        assert isinstance(data, dict)
+        assert isinstance(data2, dict)
+        assert data.has_key("name")
+        assert data.has_key("path")
+        self.assertEqual(data['name'], u'Untitled0.ipynb')
+        self.assertEqual(data['path'], u'/foo/')
+        assert data2.has_key("name")
+        assert data2.has_key("path")
+        self.assertEqual(data2['name'], u'Untitled0.ipynb')
+        self.assertEqual(data2['path'], u'/foo/bar/')
+
+        # GET request on notebooks one and two levels down.
+        r = requests.get(url+'Untitled0.ipynb')
+        r2 = requests.get(url2+'Untitled0.ipynb')
+        assert isinstance(r.json(), dict)
+        self.assertEqual(r.json(), data)
+        assert isinstance(r2.json(), dict)
+        self.assertEqual(r2.json(), data2)
+
+        # PATCH notebooks that are one and two levels down.
+        new_name = {'name': 'testfoo.ipynb'}
+        r = requests.patch(url+'Untitled0.ipynb', data=jsonapi.dumps(new_name))
+        r = requests.get(url+'testfoo.ipynb')
+        data = r.json()
+        assert isinstance(data, dict)
+        assert data.has_key('name')
+        self.assertEqual(data['name'], 'testfoo.ipynb')
+        r = requests.get(url+'Untitled0.ipynb')
+        self.assertEqual(r.status_code, 404)
+        
+        # DELETE notebooks
+        r = self.delnb('testfoo.ipynb', '/foo/')
+        r2 = self.delnb('Untitled0.ipynb', '/foo/bar/') 
+        self.assertEqual(r, 204)
+        self.assertEqual(r2, 204)
\ No newline at end of file
diff --git a/IPython/html/tests/launchnotebook.py b/IPython/html/tests/launchnotebook.py
index a83cbc3..d64929c 100644
--- a/IPython/html/tests/launchnotebook.py
+++ b/IPython/html/tests/launchnotebook.py
@@ -28,6 +28,7 @@ class NotebookTestBase(TestCase):
             '--ipython-dir=%s' % self.ipython_dir.name,
             '--notebook-dir=%s' % self.notebook_dir.name
         ]
+        #self.notebook = Popen(notebook_args)
         self.notebook = Popen(notebook_args, stdout=PIPE, stderr=PIPE)
         time.sleep(3.0)