##// END OF EJS Templates
test that FileContentsManager.open turns permission error into 403
Min RK -
Show More
@@ -1,441 +1,461 b''
1 1 # coding: utf-8
2 2 """Tests for the notebook manager."""
3 3 from __future__ import print_function
4 4
5 5 import os
6 6 import time
7 7
8 from nose import SkipTest
8 9 from tornado.web import HTTPError
9 10 from unittest import TestCase
10 11 from tempfile import NamedTemporaryFile
11 12
12 13 from IPython.nbformat import v4 as nbformat
13 14
14 15 from IPython.utils.tempdir import TemporaryDirectory
15 16 from IPython.utils.traitlets import TraitError
16 17 from IPython.html.utils import url_path_join
17 18 from IPython.testing import decorators as dec
18 19
19 20 from ..filemanager import FileContentsManager
20 21
21 22
22 23 def _make_dir(contents_manager, api_path):
23 24 """
24 25 Make a directory.
25 26 """
26 27 os_path = contents_manager._get_os_path(api_path)
27 28 try:
28 29 os.makedirs(os_path)
29 30 except OSError:
30 31 print("Directory already exists: %r" % os_path)
31 32
32 33
33 34 class TestFileContentsManager(TestCase):
34 35
35 36 def symlink(self, contents_manager, src, dst):
36 37 """Make a symlink to src from dst
37 38
38 39 src and dst are api_paths
39 40 """
40 41 src_os_path = contents_manager._get_os_path(src)
41 42 dst_os_path = contents_manager._get_os_path(dst)
42 43 print(src_os_path, dst_os_path, os.path.isfile(src_os_path))
43 44 os.symlink(src_os_path, dst_os_path)
44 45
45 46 def test_root_dir(self):
46 47 with TemporaryDirectory() as td:
47 48 fm = FileContentsManager(root_dir=td)
48 49 self.assertEqual(fm.root_dir, td)
49 50
50 51 def test_missing_root_dir(self):
51 52 with TemporaryDirectory() as td:
52 53 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
53 54 self.assertRaises(TraitError, FileContentsManager, root_dir=root)
54 55
55 56 def test_invalid_root_dir(self):
56 57 with NamedTemporaryFile() as tf:
57 58 self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
58 59
59 60 def test_get_os_path(self):
60 61 # full filesystem path should be returned with correct operating system
61 62 # separators.
62 63 with TemporaryDirectory() as td:
63 64 root = td
64 65 fm = FileContentsManager(root_dir=root)
65 66 path = fm._get_os_path('/path/to/notebook/test.ipynb')
66 67 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
67 68 fs_path = os.path.join(fm.root_dir, *rel_path_list)
68 69 self.assertEqual(path, fs_path)
69 70
70 71 fm = FileContentsManager(root_dir=root)
71 72 path = fm._get_os_path('test.ipynb')
72 73 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
73 74 self.assertEqual(path, fs_path)
74 75
75 76 fm = FileContentsManager(root_dir=root)
76 77 path = fm._get_os_path('////test.ipynb')
77 78 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
78 79 self.assertEqual(path, fs_path)
79 80
80 81 def test_checkpoint_subdir(self):
81 82 subd = u'sub βˆ‚ir'
82 83 cp_name = 'test-cp.ipynb'
83 84 with TemporaryDirectory() as td:
84 85 root = td
85 86 os.mkdir(os.path.join(td, subd))
86 87 fm = FileContentsManager(root_dir=root)
87 88 cpm = fm.checkpoints
88 89 cp_dir = cpm.checkpoint_path(
89 90 'cp', 'test.ipynb'
90 91 )
91 92 cp_subdir = cpm.checkpoint_path(
92 93 'cp', '/%s/test.ipynb' % subd
93 94 )
94 95 self.assertNotEqual(cp_dir, cp_subdir)
95 96 self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name))
96 97 self.assertEqual(cp_subdir, os.path.join(root, subd, cpm.checkpoint_dir, cp_name))
97 98
98 99 @dec.skip_win32
99 100 def test_bad_symlink(self):
100 101 with TemporaryDirectory() as td:
101 102 cm = FileContentsManager(root_dir=td)
102 103 path = 'test bad symlink'
103 104 _make_dir(cm, path)
104 105
105 106 file_model = cm.new_untitled(path=path, ext='.txt')
106 107
107 108 # create a broken symlink
108 109 self.symlink(cm, "target", '%s/%s' % (path, 'bad symlink'))
109 110 model = cm.get(path)
110 111 self.assertEqual(model['content'], [file_model])
111 112
112 113 @dec.skip_win32
113 114 def test_good_symlink(self):
114 115 with TemporaryDirectory() as td:
115 116 cm = FileContentsManager(root_dir=td)
116 117 parent = 'test good symlink'
117 118 name = 'good symlink'
118 119 path = '{0}/{1}'.format(parent, name)
119 120 _make_dir(cm, parent)
120 121
121 122 file_model = cm.new(path=parent + '/zfoo.txt')
122 123
123 124 # create a good symlink
124 125 self.symlink(cm, file_model['path'], path)
125 126 symlink_model = cm.get(path, content=False)
126 127 dir_model = cm.get(parent)
127 128 self.assertEqual(
128 129 sorted(dir_model['content'], key=lambda x: x['name']),
129 130 [symlink_model, file_model],
130 131 )
132
133 def test_403(self):
134 if hasattr(os, 'getuid'):
135 if os.getuid() == 0:
136 raise SkipTest("Can't test permissions as root")
137
138 with TemporaryDirectory() as td:
139 cm = FileContentsManager(root_dir=td)
140 model = cm.new_untitled(type='file')
141 os_path = cm._get_os_path(model['path'])
142
143 os.chmod(os_path, 0o400)
144 try:
145 with cm.open(os_path, 'w') as f:
146 f.write(u"don't care")
147 except HTTPError as e:
148 self.assertEqual(e.status_code, 403)
149 else:
150 self.fail("Should have raised HTTPError(403)")
131 151
132 152
133 153 class TestContentsManager(TestCase):
134 154
135 155 def setUp(self):
136 156 self._temp_dir = TemporaryDirectory()
137 157 self.td = self._temp_dir.name
138 158 self.contents_manager = FileContentsManager(
139 159 root_dir=self.td,
140 160 )
141 161
142 162 def tearDown(self):
143 163 self._temp_dir.cleanup()
144 164
145 165 def make_dir(self, api_path):
146 166 """make a subdirectory at api_path
147 167
148 168 override in subclasses if contents are not on the filesystem.
149 169 """
150 170 _make_dir(self.contents_manager, api_path)
151 171
152 172 def add_code_cell(self, nb):
153 173 output = nbformat.new_output("display_data", {'application/javascript': "alert('hi');"})
154 174 cell = nbformat.new_code_cell("print('hi')", outputs=[output])
155 175 nb.cells.append(cell)
156 176
157 177 def new_notebook(self):
158 178 cm = self.contents_manager
159 179 model = cm.new_untitled(type='notebook')
160 180 name = model['name']
161 181 path = model['path']
162 182
163 183 full_model = cm.get(path)
164 184 nb = full_model['content']
165 185 nb['metadata']['counter'] = int(1e6 * time.time())
166 186 self.add_code_cell(nb)
167 187
168 188 cm.save(full_model, path)
169 189 return nb, name, path
170 190
171 191 def test_new_untitled(self):
172 192 cm = self.contents_manager
173 193 # Test in root directory
174 194 model = cm.new_untitled(type='notebook')
175 195 assert isinstance(model, dict)
176 196 self.assertIn('name', model)
177 197 self.assertIn('path', model)
178 198 self.assertIn('type', model)
179 199 self.assertEqual(model['type'], 'notebook')
180 200 self.assertEqual(model['name'], 'Untitled.ipynb')
181 201 self.assertEqual(model['path'], 'Untitled.ipynb')
182 202
183 203 # Test in sub-directory
184 204 model = cm.new_untitled(type='directory')
185 205 assert isinstance(model, dict)
186 206 self.assertIn('name', model)
187 207 self.assertIn('path', model)
188 208 self.assertIn('type', model)
189 209 self.assertEqual(model['type'], 'directory')
190 210 self.assertEqual(model['name'], 'Untitled Folder')
191 211 self.assertEqual(model['path'], 'Untitled Folder')
192 212 sub_dir = model['path']
193 213
194 214 model = cm.new_untitled(path=sub_dir)
195 215 assert isinstance(model, dict)
196 216 self.assertIn('name', model)
197 217 self.assertIn('path', model)
198 218 self.assertIn('type', model)
199 219 self.assertEqual(model['type'], 'file')
200 220 self.assertEqual(model['name'], 'untitled')
201 221 self.assertEqual(model['path'], '%s/untitled' % sub_dir)
202 222
203 223 def test_get(self):
204 224 cm = self.contents_manager
205 225 # Create a notebook
206 226 model = cm.new_untitled(type='notebook')
207 227 name = model['name']
208 228 path = model['path']
209 229
210 230 # Check that we 'get' on the notebook we just created
211 231 model2 = cm.get(path)
212 232 assert isinstance(model2, dict)
213 233 self.assertIn('name', model2)
214 234 self.assertIn('path', model2)
215 235 self.assertEqual(model['name'], name)
216 236 self.assertEqual(model['path'], path)
217 237
218 238 nb_as_file = cm.get(path, content=True, type='file')
219 239 self.assertEqual(nb_as_file['path'], path)
220 240 self.assertEqual(nb_as_file['type'], 'file')
221 241 self.assertEqual(nb_as_file['format'], 'text')
222 242 self.assertNotIsInstance(nb_as_file['content'], dict)
223 243
224 244 nb_as_bin_file = cm.get(path, content=True, type='file', format='base64')
225 245 self.assertEqual(nb_as_bin_file['format'], 'base64')
226 246
227 247 # Test in sub-directory
228 248 sub_dir = '/foo/'
229 249 self.make_dir('foo')
230 250 model = cm.new_untitled(path=sub_dir, ext='.ipynb')
231 251 model2 = cm.get(sub_dir + name)
232 252 assert isinstance(model2, dict)
233 253 self.assertIn('name', model2)
234 254 self.assertIn('path', model2)
235 255 self.assertIn('content', model2)
236 256 self.assertEqual(model2['name'], 'Untitled.ipynb')
237 257 self.assertEqual(model2['path'], '{0}/{1}'.format(sub_dir.strip('/'), name))
238 258
239 259 # Test with a regular file.
240 260 file_model_path = cm.new_untitled(path=sub_dir, ext='.txt')['path']
241 261 file_model = cm.get(file_model_path)
242 262 self.assertDictContainsSubset(
243 263 {
244 264 'content': u'',
245 265 'format': u'text',
246 266 'mimetype': u'text/plain',
247 267 'name': u'untitled.txt',
248 268 'path': u'foo/untitled.txt',
249 269 'type': u'file',
250 270 'writable': True,
251 271 },
252 272 file_model,
253 273 )
254 274 self.assertIn('created', file_model)
255 275 self.assertIn('last_modified', file_model)
256 276
257 277 # Test getting directory model
258 278
259 279 # Create a sub-sub directory to test getting directory contents with a
260 280 # subdir.
261 281 self.make_dir('foo/bar')
262 282 dirmodel = cm.get('foo')
263 283 self.assertEqual(dirmodel['type'], 'directory')
264 284 self.assertIsInstance(dirmodel['content'], list)
265 285 self.assertEqual(len(dirmodel['content']), 3)
266 286 self.assertEqual(dirmodel['path'], 'foo')
267 287 self.assertEqual(dirmodel['name'], 'foo')
268 288
269 289 # Directory contents should match the contents of each individual entry
270 290 # when requested with content=False.
271 291 model2_no_content = cm.get(sub_dir + name, content=False)
272 292 file_model_no_content = cm.get(u'foo/untitled.txt', content=False)
273 293 sub_sub_dir_no_content = cm.get('foo/bar', content=False)
274 294 self.assertEqual(sub_sub_dir_no_content['path'], 'foo/bar')
275 295 self.assertEqual(sub_sub_dir_no_content['name'], 'bar')
276 296
277 297 for entry in dirmodel['content']:
278 298 # Order isn't guaranteed by the spec, so this is a hacky way of
279 299 # verifying that all entries are matched.
280 300 if entry['path'] == sub_sub_dir_no_content['path']:
281 301 self.assertEqual(entry, sub_sub_dir_no_content)
282 302 elif entry['path'] == model2_no_content['path']:
283 303 self.assertEqual(entry, model2_no_content)
284 304 elif entry['path'] == file_model_no_content['path']:
285 305 self.assertEqual(entry, file_model_no_content)
286 306 else:
287 307 self.fail("Unexpected directory entry: %s" % entry())
288 308
289 309 with self.assertRaises(HTTPError):
290 310 cm.get('foo', type='file')
291 311
292 312 def test_update(self):
293 313 cm = self.contents_manager
294 314 # Create a notebook
295 315 model = cm.new_untitled(type='notebook')
296 316 name = model['name']
297 317 path = model['path']
298 318
299 319 # Change the name in the model for rename
300 320 model['path'] = 'test.ipynb'
301 321 model = cm.update(model, path)
302 322 assert isinstance(model, dict)
303 323 self.assertIn('name', model)
304 324 self.assertIn('path', model)
305 325 self.assertEqual(model['name'], 'test.ipynb')
306 326
307 327 # Make sure the old name is gone
308 328 self.assertRaises(HTTPError, cm.get, path)
309 329
310 330 # Test in sub-directory
311 331 # Create a directory and notebook in that directory
312 332 sub_dir = '/foo/'
313 333 self.make_dir('foo')
314 334 model = cm.new_untitled(path=sub_dir, type='notebook')
315 335 path = model['path']
316 336
317 337 # Change the name in the model for rename
318 338 d = path.rsplit('/', 1)[0]
319 339 new_path = model['path'] = d + '/test_in_sub.ipynb'
320 340 model = cm.update(model, path)
321 341 assert isinstance(model, dict)
322 342 self.assertIn('name', model)
323 343 self.assertIn('path', model)
324 344 self.assertEqual(model['name'], 'test_in_sub.ipynb')
325 345 self.assertEqual(model['path'], new_path)
326 346
327 347 # Make sure the old name is gone
328 348 self.assertRaises(HTTPError, cm.get, path)
329 349
330 350 def test_save(self):
331 351 cm = self.contents_manager
332 352 # Create a notebook
333 353 model = cm.new_untitled(type='notebook')
334 354 name = model['name']
335 355 path = model['path']
336 356
337 357 # Get the model with 'content'
338 358 full_model = cm.get(path)
339 359
340 360 # Save the notebook
341 361 model = cm.save(full_model, path)
342 362 assert isinstance(model, dict)
343 363 self.assertIn('name', model)
344 364 self.assertIn('path', model)
345 365 self.assertEqual(model['name'], name)
346 366 self.assertEqual(model['path'], path)
347 367
348 368 # Test in sub-directory
349 369 # Create a directory and notebook in that directory
350 370 sub_dir = '/foo/'
351 371 self.make_dir('foo')
352 372 model = cm.new_untitled(path=sub_dir, type='notebook')
353 373 name = model['name']
354 374 path = model['path']
355 375 model = cm.get(path)
356 376
357 377 # Change the name in the model for rename
358 378 model = cm.save(model, path)
359 379 assert isinstance(model, dict)
360 380 self.assertIn('name', model)
361 381 self.assertIn('path', model)
362 382 self.assertEqual(model['name'], 'Untitled.ipynb')
363 383 self.assertEqual(model['path'], 'foo/Untitled.ipynb')
364 384
365 385 def test_delete(self):
366 386 cm = self.contents_manager
367 387 # Create a notebook
368 388 nb, name, path = self.new_notebook()
369 389
370 390 # Delete the notebook
371 391 cm.delete(path)
372 392
373 393 # Check that deleting a non-existent path raises an error.
374 394 self.assertRaises(HTTPError, cm.delete, path)
375 395
376 396 # Check that a 'get' on the deleted notebook raises and error
377 397 self.assertRaises(HTTPError, cm.get, path)
378 398
379 399 def test_copy(self):
380 400 cm = self.contents_manager
381 401 parent = u'Γ₯ b'
382 402 name = u'nb √.ipynb'
383 403 path = u'{0}/{1}'.format(parent, name)
384 404 self.make_dir(parent)
385 405
386 406 orig = cm.new(path=path)
387 407 # copy with unspecified name
388 408 copy = cm.copy(path)
389 409 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy1.ipynb'))
390 410
391 411 # copy with specified name
392 412 copy2 = cm.copy(path, u'Γ₯ b/copy 2.ipynb')
393 413 self.assertEqual(copy2['name'], u'copy 2.ipynb')
394 414 self.assertEqual(copy2['path'], u'Γ₯ b/copy 2.ipynb')
395 415 # copy with specified path
396 416 copy2 = cm.copy(path, u'/')
397 417 self.assertEqual(copy2['name'], name)
398 418 self.assertEqual(copy2['path'], name)
399 419
400 420 def test_trust_notebook(self):
401 421 cm = self.contents_manager
402 422 nb, name, path = self.new_notebook()
403 423
404 424 untrusted = cm.get(path)['content']
405 425 assert not cm.notary.check_cells(untrusted)
406 426
407 427 # print(untrusted)
408 428 cm.trust_notebook(path)
409 429 trusted = cm.get(path)['content']
410 430 # print(trusted)
411 431 assert cm.notary.check_cells(trusted)
412 432
413 433 def test_mark_trusted_cells(self):
414 434 cm = self.contents_manager
415 435 nb, name, path = self.new_notebook()
416 436
417 437 cm.mark_trusted_cells(nb, path)
418 438 for cell in nb.cells:
419 439 if cell.cell_type == 'code':
420 440 assert not cell.metadata.trusted
421 441
422 442 cm.trust_notebook(path)
423 443 nb = cm.get(path)['content']
424 444 for cell in nb.cells:
425 445 if cell.cell_type == 'code':
426 446 assert cell.metadata.trusted
427 447
428 448 def test_check_and_sign(self):
429 449 cm = self.contents_manager
430 450 nb, name, path = self.new_notebook()
431 451
432 452 cm.mark_trusted_cells(nb, path)
433 453 cm.check_and_sign(nb, path)
434 454 assert not cm.notary.check_signature(nb)
435 455
436 456 cm.trust_notebook(path)
437 457 nb = cm.get(path)['content']
438 458 cm.mark_trusted_cells(nb, path)
439 459 cm.check_and_sign(nb, path)
440 460 assert cm.notary.check_signature(nb)
441 461
General Comments 0
You need to be logged in to leave comments. Login now