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