##// END OF EJS Templates
TEST: Verify base64 return values after decoding.
Scott Sanderson -
Show More
@@ -1,540 +1,542 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test the contents webservice API."""
2 """Test the contents webservice API."""
3
3
4 import base64
4 import base64
5 import io
5 import io
6 import json
6 import json
7 import os
7 import os
8 import shutil
8 import shutil
9 from unicodedata import normalize
9 from unicodedata import normalize
10
10
11 pjoin = os.path.join
11 pjoin = os.path.join
12
12
13 import requests
13 import requests
14
14
15 from IPython.html.utils import url_path_join, url_escape, to_os_path
15 from IPython.html.utils import url_path_join, url_escape, to_os_path
16 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
16 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
17 from IPython.nbformat import read, write, from_dict
17 from IPython.nbformat import read, write, from_dict
18 from IPython.nbformat.v4 import (
18 from IPython.nbformat.v4 import (
19 new_notebook, new_markdown_cell,
19 new_notebook, new_markdown_cell,
20 )
20 )
21 from IPython.nbformat import v2
21 from IPython.nbformat import v2
22 from IPython.utils import py3compat
22 from IPython.utils import py3compat
23 from IPython.utils.data import uniq_stable
23 from IPython.utils.data import uniq_stable
24
24
25
25
26 def notebooks_only(dir_model):
26 def notebooks_only(dir_model):
27 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
27 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
28
28
29 def dirs_only(dir_model):
29 def dirs_only(dir_model):
30 return [x for x in dir_model['content'] if x['type']=='directory']
30 return [x for x in dir_model['content'] if x['type']=='directory']
31
31
32
32
33 class API(object):
33 class API(object):
34 """Wrapper for contents API calls."""
34 """Wrapper for contents API calls."""
35 def __init__(self, base_url):
35 def __init__(self, base_url):
36 self.base_url = base_url
36 self.base_url = base_url
37
37
38 def _req(self, verb, path, body=None, params=None):
38 def _req(self, verb, path, body=None, params=None):
39 response = requests.request(verb,
39 response = requests.request(verb,
40 url_path_join(self.base_url, 'api/contents', path),
40 url_path_join(self.base_url, 'api/contents', path),
41 data=body, params=params,
41 data=body, params=params,
42 )
42 )
43 response.raise_for_status()
43 response.raise_for_status()
44 return response
44 return response
45
45
46 def list(self, path='/'):
46 def list(self, path='/'):
47 return self._req('GET', path)
47 return self._req('GET', path)
48
48
49 def read(self, path, type=None, format=None):
49 def read(self, path, type=None, format=None):
50 params = {}
50 params = {}
51 if type is not None:
51 if type is not None:
52 params['type'] = type
52 params['type'] = type
53 if format is not None:
53 if format is not None:
54 params['format'] = format
54 params['format'] = format
55 return self._req('GET', path, params=params)
55 return self._req('GET', path, params=params)
56
56
57 def create_untitled(self, path='/', ext='.ipynb'):
57 def create_untitled(self, path='/', ext='.ipynb'):
58 body = None
58 body = None
59 if ext:
59 if ext:
60 body = json.dumps({'ext': ext})
60 body = json.dumps({'ext': ext})
61 return self._req('POST', path, body)
61 return self._req('POST', path, body)
62
62
63 def mkdir_untitled(self, path='/'):
63 def mkdir_untitled(self, path='/'):
64 return self._req('POST', path, json.dumps({'type': 'directory'}))
64 return self._req('POST', path, json.dumps({'type': 'directory'}))
65
65
66 def copy(self, copy_from, path='/'):
66 def copy(self, copy_from, path='/'):
67 body = json.dumps({'copy_from':copy_from})
67 body = json.dumps({'copy_from':copy_from})
68 return self._req('POST', path, body)
68 return self._req('POST', path, body)
69
69
70 def create(self, path='/'):
70 def create(self, path='/'):
71 return self._req('PUT', path)
71 return self._req('PUT', path)
72
72
73 def upload(self, path, body):
73 def upload(self, path, body):
74 return self._req('PUT', path, body)
74 return self._req('PUT', path, body)
75
75
76 def mkdir(self, path='/'):
76 def mkdir(self, path='/'):
77 return self._req('PUT', path, json.dumps({'type': 'directory'}))
77 return self._req('PUT', path, json.dumps({'type': 'directory'}))
78
78
79 def copy_put(self, copy_from, path='/'):
79 def copy_put(self, copy_from, path='/'):
80 body = json.dumps({'copy_from':copy_from})
80 body = json.dumps({'copy_from':copy_from})
81 return self._req('PUT', path, body)
81 return self._req('PUT', path, body)
82
82
83 def save(self, path, body):
83 def save(self, path, body):
84 return self._req('PUT', path, body)
84 return self._req('PUT', path, body)
85
85
86 def delete(self, path='/'):
86 def delete(self, path='/'):
87 return self._req('DELETE', path)
87 return self._req('DELETE', path)
88
88
89 def rename(self, path, new_path):
89 def rename(self, path, new_path):
90 body = json.dumps({'path': new_path})
90 body = json.dumps({'path': new_path})
91 return self._req('PATCH', path, body)
91 return self._req('PATCH', path, body)
92
92
93 def get_checkpoints(self, path):
93 def get_checkpoints(self, path):
94 return self._req('GET', url_path_join(path, 'checkpoints'))
94 return self._req('GET', url_path_join(path, 'checkpoints'))
95
95
96 def new_checkpoint(self, path):
96 def new_checkpoint(self, path):
97 return self._req('POST', url_path_join(path, 'checkpoints'))
97 return self._req('POST', url_path_join(path, 'checkpoints'))
98
98
99 def restore_checkpoint(self, path, checkpoint_id):
99 def restore_checkpoint(self, path, checkpoint_id):
100 return self._req('POST', url_path_join(path, 'checkpoints', checkpoint_id))
100 return self._req('POST', url_path_join(path, 'checkpoints', checkpoint_id))
101
101
102 def delete_checkpoint(self, path, checkpoint_id):
102 def delete_checkpoint(self, path, checkpoint_id):
103 return self._req('DELETE', url_path_join(path, 'checkpoints', checkpoint_id))
103 return self._req('DELETE', url_path_join(path, 'checkpoints', checkpoint_id))
104
104
105 class APITest(NotebookTestBase):
105 class APITest(NotebookTestBase):
106 """Test the kernels web service API"""
106 """Test the kernels web service API"""
107 dirs_nbs = [('', 'inroot'),
107 dirs_nbs = [('', 'inroot'),
108 ('Directory with spaces in', 'inspace'),
108 ('Directory with spaces in', 'inspace'),
109 (u'unicodΓ©', 'innonascii'),
109 (u'unicodΓ©', 'innonascii'),
110 ('foo', 'a'),
110 ('foo', 'a'),
111 ('foo', 'b'),
111 ('foo', 'b'),
112 ('foo', 'name with spaces'),
112 ('foo', 'name with spaces'),
113 ('foo', u'unicodΓ©'),
113 ('foo', u'unicodΓ©'),
114 ('foo/bar', 'baz'),
114 ('foo/bar', 'baz'),
115 ('ordering', 'A'),
115 ('ordering', 'A'),
116 ('ordering', 'b'),
116 ('ordering', 'b'),
117 ('ordering', 'C'),
117 ('ordering', 'C'),
118 (u'Γ₯ b', u'Γ§ d'),
118 (u'Γ₯ b', u'Γ§ d'),
119 ]
119 ]
120 hidden_dirs = ['.hidden', '__pycache__']
120 hidden_dirs = ['.hidden', '__pycache__']
121
121
122 dirs = uniq_stable([py3compat.cast_unicode(d) for (d,n) in dirs_nbs])
122 dirs = uniq_stable([py3compat.cast_unicode(d) for (d,n) in dirs_nbs])
123 del dirs[0] # remove ''
123 del dirs[0] # remove ''
124 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
124 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
125
125
126 @staticmethod
126 @staticmethod
127 def _blob_for_name(name):
127 def _blob_for_name(name):
128 return name.encode('utf-8') + b'\xFF'
128 return name.encode('utf-8') + b'\xFF'
129
129
130 @staticmethod
130 @staticmethod
131 def _txt_for_name(name):
131 def _txt_for_name(name):
132 return u'%s text file' % name
132 return u'%s text file' % name
133
133
134 def to_os_path(self, api_path):
134 def to_os_path(self, api_path):
135 return to_os_path(api_path, root=self.notebook_dir.name)
135 return to_os_path(api_path, root=self.notebook_dir.name)
136
136
137 def make_dir(self, api_path):
137 def make_dir(self, api_path):
138 """Create a directory at api_path"""
138 """Create a directory at api_path"""
139 os_path = self.to_os_path(api_path)
139 os_path = self.to_os_path(api_path)
140 try:
140 try:
141 os.makedirs(os_path)
141 os.makedirs(os_path)
142 except OSError:
142 except OSError:
143 print("Directory already exists: %r" % os_path)
143 print("Directory already exists: %r" % os_path)
144
144
145 def make_txt(self, api_path, txt):
145 def make_txt(self, api_path, txt):
146 """Make a text file at a given api_path"""
146 """Make a text file at a given api_path"""
147 os_path = self.to_os_path(api_path)
147 os_path = self.to_os_path(api_path)
148 with io.open(os_path, 'w', encoding='utf-8') as f:
148 with io.open(os_path, 'w', encoding='utf-8') as f:
149 f.write(txt)
149 f.write(txt)
150
150
151 def make_blob(self, api_path, blob):
151 def make_blob(self, api_path, blob):
152 """Make a binary file at a given api_path"""
152 """Make a binary file at a given api_path"""
153 os_path = self.to_os_path(api_path)
153 os_path = self.to_os_path(api_path)
154 with io.open(os_path, 'wb') as f:
154 with io.open(os_path, 'wb') as f:
155 f.write(blob)
155 f.write(blob)
156
156
157 def make_nb(self, api_path, nb):
157 def make_nb(self, api_path, nb):
158 """Make a notebook file at a given api_path"""
158 """Make a notebook file at a given api_path"""
159 os_path = self.to_os_path(api_path)
159 os_path = self.to_os_path(api_path)
160
160
161 with io.open(os_path, 'w', encoding='utf-8') as f:
161 with io.open(os_path, 'w', encoding='utf-8') as f:
162 write(nb, f, version=4)
162 write(nb, f, version=4)
163
163
164 def delete_dir(self, api_path):
164 def delete_dir(self, api_path):
165 """Delete a directory at api_path, removing any contents."""
165 """Delete a directory at api_path, removing any contents."""
166 os_path = self.to_os_path(api_path)
166 os_path = self.to_os_path(api_path)
167 shutil.rmtree(os_path, ignore_errors=True)
167 shutil.rmtree(os_path, ignore_errors=True)
168
168
169 def delete_file(self, api_path):
169 def delete_file(self, api_path):
170 """Delete a file at the given path if it exists."""
170 """Delete a file at the given path if it exists."""
171 if self.isfile(api_path):
171 if self.isfile(api_path):
172 os.unlink(self.to_os_path(api_path))
172 os.unlink(self.to_os_path(api_path))
173
173
174 def isfile(self, api_path):
174 def isfile(self, api_path):
175 return os.path.isfile(self.to_os_path(api_path))
175 return os.path.isfile(self.to_os_path(api_path))
176
176
177 def isdir(self, api_path):
177 def isdir(self, api_path):
178 return os.path.isdir(self.to_os_path(api_path))
178 return os.path.isdir(self.to_os_path(api_path))
179
179
180 def setUp(self):
180 def setUp(self):
181
181
182 for d in (self.dirs + self.hidden_dirs):
182 for d in (self.dirs + self.hidden_dirs):
183 self.make_dir(d)
183 self.make_dir(d)
184
184
185 for d, name in self.dirs_nbs:
185 for d, name in self.dirs_nbs:
186 # create a notebook
186 # create a notebook
187 nb = new_notebook()
187 nb = new_notebook()
188 self.make_nb(u'{}/{}.ipynb'.format(d, name), nb)
188 self.make_nb(u'{}/{}.ipynb'.format(d, name), nb)
189
189
190 # create a text file
190 # create a text file
191 txt = self._txt_for_name(name)
191 txt = self._txt_for_name(name)
192 self.make_txt(u'{}/{}.txt'.format(d, name), txt)
192 self.make_txt(u'{}/{}.txt'.format(d, name), txt)
193
193
194 # create a binary file
194 # create a binary file
195 blob = self._blob_for_name(name)
195 blob = self._blob_for_name(name)
196 self.make_blob(u'{}/{}.blob'.format(d, name), blob)
196 self.make_blob(u'{}/{}.blob'.format(d, name), blob)
197
197
198 self.api = API(self.base_url())
198 self.api = API(self.base_url())
199
199
200 def tearDown(self):
200 def tearDown(self):
201 for dname in (list(self.top_level_dirs) + self.hidden_dirs):
201 for dname in (list(self.top_level_dirs) + self.hidden_dirs):
202 self.delete_dir(dname)
202 self.delete_dir(dname)
203 self.delete_file('inroot.ipynb')
203 self.delete_file('inroot.ipynb')
204
204
205 def test_list_notebooks(self):
205 def test_list_notebooks(self):
206 nbs = notebooks_only(self.api.list().json())
206 nbs = notebooks_only(self.api.list().json())
207 self.assertEqual(len(nbs), 1)
207 self.assertEqual(len(nbs), 1)
208 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
208 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
209
209
210 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
210 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
211 self.assertEqual(len(nbs), 1)
211 self.assertEqual(len(nbs), 1)
212 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
212 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
213
213
214 nbs = notebooks_only(self.api.list(u'/unicodΓ©/').json())
214 nbs = notebooks_only(self.api.list(u'/unicodΓ©/').json())
215 self.assertEqual(len(nbs), 1)
215 self.assertEqual(len(nbs), 1)
216 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
216 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
217 self.assertEqual(nbs[0]['path'], u'unicodΓ©/innonascii.ipynb')
217 self.assertEqual(nbs[0]['path'], u'unicodΓ©/innonascii.ipynb')
218
218
219 nbs = notebooks_only(self.api.list('/foo/bar/').json())
219 nbs = notebooks_only(self.api.list('/foo/bar/').json())
220 self.assertEqual(len(nbs), 1)
220 self.assertEqual(len(nbs), 1)
221 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
221 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
222 self.assertEqual(nbs[0]['path'], 'foo/bar/baz.ipynb')
222 self.assertEqual(nbs[0]['path'], 'foo/bar/baz.ipynb')
223
223
224 nbs = notebooks_only(self.api.list('foo').json())
224 nbs = notebooks_only(self.api.list('foo').json())
225 self.assertEqual(len(nbs), 4)
225 self.assertEqual(len(nbs), 4)
226 nbnames = { normalize('NFC', n['name']) for n in nbs }
226 nbnames = { normalize('NFC', n['name']) for n in nbs }
227 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
227 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
228 expected = { normalize('NFC', name) for name in expected }
228 expected = { normalize('NFC', name) for name in expected }
229 self.assertEqual(nbnames, expected)
229 self.assertEqual(nbnames, expected)
230
230
231 nbs = notebooks_only(self.api.list('ordering').json())
231 nbs = notebooks_only(self.api.list('ordering').json())
232 nbnames = [n['name'] for n in nbs]
232 nbnames = [n['name'] for n in nbs]
233 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
233 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
234 self.assertEqual(nbnames, expected)
234 self.assertEqual(nbnames, expected)
235
235
236 def test_list_dirs(self):
236 def test_list_dirs(self):
237 dirs = dirs_only(self.api.list().json())
237 dirs = dirs_only(self.api.list().json())
238 dir_names = {normalize('NFC', d['name']) for d in dirs}
238 dir_names = {normalize('NFC', d['name']) for d in dirs}
239 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
239 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
240
240
241 def test_list_nonexistant_dir(self):
241 def test_list_nonexistant_dir(self):
242 with assert_http_error(404):
242 with assert_http_error(404):
243 self.api.list('nonexistant')
243 self.api.list('nonexistant')
244
244
245 def test_get_nb_contents(self):
245 def test_get_nb_contents(self):
246 for d, name in self.dirs_nbs:
246 for d, name in self.dirs_nbs:
247 path = url_path_join(d, name + '.ipynb')
247 path = url_path_join(d, name + '.ipynb')
248 nb = self.api.read(path).json()
248 nb = self.api.read(path).json()
249 self.assertEqual(nb['name'], u'%s.ipynb' % name)
249 self.assertEqual(nb['name'], u'%s.ipynb' % name)
250 self.assertEqual(nb['path'], path)
250 self.assertEqual(nb['path'], path)
251 self.assertEqual(nb['type'], 'notebook')
251 self.assertEqual(nb['type'], 'notebook')
252 self.assertIn('content', nb)
252 self.assertIn('content', nb)
253 self.assertEqual(nb['format'], 'json')
253 self.assertEqual(nb['format'], 'json')
254 self.assertIn('content', nb)
254 self.assertIn('content', nb)
255 self.assertIn('metadata', nb['content'])
255 self.assertIn('metadata', nb['content'])
256 self.assertIsInstance(nb['content']['metadata'], dict)
256 self.assertIsInstance(nb['content']['metadata'], dict)
257
257
258 def test_get_contents_no_such_file(self):
258 def test_get_contents_no_such_file(self):
259 # Name that doesn't exist - should be a 404
259 # Name that doesn't exist - should be a 404
260 with assert_http_error(404):
260 with assert_http_error(404):
261 self.api.read('foo/q.ipynb')
261 self.api.read('foo/q.ipynb')
262
262
263 def test_get_text_file_contents(self):
263 def test_get_text_file_contents(self):
264 for d, name in self.dirs_nbs:
264 for d, name in self.dirs_nbs:
265 path = url_path_join(d, name + '.txt')
265 path = url_path_join(d, name + '.txt')
266 model = self.api.read(path).json()
266 model = self.api.read(path).json()
267 self.assertEqual(model['name'], u'%s.txt' % name)
267 self.assertEqual(model['name'], u'%s.txt' % name)
268 self.assertEqual(model['path'], path)
268 self.assertEqual(model['path'], path)
269 self.assertIn('content', model)
269 self.assertIn('content', model)
270 self.assertEqual(model['format'], 'text')
270 self.assertEqual(model['format'], 'text')
271 self.assertEqual(model['type'], 'file')
271 self.assertEqual(model['type'], 'file')
272 self.assertEqual(model['content'], self._txt_for_name(name))
272 self.assertEqual(model['content'], self._txt_for_name(name))
273
273
274 # Name that doesn't exist - should be a 404
274 # Name that doesn't exist - should be a 404
275 with assert_http_error(404):
275 with assert_http_error(404):
276 self.api.read('foo/q.txt')
276 self.api.read('foo/q.txt')
277
277
278 # Specifying format=text should fail on a non-UTF-8 file
278 # Specifying format=text should fail on a non-UTF-8 file
279 with assert_http_error(400):
279 with assert_http_error(400):
280 self.api.read('foo/bar/baz.blob', type='file', format='text')
280 self.api.read('foo/bar/baz.blob', type='file', format='text')
281
281
282 def test_get_binary_file_contents(self):
282 def test_get_binary_file_contents(self):
283 for d, name in self.dirs_nbs:
283 for d, name in self.dirs_nbs:
284 path = url_path_join(d, name + '.blob')
284 path = url_path_join(d, name + '.blob')
285 model = self.api.read(path).json()
285 model = self.api.read(path).json()
286 self.assertEqual(model['name'], u'%s.blob' % name)
286 self.assertEqual(model['name'], u'%s.blob' % name)
287 self.assertEqual(model['path'], path)
287 self.assertEqual(model['path'], path)
288 self.assertIn('content', model)
288 self.assertIn('content', model)
289 self.assertEqual(model['format'], 'base64')
289 self.assertEqual(model['format'], 'base64')
290 self.assertEqual(model['type'], 'file')
290 self.assertEqual(model['type'], 'file')
291 b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii')
291 self.assertEqual(
292 self.assertEqual(model['content'], b64_data)
292 base64.decodestring(model['content']),
293 self._blob_for_name(name),
294 )
293
295
294 # Name that doesn't exist - should be a 404
296 # Name that doesn't exist - should be a 404
295 with assert_http_error(404):
297 with assert_http_error(404):
296 self.api.read('foo/q.txt')
298 self.api.read('foo/q.txt')
297
299
298 def test_get_bad_type(self):
300 def test_get_bad_type(self):
299 with assert_http_error(400):
301 with assert_http_error(400):
300 self.api.read(u'unicodΓ©', type='file') # this is a directory
302 self.api.read(u'unicodΓ©', type='file') # this is a directory
301
303
302 with assert_http_error(400):
304 with assert_http_error(400):
303 self.api.read(u'unicodΓ©/innonascii.ipynb', type='directory')
305 self.api.read(u'unicodΓ©/innonascii.ipynb', type='directory')
304
306
305 def _check_created(self, resp, path, type='notebook'):
307 def _check_created(self, resp, path, type='notebook'):
306 self.assertEqual(resp.status_code, 201)
308 self.assertEqual(resp.status_code, 201)
307 location_header = py3compat.str_to_unicode(resp.headers['Location'])
309 location_header = py3compat.str_to_unicode(resp.headers['Location'])
308 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path)))
310 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path)))
309 rjson = resp.json()
311 rjson = resp.json()
310 self.assertEqual(rjson['name'], path.rsplit('/', 1)[-1])
312 self.assertEqual(rjson['name'], path.rsplit('/', 1)[-1])
311 self.assertEqual(rjson['path'], path)
313 self.assertEqual(rjson['path'], path)
312 self.assertEqual(rjson['type'], type)
314 self.assertEqual(rjson['type'], type)
313 isright = self.isdir if type == 'directory' else self.isfile
315 isright = self.isdir if type == 'directory' else self.isfile
314 assert isright(path)
316 assert isright(path)
315
317
316 def test_create_untitled(self):
318 def test_create_untitled(self):
317 resp = self.api.create_untitled(path=u'Γ₯ b')
319 resp = self.api.create_untitled(path=u'Γ₯ b')
318 self._check_created(resp, u'Γ₯ b/Untitled.ipynb')
320 self._check_created(resp, u'Γ₯ b/Untitled.ipynb')
319
321
320 # Second time
322 # Second time
321 resp = self.api.create_untitled(path=u'Γ₯ b')
323 resp = self.api.create_untitled(path=u'Γ₯ b')
322 self._check_created(resp, u'Γ₯ b/Untitled1.ipynb')
324 self._check_created(resp, u'Γ₯ b/Untitled1.ipynb')
323
325
324 # And two directories down
326 # And two directories down
325 resp = self.api.create_untitled(path='foo/bar')
327 resp = self.api.create_untitled(path='foo/bar')
326 self._check_created(resp, 'foo/bar/Untitled.ipynb')
328 self._check_created(resp, 'foo/bar/Untitled.ipynb')
327
329
328 def test_create_untitled_txt(self):
330 def test_create_untitled_txt(self):
329 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
331 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
330 self._check_created(resp, 'foo/bar/untitled.txt', type='file')
332 self._check_created(resp, 'foo/bar/untitled.txt', type='file')
331
333
332 resp = self.api.read(path='foo/bar/untitled.txt')
334 resp = self.api.read(path='foo/bar/untitled.txt')
333 model = resp.json()
335 model = resp.json()
334 self.assertEqual(model['type'], 'file')
336 self.assertEqual(model['type'], 'file')
335 self.assertEqual(model['format'], 'text')
337 self.assertEqual(model['format'], 'text')
336 self.assertEqual(model['content'], '')
338 self.assertEqual(model['content'], '')
337
339
338 def test_upload(self):
340 def test_upload(self):
339 nb = new_notebook()
341 nb = new_notebook()
340 nbmodel = {'content': nb, 'type': 'notebook'}
342 nbmodel = {'content': nb, 'type': 'notebook'}
341 path = u'Γ₯ b/Upload tΓ©st.ipynb'
343 path = u'Γ₯ b/Upload tΓ©st.ipynb'
342 resp = self.api.upload(path, body=json.dumps(nbmodel))
344 resp = self.api.upload(path, body=json.dumps(nbmodel))
343 self._check_created(resp, path)
345 self._check_created(resp, path)
344
346
345 def test_mkdir_untitled(self):
347 def test_mkdir_untitled(self):
346 resp = self.api.mkdir_untitled(path=u'Γ₯ b')
348 resp = self.api.mkdir_untitled(path=u'Γ₯ b')
347 self._check_created(resp, u'Γ₯ b/Untitled Folder', type='directory')
349 self._check_created(resp, u'Γ₯ b/Untitled Folder', type='directory')
348
350
349 # Second time
351 # Second time
350 resp = self.api.mkdir_untitled(path=u'Γ₯ b')
352 resp = self.api.mkdir_untitled(path=u'Γ₯ b')
351 self._check_created(resp, u'Γ₯ b/Untitled Folder 1', type='directory')
353 self._check_created(resp, u'Γ₯ b/Untitled Folder 1', type='directory')
352
354
353 # And two directories down
355 # And two directories down
354 resp = self.api.mkdir_untitled(path='foo/bar')
356 resp = self.api.mkdir_untitled(path='foo/bar')
355 self._check_created(resp, 'foo/bar/Untitled Folder', type='directory')
357 self._check_created(resp, 'foo/bar/Untitled Folder', type='directory')
356
358
357 def test_mkdir(self):
359 def test_mkdir(self):
358 path = u'Γ₯ b/New βˆ‚ir'
360 path = u'Γ₯ b/New βˆ‚ir'
359 resp = self.api.mkdir(path)
361 resp = self.api.mkdir(path)
360 self._check_created(resp, path, type='directory')
362 self._check_created(resp, path, type='directory')
361
363
362 def test_mkdir_hidden_400(self):
364 def test_mkdir_hidden_400(self):
363 with assert_http_error(400):
365 with assert_http_error(400):
364 resp = self.api.mkdir(u'Γ₯ b/.hidden')
366 resp = self.api.mkdir(u'Γ₯ b/.hidden')
365
367
366 def test_upload_txt(self):
368 def test_upload_txt(self):
367 body = u'ΓΌnicode tΓ©xt'
369 body = u'ΓΌnicode tΓ©xt'
368 model = {
370 model = {
369 'content' : body,
371 'content' : body,
370 'format' : 'text',
372 'format' : 'text',
371 'type' : 'file',
373 'type' : 'file',
372 }
374 }
373 path = u'Γ₯ b/Upload tΓ©st.txt'
375 path = u'Γ₯ b/Upload tΓ©st.txt'
374 resp = self.api.upload(path, body=json.dumps(model))
376 resp = self.api.upload(path, body=json.dumps(model))
375
377
376 # check roundtrip
378 # check roundtrip
377 resp = self.api.read(path)
379 resp = self.api.read(path)
378 model = resp.json()
380 model = resp.json()
379 self.assertEqual(model['type'], 'file')
381 self.assertEqual(model['type'], 'file')
380 self.assertEqual(model['format'], 'text')
382 self.assertEqual(model['format'], 'text')
381 self.assertEqual(model['content'], body)
383 self.assertEqual(model['content'], body)
382
384
383 def test_upload_b64(self):
385 def test_upload_b64(self):
384 body = b'\xFFblob'
386 body = b'\xFFblob'
385 b64body = base64.encodestring(body).decode('ascii')
387 b64body = base64.encodestring(body).decode('ascii')
386 model = {
388 model = {
387 'content' : b64body,
389 'content' : b64body,
388 'format' : 'base64',
390 'format' : 'base64',
389 'type' : 'file',
391 'type' : 'file',
390 }
392 }
391 path = u'Γ₯ b/Upload tΓ©st.blob'
393 path = u'Γ₯ b/Upload tΓ©st.blob'
392 resp = self.api.upload(path, body=json.dumps(model))
394 resp = self.api.upload(path, body=json.dumps(model))
393
395
394 # check roundtrip
396 # check roundtrip
395 resp = self.api.read(path)
397 resp = self.api.read(path)
396 model = resp.json()
398 model = resp.json()
397 self.assertEqual(model['type'], 'file')
399 self.assertEqual(model['type'], 'file')
398 self.assertEqual(model['path'], path)
400 self.assertEqual(model['path'], path)
399 self.assertEqual(model['format'], 'base64')
401 self.assertEqual(model['format'], 'base64')
400 decoded = base64.decodestring(model['content'].encode('ascii'))
402 decoded = base64.decodestring(model['content'].encode('ascii'))
401 self.assertEqual(decoded, body)
403 self.assertEqual(decoded, body)
402
404
403 def test_upload_v2(self):
405 def test_upload_v2(self):
404 nb = v2.new_notebook()
406 nb = v2.new_notebook()
405 ws = v2.new_worksheet()
407 ws = v2.new_worksheet()
406 nb.worksheets.append(ws)
408 nb.worksheets.append(ws)
407 ws.cells.append(v2.new_code_cell(input='print("hi")'))
409 ws.cells.append(v2.new_code_cell(input='print("hi")'))
408 nbmodel = {'content': nb, 'type': 'notebook'}
410 nbmodel = {'content': nb, 'type': 'notebook'}
409 path = u'Γ₯ b/Upload tΓ©st.ipynb'
411 path = u'Γ₯ b/Upload tΓ©st.ipynb'
410 resp = self.api.upload(path, body=json.dumps(nbmodel))
412 resp = self.api.upload(path, body=json.dumps(nbmodel))
411 self._check_created(resp, path)
413 self._check_created(resp, path)
412 resp = self.api.read(path)
414 resp = self.api.read(path)
413 data = resp.json()
415 data = resp.json()
414 self.assertEqual(data['content']['nbformat'], 4)
416 self.assertEqual(data['content']['nbformat'], 4)
415
417
416 def test_copy(self):
418 def test_copy(self):
417 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
419 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
418 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy1.ipynb')
420 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy1.ipynb')
419
421
420 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
422 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
421 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy2.ipynb')
423 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy2.ipynb')
422
424
423 def test_copy_copy(self):
425 def test_copy_copy(self):
424 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
426 resp = self.api.copy(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b')
425 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy1.ipynb')
427 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy1.ipynb')
426
428
427 resp = self.api.copy(u'Γ₯ b/Γ§ d-Copy1.ipynb', u'Γ₯ b')
429 resp = self.api.copy(u'Γ₯ b/Γ§ d-Copy1.ipynb', u'Γ₯ b')
428 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy2.ipynb')
430 self._check_created(resp, u'Γ₯ b/Γ§ d-Copy2.ipynb')
429
431
430 def test_copy_path(self):
432 def test_copy_path(self):
431 resp = self.api.copy(u'foo/a.ipynb', u'Γ₯ b')
433 resp = self.api.copy(u'foo/a.ipynb', u'Γ₯ b')
432 self._check_created(resp, u'Γ₯ b/a.ipynb')
434 self._check_created(resp, u'Γ₯ b/a.ipynb')
433
435
434 resp = self.api.copy(u'foo/a.ipynb', u'Γ₯ b')
436 resp = self.api.copy(u'foo/a.ipynb', u'Γ₯ b')
435 self._check_created(resp, u'Γ₯ b/a-Copy1.ipynb')
437 self._check_created(resp, u'Γ₯ b/a-Copy1.ipynb')
436
438
437 def test_copy_put_400(self):
439 def test_copy_put_400(self):
438 with assert_http_error(400):
440 with assert_http_error(400):
439 resp = self.api.copy_put(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b/cΓΈpy.ipynb')
441 resp = self.api.copy_put(u'Γ₯ b/Γ§ d.ipynb', u'Γ₯ b/cΓΈpy.ipynb')
440
442
441 def test_copy_dir_400(self):
443 def test_copy_dir_400(self):
442 # can't copy directories
444 # can't copy directories
443 with assert_http_error(400):
445 with assert_http_error(400):
444 resp = self.api.copy(u'Γ₯ b', u'foo')
446 resp = self.api.copy(u'Γ₯ b', u'foo')
445
447
446 def test_delete(self):
448 def test_delete(self):
447 for d, name in self.dirs_nbs:
449 for d, name in self.dirs_nbs:
448 print('%r, %r' % (d, name))
450 print('%r, %r' % (d, name))
449 resp = self.api.delete(url_path_join(d, name + '.ipynb'))
451 resp = self.api.delete(url_path_join(d, name + '.ipynb'))
450 self.assertEqual(resp.status_code, 204)
452 self.assertEqual(resp.status_code, 204)
451
453
452 for d in self.dirs + ['/']:
454 for d in self.dirs + ['/']:
453 nbs = notebooks_only(self.api.list(d).json())
455 nbs = notebooks_only(self.api.list(d).json())
454 print('------')
456 print('------')
455 print(d)
457 print(d)
456 print(nbs)
458 print(nbs)
457 self.assertEqual(nbs, [])
459 self.assertEqual(nbs, [])
458
460
459 def test_delete_dirs(self):
461 def test_delete_dirs(self):
460 # depth-first delete everything, so we don't try to delete empty directories
462 # depth-first delete everything, so we don't try to delete empty directories
461 for name in sorted(self.dirs + ['/'], key=len, reverse=True):
463 for name in sorted(self.dirs + ['/'], key=len, reverse=True):
462 listing = self.api.list(name).json()['content']
464 listing = self.api.list(name).json()['content']
463 for model in listing:
465 for model in listing:
464 self.api.delete(model['path'])
466 self.api.delete(model['path'])
465 listing = self.api.list('/').json()['content']
467 listing = self.api.list('/').json()['content']
466 self.assertEqual(listing, [])
468 self.assertEqual(listing, [])
467
469
468 def test_delete_non_empty_dir(self):
470 def test_delete_non_empty_dir(self):
469 """delete non-empty dir raises 400"""
471 """delete non-empty dir raises 400"""
470 with assert_http_error(400):
472 with assert_http_error(400):
471 self.api.delete(u'Γ₯ b')
473 self.api.delete(u'Γ₯ b')
472
474
473 def test_rename(self):
475 def test_rename(self):
474 resp = self.api.rename('foo/a.ipynb', 'foo/z.ipynb')
476 resp = self.api.rename('foo/a.ipynb', 'foo/z.ipynb')
475 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
477 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
476 self.assertEqual(resp.json()['name'], 'z.ipynb')
478 self.assertEqual(resp.json()['name'], 'z.ipynb')
477 self.assertEqual(resp.json()['path'], 'foo/z.ipynb')
479 self.assertEqual(resp.json()['path'], 'foo/z.ipynb')
478 assert self.isfile('foo/z.ipynb')
480 assert self.isfile('foo/z.ipynb')
479
481
480 nbs = notebooks_only(self.api.list('foo').json())
482 nbs = notebooks_only(self.api.list('foo').json())
481 nbnames = set(n['name'] for n in nbs)
483 nbnames = set(n['name'] for n in nbs)
482 self.assertIn('z.ipynb', nbnames)
484 self.assertIn('z.ipynb', nbnames)
483 self.assertNotIn('a.ipynb', nbnames)
485 self.assertNotIn('a.ipynb', nbnames)
484
486
485 def test_rename_existing(self):
487 def test_rename_existing(self):
486 with assert_http_error(409):
488 with assert_http_error(409):
487 self.api.rename('foo/a.ipynb', 'foo/b.ipynb')
489 self.api.rename('foo/a.ipynb', 'foo/b.ipynb')
488
490
489 def test_save(self):
491 def test_save(self):
490 resp = self.api.read('foo/a.ipynb')
492 resp = self.api.read('foo/a.ipynb')
491 nbcontent = json.loads(resp.text)['content']
493 nbcontent = json.loads(resp.text)['content']
492 nb = from_dict(nbcontent)
494 nb = from_dict(nbcontent)
493 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
495 nb.cells.append(new_markdown_cell(u'Created by test Β³'))
494
496
495 nbmodel= {'content': nb, 'type': 'notebook'}
497 nbmodel= {'content': nb, 'type': 'notebook'}
496 resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel))
498 resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel))
497
499
498 nbcontent = self.api.read('foo/a.ipynb').json()['content']
500 nbcontent = self.api.read('foo/a.ipynb').json()['content']
499 newnb = from_dict(nbcontent)
501 newnb = from_dict(nbcontent)
500 self.assertEqual(newnb.cells[0].source,
502 self.assertEqual(newnb.cells[0].source,
501 u'Created by test Β³')
503 u'Created by test Β³')
502
504
503
505
504 def test_checkpoints(self):
506 def test_checkpoints(self):
505 resp = self.api.read('foo/a.ipynb')
507 resp = self.api.read('foo/a.ipynb')
506 r = self.api.new_checkpoint('foo/a.ipynb')
508 r = self.api.new_checkpoint('foo/a.ipynb')
507 self.assertEqual(r.status_code, 201)
509 self.assertEqual(r.status_code, 201)
508 cp1 = r.json()
510 cp1 = r.json()
509 self.assertEqual(set(cp1), {'id', 'last_modified'})
511 self.assertEqual(set(cp1), {'id', 'last_modified'})
510 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
512 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
511
513
512 # Modify it
514 # Modify it
513 nbcontent = json.loads(resp.text)['content']
515 nbcontent = json.loads(resp.text)['content']
514 nb = from_dict(nbcontent)
516 nb = from_dict(nbcontent)
515 hcell = new_markdown_cell('Created by test')
517 hcell = new_markdown_cell('Created by test')
516 nb.cells.append(hcell)
518 nb.cells.append(hcell)
517 # Save
519 # Save
518 nbmodel= {'content': nb, 'type': 'notebook'}
520 nbmodel= {'content': nb, 'type': 'notebook'}
519 resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel))
521 resp = self.api.save('foo/a.ipynb', body=json.dumps(nbmodel))
520
522
521 # List checkpoints
523 # List checkpoints
522 cps = self.api.get_checkpoints('foo/a.ipynb').json()
524 cps = self.api.get_checkpoints('foo/a.ipynb').json()
523 self.assertEqual(cps, [cp1])
525 self.assertEqual(cps, [cp1])
524
526
525 nbcontent = self.api.read('foo/a.ipynb').json()['content']
527 nbcontent = self.api.read('foo/a.ipynb').json()['content']
526 nb = from_dict(nbcontent)
528 nb = from_dict(nbcontent)
527 self.assertEqual(nb.cells[0].source, 'Created by test')
529 self.assertEqual(nb.cells[0].source, 'Created by test')
528
530
529 # Restore cp1
531 # Restore cp1
530 r = self.api.restore_checkpoint('foo/a.ipynb', cp1['id'])
532 r = self.api.restore_checkpoint('foo/a.ipynb', cp1['id'])
531 self.assertEqual(r.status_code, 204)
533 self.assertEqual(r.status_code, 204)
532 nbcontent = self.api.read('foo/a.ipynb').json()['content']
534 nbcontent = self.api.read('foo/a.ipynb').json()['content']
533 nb = from_dict(nbcontent)
535 nb = from_dict(nbcontent)
534 self.assertEqual(nb.cells, [])
536 self.assertEqual(nb.cells, [])
535
537
536 # Delete cp1
538 # Delete cp1
537 r = self.api.delete_checkpoint('foo/a.ipynb', cp1['id'])
539 r = self.api.delete_checkpoint('foo/a.ipynb', cp1['id'])
538 self.assertEqual(r.status_code, 204)
540 self.assertEqual(r.status_code, 204)
539 cps = self.api.get_checkpoints('foo/a.ipynb').json()
541 cps = self.api.get_checkpoints('foo/a.ipynb').json()
540 self.assertEqual(cps, [])
542 self.assertEqual(cps, [])
General Comments 0
You need to be logged in to leave comments. Login now