123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662 |
- # -*- coding: utf-8 -*-
- import io
-
- import pytest
- from sqlalchemy.exc import InvalidRequestError
- from wsgidav.wsgidav_app import DEFAULT_CONFIG
- from tracim_backend import WebdavAppFactory
- from tracim_backend.lib.core.user import UserApi
- from tracim_backend.lib.webdav import TracimDomainController
- from tracim_backend.tests import eq_
- from tracim_backend.lib.core.notifications import DummyNotifier
- from tracim_backend.lib.webdav.dav_provider import Provider
- from tracim_backend.lib.webdav.resources import RootResource
- from tracim_backend.models import Content
- from tracim_backend.models import ContentRevisionRO
- from tracim_backend.tests import StandardTest
- from tracim_backend.fixtures.content import Content as ContentFixtures
- from tracim_backend.fixtures.users_and_groups import Base as BaseFixture
- from wsgidav import util
- from unittest.mock import MagicMock
-
-
- class TestWebdavFactory(StandardTest):
-
- def test_unit__initConfig__ok__nominal_case(self):
- """
- Check if config is correctly modify for wsgidav using mocked
- wsgidav and tracim conf (as dict)
- :return:
- """
- tracim_settings = {
- 'website.base_url': 'http://localhost:6543',
- 'sqlalchemy.url': 'sqlite:///:memory:',
- 'user.auth_token.validity': '604800',
- 'depot_storage_dir': '/tmp/test/depot',
- 'depot_storage_name': 'test',
- 'preview_cache_dir': '/tmp/test/preview_cache',
- 'wsgidav.config_path': 'development.ini'
-
- }
- wsgidav_setting = DEFAULT_CONFIG.copy()
- wsgidav_setting.update(
- {
- 'root_path': '',
- 'acceptbasic': True,
- 'acceptdigest': False,
- 'defaultdigest': False,
- }
- )
- mock = MagicMock()
- mock._initConfig = WebdavAppFactory._initConfig
- mock._readConfigFile.return_value = wsgidav_setting
- mock._get_tracim_settings.return_value = tracim_settings
- config = mock._initConfig(mock)
- assert config
- assert config['acceptbasic'] is True
- assert config['acceptdigest'] is False
- assert config['defaultdigest'] is False
- # TODO - G.M - 25-05-2018 - Better check for middleware stack config
- assert 'middleware_stack' in config
- assert len(config['middleware_stack']) == 7
- assert 'root_path' in config
- assert 'provider_mapping' in config
- assert config['root_path'] in config['provider_mapping']
- assert isinstance(config['provider_mapping'][config['root_path']], Provider) # nopep8
- assert 'domaincontroller' in config
- assert isinstance(config['domaincontroller'], TracimDomainController)
-
-
- class TestWebDav(StandardTest):
- fixtures = [BaseFixture, ContentFixtures]
-
- def _get_provider(self, config):
- return Provider(
- show_archived=False,
- show_deleted=False,
- show_history=False,
- app_config=config,
- )
-
- def _get_environ(
- self,
- provider: Provider,
- username: str,
- ) -> dict:
- return {
- 'http_authenticator.username': username,
- 'http_authenticator.realm': '/',
- 'wsgidav.provider': provider,
- 'tracim_user': self._get_user(username),
- 'tracim_dbsession': self.session,
- }
-
- def _get_user(self, email):
- return UserApi(None,
- self.session,
- self.app_config
- ).get_one_by_email(email)
-
- def _put_new_text_file(
- self,
- provider,
- environ,
- file_path,
- file_content,
- ):
- # This part id a reproduction of
- # wsgidav.request_server.RequestServer#doPUT
-
- # Grab parent folder where create file
- parentRes = provider.getResourceInst(
- util.getUriParent(file_path),
- environ,
- )
- assert parentRes, 'we should found folder for {0}'.format(file_path)
-
- new_resource = parentRes.createEmptyResource(
- util.getUriName(file_path),
- )
- write_object = new_resource.beginWrite(
- contentType='application/octet-stream',
- )
- write_object.write(file_content)
- write_object.close()
- new_resource.endWrite(withErrors=False)
-
- # Now file should exist
- return provider.getResourceInst(
- file_path,
- environ,
- )
-
- def test_unit__get_root__ok(self):
- provider = self._get_provider(self.app_config)
- root = provider.getResourceInst(
- '/',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
- assert root, 'Path / should return a RootResource instance'
- assert isinstance(root, RootResource)
-
- def test_unit__list_workspaces_with_user__ok(self):
- provider = self._get_provider(self.app_config)
- root = provider.getResourceInst(
- '/',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
- assert root, 'Path / should return a RootResource instance'
- assert isinstance(root, RootResource), 'Path / should return a RootResource instance'
-
- children = root.getMemberList()
- eq_(
- 2,
- len(children),
- msg='RootResource should return 2 workspaces instead {0}'.format(
- len(children),
- )
- )
-
- workspaces_names = [w.name for w in children]
- assert 'Recipes' in workspaces_names, \
- 'Recipes should be in names ({0})'.format(
- workspaces_names,
- )
- assert 'Others' in workspaces_names, 'Others should be in names ({0})'.format(
- workspaces_names,
- )
-
- def test_unit__list_workspaces_with_admin__ok(self):
- provider = self._get_provider(self.app_config)
- root = provider.getResourceInst(
- '/',
- self._get_environ(
- provider,
- 'admin@admin.admin',
- )
- )
- assert root, 'Path / should return a RootResource instance'
- assert isinstance(root, RootResource), 'Path / should return a RootResource instance'
-
- children = root.getMemberList()
- eq_(
- 2,
- len(children),
- msg='RootResource should return 3 workspaces instead {0}'.format(
- len(children),
- )
- )
-
- workspaces_names = [w.name for w in children]
- assert 'Recipes' in workspaces_names, 'Recipes should be in names ({0})'.format(
- workspaces_names,
- )
- assert 'Business' in workspaces_names, 'Business should be in names ({0})'.format(
- workspaces_names,
- )
-
- def test_unit__list_workspace_folders__ok(self):
- provider = self._get_provider(self.app_config)
- Recipes = provider.getResourceInst(
- '/Recipes/',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
- assert Recipes, 'Path /Recipes should return a Wrkspace instance'
-
- children = Recipes.getMemberList()
- eq_(
- 2,
- len(children),
- msg='Recipes should list 2 folders instead {0}'.format(
- len(children),
- ),
- )
-
- folders_names = [f.name for f in children]
- assert 'Salads' in folders_names, 'Salads should be in names ({0})'.format(
- folders_names,
- )
- assert 'Desserts' in folders_names, 'Desserts should be in names ({0})'.format(
- folders_names,
- )
-
- def test_unit__list_content__ok(self):
- provider = self._get_provider(self.app_config)
- Salads = provider.getResourceInst(
- '/Recipes/Desserts',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
- assert Salads, 'Path /Salads should return a Wrkspace instance'
-
- children = Salads.getMemberList()
- eq_(
- 5,
- len(children),
- msg='Salads should list 5 Files instead {0}'.format(
- len(children),
- ),
- )
-
- content_names = [c.name for c in children]
- assert 'Brownie Recipe.html' in content_names, \
- 'Brownie Recipe.html should be in names ({0})'.format(
- content_names,
- )
-
- assert 'Best Cakesʔ.html' in content_names,\
- 'Best Cakesʔ.html should be in names ({0})'.format(
- content_names,
- )
- assert 'Apple_Pie.txt' in content_names,\
- 'Apple_Pie.txt should be in names ({0})'.format(content_names,)
-
- assert 'Fruits Desserts' in content_names, \
- 'Fruits Desserts should be in names ({0})'.format(
- content_names,
- )
-
- assert 'Tiramisu Recipe.html' in content_names,\
- 'Tiramisu Recipe.html should be in names ({0})'.format(
- content_names,
- )
-
- def test_unit__get_content__ok(self):
- provider = self._get_provider(self.app_config)
- pie = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
-
- assert pie, 'Apple_Pie should be found'
- eq_('Apple_Pie.txt', pie.name)
-
- def test_unit__delete_content__ok(self):
- provider = self._get_provider(self.app_config)
- pie = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
-
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'Apple_Pie') \
- .one() # It must exist only one revision, cf fixtures
- eq_(
- False,
- content_pie.is_deleted,
- msg='Content should not be deleted !'
- )
- content_pie_id = content_pie.content_id
-
- pie.delete()
-
- self.session.flush()
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.content_id == content_pie_id) \
- .order_by(Content.revision_id.desc()) \
- .first()
- eq_(
- True,
- content_pie.is_deleted,
- msg='Content should be deleted!'
- )
-
- result = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
- eq_(None, result, msg='Result should be None instead {0}'.format(
- result
- ))
-
- def test_unit__create_content__ok(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- result = provider.getResourceInst(
- '/Recipes/Salads/greek_salad.txt',
- environ,
- )
-
- eq_(None, result, msg='Result should be None instead {0}'.format(
- result
- ))
-
- result = self._put_new_text_file(
- provider,
- environ,
- '/Recipes/Salads/greek_salad.txt',
- b'Greek Salad\n',
- )
-
- assert result, 'Result should not be None instead {0}'.format(
- result
- )
- eq_(
- b'Greek Salad\n',
- result.content.depot_file.file.read(),
- msg='fiel content should be "Greek Salad\n" but it is {0}'.format(
- result.content.depot_file.file.read()
- )
- )
-
- def test_unit__create_delete_and_create_file__ok(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- new_file = provider.getResourceInst(
- '/Recipes/Salads/greek_salad.txt',
- environ,
- )
-
- eq_(None, new_file, msg='Result should be None instead {0}'.format(
- new_file
- ))
-
- # create it
- new_file = self._put_new_text_file(
- provider,
- environ,
- '/Recipes/Salads/greek_salad.txt',
- b'Greek Salad\n',
- )
- assert new_file, 'Result should not be None instead {0}'.format(
- new_file
- )
-
- content_new_file = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'greek_salad') \
- .one() # It must exist only one revision
- eq_(
- False,
- content_new_file.is_deleted,
- msg='Content should not be deleted!'
- )
- content_new_file_id = content_new_file.content_id
-
- # Delete if
- new_file.delete()
-
- self.session.flush()
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.content_id == content_new_file_id) \
- .order_by(Content.revision_id.desc()) \
- .first()
- eq_(
- True,
- content_pie.is_deleted,
- msg='Content should be deleted!'
- )
-
- result = provider.getResourceInst(
- '/Recipes/Salads/greek_salad.txt',
- self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- )
- eq_(None, result, msg='Result should be None instead {0}'.format(
- result
- ))
-
- # Then create it again
- new_file = self._put_new_text_file(
- provider,
- environ,
- '/Recipes/Salads/greek_salad.txt',
- b'greek_salad\n',
- )
- assert new_file, 'Result should not be None instead {0}'.format(
- new_file
- )
-
- # Previous file is still dleeted
- self.session.flush()
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.content_id == content_new_file_id) \
- .order_by(Content.revision_id.desc()) \
- .first()
- eq_(
- True,
- content_pie.is_deleted,
- msg='Content should be deleted!'
- )
-
- # And an other file exist for this name
- content_new_new_file = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'greek_salad') \
- .order_by(Content.revision_id.desc()) \
- .first()
- assert content_new_new_file.content_id != content_new_file_id,\
- 'Contents ids should not be same!'
-
- eq_(
- False,
- content_new_new_file.is_deleted,
- msg='Content should not be deleted!'
- )
-
- def test_unit__rename_content__ok(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- pie = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- environ,
- )
-
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'Apple_Pie') \
- .one() # It must exist only one revision, cf fixtures
- assert content_pie, 'Apple_Pie should be exist'
- content_pie_id = content_pie.content_id
-
- pie.moveRecursive('/Recipes/Desserts/Apple_Pie_RENAMED.txt')
-
- # Database content is renamed
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(ContentRevisionRO.content_id == content_pie_id) \
- .order_by(ContentRevisionRO.revision_id.desc()) \
- .first()
- eq_(
- 'Apple_Pie_RENAMED',
- content_pie.label,
- msg='File should be labeled Apple_Pie_RENAMED, not {0}'.format(
- content_pie.label
- )
- )
-
- def test_unit__move_content__ok(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- pie = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- environ,
- )
-
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'Apple_Pie') \
- .one() # It must exist only one revision, cf fixtures
- assert content_pie, 'Apple_Pie should be exist'
- content_pie_id = content_pie.content_id
- content_pie_parent = content_pie.parent
- eq_(
- content_pie_parent.label,
- 'Desserts',
- msg='field parent should be Desserts',
- )
-
- pie.moveRecursive('/Recipes/Salads/Apple_Pie.txt') # move in f2
-
- # Database content is moved
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(ContentRevisionRO.content_id == content_pie_id) \
- .order_by(ContentRevisionRO.revision_id.desc()) \
- .first()
-
- assert content_pie.parent.label != content_pie_parent.label,\
- 'file should be moved in Salads but is in {0}'.format(
- content_pie.parent.label
- )
-
- def test_unit__move_and_rename_content__ok(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- pie = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- environ,
- )
-
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'Apple_Pie') \
- .one() # It must exist only one revision, cf fixtures
- assert content_pie, 'Apple_Pie should be exist'
- content_pie_id = content_pie.content_id
- content_pie_parent = content_pie.parent
- eq_(
- content_pie_parent.label,
- 'Desserts',
- msg='field parent should be Desserts',
- )
-
- pie.moveRecursive('/Others/Infos/Apple_Pie_RENAMED.txt')
-
- # Database content is moved
- content_pie = self.session.query(ContentRevisionRO) \
- .filter(ContentRevisionRO.content_id == content_pie_id) \
- .order_by(ContentRevisionRO.revision_id.desc()) \
- .first()
- assert content_pie.parent.label != content_pie_parent.label,\
- 'file should be moved in Recipesf2 but is in {0}'.format(
- content_pie.parent.label
- )
- eq_(
- 'Apple_Pie_RENAMED',
- content_pie.label,
- msg='File should be labeled Apple_Pie_RENAMED, not {0}'.format(
- content_pie.label
- )
- )
-
- def test_unit__move_content__ok__another_workspace(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- content_to_move_res = provider.getResourceInst(
- '/Recipes/Desserts/Apple_Pie.txt',
- environ,
- )
-
- content_to_move = self.session.query(ContentRevisionRO) \
- .filter(Content.label == 'Apple_Pie') \
- .one() # It must exist only one revision, cf fixtures
- assert content_to_move, 'Apple_Pie should be exist'
- content_to_move_id = content_to_move.content_id
- content_to_move_parent = content_to_move.parent
- eq_(
- content_to_move_parent.label,
- 'Desserts',
- msg='field parent should be Desserts',
- )
-
- content_to_move_res.moveRecursive('/Others/Infos/Apple_Pie.txt') # move in Business, f1
-
- # Database content is moved
- content_to_move = self.session.query(ContentRevisionRO) \
- .filter(ContentRevisionRO.content_id == content_to_move_id) \
- .order_by(ContentRevisionRO.revision_id.desc()) \
- .first()
-
- assert content_to_move.parent, 'Content should have a parent'
-
- assert content_to_move.parent.label == 'Infos',\
- 'file should be moved in Infos but is in {0}'.format(
- content_to_move.parent.label
- )
-
- def test_unit__update_content__ok(self):
- provider = self._get_provider(self.app_config)
- environ = self._get_environ(
- provider,
- 'bob@fsf.local',
- )
- result = provider.getResourceInst(
- '/Recipes/Salads/greek_salad.txt',
- environ,
- )
-
- eq_(None, result, msg='Result should be None instead {0}'.format(
- result
- ))
-
- result = self._put_new_text_file(
- provider,
- environ,
- '/Recipes/Salads/greek_salad.txt',
- b'hello\n',
- )
-
- assert result, 'Result should not be None instead {0}'.format(
- result
- )
- eq_(
- b'hello\n',
- result.content.depot_file.file.read(),
- msg='fiel content should be "hello\n" but it is {0}'.format(
- result.content.depot_file.file.read()
- )
- )
-
- # ReInit DummyNotifier counter
- DummyNotifier.send_count = 0
-
- # Update file content
- write_object = result.beginWrite(
- contentType='application/octet-stream',
- )
- write_object.write(b'An other line')
- write_object.close()
- result.endWrite(withErrors=False)
-
- eq_(
- 1,
- DummyNotifier.send_count,
- msg='DummyNotifier should send 1 mail, not {}'.format(
- DummyNotifier.send_count
- ),
- )
|