##// END OF EJS Templates
Add test for copying notebook through REST API
Thomas Kluyver -
Show More
@@ -1,193 +1,200 b''
1 1 """Test the notebooks webservice API."""
2 2
3 3 import io
4 4 import os
5 5 import shutil
6 6 from zmq.utils import jsonapi
7 7
8 8 pjoin = os.path.join
9 9
10 10 import requests
11 11
12 12 from IPython.html.utils import url_path_join
13 13 from IPython.html.tests.launchnotebook import NotebookTestBase
14 14 from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
15 15 new_heading_cell, to_notebook_json)
16 16 from IPython.utils.data import uniq_stable
17 17
18 18 class NBAPI(object):
19 19 """Wrapper for notebook API calls."""
20 20 def __init__(self, base_url):
21 21 self.base_url = base_url
22 22
23 23 @property
24 24 def nb_url(self):
25 25 return url_path_join(self.base_url, 'api/notebooks')
26 26
27 27 def _req(self, verb, path, body=None):
28 28 response = requests.request(verb,
29 29 url_path_join(self.base_url, 'api/notebooks', path), data=body)
30 30 response.raise_for_status()
31 31 return response
32 32
33 33 def list(self, path='/'):
34 34 return self._req('GET', path)
35 35
36 36 def read(self, name, path='/'):
37 37 return self._req('GET', url_path_join(path, name))
38 38
39 39 def create_untitled(self, path='/'):
40 40 return self._req('POST', path)
41 41
42 42 def upload(self, name, body, path='/'):
43 43 return self._req('POST', url_path_join(path, name), body)
44 44
45 def copy(self, name, path='/'):
46 return self._req('POST', url_path_join(path, name, 'copy'))
47
45 48 def save(self, name, body, path='/'):
46 49 return self._req('PUT', url_path_join(path, name), body)
47 50
48 51 def delete(self, name, path='/'):
49 52 return self._req('DELETE', url_path_join(path, name))
50 53
51 54 def rename(self, name, path, new_name):
52 55 body = jsonapi.dumps({'name': new_name})
53 56 return self._req('PATCH', url_path_join(path, name), body)
54 57
55 58 class APITest(NotebookTestBase):
56 59 """Test the kernels web service API"""
57 60 dirs_nbs = [('', 'inroot'),
58 61 ('Directory with spaces in', 'inspace'),
59 62 (u'unicodΓ©', 'innonascii'),
60 63 ('foo', 'a'),
61 64 ('foo', 'b'),
62 65 ('foo', 'name with spaces'),
63 66 ('foo', u'unicodΓ©'),
64 67 ('foo/bar', 'baz'),
65 68 ]
66 69
67 70 dirs = uniq_stable([d for (d,n) in dirs_nbs])
68 71 del dirs[0] # remove ''
69 72
70 73 def setUp(self):
71 74 nbdir = self.notebook_dir.name
72 75
73 76 for d in self.dirs:
74 77 os.mkdir(pjoin(nbdir, d))
75 78
76 79 for d, name in self.dirs_nbs:
77 80 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w') as f:
78 81 nb = new_notebook(name=name)
79 82 write(nb, f, format='ipynb')
80 83
81 84 self.nb_api = NBAPI(self.base_url())
82 85
83 86 def tearDown(self):
84 87 nbdir = self.notebook_dir.name
85 88
86 89 for dname in ['foo', 'Directory with spaces in', u'unicodΓ©']:
87 90 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
88 91
89 92 if os.path.isfile(pjoin(nbdir, 'inroot.ipynb')):
90 93 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
91 94
92 95 def test_list_notebooks(self):
93 96 nbs = self.nb_api.list().json()
94 97 self.assertEqual(len(nbs), 1)
95 98 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
96 99
97 100 nbs = self.nb_api.list('/Directory with spaces in/').json()
98 101 self.assertEqual(len(nbs), 1)
99 102 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
100 103
101 104 nbs = self.nb_api.list(u'/unicodΓ©/').json()
102 105 self.assertEqual(len(nbs), 1)
103 106 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
104 107
105 108 nbs = self.nb_api.list('/foo/bar/').json()
106 109 self.assertEqual(len(nbs), 1)
107 110 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
108 111
109 112 nbs = self.nb_api.list('foo').json()
110 113 self.assertEqual(len(nbs), 4)
111 114 nbnames = set(n['name'] for n in nbs)
112 115 self.assertEqual(nbnames, {'a.ipynb', 'b.ipynb',
113 116 'name with spaces.ipynb', u'unicodΓ©.ipynb'})
114 117
115 118 def test_get_contents(self):
116 119 for d, name in self.dirs_nbs:
117 120 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
118 121 self.assertEqual(nb['name'], '%s.ipynb' % name)
119 122 self.assertIn('content', nb)
120 123 self.assertIn('metadata', nb['content'])
121 124 self.assertIsInstance(nb['content']['metadata'], dict)
122 125
123 126 # Name that doesn't exist - should be a 404
124 127 try:
125 128 self.nb_api.read('q.ipynb', 'foo')
126 129 except requests.HTTPError as e:
127 130 self.assertEqual(e.response.status_code, 404)
128 131 else:
129 132 assert False, "Reading a non-existent notebook should fail"
130 133
131 134 def _check_nb_created(self, resp, name, path):
132 135 self.assertEqual(resp.status_code, 201)
133 136 self.assertEqual(resp.headers['Location'].split('/')[-1], name)
134 137 self.assertEqual(resp.json()['name'], name)
135 138 assert os.path.isfile(pjoin(self.notebook_dir.name, path, name))
136 139
137 140 def test_create_untitled(self):
138 141 resp = self.nb_api.create_untitled(path='foo')
139 142 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo')
140 143
141 144 # Second time
142 145 resp = self.nb_api.create_untitled(path='foo')
143 146 self._check_nb_created(resp, 'Untitled1.ipynb', 'foo')
144 147
145 148 # And two directories down
146 149 resp = self.nb_api.create_untitled(path='foo/bar')
147 150 self._check_nb_created(resp, 'Untitled0.ipynb', pjoin('foo', 'bar'))
148 151
149 152 def test_upload(self):
150 153 nb = new_notebook(name='Upload test')
151 154 nbmodel = {'content': nb}
152 155 resp = self.nb_api.upload('Upload test.ipynb', path='foo',
153 156 body=jsonapi.dumps(nbmodel))
154 157 self._check_nb_created(resp, 'Upload test.ipynb', 'foo')
155 158
159 def test_copy(self):
160 resp = self.nb_api.copy('a.ipynb', path='foo')
161 self._check_nb_created(resp, 'a-Copy0.ipynb', 'foo')
162
156 163 def test_delete(self):
157 164 for d, name in self.dirs_nbs:
158 165 resp = self.nb_api.delete('%s.ipynb' % name, d)
159 166 self.assertEqual(resp.status_code, 204)
160 167
161 168 for d in self.dirs + ['/']:
162 169 nbs = self.nb_api.list(d).json()
163 170 self.assertEqual(len(nbs), 0)
164 171
165 172 def test_rename(self):
166 173 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
167 174 if False:
168 175 # XXX: Spec says this should be set, but it isn't
169 176 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
170 177 self.assertEqual(resp.json()['name'], 'z.ipynb')
171 178 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
172 179
173 180 nbs = self.nb_api.list('foo').json()
174 181 nbnames = set(n['name'] for n in nbs)
175 182 self.assertIn('z.ipynb', nbnames)
176 183 self.assertNotIn('a.ipynb', nbnames)
177 184
178 185 def test_save(self):
179 186 resp = self.nb_api.read('a.ipynb', 'foo')
180 187 nbcontent = jsonapi.loads(resp.text)['content']
181 188 nb = to_notebook_json(nbcontent)
182 189 ws = new_worksheet()
183 190 nb.worksheets = [ws]
184 191 ws.cells.append(new_heading_cell('Created by test'))
185 192
186 193 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
187 194 resp = self.nb_api.save('a.ipynb', path='foo', body=jsonapi.dumps(nbmodel))
188 195
189 196 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
190 197 with open(nbfile, 'r') as f:
191 198 newnb = read(f, format='ipynb')
192 199 self.assertEqual(newnb.worksheets[0].cells[0].source,
193 200 'Created by test')
General Comments 0
You need to be logged in to leave comments. Login now