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