Browse Source

restore webdav tests

Guénaël Muller 7 years ago
parent
commit
882b924645

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

@@ -32,6 +32,7 @@ class Content(Fixture):
32 32
         content_api = ContentApi(
33 33
             current_user=admin,
34 34
             session=self._session,
35
+            config=self._config
35 36
         )
36 37
         role_api = RoleApi(
37 38
             current_user=admin,

+ 13 - 4
tracim/lib/webdav/resources.py View File

@@ -253,6 +253,7 @@ class Workspace(DAVCollection):
253 253
             content = resource.content
254 254
 
255 255
         return FakeFileStream(
256
+            session=self.session,
256 257
             file_name=file_name,
257 258
             content_api=self.content_api,
258 259
             workspace=self.workspace,
@@ -1045,7 +1046,8 @@ class File(DAVNonCollection):
1045 1046
             content_api=self.content_api,
1046 1047
             file_name=self.content.get_label_as_file(),
1047 1048
             workspace=self.content.workspace,
1048
-            path=self.path
1049
+            path=self.path,
1050
+            session=self.session,
1049 1051
         )
1050 1052
 
1051 1053
     def moveRecursive(self, destpath):
@@ -1136,8 +1138,15 @@ class File(DAVNonCollection):
1136 1138
                 self.content_api.save(self.content)
1137 1139
 
1138 1140
             # INFO - G.M - 2018-03-09 - Moving file if needed
1139
-            workspace_api = WorkspaceApi(self.user)
1140
-            content_api = ContentApi(self.user)
1141
+            workspace_api = WorkspaceApi(
1142
+                current_user=self.user,
1143
+                session=self.session,
1144
+                )
1145
+            content_api = ContentApi(
1146
+                current_user=self.user,
1147
+                session=self.session,
1148
+                config=self.provider.app_config
1149
+            )
1141 1150
 
1142 1151
             destination_workspace = self.provider.get_workspace_from_path(
1143 1152
                 destpath,
@@ -1314,7 +1323,7 @@ class HistoryOtherFile(OtherFile):
1314 1323
     A virtual resource corresponding to a specific tracim's revision's page and thread
1315 1324
     """
1316 1325
     def __init__(self, path: str, environ: dict, content: Content, user:User, content_revision: ContentRevisionRO):
1317
-        super(HistoryOtherFile, self).__init__(path, environ, content, user=user, session=session)
1326
+        super(HistoryOtherFile, self).__init__(path, environ, content, user=user, session=self.session)
1318 1327
         self.content_revision = content_revision
1319 1328
         self.content_designed = self.design()
1320 1329
 

+ 9 - 2
tracim/lib/webdav/utils.py View File

@@ -2,6 +2,8 @@
2 2
 
3 3
 import transaction
4 4
 from os.path import normpath as base_normpath
5
+
6
+from sqlalchemy.orm import Session
5 7
 from wsgidav import util
6 8
 from wsgidav import compat
7 9
 
@@ -97,6 +99,7 @@ class FakeFileStream(object):
97 99
 
98 100
     def __init__(
99 101
             self,
102
+            session: Session,
100 103
             content_api: ContentApi,
101 104
             workspace: Workspace,
102 105
             path: str,
@@ -114,7 +117,7 @@ class FakeFileStream(object):
114 117
         :param parent:
115 118
         """
116 119
         self._file_stream = compat.BytesIO()
117
-
120
+        self._session = session
118 121
         self._file_name = file_name if file_name != '' else self._content.file_name
119 122
         self._content = content
120 123
         self._api = content_api
@@ -193,7 +196,11 @@ class FakeFileStream(object):
193 196
         Called when we're updating an existing content; we create a new revision and update the file content
194 197
         """
195 198
 
196
-        with new_revision(self._content):
199
+        with new_revision(
200
+                session=self._session,
201
+                content=self._content,
202
+                tm=transaction.manager,
203
+        ):
197 204
             self._api.update_file_data(
198 205
                 self._content,
199 206
                 self._file_name,

+ 19 - 14
tracim/models/data.py View File

@@ -211,19 +211,19 @@ class ActionDescription(object):
211 211
     MOVE = 'move'
212 212
 
213 213
     # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
214
-    # _ICONS = {
215
-    #     'archiving': 'fa fa-archive',
216
-    #     'content-comment': 'fa-comment-o',
217
-    #     'creation': 'fa-magic',
218
-    #     'deletion': 'fa-trash',
219
-    #     'edition': 'fa-edit',
220
-    #     'revision': 'fa-history',
221
-    #     'status-update': 'fa-random',
222
-    #     'unarchiving': 'fa-file-archive-o',
223
-    #     'undeletion': 'fa-trash-o',
224
-    #     'move': 'fa-arrows',
225
-    #     'copy': 'fa-files-o',
226
-    # }
214
+    _ICONS = {
215
+        'archiving': 'fa fa-archive',
216
+        'content-comment': 'fa-comment-o',
217
+        'creation': 'fa-magic',
218
+        'deletion': 'fa-trash',
219
+        'edition': 'fa-edit',
220
+        'revision': 'fa-history',
221
+        'status-update': 'fa-random',
222
+        'unarchiving': 'fa-file-archive-o',
223
+        'undeletion': 'fa-trash-o',
224
+        'move': 'fa-arrows',
225
+        'copy': 'fa-files-o',
226
+    }
227 227
     #
228 228
     # _LABELS = {
229 229
     #     'archiving': l_('archive'),
@@ -242,9 +242,14 @@ class ActionDescription(object):
242 242
     def __init__(self, id):
243 243
         assert id in ActionDescription.allowed_values()
244 244
         self.id = id
245
+        # FIXME - G.M - 17-04-2018 - Label and icon needed for webdav
246
+        #  design template,
247
+        # find a way to not rely on this.
248
+        self.label = self.id
249
+        self.icon = ActionDescription._ICONS[id]
245 250
         # TODO - G.M - 10-04-2018 - [Cleanup] Drop this
246 251
         # self.label = ActionDescription._LABELS[id]
247
-        # self.icon = ActionDescription._ICONS[id]
252
+
248 253
         # self.css = ''
249 254
 
250 255
     @classmethod

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

@@ -0,0 +1,607 @@
1
+# -*- coding: utf-8 -*-
2
+import io
3
+
4
+from tracim.lib.core.user import UserApi
5
+from tracim.tests import eq_
6
+from tracim.lib.core.notifications import DummyNotifier
7
+from tracim.lib.webdav.dav_provider import Provider
8
+from tracim.lib.webdav.resources import Root
9
+from tracim.models import Content
10
+from tracim.models import ContentRevisionRO
11
+from tracim.tests import StandardTest
12
+from tracim.fixtures.content import Content as ContentFixtures
13
+from tracim.fixtures.users_and_groups import Base as BaseFixture
14
+from wsgidav import util
15
+
16
+
17
+class TestWebDav(StandardTest):
18
+    fixtures = [BaseFixture, ContentFixtures]
19
+
20
+    def _get_provider(self, config):
21
+        return Provider(
22
+            show_archived=False,
23
+            show_deleted=False,
24
+            show_history=False,
25
+            app_config=config,
26
+        )
27
+
28
+    def _get_environ(
29
+            self,
30
+            provider: Provider,
31
+            username: str,
32
+    ) -> dict:
33
+        return {
34
+            'http_authenticator.username': username,
35
+            'http_authenticator.realm': '/',
36
+            'wsgidav.provider': provider,
37
+            'tracim_user': self._get_user(username),
38
+            'tracim_dbsession': self.session,
39
+        }
40
+
41
+    def _get_user(self, email):
42
+        return UserApi(None,
43
+                       self.session,
44
+                       self.app_config
45
+                       ).get_one_by_email(email)
46
+    def _put_new_text_file(
47
+            self,
48
+            provider,
49
+            environ,
50
+            file_path,
51
+            file_content,
52
+    ):
53
+        # This part id a reproduction of
54
+        # wsgidav.request_server.RequestServer#doPUT
55
+
56
+        # Grab parent folder where create file
57
+        parentRes = provider.getResourceInst(
58
+            util.getUriParent(file_path),
59
+            environ,
60
+        )
61
+        assert parentRes, 'we should found folder for {0}'.format(file_path)
62
+
63
+        new_resource = parentRes.createEmptyResource(
64
+            util.getUriName(file_path),
65
+        )
66
+        write_object = new_resource.beginWrite(
67
+            contentType='application/octet-stream',
68
+        )
69
+        write_object.write(file_content)
70
+        write_object.close()
71
+        new_resource.endWrite(withErrors=False)
72
+
73
+        # Now file should exist
74
+        return provider.getResourceInst(
75
+            file_path,
76
+            environ,
77
+        )
78
+
79
+    def test_unit__get_root__ok(self):
80
+        provider = self._get_provider(self.app_config)
81
+        root = provider.getResourceInst(
82
+            '/',
83
+            self._get_environ(
84
+                provider,
85
+                'bob@fsf.local',
86
+            )
87
+        )
88
+        assert root, 'Path / should return a Root instance'
89
+        assert isinstance(root, Root)
90
+
91
+    def test_unit__list_workspaces_with_user__ok(self):
92
+        provider = self._get_provider(self.app_config)
93
+        root = provider.getResourceInst(
94
+            '/',
95
+            self._get_environ(
96
+                provider,
97
+                'bob@fsf.local',
98
+            )
99
+        )
100
+        assert root, 'Path / should return a Root instance'
101
+        assert isinstance(root, Root), 'Path / should return a Root instance'
102
+
103
+        children = root.getMemberList()
104
+        eq_(
105
+            2,
106
+            len(children),
107
+            msg='Root should return 2 workspaces instead {0}'.format(
108
+                len(children),
109
+            )
110
+        )
111
+
112
+        workspaces_names = [w.name for w in children]
113
+        assert 'w1' in workspaces_names, \
114
+            'w1 should be in names ({0})'.format(
115
+                workspaces_names,
116
+        )
117
+        assert 'w2' in workspaces_names, 'w2 should be in names ({0})'.format(
118
+            workspaces_names,
119
+        )
120
+
121
+    def test_unit__list_workspaces_with_admin__ok(self):
122
+        provider = self._get_provider(self.app_config)
123
+        root = provider.getResourceInst(
124
+            '/',
125
+            self._get_environ(
126
+                provider,
127
+                'admin@admin.admin',
128
+            )
129
+        )
130
+        assert root, 'Path / should return a Root instance'
131
+        assert isinstance(root, Root), 'Path / should return a Root instance'
132
+
133
+        children = root.getMemberList()
134
+        eq_(
135
+            2,
136
+            len(children),
137
+            msg='Root should return 2 workspaces instead {0}'.format(
138
+                len(children),
139
+            )
140
+        )
141
+
142
+        workspaces_names = [w.name for w in children]
143
+        assert 'w1' in workspaces_names, 'w1 should be in names ({0})'.format(
144
+            workspaces_names,
145
+        )
146
+        assert 'w3' in workspaces_names, 'w3 should be in names ({0})'.format(
147
+            workspaces_names,
148
+        )
149
+
150
+    def test_unit__list_workspace_folders__ok(self):
151
+        provider = self._get_provider(self.app_config)
152
+        w1 = provider.getResourceInst(
153
+            '/w1/',
154
+            self._get_environ(
155
+                provider,
156
+                'bob@fsf.local',
157
+            )
158
+        )
159
+        assert w1, 'Path /w1 should return a Wrkspace instance'
160
+
161
+        children = w1.getMemberList()
162
+        eq_(
163
+            2,
164
+            len(children),
165
+            msg='w1 should list 2 folders instead {0}'.format(
166
+                len(children),
167
+            ),
168
+        )
169
+
170
+        folders_names = [f.name for f in children]
171
+        assert 'w1f1' in folders_names, 'w1f1 should be in names ({0})'.format(
172
+                folders_names,
173
+        )
174
+        assert 'w1f2' in folders_names, 'w1f2 should be in names ({0})'.format(
175
+                folders_names,
176
+        )
177
+
178
+    def test_unit__list_content__ok(self):
179
+        provider = self._get_provider(self.app_config)
180
+        w1f1 = provider.getResourceInst(
181
+            '/w1/w1f1',
182
+            self._get_environ(
183
+                provider,
184
+                'bob@fsf.local',
185
+            )
186
+        )
187
+        assert w1f1, 'Path /w1f1 should return a Wrkspace instance'
188
+
189
+        children = w1f1.getMemberList()
190
+        eq_(
191
+            5,
192
+            len(children),
193
+            msg='w1f1 should list 5 folders instead {0}'.format(
194
+                len(children),
195
+            ),
196
+        )
197
+
198
+        content_names = [c.name for c in children]
199
+        assert 'w1f1p1.html' in content_names, \
200
+            'w1f1.html should be in names ({0})'.format(
201
+                content_names,
202
+        )
203
+
204
+        assert 'w1f1t1.html' in content_names,\
205
+            'w1f1t1.html should be in names ({0})'.format(
206
+                content_names,
207
+        )
208
+        assert 'w1f1d1.txt' in content_names,\
209
+            'w1f1d1.txt should be in names ({0})'.format(content_names,)
210
+
211
+        assert 'w1f1f1' in content_names, \
212
+            'w1f1f1 should be in names ({0})'.format(
213
+                content_names,
214
+        )
215
+
216
+        assert 'w1f1d2.html' in content_names,\
217
+            'w1f1d2.html should be in names ({0})'.format(
218
+                content_names,
219
+        )
220
+
221
+    def test_unit__get_content__ok(self):
222
+        provider = self._get_provider(self.app_config)
223
+        w1f1d1 = provider.getResourceInst(
224
+            '/w1/w1f1/w1f1d1.txt',
225
+            self._get_environ(
226
+                provider,
227
+                'bob@fsf.local',
228
+            )
229
+        )
230
+
231
+        assert w1f1d1, 'w1f1d1 should be found'
232
+        eq_('w1f1d1.txt', w1f1d1.name)
233
+
234
+    def test_unit__delete_content__ok(self):
235
+        provider = self._get_provider(self.app_config)
236
+        w1f1d1 = provider.getResourceInst(
237
+            '/w1/w1f1/w1f1d1.txt',
238
+            self._get_environ(
239
+                provider,
240
+                'bob@fsf.local',
241
+            )
242
+        )
243
+        
244
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
245
+            .filter(Content.label == 'w1f1d1') \
246
+            .one()  # It must exist only one revision, cf fixtures
247
+        eq_(
248
+            False,
249
+            content_w1f1d1.is_deleted,
250
+            msg='Content should not be deleted !'
251
+        )
252
+        content_w1f1d1_id = content_w1f1d1.content_id
253
+
254
+        w1f1d1.delete()
255
+
256
+        self.session.flush()
257
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
258
+            .filter(Content.content_id == content_w1f1d1_id) \
259
+            .order_by(Content.revision_id.desc()) \
260
+            .first()
261
+        eq_(
262
+            True,
263
+            content_w1f1d1.is_deleted,
264
+            msg='Content should be deleted !'
265
+        )
266
+
267
+        result = provider.getResourceInst(
268
+            '/w1/w1f1/w1f1d1.txt',
269
+            self._get_environ(
270
+                provider,
271
+                'bob@fsf.local',
272
+            )
273
+        )
274
+        eq_(None, result, msg='Result should be None instead {0}'.format(
275
+            result
276
+        ))
277
+
278
+    def test_unit__create_content__ok(self):
279
+        provider = self._get_provider(self.app_config)
280
+        environ = self._get_environ(
281
+            provider,
282
+            'bob@fsf.local',
283
+        )
284
+        result = provider.getResourceInst(
285
+            '/w1/w1f1/new_file.txt',
286
+            environ,
287
+        )
288
+
289
+        eq_(None, result, msg='Result should be None instead {0}'.format(
290
+            result
291
+        ))
292
+
293
+        result = self._put_new_text_file(
294
+            provider,
295
+            environ,
296
+            '/w1/w1f1/new_file.txt',
297
+            b'hello\n',
298
+        )
299
+
300
+        assert result, 'Result should not be None instead {0}'.format(
301
+            result
302
+        )
303
+        eq_(
304
+            b'hello\n',
305
+            result.content.depot_file.file.read(),
306
+            msg='fiel content should be "hello\n" but it is {0}'.format(
307
+                result.content.depot_file.file.read()
308
+            )
309
+        )
310
+
311
+    def test_unit__create_delete_and_create_file__ok(self):
312
+        provider = self._get_provider(self.app_config)
313
+        environ = self._get_environ(
314
+            provider,
315
+            'bob@fsf.local',
316
+        )
317
+        new_file = provider.getResourceInst(
318
+            '/w1/w1f1/new_file.txt',
319
+            environ,
320
+        )
321
+
322
+        eq_(None, new_file, msg='Result should be None instead {0}'.format(
323
+            new_file
324
+        ))
325
+
326
+        # create it
327
+        new_file = self._put_new_text_file(
328
+            provider,
329
+            environ,
330
+            '/w1/w1f1/new_file.txt',
331
+            b'hello\n',
332
+        )
333
+        assert new_file, 'Result should not be None instead {0}'.format(
334
+            new_file
335
+        )
336
+
337
+        content_new_file = self.session.query(ContentRevisionRO) \
338
+            .filter(Content.label == 'new_file') \
339
+            .one()  # It must exist only one revision
340
+        eq_(
341
+            False,
342
+            content_new_file.is_deleted,
343
+            msg='Content should not be deleted !'
344
+        )
345
+        content_new_file_id = content_new_file.content_id
346
+
347
+        # Delete if
348
+        new_file.delete()
349
+
350
+        self.session.flush()
351
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
352
+            .filter(Content.content_id == content_new_file_id) \
353
+            .order_by(Content.revision_id.desc()) \
354
+            .first()
355
+        eq_(
356
+            True,
357
+            content_w1f1d1.is_deleted,
358
+            msg='Content should be deleted !'
359
+        )
360
+
361
+        result = provider.getResourceInst(
362
+            '/w1/w1f1/new_file.txt',
363
+            self._get_environ(
364
+                provider,
365
+                'bob@fsf.local',
366
+            )
367
+        )
368
+        eq_(None, result, msg='Result should be None instead {0}'.format(
369
+            result
370
+        ))
371
+
372
+        # Then create it again
373
+        new_file = self._put_new_text_file(
374
+            provider,
375
+            environ,
376
+            '/w1/w1f1/new_file.txt',
377
+            b'hello\n',
378
+        )
379
+        assert new_file, 'Result should not be None instead {0}'.format(
380
+            new_file
381
+        )
382
+
383
+        # Previous file is still dleeted
384
+        self.session.flush()
385
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
386
+            .filter(Content.content_id == content_new_file_id) \
387
+            .order_by(Content.revision_id.desc()) \
388
+            .first()
389
+        eq_(
390
+            True,
391
+            content_w1f1d1.is_deleted,
392
+            msg='Content should be deleted !'
393
+        )
394
+
395
+        # And an other file exist for this name
396
+        content_new_new_file = self.session.query(ContentRevisionRO) \
397
+            .filter(Content.label == 'new_file') \
398
+            .order_by(Content.revision_id.desc()) \
399
+            .first()
400
+        assert content_new_new_file.content_id != content_new_file_id,\
401
+            'Contents ids should not be same !'
402
+
403
+        eq_(
404
+            False,
405
+            content_new_new_file.is_deleted,
406
+            msg='Content should not be deleted !'
407
+        )
408
+
409
+    def test_unit__rename_content__ok(self):
410
+        provider = self._get_provider(self.app_config)
411
+        environ = self._get_environ(
412
+            provider,
413
+            'bob@fsf.local',
414
+        )
415
+        w1f1d1 = provider.getResourceInst(
416
+            '/w1/w1f1/w1f1d1.txt',
417
+            environ,
418
+        )
419
+
420
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
421
+            .filter(Content.label == 'w1f1d1') \
422
+            .one()  # It must exist only one revision, cf fixtures
423
+        assert content_w1f1d1, 'w1f1d1 should be exist'
424
+        content_w1f1d1_id = content_w1f1d1.content_id
425
+
426
+        w1f1d1.moveRecursive('/w1/w1f1/w1f1d1_RENAMED.txt')
427
+
428
+        # Database content is renamed
429
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
430
+            .filter(ContentRevisionRO.content_id == content_w1f1d1_id) \
431
+            .order_by(ContentRevisionRO.revision_id.desc()) \
432
+            .first()
433
+        eq_(
434
+            'w1f1d1_RENAMED',
435
+            content_w1f1d1.label,
436
+            msg='File should be labeled w1f1d1_RENAMED, not {0}'.format(
437
+                content_w1f1d1.label
438
+            )
439
+        )
440
+
441
+    def test_unit__move_content__ok(self):
442
+        provider = self._get_provider(self.app_config)
443
+        environ = self._get_environ(
444
+            provider,
445
+            'bob@fsf.local',
446
+        )
447
+        w1f1d1 = provider.getResourceInst(
448
+            '/w1/w1f1/w1f1d1.txt',
449
+            environ,
450
+        )
451
+
452
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
453
+            .filter(Content.label == 'w1f1d1') \
454
+            .one()  # It must exist only one revision, cf fixtures
455
+        assert content_w1f1d1, 'w1f1d1 should be exist'
456
+        content_w1f1d1_id = content_w1f1d1.content_id
457
+        content_w1f1d1_parent = content_w1f1d1.parent
458
+        eq_(
459
+            content_w1f1d1_parent.label,
460
+            'w1f1',
461
+            msg='field parent should be w1f1',
462
+        )
463
+
464
+        w1f1d1.moveRecursive('/w1/w1f2/w1f1d1.txt')  # move in f2
465
+
466
+        # Database content is moved
467
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
468
+            .filter(ContentRevisionRO.content_id == content_w1f1d1_id) \
469
+            .order_by(ContentRevisionRO.revision_id.desc()) \
470
+            .first()
471
+
472
+        assert content_w1f1d1.parent.label != content_w1f1d1_parent.label,\
473
+            'file should be moved in w1f2 but is in {0}'.format(
474
+                content_w1f1d1.parent.label
475
+        )
476
+
477
+    def test_unit__move_and_rename_content__ok(self):
478
+        provider = self._get_provider(self.app_config)
479
+        environ = self._get_environ(
480
+            provider,
481
+            'bob@fsf.local',
482
+        )
483
+        w1f1d1 = provider.getResourceInst(
484
+            '/w1/w1f1/w1f1d1.txt',
485
+            environ,
486
+        )
487
+
488
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
489
+            .filter(Content.label == 'w1f1d1') \
490
+            .one()  # It must exist only one revision, cf fixtures
491
+        assert content_w1f1d1, 'w1f1d1 should be exist'
492
+        content_w1f1d1_id = content_w1f1d1.content_id
493
+        content_w1f1d1_parent = content_w1f1d1.parent
494
+        eq_(
495
+            content_w1f1d1_parent.label,
496
+            'w1f1',
497
+            msg='field parent should be w1f1',
498
+        )
499
+
500
+        w1f1d1.moveRecursive('/w1/w1f2/w1f1d1_RENAMED.txt')
501
+
502
+        # Database content is moved
503
+        content_w1f1d1 = self.session.query(ContentRevisionRO) \
504
+            .filter(ContentRevisionRO.content_id == content_w1f1d1_id) \
505
+            .order_by(ContentRevisionRO.revision_id.desc()) \
506
+            .first()
507
+        assert content_w1f1d1.parent.label != content_w1f1d1_parent.label,\
508
+            'file should be moved in w1f2 but is in {0}'.format(
509
+                content_w1f1d1.parent.label
510
+        )
511
+        eq_(
512
+            'w1f1d1_RENAMED',
513
+            content_w1f1d1.label,
514
+            msg='File should be labeled w1f1d1_RENAMED, not {0}'.format(
515
+                content_w1f1d1.label
516
+            )
517
+        )
518
+
519
+    def test_unit__move_content__ok__another_workspace(self):
520
+        provider = self._get_provider(self.app_config)
521
+        environ = self._get_environ(
522
+            provider,
523
+            'bob@fsf.local',
524
+        )
525
+        content_to_move_res = provider.getResourceInst(
526
+            '/w1/w1f1/w1f1d1.txt',
527
+            environ,
528
+        )
529
+
530
+        content_to_move = self.session.query(ContentRevisionRO) \
531
+            .filter(Content.label == 'w1f1d1') \
532
+            .one()  # It must exist only one revision, cf fixtures
533
+        assert content_to_move, 'w1f1d1 should be exist'
534
+        content_to_move_id = content_to_move.content_id
535
+        content_to_move_parent = content_to_move.parent
536
+        eq_(
537
+            content_to_move_parent.label,
538
+            'w1f1',
539
+            msg='field parent should be w1f1',
540
+        )
541
+
542
+        content_to_move_res.moveRecursive('/w2/w2f1/w1f1d1.txt')  # move in w2, f1
543
+
544
+        # Database content is moved
545
+        content_to_move = self.session.query(ContentRevisionRO) \
546
+            .filter(ContentRevisionRO.content_id == content_to_move_id) \
547
+            .order_by(ContentRevisionRO.revision_id.desc()) \
548
+            .first()
549
+
550
+        assert content_to_move.parent, 'Content should have a parent'
551
+
552
+        assert content_to_move.parent.label == 'w2f1',\
553
+            'file should be moved in w2f1 but is in {0}'.format(
554
+                content_to_move.parent.label
555
+        )
556
+
557
+    def test_unit__update_content__ok(self):
558
+        provider = self._get_provider(self.app_config)
559
+        environ = self._get_environ(
560
+            provider,
561
+            'bob@fsf.local',
562
+        )
563
+        result = provider.getResourceInst(
564
+            '/w1/w1f1/new_file.txt',
565
+            environ,
566
+        )
567
+
568
+        eq_(None, result, msg='Result should be None instead {0}'.format(
569
+            result
570
+        ))
571
+
572
+        result = self._put_new_text_file(
573
+            provider,
574
+            environ,
575
+            '/w1/w1f1/new_file.txt',
576
+            b'hello\n',
577
+        )
578
+
579
+        assert result, 'Result should not be None instead {0}'.format(
580
+            result
581
+        )
582
+        eq_(
583
+            b'hello\n',
584
+            result.content.depot_file.file.read(),
585
+            msg='fiel content should be "hello\n" but it is {0}'.format(
586
+                result.content.depot_file.file.read()
587
+            )
588
+        )
589
+
590
+        # ReInit DummyNotifier counter
591
+        DummyNotifier.send_count = 0
592
+
593
+        # Update file content
594
+        write_object = result.beginWrite(
595
+            contentType='application/octet-stream',
596
+        )
597
+        write_object.write(b'An other line')
598
+        write_object.close()
599
+        result.endWrite(withErrors=False)
600
+
601
+        eq_(
602
+            1,
603
+            DummyNotifier.send_count,
604
+            msg='DummyNotifier should send 1 mail, not {}'.format(
605
+                DummyNotifier.send_count
606
+            ),
607
+        )