Bastien Sevajol (Algoo) 8 years ago
parent
commit
4d1e40cba3

+ 114 - 0
tracim/tracim/fixtures/content.py View File

@@ -0,0 +1,114 @@
1
+# -*- coding: utf-8 -*-
2
+from tracim import model
3
+from tracim.fixtures import Fixture
4
+from tracim.fixtures.users_and_groups import Test
5
+from tracim.lib.content import ContentApi
6
+from tracim.lib.userworkspace import RoleApi
7
+from tracim.lib.workspace import WorkspaceApi
8
+from tracim.model.data import ContentType
9
+from tracim.model.data import UserRoleInWorkspace
10
+
11
+
12
+class Content(Fixture):
13
+    require = [Test]
14
+
15
+    def insert(self):
16
+        admin = self._session.query(model.User) \
17
+            .filter(model.User.email == 'admin@admin.admin') \
18
+            .one()
19
+        bob = self._session.query(model.User) \
20
+            .filter(model.User.email == 'bob@fsf.local') \
21
+            .one()
22
+        workspace_api = WorkspaceApi(admin)
23
+        content_api = ContentApi(admin)
24
+        role_api = RoleApi(admin)
25
+
26
+        # Workspaces
27
+        w1 = workspace_api.create_workspace('w1', save_now=True)
28
+        w2 = workspace_api.create_workspace('w2', save_now=True)
29
+        w3 = workspace_api.create_workspace('w3', save_now=True)
30
+
31
+        # Workspaces roles
32
+        role_api.create_one(
33
+            user=bob,
34
+            workspace=w1,
35
+            role_level=UserRoleInWorkspace.CONTENT_MANAGER,
36
+            with_notif=False,
37
+        )
38
+        role_api.create_one(
39
+            user=bob,
40
+            workspace=w2,
41
+            role_level=UserRoleInWorkspace.CONTENT_MANAGER,
42
+            with_notif=False,
43
+        )
44
+
45
+        w1f1 = content_api.create(
46
+            content_type=ContentType.Folder,
47
+            workspace=w1,
48
+            label='w1f1',
49
+            do_save=True,
50
+        )
51
+        w1f2 = content_api.create(
52
+            content_type=ContentType.Folder,
53
+            workspace=w1,
54
+            label='w1f2',
55
+            do_save=True,
56
+        )
57
+
58
+        # Folders
59
+        w2f1 = content_api.create(
60
+            content_type=ContentType.Folder,
61
+            workspace=w2,
62
+            label='w1f1',
63
+            do_save=True,
64
+        )
65
+        w2f2 = content_api.create(
66
+            content_type=ContentType.Folder,
67
+            workspace=w2,
68
+            label='w2f2',
69
+            do_save=True,
70
+        )
71
+
72
+        w3f1 = content_api.create(
73
+            content_type=ContentType.Folder,
74
+            workspace=w3,
75
+            label='w3f3',
76
+            do_save=True,
77
+        )
78
+
79
+        # Pages, threads, ..
80
+        w1f1p1 = content_api.create(
81
+            content_type=ContentType.Page,
82
+            workspace=w1,
83
+            parent=w1f1,
84
+            label='w1f1p1',
85
+            do_save=True,
86
+        )
87
+        w1f1t1 = content_api.create(
88
+            content_type=ContentType.Thread,
89
+            workspace=w1,
90
+            parent=w1f1,
91
+            label='w1f1t1',
92
+            do_save=False,
93
+        )
94
+        w1f1t1.description = 'w1f1t1 description'
95
+        self._session.add(w1f1t1)
96
+        w1f1d1 = content_api.create(
97
+            content_type=ContentType.File,
98
+            workspace=w1,
99
+            parent=w1f1,
100
+            label='w1f1d1',
101
+            do_save=False,
102
+        )
103
+        w1f1d1.file_extension = '.txt'
104
+        w1f1d1.file_content = b'w1f1d1 content'
105
+        self._session.add(w1f1d1)
106
+
107
+        w2f1p1 = content_api.create(
108
+            content_type=ContentType.Page,
109
+            workspace=w2,
110
+            parent=w2f1,
111
+            label='w2f1p1',
112
+            do_save=True,
113
+        )
114
+        self._session.flush()

+ 6 - 4
tracim/tracim/lib/webdav/sql_dav_provider.py View File

@@ -241,11 +241,13 @@ class Provider(DAVProvider):
241 241
         path = self.reduce_path(path)
242 242
         parent_path = dirname(path)
243 243
 
244
-        blbl = parent_path.replace('/'+workspace.label, '')
244
+        relative_parents_path = parent_path[len(workspace.label)+1:]
245
+        parents = relative_parents_path.split('/')
245 246
 
246
-        parents = blbl.split('/')
247
-
248
-        parents.remove('')
247
+        try:
248
+            parents.remove('')
249
+        except ValueError:
250
+            pass
249 251
         parents = [transform_to_bdd(x) for x in parents]
250 252
 
251 253
         try:

+ 2 - 0
tracim/tracim/lib/webdav/sql_resources.py View File

@@ -964,6 +964,8 @@ class File(DAVNonCollection):
964 964
                 self.content.file_extension = new_file_extension
965 965
                 self.content_api.save(self.content)
966 966
             else:
967
+                # TODO: ???le new parent ne peut être le parent actuel ?
968
+
967 969
                 self.content_api.move(
968 970
                     item=self.content,
969 971
                     new_parent=parent,

+ 440 - 0
tracim/tracim/tests/library/test_webdav.py View File

@@ -0,0 +1,440 @@
1
+# -*- coding: utf-8 -*-
2
+import io
3
+from nose.tools import eq_
4
+from nose.tools import ok_
5
+from tracim.lib.webdav.sql_dav_provider import Provider
6
+from tracim.lib.webdav.sql_resources import Root
7
+from tracim.model import Content
8
+from tracim.model import ContentRevisionRO
9
+from tracim.model import DBSession
10
+from tracim.tests import TestStandard
11
+from tracim.fixtures.content import Content as ContentFixtures
12
+from wsgidav import util
13
+
14
+
15
+class TestWebDav(TestStandard):
16
+    fixtures = [ContentFixtures]
17
+
18
+    def _get_provider(self):
19
+        return Provider(
20
+            show_archived=False,
21
+            show_deleted=False,
22
+            show_history=False,
23
+        )
24
+
25
+    def _get_environ(
26
+            self,
27
+            provider: Provider,
28
+            username: str,
29
+    ) -> dict:
30
+        return {
31
+            'http_authenticator.username': username,
32
+            'http_authenticator.realm': '/',
33
+            'wsgidav.provider': provider,
34
+        }
35
+
36
+    def _put_new_text_file(
37
+            self,
38
+            provider,
39
+            environ,
40
+            file_path,
41
+            file_content,
42
+    ):
43
+        # This part id a reproduction of
44
+        # wsgidav.request_server.RequestServer#doPUT
45
+
46
+        # Grab parent folder where create file
47
+        parentRes = provider.getResourceInst(
48
+            util.getUriParent(file_path),
49
+            environ,
50
+        )
51
+        ok_(parentRes, msg='we should found folder for {0}'.format(file_path))
52
+
53
+        new_resource = parentRes.createEmptyResource(
54
+            util.getUriName(file_path),
55
+        )
56
+        write_object = new_resource.beginWrite(
57
+            contentType='application/octet-stream',
58
+        )
59
+        write_object.write(b'hello\n')
60
+        write_object.close()
61
+        new_resource.endWrite(withErrors=False)
62
+
63
+        # Now file should exist
64
+        return provider.getResourceInst(
65
+            file_path,
66
+            environ,
67
+        )
68
+
69
+    def test_unit__get_root__ok(self):
70
+        provider = self._get_provider()
71
+        root = provider.getResourceInst(
72
+            '/',
73
+            self._get_environ(
74
+                provider,
75
+                'bob@fsf.local',
76
+            )
77
+        )
78
+        ok_(root, msg='Path / should return a Root instance')
79
+        ok_(isinstance(root, Root))
80
+
81
+    def test_unit__list_workspaces_with_admin__ok(self):
82
+        provider = self._get_provider()
83
+        root = provider.getResourceInst(
84
+            '/',
85
+            self._get_environ(
86
+                provider,
87
+                'bob@fsf.local',
88
+            )
89
+        )
90
+        ok_(root, msg='Path / should return a Root instance')
91
+        ok_(isinstance(root, Root), msg='Path / should return a Root instance')
92
+
93
+        children = root.getMemberList()
94
+        eq_(
95
+            2,
96
+            len(children),
97
+            msg='Root should return 2 workspaces instead {0}'.format(
98
+                len(children),
99
+            )
100
+        )
101
+
102
+        workspaces_names = [w.name for w in children]
103
+        ok_('w1' in workspaces_names, msg='w1 should be in names ({0})'.format(
104
+            workspaces_names,
105
+        ))
106
+        ok_('w2' in workspaces_names, msg='w2 should be in names ({0})'.format(
107
+            workspaces_names,
108
+        ))
109
+
110
+    def test_unit__list_workspace_folders__ok(self):
111
+        provider = self._get_provider()
112
+        w1 = provider.getResourceInst(
113
+            '/w1/',
114
+            self._get_environ(
115
+                provider,
116
+                'bob@fsf.local',
117
+            )
118
+        )
119
+        ok_(w1, msg='Path /w1 should return a Wrkspace instance')
120
+
121
+        children = w1.getMemberList()
122
+        eq_(
123
+            2,
124
+            len(children),
125
+            msg='w1 should list 2 folders instead {0}'.format(
126
+                len(children),
127
+            ),
128
+        )
129
+
130
+        folders_names = [f.name for f in children]
131
+        ok_(
132
+            'w1f1' in folders_names,
133
+            msg='w1f1 should be in names ({0})'.format(
134
+                folders_names,
135
+            )
136
+        )
137
+        ok_(
138
+            'w1f2' in folders_names,
139
+            msg='w1f2 should be in names ({0})'.format(
140
+                folders_names,
141
+            )
142
+        )
143
+
144
+    def test_unit__list_content__ok(self):
145
+        provider = self._get_provider()
146
+        w1f1 = provider.getResourceInst(
147
+            '/w1/w1f1',
148
+            self._get_environ(
149
+                provider,
150
+                'bob@fsf.local',
151
+            )
152
+        )
153
+        ok_(w1f1, msg='Path /w1f1 should return a Wrkspace instance')
154
+
155
+        children = w1f1.getMemberList()
156
+        eq_(
157
+            3,
158
+            len(children),
159
+            msg='w1f1 should list 3 folders instead {0}'.format(
160
+                len(children),
161
+            ),
162
+        )
163
+
164
+        content_names = [c.name for c in children]
165
+        ok_(
166
+            'w1f1p1.html' in content_names,
167
+            msg='w1f1.html should be in names ({0})'.format(
168
+                content_names,
169
+            )
170
+        )
171
+        ok_(
172
+            'w1f1t1.html' in content_names,
173
+            msg='w1f1t1.html should be in names ({0})'.format(
174
+                content_names,
175
+            )
176
+        )
177
+        ok_(
178
+            'w1f1d1.txt' in content_names,
179
+            msg='w1f1d1.txt should be in names ({0})'.format(
180
+                content_names,
181
+            )
182
+        )
183
+
184
+    def test_unit__get_content__ok(self):
185
+        provider = self._get_provider()
186
+        w1f1d1 = provider.getResourceInst(
187
+            '/w1/w1f1/w1f1d1.txt',
188
+            self._get_environ(
189
+                provider,
190
+                'bob@fsf.local',
191
+            )
192
+        )
193
+
194
+        ok_(w1f1d1, msg='w1f1d1 should be found')
195
+        eq_('w1f1d1.txt', w1f1d1.name)
196
+
197
+    def test_unit__delete_content__ok(self):
198
+        provider = self._get_provider()
199
+        w1f1d1 = provider.getResourceInst(
200
+            '/w1/w1f1/w1f1d1.txt',
201
+            self._get_environ(
202
+                provider,
203
+                'bob@fsf.local',
204
+            )
205
+        )
206
+
207
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
208
+            .filter(Content.label == 'w1f1d1') \
209
+            .one()  # It must exist only one revision, cf fixtures
210
+        eq_(
211
+            False,
212
+            content_w1f1d1.is_deleted,
213
+            msg='Content should not be deleted !'
214
+        )
215
+        content_w1f1d1_id = content_w1f1d1.content_id
216
+
217
+        w1f1d1.delete()
218
+
219
+        DBSession.flush()
220
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
221
+            .filter(Content.content_id == content_w1f1d1_id) \
222
+            .order_by(Content.revision_id.desc()) \
223
+            .first()
224
+        eq_(
225
+            True,
226
+            content_w1f1d1.is_deleted,
227
+            msg='Content should be deleted !'
228
+        )
229
+
230
+        result = provider.getResourceInst(
231
+            '/w1/w1f1/w1f1d1.txt',
232
+            self._get_environ(
233
+                provider,
234
+                'bob@fsf.local',
235
+            )
236
+        )
237
+        eq_(None, result, msg='Result should be None instead {0}'.format(
238
+            result
239
+        ))
240
+
241
+    def test_unit__create_content__ok(self):
242
+        provider = self._get_provider()
243
+        environ = self._get_environ(
244
+            provider,
245
+            'bob@fsf.local',
246
+        )
247
+        result = provider.getResourceInst(
248
+            '/w1/w1f1/new_file.txt',
249
+            environ,
250
+        )
251
+
252
+        eq_(None, result, msg='Result should be None instead {0}'.format(
253
+            result
254
+        ))
255
+
256
+        result = self._put_new_text_file(
257
+            provider,
258
+            environ,
259
+            '/w1/w1f1/new_file.txt',
260
+            b'hello\n',
261
+        )
262
+
263
+        ok_(result, msg='Result should not be None instead {0}'.format(
264
+            result
265
+        ))
266
+        eq_(
267
+            b'hello\n',
268
+            result.content.file_content,
269
+            msg='fiel content should be "hello\n" but it is {0}'.format(
270
+                result.content.file_content
271
+            )
272
+        )
273
+
274
+    def test_unit__create_delete_and_create_file__ok(self):
275
+        provider = self._get_provider()
276
+        environ = self._get_environ(
277
+            provider,
278
+            'bob@fsf.local',
279
+        )
280
+        new_file = provider.getResourceInst(
281
+            '/w1/w1f1/new_file.txt',
282
+            environ,
283
+        )
284
+
285
+        eq_(None, new_file, msg='Result should be None instead {0}'.format(
286
+            new_file
287
+        ))
288
+
289
+        # create it
290
+        new_file = self._put_new_text_file(
291
+            provider,
292
+            environ,
293
+            '/w1/w1f1/new_file.txt',
294
+            b'hello\n',
295
+        )
296
+        ok_(new_file, msg='Result should not be None instead {0}'.format(
297
+            new_file
298
+        ))
299
+
300
+        content_new_file = DBSession.query(ContentRevisionRO) \
301
+            .filter(Content.label == 'new_file') \
302
+            .one()  # It must exist only one revision
303
+        eq_(
304
+            False,
305
+            content_new_file.is_deleted,
306
+            msg='Content should not be deleted !'
307
+        )
308
+        content_new_file_id = content_new_file.content_id
309
+
310
+        # Delete if
311
+        new_file.delete()
312
+
313
+        DBSession.flush()
314
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
315
+            .filter(Content.content_id == content_new_file_id) \
316
+            .order_by(Content.revision_id.desc()) \
317
+            .first()
318
+        eq_(
319
+            True,
320
+            content_w1f1d1.is_deleted,
321
+            msg='Content should be deleted !'
322
+        )
323
+
324
+        result = provider.getResourceInst(
325
+            '/w1/w1f1/new_file.txt',
326
+            self._get_environ(
327
+                provider,
328
+                'bob@fsf.local',
329
+            )
330
+        )
331
+        eq_(None, result, msg='Result should be None instead {0}'.format(
332
+            result
333
+        ))
334
+
335
+        # Then create it again
336
+        new_file = self._put_new_text_file(
337
+            provider,
338
+            environ,
339
+            '/w1/w1f1/new_file.txt',
340
+            b'hello\n',
341
+        )
342
+        ok_(new_file, msg='Result should not be None instead {0}'.format(
343
+            new_file
344
+        ))
345
+
346
+        # Previous file is still dleeted
347
+        DBSession.flush()
348
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
349
+            .filter(Content.content_id == content_new_file_id) \
350
+            .order_by(Content.revision_id.desc()) \
351
+            .first()
352
+        eq_(
353
+            True,
354
+            content_w1f1d1.is_deleted,
355
+            msg='Content should be deleted !'
356
+        )
357
+
358
+        # And an other file exist for this name
359
+        content_new_new_file = DBSession.query(ContentRevisionRO) \
360
+            .filter(Content.label == 'new_file') \
361
+            .order_by(Content.revision_id.desc()) \
362
+            .first()
363
+        ok_(
364
+            content_new_new_file.content_id != content_new_file_id,
365
+            msg='Contents ids should not be same !'
366
+        )
367
+        eq_(
368
+            False,
369
+            content_new_new_file.is_deleted,
370
+            msg='Content should not be deleted !'
371
+        )
372
+
373
+    def test_unit__rename_content__ok(self):
374
+        provider = self._get_provider()
375
+        environ = self._get_environ(
376
+            provider,
377
+            'bob@fsf.local',
378
+        )
379
+        w1f1d1 = provider.getResourceInst(
380
+            '/w1/w1f1/w1f1d1.txt',
381
+            environ,
382
+        )
383
+
384
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
385
+            .filter(Content.label == 'w1f1d1') \
386
+            .one()  # It must exist only one revision, cf fixtures
387
+        ok_(content_w1f1d1, msg='w1f1d1 should be exist')
388
+        content_w1f1d1_id = content_w1f1d1.content_id
389
+
390
+        w1f1d1.moveRecursive('/w1/w1f1/w1f1d1_RENAMED.txt')
391
+
392
+        # Database content is renamed
393
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
394
+            .filter(ContentRevisionRO.content_id == content_w1f1d1_id) \
395
+            .order_by(ContentRevisionRO.revision_id.desc()) \
396
+            .first()
397
+        eq_(
398
+            'w1f1d1_RENAMED',
399
+            content_w1f1d1.label,
400
+            msg='File should be labeled w1f1d1_RENAMED, not {0}'.format(
401
+                content_w1f1d1.label
402
+            )
403
+        )
404
+
405
+    def test_unit__move_content__ok(self):
406
+        provider = self._get_provider()
407
+        environ = self._get_environ(
408
+            provider,
409
+            'bob@fsf.local',
410
+        )
411
+        w1f1d1 = provider.getResourceInst(
412
+            '/w1/w1f1/w1f1d1.txt',
413
+            environ,
414
+        )
415
+
416
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
417
+            .filter(Content.label == 'w1f1d1') \
418
+            .one()  # It must exist only one revision, cf fixtures
419
+        ok_(content_w1f1d1, msg='w1f1d1 should be exist')
420
+        content_w1f1d1_id = content_w1f1d1.content_id
421
+        content_w1f1d1_parent = content_w1f1d1.parent
422
+        eq_(
423
+            content_w1f1d1_parent.label,
424
+            'w1f1',
425
+            msg='field parent should be w1f1',
426
+        )
427
+
428
+        w1f1d1.moveRecursive('/w1/w1f2/w1f1d1.txt')  # move in f2
429
+
430
+        # Database content is moved
431
+        content_w1f1d1 = DBSession.query(ContentRevisionRO) \
432
+            .filter(ContentRevisionRO.content_id == content_w1f1d1_id) \
433
+            .order_by(ContentRevisionRO.revision_id.desc()) \
434
+            .first()
435
+        ok_(
436
+            content_w1f1d1.parent.label != content_w1f1d1_parent.label,
437
+            msg='file should be moved in w1f2 but is in {0}'.format(
438
+                content_w1f1d1.parent.label
439
+            )
440
+        )