Exemple #1
0
    def test_local_replace(self):
        local = LocalClient(self.local_test_folder_1)
        remote = self.remote_document_client_1
        self.engine_1.start()
        self.wait_sync(wait_for_async=True)

        # Create 2 files with the same name but different content
        # in separate folders
        local.make_file('/', 'test.odt', 'Some content.')
        local.make_folder('/', 'folder')
        shutil.copyfile(
            os.path.join(self.local_test_folder_1, 'test.odt'),
            os.path.join(self.local_test_folder_1, 'folder', 'test.odt'))
        local.update_content('/folder/test.odt', 'Updated content.')

        # Copy the newest file to the root workspace and synchronize it
        sync_root = os.path.join(self.local_nxdrive_folder_1,
                                 self.workspace_title)
        test_file = os.path.join(self.local_test_folder_1, 'folder',
                                 'test.odt')
        shutil.copyfile(test_file, os.path.join(sync_root, 'test.odt'))
        self.wait_sync()
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEqual(remote.get_content('/test.odt'), 'Updated content.')

        # Copy the oldest file to the root workspace and synchronize it.
        # First wait a bit for file time stamps to increase enough.
        time.sleep(OS_STAT_MTIME_RESOLUTION)
        shutil.copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                        os.path.join(sync_root, 'test.odt'))
        self.wait_sync()
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEqual(remote.get_content('/test.odt'), 'Some content.')
    def test_local_replace(self):
        local = LocalClient(self.local_test_folder_1)
        remote = self.remote_document_client_1
        self.engine_1.start()
        self.wait_sync(wait_for_async=True)

        # Create 2 files with the same name but different content
        # in separate folders
        local.make_file('/', 'test.odt', 'Some content.')
        local.make_folder('/', 'folder')
        copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                 os.path.join(self.local_test_folder_1, 'folder', 'test.odt'))
        local.update_content('/folder/test.odt', 'Updated content.')

        # Copy the newest file to the root workspace and synchronize it
        sync_root = os.path.join(self.local_nxdrive_folder_1,
                                 self.workspace_title)
        test_file = os.path.join(self.local_test_folder_1, 'folder',
                                 'test.odt')
        copyfile(test_file, os.path.join(sync_root, 'test.odt'))
        self.wait_sync()
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Updated content.')

        # Copy the oldest file to the root workspace and synchronize it.
        # First wait a bit for file time stamps to increase enough.
        time.sleep(OS_STAT_MTIME_RESOLUTION)
        copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                 os.path.join(sync_root, 'test.odt'))
        self.wait_sync()
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Some content.')
    def test_delete_local_folder_delay_remote_changes_fetch(self):

        # Get local and remote clients
        local = LocalClient(self.local_nxdrive_folder_1)
        remote = self.remote_document_client_1

        # Bind server and test workspace for nuxeoDriveTestUser_user_1
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait_audit_change_finder_if_needed()
        self.wait()
        sync = ctl.synchronizer
        sync.loop(delay=0.1, max_loops=1)

        # Test workspace should be created locally
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace'))

        # Create a local folder in the test workspace and a file inside
        # this folder, then synchronize
        local.make_folder('/Nuxeo Drive Test Workspace', 'Test folder')
        local.make_file('/Nuxeo Drive Test Workspace/Test folder',
                        'test.odt', 'Some content.')

        sync.loop(delay=0.1, max_loops=1)

        # Test folder should be created remotely in the test workspace
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(remote.exists('/Test folder/test.odt'))

        # Delete Test folder locally before fetching remote changes,
        # then synchronize
        local.delete('/Nuxeo Drive Test Workspace/Test folder')
        self.assertFalse(local.exists(
                                    '/Nuxeo Drive Test Workspace/Test folder'))

        self.wait_audit_change_finder_if_needed()
        self.wait()
        sync.loop(delay=0.1, max_loops=1)

        # Test folder should be deleted remotely in the test workspace.
        # Even though fetching the remote changes will send
        # 'documentCreated' events for Test folder and its child file
        # as a result of the previous synchronization loop, since the folder
        # will not have been renamed nor moved since last synchronization,
        # its remote pair state will not be marked as 'modified',
        # see Model.update_remote().
        # Thus the pair state will be ('deleted', 'synchronized'), resolved as
        # 'locally_deleted'.
        self.assertFalse(remote.exists('Test folder'))

        # Check Test folder has not been re-created locally
        self.assertFalse(local.exists(
                                    '/Nuxeo Drive Test Workspace/Test folder'))
    def test_synchronization_modification_on_created_file(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")
        ctl = self.controller_1
        # Regression test: a file is created locally, then modification is
        # detected before first upload
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)
        syn = ctl.synchronizer
        expected_folder = os.path.join(self.local_nxdrive_folder_1,
                                       self.workspace_title)
        self.assertEquals(ctl.list_pending(), [])

        self.wait()
        syn.loop(delay=0.010, max_loops=1)

        # Let's create some document on the client and the server
        local = LocalClient(expected_folder)
        local.make_folder('/', 'Folder')
        local.make_file('/Folder', 'File.txt', content='Some content.')

        # First local scan (assuming the network is offline):
        syn.scan_local(self.local_nxdrive_folder_1)
        self.assertEquals(len(ctl.list_pending()), 2)
        self.assertEquals(ctl.children_states(expected_folder), [
            (u'Folder', 'children_modified'),
        ])
        self.assertEquals(ctl.children_states(expected_folder + '/Folder'), [
            (u'File.txt', u'unknown'),
        ])

        # Wait a bit for file time stamps to increase enough: on most OS
        # the file modification time resolution is 1s
        time.sleep(OS_STAT_MTIME_RESOLUTION)

        # Let's modify it offline and rescan locally
        local.update_content('/Folder/File.txt', content='Some content.')
        syn.scan_local(self.local_nxdrive_folder_1)
        self.assertEquals(len(ctl.list_pending()), 2)
        self.assertEquals(ctl.children_states(expected_folder), [
            (u'Folder', u'children_modified'),
        ])
        self.assertEquals(ctl.children_states(expected_folder + '/Folder'), [
            (u'File.txt', u'locally_modified'),
        ])

        # Assume the computer is back online, the synchronization should occur
        # as if the document was just created and not trigger an update
        self.wait()
        syn.loop(delay=0.010, max_loops=1)
        self.assertEquals(ctl.list_pending(), [])
        self.assertEquals(ctl.children_states(expected_folder), [
            (u'Folder', u'synchronized'),
        ])
        self.assertEquals(ctl.children_states(expected_folder + '/Folder'), [
            (u'File.txt', u'synchronized'),
        ])
    def test_synchronize_remote_deletion(self):
        raise SkipTest("Skipped for the moment as it generates too much"
                       " error logs")

        """Test that deleting remote root document while uploading is handled

        See https://jira.nuxeo.com/browse/NXDRIVE-39
        See TestIntegrationSecurityUpdates.test_synchronize_denying_read_access
        as the same uses cases are tested
        """
        # Bind the server and root workspace
        ctl = self.controller_1
        # Override the behavior to force use of trash
        ctl.trash_modified_file = lambda: True
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1

        syn = ctl.synchronizer
        self._synchronize(syn)
        # Create documents in the local root workspace
        # then synchronize
        local.make_folder('/', 'Test folder')
        i = 0
        while i < 400:
            local.make_file('/Test folder', ('joe%d.bin' % i), 'Some content')
            i += 1

        self._synchronize(syn)
        # All files should not be synchronized
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(remote.exists('/Test folder/joe0.bin'))
        self.assertFalse(remote.exists('/Test folder/joe399.bin'))

        # Delete remote folder then synchronize
        remote.delete('/Test folder')
        # Error counter should be in place
        self._synchronize(syn)
        self.assertFalse(local.exists('/Test folder'))
Exemple #6
0
    def test_synchronize_remote_deletion(self):
        raise SkipTest("Skipped for the moment as it generates too much"
                       " error logs")
        """Test that deleting remote root document while uploading is handled

        See https://jira.nuxeo.com/browse/NXDRIVE-39
        See TestIntegrationSecurityUpdates.test_synchronize_denying_read_access
        as the same uses cases are tested
        """
        # Bind the server and root workspace
        ctl = self.controller_1
        # Override the behavior to force use of trash
        ctl.trash_modified_file = lambda: True
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(
            os.path.join(self.local_nxdrive_folder_1, self.workspace_title))
        remote = self.remote_document_client_1

        syn = ctl.synchronizer
        self._synchronize(syn)
        # Create documents in the local root workspace
        # then synchronize
        local.make_folder('/', 'Test folder')
        i = 0
        while i < 400:
            local.make_file('/Test folder', ('joe%d.bin' % i), 'Some content')
            i += 1

        self._synchronize(syn)
        # All files should not be synchronized
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(remote.exists('/Test folder/joe0.bin'))
        self.assertFalse(remote.exists('/Test folder/joe399.bin'))

        # Delete remote folder then synchronize
        remote.delete('/Test folder')
        # Error counter should be in place
        self._synchronize(syn)
        self.assertFalse(local.exists('/Test folder'))
    def test_local_replace(self):
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait_audit_change_finder_if_needed()
        self.wait()
        syn = ctl.synchronizer
        syn.loop(delay=0.1, max_loops=1)

        # Get local and remote clients
        local = LocalClient(self.local_test_folder_1)
        remote = self.remote_document_client_1

        # Create 2 files with the same name but different content
        # in separate folders
        local.make_file('/', 'test.odt', 'Some content.')
        local.make_folder('/', 'folder')
        copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                 os.path.join(self.local_test_folder_1, 'folder', 'test.odt'))
        local.update_content('/folder/test.odt', 'Updated content.')

        # Copy the newest file to the root workspace and synchronize it
        sync_root = os.path.join(self.local_nxdrive_folder_1,
                                 self.workspace_title)
        test_file = os.path.join(self.local_test_folder_1, 'folder',
                                 'test.odt')
        copyfile(test_file, os.path.join(sync_root, 'test.odt'))
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Updated content.')

        # Copy the oldest file to the root workspace and synchronize it.
        # First wait a bit for file time stamps to increase enough.
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                 os.path.join(sync_root, 'test.odt'))
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Some content.')
    def test_local_replace(self):
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn = ctl.synchronizer
        syn.loop(delay=0.1, max_loops=1)

        # Get local and remote clients
        local = LocalClient(self.local_test_folder_1)
        remote = self.remote_document_client_1

        # Create 2 files with the same name but different content
        # in separate folders
        local.make_file('/', 'test.odt', 'Some content.')
        local.make_folder('/', 'folder')
        copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                 os.path.join(self.local_test_folder_1, 'folder', 'test.odt'))
        local.update_content('/folder/test.odt', 'Updated content.')

        # Copy the newest file to the root workspace and synchronize it
        sync_root = os.path.join(self.local_nxdrive_folder_1,
                                 self.workspace_title)
        copyfile(os.path.join(self.local_test_folder_1, 'folder', 'test.odt'),
                 os.path.join(sync_root, 'test.odt'))
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Updated content.')

        # Copy the oldest file to the root workspace and synchronize it.
        # First wait a bit for file time stamps to increase enough.
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        copyfile(os.path.join(self.local_test_folder_1, 'test.odt'),
                 os.path.join(sync_root, 'test.odt'))
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Some content.')
    def test_local_replace(self):
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url, self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn = ctl.synchronizer
        syn.loop(delay=0.1, max_loops=1)

        # Get local and remote clients
        local = LocalClient(self.local_test_folder_1)
        remote = self.remote_document_client_1

        # Create 2 files with the same name but different content
        # in separate folders
        local.make_file("/", "test.odt", "Some content.")
        local.make_folder("/", "folder")
        copyfile(
            os.path.join(self.local_test_folder_1, "test.odt"),
            os.path.join(self.local_test_folder_1, "folder", "test.odt"),
        )
        local.update_content("/folder/test.odt", "Updated content.")

        # Copy the newest file to the root workspace and synchronize it
        sync_root = os.path.join(self.local_nxdrive_folder_1, self.workspace_title)
        copyfile(os.path.join(self.local_test_folder_1, "folder", "test.odt"), os.path.join(sync_root, "test.odt"))
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists("/test.odt"))
        self.assertEquals(remote.get_content("/test.odt"), "Updated content.")

        # Copy the oldest file to the root workspace and synchronize it.
        # First wait a bit for file time stamps to increase enough.
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        copyfile(os.path.join(self.local_test_folder_1, "test.odt"), os.path.join(sync_root, "test.odt"))
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists("/test.odt"))
        self.assertEquals(remote.get_content("/test.odt"), "Some content.")
    def test_trash_modified_file(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")
        """Test deleting a remote folder while a file is locally created in it.

        See https://jira.nuxeo.com/browse/NXDRIVE-39
        See TestIntegrationRemoteDeletion.test_synchronize_remote_deletion_local_modification
        as the same use case is tested.
        """
        ctl = self.controller_1
        # Override the behavior to force use of trash
        ctl.trash_modified_file = lambda: True

        # Bind the server and root workspace
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1

        syn = ctl.synchronizer

        # Create a remote folder then synchronize
        remote.make_folder('/', 'Test folder')
        self._synchronize(syn)
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder'))

        # Delete remote folder and create files in the local one then
        # synchronize
        remote.delete('/Test folder')
        local.make_file('/Test folder', 'joe.txt', 'My name is Joe.')
        self._synchronize(syn)

        self.assertFalse(remote.exists('/Test folder'))
        self.assertFalse(local.exists('/Test folder'))
Exemple #11
0
    def test_trash_modified_file(self):
        """Test deleting a remote folder while a file is locally created in it.

        See https://jira.nuxeo.com/browse/NXDRIVE-39
        See TestIntegrationRemoteDeletion.test_synchronize_remote_deletion_local_modification
        as the same use case is tested.
        """
        ctl = self.controller_1
        # Override the behavior to force use of trash
        ctl.trash_modified_file = lambda: True

        # Bind the server and root workspace
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(
            os.path.join(self.local_nxdrive_folder_1, self.workspace_title))
        remote = self.remote_document_client_1

        syn = ctl.synchronizer

        # Create a remote folder then synchronize
        remote.make_folder('/', 'Test folder')
        self._synchronize(syn)
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder'))

        # Delete remote folder and create files in the local one then
        # synchronize
        remote.delete('/Test folder')
        local.make_file('/Test folder', 'joe.txt', 'My name is Joe.')
        self._synchronize(syn)

        self.assertFalse(remote.exists('/Test folder'))
        self.assertFalse(local.exists('/Test folder'))
def test_synchronization_modification_on_created_file():
    # Regression test: a file is created locally, then modification is detected
    # before first upload
    ctl.bind_server(LOCAL_NXDRIVE_FOLDER, NUXEO_URL, USER, PASSWORD)
    ctl.bind_root(LOCAL_NXDRIVE_FOLDER, TEST_WORKSPACE)
    expected_folder = os.path.join(LOCAL_NXDRIVE_FOLDER, TEST_WORKSPACE_TITLE)
    assert_equal(ctl.list_pending(), [])

    # Let's create some document on the client and the server
    local = LocalClient(expected_folder)
    local.make_folder("/", "Folder")
    local.make_file("/Folder", "File.txt", content="Some content.")

    # First local scan (assuming the network is offline):
    ctl.scan_local(expected_folder)
    assert_equal(len(ctl.list_pending()), 2)
    assert_equal(ctl.children_states(expected_folder), [(u"/Folder", "children_modified")])
    assert_equal(ctl.children_states(expected_folder + "/Folder"), [(u"/Folder/File.txt", u"unknown")])

    # Wait a bit for file time stamps to increase enough: on most OS the file
    # modification time resolution is 1s
    time.sleep(1.0)

    # Let's modify it offline and rescan locally
    local.update_content("/Folder/File.txt", content="Some content.")
    ctl.scan_local(expected_folder)
    assert_equal(len(ctl.list_pending()), 2)
    assert_equal(ctl.children_states(expected_folder), [(u"/Folder", u"children_modified")])
    assert_equal(ctl.children_states(expected_folder + "/Folder"), [(u"/Folder/File.txt", u"locally_modified")])

    # Assume the computer is back online, the synchronization should occur as if
    # the document was just created and not trigger an update
    ctl.loop(full_local_scan=True, full_remote_scan=True, delay=0.010, max_loops=1, fault_tolerant=False)
    assert_equal(len(ctl.list_pending()), 0)
    assert_equal(ctl.children_states(expected_folder), [(u"/Folder", u"synchronized")])
    assert_equal(ctl.children_states(expected_folder + "/Folder"), [(u"/Folder/File.txt", u"synchronized")])
    def test_create_content_in_readonly_area(self):
        # XXX: implement permission checks on the client and leverage this
        # info in the synchronizer to not try to sync unsyncable stuff
        # and avoid logging errors in that case (just debug info)

        # Let's bind a the server but no root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        syn = ctl.synchronizer
        self.wait()

        syn.loop(delay=0.1, max_loops=1)
        self.assertEquals(ctl.list_pending(), [])

        # Let's create a subfolder of the main readonly folder
        local = LocalClient(self.local_nxdrive_folder_1)
        local.make_folder('/', 'Folder 3')
        local.make_file('/Folder 3', 'File 1.txt', content='Some content.')
        local.make_folder('/Folder 3', 'Sub Folder 1')
        local.make_file('/Folder 3/Sub Folder 1', 'File 2.txt',
                        content='Some other content.')
        syn.loop(delay=0.1, max_loops=1)

        # Pairs have been created for the subfolder and its content,
        # marked as synchronized
        self.assertEquals(self.get_all_states(), [
            (u'/', u'synchronized', u'synchronized'),
            (u'/Folder 3', u'synchronized', u'synchronized'),
            (u'/Folder 3/File 1.txt', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1/File 2.txt',
             u'synchronized', u'synchronized'),
        ])
        self.assertEquals(ctl.list_pending(), [])

        # Let's create a file in the main readonly folder
        local.make_file('/', 'A file in a readonly folder.txt',
            content='Some Content')
        syn.loop(delay=0.1, max_loops=1)

        # A pair has been created, marked as synchronized
        self.assertEquals(self.get_all_states(), [
            (u'/', u'synchronized', u'synchronized'),
            (u'/A file in a readonly folder.txt',
             u'synchronized', u'synchronized'),
            (u'/Folder 3', u'synchronized', u'synchronized'),
            (u'/Folder 3/File 1.txt', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1/File 2.txt',
             u'synchronized', u'synchronized'),
        ])
        self.assertEquals(len(ctl.list_pending(ignore_in_error=300)), 0)
    def test_delete_local_folder_update_remote_folder_property(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")

        # Get local and remote clients
        local = LocalClient(self.local_nxdrive_folder_1)
        remote = self.remote_document_client_1

        # Bind server and test workspace for nuxeoDriveTestUser_user_1
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait()
        sync = ctl.synchronizer
        sync.loop(delay=0.1, max_loops=1)

        # Test workspace should be created locally
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace'))

        # Create a local folder in the test workspace and a file inside
        # this folder, then synchronize
        local.make_folder('/Nuxeo Drive Test Workspace', 'Test folder')
        local.make_file('/Nuxeo Drive Test Workspace/Test folder',
                        'test.odt', 'Some content.')

        sync.loop(delay=0.1, max_loops=1)

        # Test folder should be created remotely in the test workspace
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(remote.exists('/Test folder/test.odt'))

        # Delete Test folder locally and remotely update one of its properties
        # concurrently, then synchronize
        local.delete('/Nuxeo Drive Test Workspace/Test folder')
        self.assertFalse(local.exists(
                                    '/Nuxeo Drive Test Workspace/Test folder'))
        test_folder_ref = remote._check_ref('/Test folder')
        # Wait for 1 second to make sure the folder's last modification time
        # will be different from the pair state's last remote update time
        time.sleep(REMOTE_MODIFICATION_TIME_RESOLUTION)
        remote.update(test_folder_ref,
                      properties={'dc:description': 'Some description.'})
        test_folder = remote.fetch(test_folder_ref)
        self.assertEqual(test_folder['properties']['dc:description'],
                         'Some description.')

        self.wait()
        sync.loop(delay=0.1, max_loops=1)

        # Test folder should be deleted remotely in the test workspace.
        # Even though fetching the remote changes will send a
        # 'documentModified' event for Test folder as a result of its
        # dc:description property update, since the folder will not have been
        # renamed nor moved since last synchronization, its remote pair state
        # will not be marked as 'modified', see Model.update_remote().
        # Thus the pair state will be ('deleted', 'synchronized'), resolved as
        # 'locally_deleted'.
        self.assertFalse(remote.exists('/Test folder'))

        # Check Test folder has not been re-created locally
        self.assertFalse(local.exists(
                                    '/Nuxeo Drive Test Workspace/Test folder'))
class TestIntegrationLocalMoveAndRename(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationLocalMoveAndRename, self).setUp()

        self.sb_1 = self.controller_1.bind_server(
            self.local_nxdrive_folder_1,
            self.nuxeo_url, self.user_1, self.password_1)

        self.controller_1.bind_root(self.local_nxdrive_folder_1,
            self.workspace)

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

        self.sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                       self.workspace_title)
        self.local_client_1 = LocalClient(self.sync_root_folder_1)

        self.local_client_1.make_file('/', u'Original File 1.txt',
            content=u'Some Content 1'.encode('utf-8'))

        self.local_client_1.make_file('/', u'Original File 2.txt',
            content=u'Some Content 2'.encode('utf-8'))

        self.local_client_1.make_folder(u'/', u'Original Folder 1')
        self.local_client_1.make_folder(
            u'/Original Folder 1', u'Sub-Folder 1.1')
        self.local_client_1.make_folder(
            u'/Original Folder 1', u'Sub-Folder 1.2')
        self.local_client_1.make_file(u'/Original Folder 1',
            u'Original File 1.1.txt',
            content=u'Some Content 1'.encode('utf-8'))  # Same content as OF1

        self.local_client_1.make_folder('/', 'Original Folder 2')
        self.local_client_1.make_file('/Original Folder 2',
            u'Original File 3.txt',
            content=u'Some Content 3'.encode('utf-8'))

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

    def test_local_rename_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid
        local_client.rename(u'/Original File 1.txt', u'Renamed File 1.txt')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed File 1.txt'))
        original_file_1_remote_info = remote_client.get_info(
            original_file_1_uid)
        self.assertEquals(original_file_1_remote_info.name,
            u'Renamed File 1.txt')

        # Rename 'Renamed File 1.txt' to 'Renamed Again File 1.txt'
        # and 'Original File 1.1.txt' to
        # 'Renamed File 1.1.txt' at the same time as they share
        # the same digest but do not live in the same folder
        original_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        local_client.rename(
            u'/Original Folder 1/Original File 1.1.txt',
            u'Renamed File 1.1 \xe9.txt')
        self.assertFalse(local_client.exists(
             '/Original Folder 1/Original File 1.1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1.1 \xe9.txt'))
        local_client.rename('/Renamed File 1.txt', 'Renamed Again File 1.txt')
        self.assertFalse(local_client.exists(u'/Renamed File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed Again File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 2)
        self.assertFalse(local_client.exists(u'/Renamed File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed Again File 1.txt'))
        self.assertFalse(local_client.exists(
             u'/Original Folder 1/Original File 1.1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1.1 \xe9.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name,
            u'Renamed Again File 1.txt')

        # User 1 does not have the rights to see the parent container
        # of the test workspace, hence set fetch_parent_uid=False
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid, fetch_parent_uid=False)
        self.assertEquals(parent_of_file_1_remote_info.name,
            self.workspace_title)

        file_1_1_remote_info = remote_client.get_info(original_1_1_uid)
        self.assertEquals(file_1_1_remote_info.name,
            u'Renamed File 1.1 \xe9.txt')

        parent_of_file_1_1_remote_info = remote_client.get_info(
            file_1_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_1_remote_info.name,
            u'Original Folder 1')

    def test_local_move_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid
        local_client.move(u'/Original File 1.txt', u'/Original Folder 1')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Original File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Original File 1.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Original File 1.txt')
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_remote_info.name,
            u'Original Folder 1')

    def test_local_move_and_rename_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid

        local_client.rename(u'/Original File 1.txt',
                            u'Renamed File 1 \xe9.txt')
        local_client.move(u'/Renamed File 1 \xe9.txt', u'/Original Folder 1')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1 \xe9.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1 \xe9.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Renamed File 1 \xe9.txt')
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_remote_info.name,
            u'Original Folder 1')

        # Nothing left to do
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_rename_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to renaming
        original_folder_1_uid = remote_client.get_info(
            u'/Original Folder 1').uid
        original_file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        original_sub_folder_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1').uid

        # Rename a non empty folder with some content
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1 \xe9')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1 \xe9'))

        # Synchronize: only the folder renaming is detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # The server folder has been renamed: the uid stays the same
        new_remote_name = remote_client.get_info(original_folder_1_uid).name
        self.assertEquals(new_remote_name, u"Renamed Folder 1 \xe9")

        # The content of the renamed folder is left unchanged
        file_1_1_info = remote_client.get_info(original_file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, original_folder_1_uid)

        sub_folder_1_1_info = remote_client.get_info(
            original_sub_folder_1_1_uid)
        self.assertEquals(sub_folder_1_1_info.name, u"Sub-Folder 1.1")
        self.assertEquals(sub_folder_1_1_info.parent_uid,
            original_folder_1_uid)

        # The more things change, the more they remain the same.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_move_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to move
        original_folder_1_uid = remote_client.get_info(
            u'/Original Folder 1').uid
        original_folder_2_uid = remote_client.get_info(
            u'/Original Folder 2').uid
        original_file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        original_sub_folder_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1').uid

        # Move a non empty folder with some content
        local_client.move(u'/Original Folder 1', u'/Original Folder 2')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 2/Original Folder 1'))

        # Synchronize: only the folder move is detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # The server folder has been moved: the uid stays the same
        remote_folder_info = remote_client.get_info(original_folder_1_uid)

        # The parent folder is not folder 2
        self.assertEquals(remote_folder_info.parent_uid,
            original_folder_2_uid)

        # The content of the renamed folder is left unchanged
        file_1_1_info = remote_client.get_info(original_file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, original_folder_1_uid)

        sub_folder_1_1_info = remote_client.get_info(
            original_sub_folder_1_1_uid)
        self.assertEquals(sub_folder_1_1_info.name, u"Sub-Folder 1.1")
        self.assertEquals(sub_folder_1_1_info.parent_uid,
            original_folder_1_uid)

        # The more things change, the more they remain the same.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_concurrent_local_rename_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to renaming
        folder_1_uid = remote_client.get_info(u'/Original Folder 1').uid
        file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        folder_2_uid = remote_client.get_info(u'/Original Folder 2').uid
        file_3_uid = remote_client.get_info(
            u'/Original Folder 2/Original File 3.txt').uid

        # Rename a non empty folders concurrently
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1')
        local_client.rename(u'/Original Folder 2', u'Renamed Folder 2')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1'))
        self.assertFalse(local_client.exists(u'/Original Folder 2'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 2'))

        # Synchronize: only the folder renamings are detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 2)

        # The server folders have been renamed: the uid stays the same
        folder_1_info = remote_client.get_info(folder_1_uid)
        self.assertEquals(folder_1_info.name, u"Renamed Folder 1")

        folder_2_info = remote_client.get_info(folder_2_uid)
        self.assertEquals(folder_2_info.name, u"Renamed Folder 2")

        # The content of the folder has been left unchanged
        file_1_1_info = remote_client.get_info(file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, folder_1_uid)

        file_3_info = remote_client.get_info(file_3_uid)
        self.assertEquals(file_3_info.name, u"Original File 3.txt")
        self.assertEquals(file_3_info.parent_uid, folder_2_uid)

        # The more things change, the more they remain the same.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_rename_sync_root_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        # Use the Administrator to be able to introspect the container of the
        # test workspace.
        remote_client = RemoteDocumentClient(
            self.nuxeo_url, self.admin_user,
            'nxdrive-test-administrator-device',
            self.password, base_folder=self.workspace)

        folder_1_uid = remote_client.get_info(u'/Original Folder 1').uid

        # Create new clients to be able to introspect the test sync root
        toplevel_local_client = LocalClient(self.local_nxdrive_folder_1)

        toplevel_local_client.rename('/' + self.workspace_title,
            'Renamed Nuxeo Drive Test Workspace')
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        workspace_info = remote_client.get_info(self.workspace)
        self.assertEquals(workspace_info.name,
            u"Renamed Nuxeo Drive Test Workspace")

        folder_1_info = remote_client.get_info(folder_1_uid)
        self.assertEquals(folder_1_info.name, u"Original Folder 1")
        self.assertEquals(folder_1_info.parent_uid, self.workspace)

    def test_local_rename_top_level_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = LocalClient(self.local_test_folder_1)
        session = ctl.get_session()

        # Check top level folder
        self.assertTrue(local_client.exists(u'/Nuxeo Drive'))
        top_level_folder_info = local_client.get_info(u'/Nuxeo Drive')
        self.assertEquals(top_level_folder_info.name, u'Nuxeo Drive')
        self.assertEquals(top_level_folder_info.filepath,
            os.path.join(self.local_test_folder_1, u'Nuxeo Drive'))
        # Check top level folder state
        top_level_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Nuxeo Drive').one()
        self.assertEquals(top_level_folder_state.local_path, '/')
        self.assertEquals(top_level_folder_state.local_name, u'Nuxeo Drive')

        # Rename top level folder
        local_client.rename(u'/Nuxeo Drive', u'Nuxeo Drive renamed')
        top_level_folder_info = local_client.get_info(u'/Nuxeo Drive renamed')
        self.assertEquals(top_level_folder_info.name, u'Nuxeo Drive renamed')
        self.assertEquals(top_level_folder_info.filepath,
            os.path.join(self.local_test_folder_1, u'Nuxeo Drive renamed'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # Check deleted server binding
        self.assertRaises(RuntimeError,
                          ctl.get_server_binding, self.local_nxdrive_folder_1,
                          raise_if_missing=True)
        # Check deleted pair state
        self.assertEquals(len(session.query(LastKnownState).all()), 0)

    def test_local_delete_top_level_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = LocalClient(self.local_test_folder_1)
        session = ctl.get_session()

        # Check top level folder
        self.assertTrue(local_client.exists(u'/Nuxeo Drive'))

        # Delete top level folder
        local_client.delete(u'/Nuxeo Drive')
        self.assertRaises(NotFound,
                          local_client.get_info, u'/Nuxeo Drive')

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # Check deleted server binding
        self.assertRaises(RuntimeError,
                          ctl.get_server_binding, self.local_nxdrive_folder_1,
                          raise_if_missing=True)
        # Check deleted pair state
        self.assertEquals(len(session.query(LastKnownState).all()), 0)

    def test_local_rename_readonly_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1
        session = ctl.get_session()

        # Check local folder
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertTrue(folder_1_state.remote_can_rename)

        # Set remote folder as readonly for test user
        folder_1_path = self.TEST_WORKSPACE_PATH + u'/Original Folder 1'
        op_input = "doc:" + folder_1_path
        self.root_remote_client.execute("Document.SetACE",
            input=op_input,
            user="******",
            permission="Write",
            grant="false")

        # Check can_rename flag in pair state
        folder_1_state.refresh_remote(
            client=self.remote_file_system_client_1)
        self.assertFalse(folder_1_state.remote_can_rename)

        # Rename local folder
        local_client.rename(u'/Original Folder 1',
                            u'Renamed Folder 1 \xe9')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1 \xe9'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # Check remote folder has not been renamed
        folder_1_remote_info = remote_client.get_info(
            u'/Original Folder 1')
        self.assertEquals(folder_1_remote_info.name,
            u'Original Folder 1')

        # Check state of local folder and its children
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Renamed Folder 1 \xe9').one()
        self.assertEquals(folder_1_state.local_name,
                          u'Renamed Folder 1 \xe9')
        self.assertEquals(folder_1_state.remote_name,
                          u'Original Folder 1')

        self.assertTrue(local_client.exists(
            u'/Renamed Folder 1 \xe9/Original File 1.1.txt'))
        file_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original File 1.1.txt').one()
        self.assertEquals(file_1_1_state.local_name,
                          u'Original File 1.1.txt')
        self.assertEquals(file_1_1_state.remote_name,
                          u'Original File 1.1.txt')

        self.assertTrue(local_client.exists(
            u'/Renamed Folder 1 \xe9/Sub-Folder 1.1'))
        folder_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.1').one()
        self.assertEquals(folder_1_1_state.local_name,
                          u'Sub-Folder 1.1')
        self.assertEquals(folder_1_1_state.remote_name,
                          u'Sub-Folder 1.1')

        self.assertTrue(local_client.exists(
            u'/Renamed Folder 1 \xe9/Sub-Folder 1.2'))
        folder_1_2_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.2').one()
        self.assertEquals(folder_1_2_state.local_name,
                          u'Sub-Folder 1.2')
        self.assertEquals(folder_1_2_state.remote_name,
                          u'Sub-Folder 1.2')

    def test_local_delete_readonly_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1
        session = ctl.get_session()

        # Check local folder
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertTrue(folder_1_state.remote_can_delete)

        # Set remote folder as readonly for test user
        folder_1_path = self.TEST_WORKSPACE_PATH + u'/Original Folder 1'
        op_input = "doc:" + folder_1_path
        self.root_remote_client.execute("Document.SetACE",
            input=op_input,
            user="******",
            permission="Write",
            grant="false")

        # Check can_delete flag in pair state
        folder_1_state.refresh_remote(
            client=self.remote_file_system_client_1)
        self.assertFalse(folder_1_state.remote_can_delete)

        # Delete local folder
        local_client.delete(u'/Original Folder 1')
        self.assertRaises(NotFound,
                          local_client.get_info, u'/Original Folder 1')

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 5)

        # Check remote folder and its children have not been deleted
        folder_1_remote_info = remote_client.get_info(
            u'/Original Folder 1')
        self.assertEquals(folder_1_remote_info.name,
            u'Original Folder 1')

        file_1_1_remote_info = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt')
        self.assertEquals(file_1_1_remote_info.name,
            u'Original File 1.1.txt')

        folder_1_1_remote_info = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1')
        self.assertEquals(folder_1_1_remote_info.name,
            u'Sub-Folder 1.1')

        folder_1_2_remote_info = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.2')
        self.assertEquals(folder_1_2_remote_info.name,
            u'Sub-Folder 1.2')

        # Check local folder and its children have been re-created
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertEquals(folder_1_state.local_name,
                          u'Original Folder 1')
        self.assertEquals(folder_1_state.remote_name,
                          u'Original Folder 1')

        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Original File 1.1.txt'))
        file_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original File 1.1.txt').one()
        self.assertEquals(file_1_1_state.local_name,
                          u'Original File 1.1.txt')
        self.assertEquals(file_1_1_state.remote_name,
                          u'Original File 1.1.txt')

        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Sub-Folder 1.1'))
        folder_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.1').one()
        self.assertEquals(folder_1_1_state.local_name,
                          u'Sub-Folder 1.1')
        self.assertEquals(folder_1_1_state.remote_name,
                          u'Sub-Folder 1.1')

        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Sub-Folder 1.2'))
        folder_1_2_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.2').one()
        self.assertEquals(folder_1_2_state.local_name,
                          u'Sub-Folder 1.2')
        self.assertEquals(folder_1_2_state.remote_name,
                          u'Sub-Folder 1.2')
    def test_create_content_in_readonly_area(self):
        self.engine_1.start()
        self.wait_sync(wait_for_async=True)

        # Let's create a subfolder of the main readonly folder
        local = LocalClient(self.local_nxdrive_folder_1)
        local.make_folder('/', 'Folder 3')
        local.make_file('/Folder 3', 'File 1.txt', content='Some content.')
        local.make_folder('/Folder 3', 'Sub Folder 1')
        local.make_file('/Folder 3/Sub Folder 1', 'File 2.txt', content='Some other content.')
        self.wait_sync()

        # States have been created for the subfolder and its content,
        # subfolder is marked as unsynchronized
        states = self.engine_1.get_dao().get_states_from_partial_local('/')
        self.assertEquals(len(states), 6)
        sorted_states = sorted(states, key=lambda x: x.local_path)
        self.assertEquals(sorted_states[0].local_name, '')
        self.assertEquals(sorted_states[0].pair_state, 'synchronized')
        self.assertEquals(sorted_states[1].local_name, 'Folder 3')
        self.assertEquals(sorted_states[1].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[2].local_name, 'File 1.txt')
        self.assertTrue(sorted_states[2].pair_state in ('locally_created', 'unsynchronized'))
        self.assertEquals(sorted_states[3].local_name, 'Sub Folder 1')
        self.assertTrue(sorted_states[3].pair_state in ('locally_created', 'unsynchronized'))
        self.assertEquals(sorted_states[4].local_name, 'File 2.txt')
        self.assertTrue(sorted_states[4].pair_state in ('locally_created', 'unsynchronized'))
        self.assertEquals(sorted_states[5].local_name, self.workspace_title)
        self.assertEquals(sorted_states[5].pair_state, 'synchronized')

        # Let's create a file in the main readonly folder
        local.make_file('/', 'A file in a readonly folder.txt', content='Some Content')
        self.wait_sync()

        # A state has been created, marked as unsynchronized
        # Other states are unchanged
        states = self.engine_1.get_dao().get_states_from_partial_local('/')
        self.assertEquals(len(states), 7)
        sorted_states = sorted(states, key=lambda x: x.local_path)
        self.assertEquals(sorted_states[0].local_name, '')
        self.assertEquals(sorted_states[0].pair_state, 'synchronized')
        self.assertEquals(sorted_states[1].local_name, 'A file in a readonly folder.txt')
        self.assertEquals(sorted_states[1].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[2].local_name, 'Folder 3')
        self.assertEquals(sorted_states[2].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[3].local_name, 'File 1.txt')
        self.assertTrue(sorted_states[3].pair_state in ('locally_created', 'unsynchronized'))
        self.assertEquals(sorted_states[4].local_name, 'Sub Folder 1')
        self.assertTrue(sorted_states[4].pair_state in ('locally_created', 'unsynchronized'))
        self.assertEquals(sorted_states[5].local_name, 'File 2.txt')
        self.assertTrue(sorted_states[5].pair_state in ('locally_created', 'unsynchronized'))
        self.assertEquals(sorted_states[6].local_name, self.workspace_title)
        self.assertEquals(sorted_states[6].pair_state, 'synchronized')

        # Let's create a file and a folder in a folder on which the Write
        # permission has been removed. Thanks to NXP-13119, this permission
        # change will be detected server-side, thus fetched by the client
        # in the remote change summary, and the remote_can_create_child flag
        # on which the synchronizer relies to check if creation is allowed
        # will be set to False and no attempt to create the remote file
        # will be made.
        # States will be marked as unsynchronized.

        # Create local folder and synchronize it remotely
        local = self.local_client_1
        local.make_folder(u'/', u'Readonly folder')
        self.wait_sync()

        remote = self.remote_document_client_1
        self.assertTrue(remote.exists(u'/Readonly folder'))

        # Check remote_can_create_child flag in pair state
        readonly_folder_state = self.engine_1.get_dao().get_state_from_local('/' + self.workspace_title
                                                                             + '/Readonly folder')
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Wait again for synchronization to detect remote folder creation triggered
        # by last synchronization and make sure we get a clean state at
        # next change summary
        self.wait_sync(wait_for_async=True)
        readonly_folder_state = self.engine_1.get_dao().get_state_from_local('/' + self.workspace_title
                                                                             + '/Readonly folder')
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Set remote folder as readonly for test user
        readonly_folder_path = TEST_WORKSPACE_PATH + u'/Readonly folder'
        op_input = "doc:" + readonly_folder_path
        self.root_remote_client.execute("Document.SetACE", op_input=op_input, user="******",
                                        permission="Read")
        self.root_remote_client.block_inheritance(readonly_folder_path, overwrite=False)

        # Wait to make sure permission change is detected.
        self.wait_sync(wait_for_async=True)
        # Re-fetch folder state and check remote_can_create_child flag has been updated
        readonly_folder_state = self.engine_1.get_dao().get_state_from_local('/' + self.workspace_title
                                                                             + '/Readonly folder')
        self.assertFalse(readonly_folder_state.remote_can_create_child)

        # Try to create a local file and folder in the readonly folder,
        # they should not be created remotely and be marked as unsynchronized.
        local.make_file(u'/Readonly folder', u'File in readonly folder', u"File content")
        local.make_folder(u'/Readonly folder', u'Folder in readonly folder')
        self.wait_sync()
        self.assertFalse(remote.exists(u'/Readonly folder/File in readonly folder'))
        self.assertFalse(remote.exists(u'/Readonly folder/Folder in readonly folder'))

        states = self.engine_1.get_dao().get_states_from_partial_local('/' + self.workspace_title + '/Readonly folder')
        self.assertEquals(len(states), 3)
        sorted_states = sorted(states, key=lambda x: x.local_path)
        self.assertEquals(sorted_states[0].local_name, 'Readonly folder')
        self.assertEquals(sorted_states[0].pair_state, 'synchronized')
        self.assertEquals(sorted_states[1].local_name, 'File in readonly folder')
        self.assertEquals(sorted_states[1].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[2].local_name, 'Folder in readonly folder')
        self.assertEquals(sorted_states[2].pair_state, 'unsynchronized')
class TestIntegrationVersioning(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationVersioning, self).setUp()

        self.controller_1.bind_server(self.local_nxdrive_folder_1,
            self.nuxeo_url, self.user_1, self.password_1)
        self.controller_2.bind_server(self.local_nxdrive_folder_2,
            self.nuxeo_url, self.user_2, self.password_2)
        self.controller_1.bind_root(self.local_nxdrive_folder_1,
            self.workspace)
        self.controller_2.bind_root(self.local_nxdrive_folder_2,
            self.workspace)

        self.syn_1 = self.controller_1.synchronizer
        self.syn_2 = self.controller_2.synchronizer
        self.syn_1.loop(delay=0.010, max_loops=1, no_event_init=True)
        self.syn_2.loop(delay=0.010, max_loops=1, no_event_init=True)

        # Fetch server bindings after sync loop as it closes the Session
        self.sb_1 = self.controller_1.get_server_binding(
            self.local_nxdrive_folder_1)
        self.sb_2 = self.controller_2.get_server_binding(
            self.local_nxdrive_folder_2)

        self.remote_client_1 = self.remote_document_client_1
        self.remote_client_2 = self.remote_document_client_2
        sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                       self.workspace_title)
        sync_root_folder_2 = os.path.join(self.local_nxdrive_folder_2,
                                       self.workspace_title)
        self.local_client_1 = LocalClient(sync_root_folder_1)
        self.local_client_2 = LocalClient(sync_root_folder_2)

        # Call the Nuxeo operation to set the versioning delay to 10 seconds
        self.versioning_delay = self.OS_STAT_MTIME_RESOLUTION * 10
        self.root_remote_client.execute(
            "NuxeoDrive.SetVersioningOptions",
            delay=str(self.versioning_delay))

    def test_versioning(self):
        # Create a file as user 1
        self.local_client_1.make_file('/', 'Test versioning.txt',
            "This is version 0")
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 0)

        # Synchronize it for user 2
        self.assertTrue(self.remote_client_2.exists('/Test versioning.txt'))
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1, wait=True)
        self.assertTrue(self.local_client_2.exists('/Test versioning.txt'))

        # Update it as user 2 => should be versioned
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        self.local_client_2.update_content('/Test versioning.txt',
            "Modified content")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 1)

        # Update it as user 2 => should NOT be versioned
        # since the versioning delay (10s) is not passed by
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        self.local_client_2.update_content('/Test versioning.txt',
            "Content twice modified")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 1)

        # Update it as user 2 after 10s => should be versioned
        # since the versioning delay is passed by
        time.sleep(self.versioning_delay + 0.1)
        self.local_client_2.update_content('/Test versioning.txt',
            "Updated again!!")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 2)

    def test_version_restore(self):
        remote_client = self.remote_client_1
        local_client = self.local_client_1

        # Create a remote doc
        doc = remote_client.make_file(self.workspace,
                                    'Document to restore.txt',
                                    content="Initial content.")
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        self.assertTrue(local_client.exists('/Document to restore.txt'))
        self.assertEquals(local_client.get_content('/Document to restore.txt'),
                          "Initial content.")

        # Create version 1.0, update content, then restore version 1.0
        remote_client.create_version(doc, 'Major')
        # Ensure that modification time is different between the version
        # and the updated live document, otherwise the synchronizer won't
        # consider the restored document (with the modification date of
        # the version) as to be updated
        time.sleep(1.0)
        remote_client.update_content(doc, "Updated content.")
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        self.assertEquals(local_client.get_content('/Document to restore.txt'),
                          "Updated content.")
        version_uid = remote_client.get_versions(doc)[0][0]
        remote_client.restore_version(version_uid)
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        self.assertEquals(local_client.get_content('/Document to restore.txt'),
                          "Initial content.")

    def _synchronize_and_assert(self, synchronizer, server_binding,
        expected_synchronized, wait=False):
        if wait:
            # Wait for audit changes to be detected after the 1 second step
            time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        n_synchronized = synchronizer.update_synchronize_server(server_binding)
        self.assertEqual(n_synchronized, expected_synchronized)

    def _assert_version(self, doc, major, minor):
        self.assertEquals(doc['properties']['uid:major_version'], major)
        self.assertEquals(doc['properties']['uid:minor_version'], minor)
    def test_update_local_file_content_update_remote_file_property(self):

        # Get local and remote clients
        local = LocalClient(self.local_nxdrive_folder_1)
        remote = self.remote_document_client_1

        # Bind server and test workspace for nuxeoDriveTestUser_user_1
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait_audit_change_finder_if_needed()
        self.wait()
        sync = ctl.synchronizer
        sync.loop(delay=0.1, max_loops=1)

        # Test workspace should be created locally
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace'))

        # Create a local file in the test workspace then synchronize
        local.make_file('/Nuxeo Drive Test Workspace',
                        'test.odt', 'Some content.')

        sync.loop(delay=0.1, max_loops=1)

        # Test file should be created remotely in the test workspace
        self.assertTrue(remote.exists('/test.odt'))

        # Locally update the file content and remotely update one of its
        # properties concurrently, then synchronize
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        local.update_content('/Nuxeo Drive Test Workspace/test.odt',
                             'Updated content.')
        self.assertEquals(local.get_content(
                                    '/Nuxeo Drive Test Workspace/test.odt'),
                          'Updated content.')
        test_file_ref = remote._check_ref('/test.odt')
        # Wait for 1 second to make sure the file's last modification time
        # will be different from the pair state's last remote update time
        time.sleep(self.REMOTE_MODIFICATION_TIME_RESOLUTION)
        remote.update(test_file_ref,
                      properties={'dc:description': 'Some description.'})
        test_file = remote.fetch(test_file_ref)
        self.assertEqual(test_file['properties']['dc:description'],
                         'Some description.')

        self.wait_audit_change_finder_if_needed()
        self.wait()
        sync.loop(delay=0.1, max_loops=2)

        # Test file should be updated remotely in the test workspace,
        # and no conflict should be detected.
        # Even though fetching the remote changes will send a
        # 'documentModified' event for the test file as a result of its
        # dc:description property update, since the file will not have been
        # renamed nor moved and its content not modified since last
        # synchronization, its remote pair state will not be marked as
        # 'modified', see Model.update_remote().
        # Thus the pair state will be ('modified', 'synchronized'), resolved as
        # 'locally_modified'.
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Updated content.')
        test_file = remote.fetch(test_file_ref)
        self.assertEqual(test_file['properties']['dc:description'],
                         'Some description.')
        self.assertEqual(len(remote.get_children_info(self.workspace)), 1)

        # Check that the content of the test file has not changed
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace/test.odt'))
        self.assertEquals(local.get_content(
                                    '/Nuxeo Drive Test Workspace/test.odt'),
                          'Updated content.')
        self.assertEqual(len(local.get_children_info(
                                            '/Nuxeo Drive Test Workspace')), 1)
Exemple #19
0
    def test_create_content_in_readonly_area(self):
        self.engine_1.start()
        self.wait_sync(wait_for_async=True)

        # Let's create a subfolder of the main readonly folder
        local = LocalClient(self.local_nxdrive_folder_1)
        local.make_folder('/', 'Folder 3')
        local.make_file('/Folder 3', 'File 1.txt', content='Some content.')
        local.make_folder('/Folder 3', 'Sub Folder 1')
        local.make_file('/Folder 3/Sub Folder 1',
                        'File 2.txt',
                        content='Some other content.')
        self.wait_sync()

        # States have been created for the subfolder and its content,
        # subfolder is marked as unsynchronized
        states = self.engine_1.get_dao().get_states_from_partial_local('/')
        self.assertEquals(len(states), 6)
        sorted_states = sorted(states, key=lambda x: x.local_path)
        self.assertEquals(sorted_states[0].local_name, '')
        self.assertEquals(sorted_states[0].pair_state, 'synchronized')
        self.assertEquals(sorted_states[1].local_name, 'Folder 3')
        self.assertEquals(sorted_states[1].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[2].local_name, 'File 1.txt')
        self.assertTrue(sorted_states[2].pair_state in ('locally_created',
                                                        'unsynchronized'))
        self.assertEquals(sorted_states[3].local_name, 'Sub Folder 1')
        self.assertTrue(sorted_states[3].pair_state in ('locally_created',
                                                        'unsynchronized'))
        self.assertEquals(sorted_states[4].local_name, 'File 2.txt')
        self.assertTrue(sorted_states[4].pair_state in ('locally_created',
                                                        'unsynchronized'))
        self.assertEquals(sorted_states[5].local_name, self.workspace_title)
        self.assertEquals(sorted_states[5].pair_state, 'synchronized')

        # Let's create a file in the main readonly folder
        local.make_file('/',
                        'A file in a readonly folder.txt',
                        content='Some Content')
        self.wait_sync()

        # A state has been created, marked as unsynchronized
        # Other states are unchanged
        states = self.engine_1.get_dao().get_states_from_partial_local('/')
        self.assertEquals(len(states), 7)
        sorted_states = sorted(states, key=lambda x: x.local_path)
        self.assertEquals(sorted_states[0].local_name, '')
        self.assertEquals(sorted_states[0].pair_state, 'synchronized')
        self.assertEquals(sorted_states[1].local_name,
                          'A file in a readonly folder.txt')
        self.assertEquals(sorted_states[1].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[2].local_name, 'Folder 3')
        self.assertEquals(sorted_states[2].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[3].local_name, 'File 1.txt')
        self.assertTrue(sorted_states[3].pair_state in ('locally_created',
                                                        'unsynchronized'))
        self.assertEquals(sorted_states[4].local_name, 'Sub Folder 1')
        self.assertTrue(sorted_states[4].pair_state in ('locally_created',
                                                        'unsynchronized'))
        self.assertEquals(sorted_states[5].local_name, 'File 2.txt')
        self.assertTrue(sorted_states[5].pair_state in ('locally_created',
                                                        'unsynchronized'))
        self.assertEquals(sorted_states[6].local_name, self.workspace_title)
        self.assertEquals(sorted_states[6].pair_state, 'synchronized')

        # Let's create a file and a folder in a folder on which the Write
        # permission has been removed. Thanks to NXP-13119, this permission
        # change will be detected server-side, thus fetched by the client
        # in the remote change summary, and the remote_can_create_child flag
        # on which the synchronizer relies to check if creation is allowed
        # will be set to False and no attempt to create the remote file
        # will be made.
        # States will be marked as unsynchronized.

        # Create local folder and synchronize it remotely
        local = self.local_client_1
        local.make_folder(u'/', u'Readonly folder')
        self.wait_sync()

        remote = self.remote_document_client_1
        self.assertTrue(remote.exists(u'/Readonly folder'))

        # Check remote_can_create_child flag in pair state
        readonly_folder_state = self.engine_1.get_dao().get_state_from_local(
            '/' + self.workspace_title + '/Readonly folder')
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Wait again for synchronization to detect remote folder creation triggered
        # by last synchronization and make sure we get a clean state at
        # next change summary
        self.wait_sync(wait_for_async=True)
        readonly_folder_state = self.engine_1.get_dao().get_state_from_local(
            '/' + self.workspace_title + '/Readonly folder')
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Set remote folder as readonly for test user
        readonly_folder_path = TEST_WORKSPACE_PATH + u'/Readonly folder'
        op_input = "doc:" + readonly_folder_path
        self.root_remote_client.execute("Document.SetACE",
                                        op_input=op_input,
                                        user=self.user_1,
                                        permission="Read")
        self.root_remote_client.block_inheritance(readonly_folder_path,
                                                  overwrite=False)

        # Wait to make sure permission change is detected.
        self.wait_sync(wait_for_async=True)
        # Re-fetch folder state and check remote_can_create_child flag has been updated
        readonly_folder_state = self.engine_1.get_dao().get_state_from_local(
            '/' + self.workspace_title + '/Readonly folder')
        self.assertFalse(readonly_folder_state.remote_can_create_child)

        # Try to create a local file and folder in the readonly folder,
        # they should not be created remotely and be marked as unsynchronized.
        local.make_file(u'/Readonly folder', u'File in readonly folder',
                        u"File content")
        local.make_folder(u'/Readonly folder', u'Folder in readonly folder')
        self.wait_sync()
        self.assertFalse(
            remote.exists(u'/Readonly folder/File in readonly folder'))
        self.assertFalse(
            remote.exists(u'/Readonly folder/Folder in readonly folder'))

        states = self.engine_1.get_dao().get_states_from_partial_local(
            '/' + self.workspace_title + '/Readonly folder')
        self.assertEquals(len(states), 3)
        sorted_states = sorted(states, key=lambda x: x.local_path)
        self.assertEquals(sorted_states[0].local_name, 'Readonly folder')
        self.assertEquals(sorted_states[0].pair_state, 'synchronized')
        self.assertEquals(sorted_states[1].local_name,
                          'File in readonly folder')
        self.assertEquals(sorted_states[1].pair_state, 'unsynchronized')
        self.assertEquals(sorted_states[2].local_name,
                          'Folder in readonly folder')
        self.assertEquals(sorted_states[2].pair_state, 'unsynchronized')
class TestIntegrationEncoding(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationEncoding, self).setUp()

        self.ctl = self.controller_1
        self.ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
            self.user_1, self.password_1)
        self.sb = self.ctl.get_server_binding(self.local_nxdrive_folder_1)
        self.ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        self.syn = self.ctl.synchronizer
        self.syn.loop(delay=0.010, max_loops=1)

        self.remote_client = self.remote_document_client_1
        sync_root_folder = os.path.join(self.local_nxdrive_folder_1,
            self.workspace_title)
        self.local_client = LocalClient(sync_root_folder)

    def test_filename_with_accents_from_server(self):
        self.remote_client.make_file(self.workspace,
            u'Nom sans accents.doc',
            u"Contenu sans accents.")
        self.remote_client.make_file(self.workspace,
            u'Nom avec accents \xe9 \xe8.doc',
            u"Contenu sans accents.")

        self._synchronize_and_assert(2, wait=True)

        self.assertEquals(self.local_client.get_content(
            u'/Nom sans accents.doc'),
            u"Contenu sans accents.")
        self.assertEquals(self.local_client.get_content(
            u'/Nom avec accents \xe9 \xe8.doc'),
            u"Contenu sans accents.")

    def test_content_with_accents_from_server(self):
        self.remote_client.make_file(self.workspace,
            u'Nom sans accents.txt',
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))
        self._synchronize_and_assert(1, wait=True)
        self.assertEquals(self.local_client.get_content(
            u'/Nom sans accents.txt'),
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))

    def test_filename_with_accents_from_client(self):
        self.local_client.make_file('/',
            u'Avec accents \xe9 \xe8.doc',
            u"Contenu sans accents.")
        self.local_client.make_file('/',
            u'Sans accents.doc',
            u"Contenu sans accents.")
        self._synchronize_and_assert(2)
        self.assertEquals(self.remote_client.get_content(
            u'/Avec accents \xe9 \xe8.doc'),
            u"Contenu sans accents.")
        self.assertEquals(self.remote_client.get_content(
            u'/Sans accents.doc'),
            u"Contenu sans accents.")

    def test_content_with_accents_from_client(self):
        self.local_client.make_file('/',
            u'Nom sans accents',
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))
        self._synchronize_and_assert(1)
        self.assertEquals(self.remote_client.get_content(
            u'/Nom sans accents'),
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))

    def _synchronize_and_assert(self, expected_synchronized, wait=False):
        if wait:
            # Wait for audit changes to be detected after the 1 second step
            time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        n_synchronized = self.syn.update_synchronize_server(self.sb)
        self.assertEqual(n_synchronized, expected_synchronized)
    def test_create_content_in_readonly_area(self):
        # TODO: ensure that a Write permission change is fetched by the client
        # in the remote change summary to update the remote_can_create_child
        # flag on which the synchronizer relies to check if creation
        # is allowed.
        # This way it won't try to sync unsyncable stuff and will avoid
        # logging errors in that case (just debug info)
        # See https://jira.nuxeo.com/browse/NXP-11159

        # Let's bind a the server but no root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        syn = ctl.synchronizer
        self.wait()

        syn.loop(delay=0.1, max_loops=1)
        self.assertEquals(ctl.list_pending(), [])

        # Let's create a subfolder of the main readonly folder
        local = LocalClient(self.local_nxdrive_folder_1)
        local.make_folder('/', 'Folder 3')
        local.make_file('/Folder 3', 'File 1.txt', content='Some content.')
        local.make_folder('/Folder 3', 'Sub Folder 1')
        local.make_file('/Folder 3/Sub Folder 1', 'File 2.txt',
                        content='Some other content.')
        syn.loop(delay=0.1, max_loops=1)

        # Pairs have been created for the subfolder and its content,
        # marked as synchronized
        self.assertEquals(self.get_all_states(), [
            (u'/', u'synchronized', u'synchronized'),
            (u'/Folder 3', u'synchronized', u'synchronized'),
            (u'/Folder 3/File 1.txt', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1/File 2.txt',
             u'synchronized', u'synchronized'),
        ])
        self.assertEquals(ctl.list_pending(), [])

        # Let's create a file in the main readonly folder
        local.make_file('/', 'A file in a readonly folder.txt',
            content='Some Content')
        syn.loop(delay=0.1, max_loops=1)

        # A pair has been created, marked as synchronized
        self.assertEquals(self.get_all_states(), [
            (u'/', u'synchronized', u'synchronized'),
            (u'/A file in a readonly folder.txt',
             u'synchronized', u'synchronized'),
            (u'/Folder 3', u'synchronized', u'synchronized'),
            (u'/Folder 3/File 1.txt', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1', u'synchronized', u'synchronized'),
            (u'/Folder 3/Sub Folder 1/File 2.txt',
             u'synchronized', u'synchronized'),
        ])
        self.assertEquals(len(ctl.list_pending(ignore_in_error=300)), 0)

        # Let's create a file and a folder in a folder on which the Write
        # permission has been removed. Waiting for NXP-11159, this permission
        # change will not be detected server-side, thus nor by the client,
        # so an attempt to create the remote file will be made and the server
        # will return a 403 status code. In this case the client should only
        # blacklist the file.

        # Bind root workspace, create local folder and synchronize it remotely
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn.loop(delay=0.1, max_loops=1)

        local = LocalClient(
            os.path.join(self.local_nxdrive_folder_1, self.workspace_title))
        local.make_folder(u'/', u'Readonly folder')
        syn.loop(delay=0.1, max_loops=1)

        remote = self.remote_document_client_1
        self.assertTrue(remote.exists(u'/Readonly folder'))

        # Check remote_can_create_child flag in pair state
        session = ctl.get_session()
        readonly_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Readonly folder').one()
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Make one sync loop to detect remote folder creation triggered
        # by last synchronization and make sure we get a clean state at
        # next change summary
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Set remote folder as readonly for test user
        readonly_folder_path = self.TEST_WORKSPACE_PATH + u'/Readonly folder'
        op_input = "doc:" + readonly_folder_path
        self.root_remote_client.execute("Document.SetACE",
            op_input=op_input,
            user="******",
            permission="Write",
            grant="false")

        # Wait to make sure permission change is detected, if such detection
        # is implemented server-side.
        # Waiting for NXP-11159 it is not, so the remote_can_create_child flag
        # should not be updated.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Try to create a local file and folder in the readonly folder,
        # they should not be created remotely and be blacklisted
        local.make_file(u'/Readonly folder', u'File in readonly folder',
                        u"File content")
        local.make_folder(u'/Readonly folder', u'Folder in readonly folder')
        syn.loop(delay=0.1, max_loops=1)
        self.assertFalse(remote.exists(
            u'/Readonly folder/File in readonly folder'))
        self.assertFalse(remote.exists(
            u'/Readonly folder/Folder in readonly folder'))
        readonly_file_state = session.query(LastKnownState).filter_by(
            local_name=u'File in readonly folder').one()
        readonly_sub_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Folder in readonly folder').one()
        self.assertIsNotNone(readonly_file_state.last_sync_error_date)
        self.assertIsNotNone(readonly_sub_folder_state.last_sync_error_date)
    def test_synchronize_paged_delete_detection(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")
        # Initialize a controller with page size = 1 for deleted items
        # detection query
        # TODO NXDRIVE-170: refactor
        #ctl = Controller(self.nxdrive_conf_folder_1, page_size=1)
        ctl = None
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait()
        syn = ctl.synchronizer
        syn.loop(delay=0.1, max_loops=1)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1

        # Create a remote folder with 2 children then synchronize
        remote.make_folder('/', 'Remote folder',)
        remote.make_file('/Remote folder', 'Remote file 1.odt',
                         'Some content.')
        remote.make_file('/Remote folder', 'Remote file 2.odt',
                         'Other content.')

        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(local.exists('/Remote folder'))
        self.assertTrue(local.exists('/Remote folder/Remote file 1.odt'))
        self.assertTrue(local.exists('/Remote folder/Remote file 2.odt'))

        # Delete remote folder then synchronize
        remote.delete('/Remote folder')

        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        self.assertFalse(local.exists('/Remote folder'))
        self.assertFalse(local.exists('/Remote folder/Remote file 1.odt'))
        self.assertFalse(local.exists('/Remote folder/Remote file 2.odt'))

        # Create a local folder with 2 children then synchronize
        local.make_folder('/', 'Local folder')
        local.make_file('/Local folder', 'Local file 1.odt', 'Some content.')
        local.make_file('/Local folder', 'Local file 2.odt', 'Other content.')

        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists('/Local folder'))
        self.assertTrue(remote.exists('/Local folder/Local file 1.odt'))
        self.assertTrue(remote.exists('/Local folder/Local file 2.odt'))

        # Delete local folder then synchronize
        time.sleep(OS_STAT_MTIME_RESOLUTION)
        local.delete('/Local folder')

        syn.loop(delay=0.1, max_loops=1)
        self.assertFalse(remote.exists('/Local folder'))
        # Wait for async completion as recursive deletion of children is done
        # by the BulkLifeCycleChangeListener which is asynchronous
        self.wait()
        self.assertFalse(remote.exists('/Local folder/Local file 1.odt'))
        self.assertFalse(remote.exists('/Local folder/Local file 2.odt'))

        # Dispose dedicated Controller instantiated for this test
        ctl.dispose()
    def test_conflict_detection_and_renaming(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)
        syn = ctl.synchronizer
        # Fetch the workspace sync root
        syn.loop(delay=0, max_loops=1, no_event_init=True)
        self.assertEquals(ctl.list_pending(), [])

        # Let's create some document on the client and synchronize it.
        local = LocalClient(self.local_nxdrive_folder_1)
        local_path = local.make_file('/' + self.workspace_title,
           'Some File.doc', content="Original content.")
        syn.loop(delay=0, max_loops=1, no_event_init=True)

        # Let's modify it concurrently but with the same content (digest)
        time.sleep(OS_STAT_MTIME_RESOLUTION)
        local.update_content(local_path, 'Same new content.')

        remote_2 = self.remote_document_client_2
        remote_2.update_content('/Some File.doc', 'Same new content.')

        # Let's synchronize and check the conflict handling: automatic
        # resolution will work for this case/
        self.wait()
        syn.loop(delay=0, max_loops=1, no_event_init=True)
        item_infos = local.get_children_info('/' + self.workspace_title)
        self.assertEquals(len(item_infos), 1)
        self.assertEquals(item_infos[0].name, 'Some File.doc')
        self.assertEquals(local.get_content(local_path), 'Same new content.')

        # Let's trigger another conflict that cannot be resolved
        # automatically:
        time.sleep(OS_STAT_MTIME_RESOLUTION)
        local.update_content(local_path, 'Local new content.')

        remote_2 = self.remote_document_client_2
        remote_2.update_content('/Some File.doc', 'Remote new content.')
        self.wait()
        # 2 loops are necessary for full conflict handling
        syn.loop(delay=0, max_loops=2, no_event_init=True)
        item_infos = local.get_children_info('/' + self.workspace_title)
        self.assertEquals(len(item_infos), 2)

        first, second = item_infos
        if first.name == 'Some File.doc':
            version_from_remote, version_from_local = first, second
        else:
            version_from_local, version_from_remote = first, second

        self.assertEquals(version_from_remote.name, 'Some File.doc')
        self.assertEquals(local.get_content(version_from_remote.path),
            'Remote new content.')

        self.assertTrue(version_from_local.name.startswith('Some File ('),
            msg="'%s' was expected to start with 'Some File ('"
                % version_from_local.name)
        self.assertTrue(version_from_local.name.endswith(').doc'),
            msg="'%s' was expected to end with ').doc'"
                % version_from_local.name)
        self.assertEquals(local.get_content(version_from_local.path),
            'Local new content.')

        # Everything is synchronized
        all_states = self.get_all_states()

        self.assertEquals(all_states[:2], [
            (u'/',
             u'synchronized', u'synchronized'),
            (u'/Nuxeo Drive Test Workspace',
             u'synchronized', u'synchronized'),
        ])
        # The filename changes with the date
        self.assertEquals(all_states[2][1:],
            (u'synchronized', u'synchronized'))
        self.assertEquals(all_states[3],
            (u'/Nuxeo Drive Test Workspace/Some File.doc',
             u'synchronized', u'synchronized'))
class TestIntegrationMoveAndRename(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationMoveAndRename, self).setUp()

        self.sb_1 = self.controller_1.bind_server(
            self.local_nxdrive_folder_1,
            self.nuxeo_url, self.user_1, self.password_1)

        self.controller_1.bind_root(self.local_nxdrive_folder_1,
            self.workspace)

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

        sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                       self.workspace_title)
        self.local_client_1 = LocalClient(sync_root_folder_1)

        self.local_client_1.make_file('/', u'Original File 1.txt',
            content=u'Some Content 1'.encode('utf-8'))

        self.local_client_1.make_file('/', u'Original File 2.txt',
            content=u'Some Content 2'.encode('utf-8'))

        self.local_client_1.make_folder(u'/', u'Original Folder 1')
        self.local_client_1.make_folder(
            u'/Original Folder 1', u'Sub-Folder 1.1')
        self.local_client_1.make_folder(
            u'/Original Folder 1', u'Sub-Folder 1.2')
        self.local_client_1.make_file(u'/Original Folder 1',
            u'Original File 1.1.txt',
            content=u'Some Content 1'.encode('utf-8'))  # Same content as OF1

        self.local_client_1.make_folder('/', 'Original Folder 2')
        self.local_client_1.make_file('/Original Folder 2',
            u'Original File 3.txt',
            content=u'Some Content 3'.encode('utf-8'))

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

    def test_local_rename_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid
        local_client.rename(u'/Original File 1.txt', u'Renamed File 1.txt')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed File 1.txt'))
        original_file_1_remote_info = remote_client.get_info(
            original_file_1_uid)
        self.assertEquals(original_file_1_remote_info.name,
            u'Renamed File 1.txt')

        # Rename 'Renamed File 1.txt' to 'Renamed Again File 1.txt'
        # and 'Original File 1.1.txt' to
        # 'Renamed File 1.1.txt' at the same time as they share
        # the same digest but do not live in the same folder
        original_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        local_client.rename(
            u'/Original Folder 1/Original File 1.1.txt',
            u'Renamed File 1.1 \xe9.txt')
        self.assertFalse(local_client.exists(
             '/Original Folder 1/Original File 1.1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1.1 \xe9.txt'))
        local_client.rename('/Renamed File 1.txt', 'Renamed Again File 1.txt')
        self.assertFalse(local_client.exists(u'/Renamed File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed Again File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 2)
        self.assertFalse(local_client.exists(u'/Renamed File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed Again File 1.txt'))
        self.assertFalse(local_client.exists(
             u'/Original Folder 1/Original File 1.1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1.1 \xe9.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name,
            u'Renamed Again File 1.txt')

        # User 1 does not have the rights to see the parent container
        # of the test workspace, hence set fetch_parent_uid=False
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid, fetch_parent_uid=False)
        self.assertEquals(parent_of_file_1_remote_info.name,
            self.workspace_title)

        file_1_1_remote_info = remote_client.get_info(original_1_1_uid)
        self.assertEquals(file_1_1_remote_info.name,
            u'Renamed File 1.1 \xe9.txt')

        parent_of_file_1_1_remote_info = remote_client.get_info(
            file_1_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_1_remote_info.name,
            u'Original Folder 1')

    def test_local_move_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid
        local_client.move(u'/Original File 1.txt', u'/Original Folder 1')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Original File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Original File 1.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Original File 1.txt')
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_remote_info.name,
            u'Original Folder 1')

    def test_local_move_and_rename_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid

        local_client.rename(u'/Original File 1.txt', u'Renamed File 1 \xe9.txt')
        local_client.move(u'/Renamed File 1 \xe9.txt', u'/Original Folder 1')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1 \xe9.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 1/Renamed File 1 \xe9.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Renamed File 1 \xe9.txt')
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_remote_info.name,
            u'Original Folder 1')

        # Nothing left to do
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_rename_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to renaming
        original_folder_1_uid = remote_client.get_info(
            u'/Original Folder 1').uid
        original_file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        original_sub_folder_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1').uid

        # Rename a non empty folder with some content
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1 \xe9')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1 \xe9'))

        # Synchronize: only the folder renaming is detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # The server folder has been renamed: the uid stays the same
        new_remote_name = remote_client.get_info(original_folder_1_uid).name
        self.assertEquals(new_remote_name, u"Renamed Folder 1 \xe9")

        # The content of the renamed folder is left unchanged
        file_1_1_info = remote_client.get_info(original_file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, original_folder_1_uid)

        sub_folder_1_1_info = remote_client.get_info(
            original_sub_folder_1_1_uid)
        self.assertEquals(sub_folder_1_1_info.name, u"Sub-Folder 1.1")
        self.assertEquals(sub_folder_1_1_info.parent_uid,
            original_folder_1_uid)

        # The more things change, the more they remain the same.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_move_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to move
        original_folder_1_uid = remote_client.get_info(
            u'/Original Folder 1').uid
        original_folder_2_uid = remote_client.get_info(
            u'/Original Folder 2').uid
        original_file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        original_sub_folder_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1').uid

        # Move a non empty folder with some content
        local_client.move(u'/Original Folder 1', u'/Original Folder 2')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(
            u'/Original Folder 2/Original Folder 1'))

        # Synchronize: only the folder move is detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # The server folder has been moved: the uid stays the same
        remote_folder_info = remote_client.get_info(original_folder_1_uid)

        # The parent folder is not folder 2
        self.assertEquals(remote_folder_info.parent_uid,
            original_folder_2_uid)

        # The content of the renamed folder is left unchanged
        file_1_1_info = remote_client.get_info(original_file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, original_folder_1_uid)

        sub_folder_1_1_info = remote_client.get_info(
            original_sub_folder_1_1_uid)
        self.assertEquals(sub_folder_1_1_info.name, u"Sub-Folder 1.1")
        self.assertEquals(sub_folder_1_1_info.parent_uid,
            original_folder_1_uid)

        # The more things change, the more they remain the same.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_concurrent_local_rename_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to renaming
        folder_1_uid = remote_client.get_info(u'/Original Folder 1').uid
        file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        folder_2_uid = remote_client.get_info(u'/Original Folder 2').uid
        file_3_uid = remote_client.get_info(
            u'/Original Folder 2/Original File 3.txt').uid

        # Rename a non empty folders concurrently
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1')
        local_client.rename(u'/Original Folder 2', u'Renamed Folder 2')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1'))
        self.assertFalse(local_client.exists(u'/Original Folder 2'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 2'))

        # Synchronize: only the folder renamings are detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 2)

        # The server folders have been renamed: the uid stays the same
        folder_1_info = remote_client.get_info(folder_1_uid)
        self.assertEquals(folder_1_info.name, u"Renamed Folder 1")

        folder_2_info = remote_client.get_info(folder_2_uid)
        self.assertEquals(folder_2_info.name, u"Renamed Folder 2")

        # The content of the folder has been left unchanged
        file_1_1_info = remote_client.get_info(file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, folder_1_uid)

        file_3_info = remote_client.get_info(file_3_uid)
        self.assertEquals(file_3_info.name, u"Original File 3.txt")
        self.assertEquals(file_3_info.parent_uid, folder_2_uid)

        # The more things change, the more they remain the same.
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_rename_sync_root_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        # Use the Administrator to be able to introspect the container of the
        # test workspace.
        remote_client = RemoteDocumentClient(
            self.nuxeo_url, self.admin_user,
            'nxdrive-test-administrator-device',
            self.password, base_folder=self.workspace)

        folder_1_uid = remote_client.get_info(u'/Original Folder 1').uid

        # Create new clients to be able to introspect the test sync root
        toplevel_local_client = LocalClient(self.local_nxdrive_folder_1)

        toplevel_local_client.rename('/' + self.workspace_title,
            'Renamed Nuxeo Drive Test Workspace')
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        workspace_info = remote_client.get_info(self.workspace)
        self.assertEquals(workspace_info.name,
            u"Renamed Nuxeo Drive Test Workspace")

        folder_1_info = remote_client.get_info(folder_1_uid)
        self.assertEquals(folder_1_info.name, u"Original Folder 1")
        self.assertEquals(folder_1_info.parent_uid, self.workspace)

    # TODO: implement me once canDelete is checked in the synchronizer
    # def test_local_move_sync_root_folder(self):
    #    pass
    def test_synchronize_remote_deletion_local_modification(self):
        """Test remote deletion with concurrent local modification

        Use cases:
          - Remotely delete a regular folder and make some
            local changes concurrently.
              => Only locally modified content should be kept
                 and should be marked as 'unsynchronized',
                 other content should be deleted.
          - Remotely restore folder from the trash.
              => Remote documents should be merged with
                 locally modified content which should be unmarked
                 as 'unsynchronized'.
          - Remotely delete a file and locally update its content concurrently.
              => File should be kept locally and be marked as 'unsynchronized'.
          - Remotely restore file from the trash.
              => Remote file should be merged with locally modified file with
                 a conflict detection and both files should be marked
                 as 'synchronized'.
          - Remotely delete a file and locally rename it concurrently.
              => File should be kept locally and be marked as 'synchronized'.
          - Remotely restore file from the trash.
              => Remote file should be merged with locally renamed file and
                 both files should be marked as 'synchronized'.

        See TestIntegrationSecurityUpdates
                .test_synchronize_denying_read_access_local_modification
        as the same uses cases are tested.

        Note that we use the .odt extension for test files to make sure
        that they are created as File and not Note documents on the server
        when synchronized upstream, as the current implementation of
        RemoteDocumentClient is File oriented.
        """
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1

        # Create documents in the remote root workspace
        # then synchronize
        remote.make_folder('/', 'Test folder')
        remote.make_file('/Test folder', 'joe.odt', 'Some content')
        remote.make_file('/Test folder', 'jack.odt', 'Some content')
        remote.make_folder('/Test folder', 'Sub folder 1')
        remote.make_file('/Test folder/Sub folder 1', 'sub file 1.txt',
                         'Content')
        syn = ctl.synchronizer
        self._synchronize(syn)
        self.assertTrue(local.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))

        # Delete remote folder and make some local changes
        # concurrently then synchronize
        remote.delete('/Test folder')
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        # Create new file
        local.make_file('/Test folder', 'new.odt', "New content")
        # Create new folder with files
        local.make_folder('/Test folder', 'Sub folder 2')
        local.make_file('/Test folder/Sub folder 2', 'sub file 2.txt',
                        'Other content')
        # Update file
        local.update_content('/Test folder/joe.odt', 'Some updated content')
        self._synchronize(syn)
        # Only locally modified content should exist
        # and should be marked as 'unsynchronized', other content should
        # have been deleted
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertEquals(local.get_content('/Test folder/joe.odt'),
                          'Some updated content')
        self.assertTrue(local.exists('/Test folder/new.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 2'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 2/sub file 2.txt'))

        self.assertFalse(local.exists('/Test folder/jack.odt'))
        self.assertFalse(local.exists('/Test folder/Sub folder 1'))
        self.assertFalse(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'unsynchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/new.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/Sub folder 2',
                               'unsynchronized')
        self._check_pair_state(session,
                               '/Test folder/Sub folder 2/sub file 2.txt',
                               'unsynchronized')
        # Remote check
        self.assertFalse(remote.exists('/Test folder'))

        # Restore remote folder and its children from trash then synchronize
        remote.undelete('/Test folder')
        remote.undelete('/Test folder/joe.odt')
        remote.undelete('/Test folder/jack.odt')
        remote.undelete('/Test folder/Sub folder 1')
        remote.undelete('/Test folder/Sub folder 1/sub file 1.txt')
        self._synchronize(syn)
        # Remotely restored documents should be merged with
        # locally modified content which should be unmarked
        # as 'unsynchronized' and therefore synchronized upstream
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        children_info = local.get_children_info('/Test folder')
        self.assertEquals(len(children_info), 6)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        self.assertTrue(local.exists(remote_version.path))
        self.assertEquals(local.get_content(remote_version.path),
                          'Some content')
        self.assertTrue(local.exists(local_version.path))
        self.assertEquals(local.get_content(local_version.path),
                          'Some updated content')
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/new.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 2'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 2/sub file 2.txt'))
        # State check
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/new.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/Sub folder 2',
                               'synchronized')
        self._check_pair_state(session,
                               '/Test folder/Sub folder 2/sub file 2.txt',
                               'synchronized')
        # Remote check
        self.assertTrue(remote.exists('/Test folder'))
        test_folder_uid = remote.get_info('/Test folder').uid
        children_info = remote.get_children_info(test_folder_uid)
        self.assertEquals(len(children_info), 6)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        remote_version_ref_length = (len(remote_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        remote_version_ref = remote_version.path[-remote_version_ref_length:]
        self.assertTrue(remote.exists(remote_version_ref))
        self.assertEquals(remote.get_content(remote_version_ref),
                          'Some content')
        local_version_ref_length = (len(local_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        local_version_ref = local_version.path[-local_version_ref_length:]
        self.assertTrue(remote.exists(local_version_ref))
        self.assertEquals(remote.get_content(local_version_ref),
                          'Some updated content')
        self.assertTrue(remote.exists('/Test folder/jack.odt'))
        self.assertTrue(remote.exists('/Test folder/new.odt'))
        self.assertTrue(remote.exists('/Test folder/Sub folder 1'))
        self.assertTrue(remote.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(remote.exists('/Test folder/Sub folder 2'))
        self.assertTrue(remote.exists(
                    '/Test folder/Sub folder 2/sub file 2.txt'))

        # Delete remote file and update its local content
        # concurrently then synchronize
        remote.delete('/Test folder/jack.odt')
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        local.update_content('/Test folder/jack.odt', 'Some updated content')
        self._synchronize(syn)
        # File should be kept locally and be marked as 'unsynchronized'.
        # Local check
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertEquals(local.get_content('/Test folder/jack.odt'),
                          'Some updated content')
        # Remote check
        self.assertFalse(remote.exists('/Test folder/jack.odt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/jack.odt',
                               'unsynchronized')

        # Remotely restore file from the trash then synchronize
        remote.undelete('/Test folder/jack.odt')
        self._synchronize(syn)
        # Remotely restored file should be merged with locally modified file
        # with a conflict detection and both files should be marked
        # as 'synchronized'
        # Local check
        children_info = local.get_children_info('/Test folder')
        for info in children_info:
            if info.name == 'jack.odt':
                remote_version = info
            elif (info.name.startswith('jack (')
                  and info.name.endswith(').odt')):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        self.assertTrue(local.exists(remote_version.path))
        self.assertEquals(local.get_content(remote_version.path),
                          'Some content')
        self.assertTrue(local.exists(local_version.path))
        self.assertEquals(local.get_content(local_version.path),
                          'Some updated content')
        # Remote check
        self.assertTrue(remote.exists(remote_version.path))
        self.assertEquals(remote.get_content(remote_version.path),
                          'Some content')
        local_version_path = self._truncate_remote_path(local_version.path)
        self.assertTrue(remote.exists(local_version_path))
        self.assertEquals(remote.get_content(local_version_path),
                          'Some updated content')
        # State check
        self._check_pair_state(session, remote_version.path,
                               'synchronized')
        self._check_pair_state(session, local_version.path,
                               'synchronized')

        # Delete remote file and rename it locally
        # concurrently then synchronize
        remote.delete('/Test folder/jack.odt')
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        local.rename('/Test folder/jack.odt', 'jack renamed.odt')
        self._synchronize(syn)
        # File should be kept locally and be marked as 'synchronized'
        # Local check
        self.assertFalse(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/jack renamed.odt'))
        self.assertEquals(local.get_content('/Test folder/jack renamed.odt'),
                          'Some content')
        # Remote check
        self.assertFalse(remote.exists('/Test folder/jack.odt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/jack renamed.odt',
                               'synchronized')

        # Remotely restore file from the trash then synchronize
        remote.undelete('/Test folder/jack.odt')
        self._synchronize(syn)
        # Remotely restored file should be merged with locally renamed file
        # and both files should be marked as 'synchronized'
        # Local check
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertEquals(local.get_content('/Test folder/jack.odt'),
                          'Some content')
        self.assertTrue(local.exists('/Test folder/jack renamed.odt'))
        self.assertEquals(local.get_content('/Test folder/jack renamed.odt'),
                          'Some content')
        # Remote check
        self.assertTrue(remote.exists('/Test folder/jack.odt'))
        self.assertEquals(remote.get_content('/Test folder/jack.odt'),
                          'Some content')
        self.assertTrue(remote.exists('/Test folder/jack renamed.odt'))
        self.assertEquals(remote.get_content('/Test folder/jack renamed.odt'),
                          'Some content')
        # State check
        self._check_pair_state(session, '/Test folder/jack.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/jack renamed.odt',
                               'synchronized')
    def test_update_local_file_content_update_remote_file_property(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")

        # Get local and remote clients
        local = LocalClient(self.local_nxdrive_folder_1)
        remote = self.remote_document_client_1

        # Bind server and test workspace for nuxeoDriveTestUser_user_1
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait()
        sync = ctl.synchronizer
        sync.loop(delay=0.1, max_loops=1)

        # Test workspace should be created locally
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace'))

        # Create a local file in the test workspace then synchronize
        local.make_file('/Nuxeo Drive Test Workspace',
                        'test.odt', 'Some content.')

        sync.loop(delay=0.1, max_loops=1)

        # Test file should be created remotely in the test workspace
        self.assertTrue(remote.exists('/test.odt'))

        # Locally update the file content and remotely update one of its
        # properties concurrently, then synchronize
        time.sleep(OS_STAT_MTIME_RESOLUTION)
        local.update_content('/Nuxeo Drive Test Workspace/test.odt',
                             'Updated content.')
        self.assertEquals(local.get_content(
                                    '/Nuxeo Drive Test Workspace/test.odt'),
                          'Updated content.')
        test_file_ref = remote._check_ref('/test.odt')
        # Wait for 1 second to make sure the file's last modification time
        # will be different from the pair state's last remote update time
        time.sleep(REMOTE_MODIFICATION_TIME_RESOLUTION)
        remote.update(test_file_ref,
                      properties={'dc:description': 'Some description.'})
        test_file = remote.fetch(test_file_ref)
        self.assertEqual(test_file['properties']['dc:description'],
                         'Some description.')

        self.wait()
        sync.loop(delay=0.1, max_loops=2)

        # Test file should be updated remotely in the test workspace,
        # and no conflict should be detected.
        # Even though fetching the remote changes will send a
        # 'documentModified' event for the test file as a result of its
        # dc:description property update, since the file will not have been
        # renamed nor moved and its content not modified since last
        # synchronization, its remote pair state will not be marked as
        # 'modified', see Model.update_remote().
        # Thus the pair state will be ('modified', 'synchronized'), resolved as
        # 'locally_modified'.
        self.assertTrue(remote.exists('/test.odt'))
        self.assertEquals(remote.get_content('/test.odt'), 'Updated content.')
        test_file = remote.fetch(test_file_ref)
        self.assertEqual(test_file['properties']['dc:description'],
                         'Some description.')
        self.assertEqual(len(remote.get_children_info(self.workspace)), 1)

        # Check that the content of the test file has not changed
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace/test.odt'))
        self.assertEquals(local.get_content(
                                    '/Nuxeo Drive Test Workspace/test.odt'),
                          'Updated content.')
        self.assertEqual(len(local.get_children_info(
                                            '/Nuxeo Drive Test Workspace')), 1)
    def test_synchronize_remote_deletion_local_modification(self):
        """Test remote deletion with concurrent local modification

        Use cases:
          - Remotely delete a regular folder and make some
            local changes concurrently.
              => Only locally modified content should be kept
                 and should be marked as 'unsynchronized',
                 other content should be deleted.
          - Remotely restore folder from the trash.
              => Remote documents should be merged with
                 locally modified content which should be unmarked
                 as 'unsynchronized'.
          - Remotely delete a file and locally update its content concurrently.
              => File should be kept locally and be marked as 'unsynchronized'.
          - Remotely restore file from the trash.
              => Remote file should be merged with locally modified file with
                 a conflict detection and both files should be marked
                 as 'synchronized'.
          - Remotely delete a file and locally rename it concurrently.
              => File should be kept locally and be marked as 'synchronized'.
          - Remotely restore file from the trash.
              => Remote file should be merged with locally renamed file and
                 both files should be marked as 'synchronized'.

        See TestIntegrationSecurityUpdates
                .test_synchronize_denying_read_access_local_modification
        as the same uses cases are tested.

        Note that we use the .odt extension for test files to make sure
        that they are created as File and not Note documents on the server
        when synchronized upstream, as the current implementation of
        RemoteDocumentClient is File oriented.
        """
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1

        # Create documents in the remote root workspace
        # then synchronize
        remote.make_folder('/', 'Test folder')
        remote.make_file('/Test folder', 'joe.odt', 'Some content')
        remote.make_file('/Test folder', 'jack.odt', 'Some content')
        remote.make_folder('/Test folder', 'Sub folder 1')
        remote.make_file('/Test folder/Sub folder 1', 'sub file 1.txt',
                         'Content')
        syn = ctl.synchronizer
        self._synchronize(syn)
        self.assertTrue(local.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))

        # Delete remote folder and make some local changes
        # concurrently then synchronize
        remote.delete('/Test folder')
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        # Create new file
        local.make_file('/Test folder', 'new.odt', "New content")
        # Create new folder with files
        local.make_folder('/Test folder', 'Sub folder 2')
        local.make_file('/Test folder/Sub folder 2', 'sub file 2.txt',
                        'Other content')
        # Update file
        local.update_content('/Test folder/joe.odt', 'Some updated content')
        self._synchronize(syn)
        # Only locally modified content should exist
        # and should be marked as 'unsynchronized', other content should
        # have been deleted
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertEquals(local.get_content('/Test folder/joe.odt'),
                          'Some updated content')
        self.assertTrue(local.exists('/Test folder/new.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 2'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 2/sub file 2.txt'))

        self.assertFalse(local.exists('/Test folder/jack.odt'))
        self.assertFalse(local.exists('/Test folder/Sub folder 1'))
        self.assertFalse(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'unsynchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/new.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/Sub folder 2',
                               'unsynchronized')
        self._check_pair_state(session,
                               '/Test folder/Sub folder 2/sub file 2.txt',
                               'unsynchronized')
        # Remote check
        self.assertFalse(remote.exists('/Test folder'))

        # Restore remote folder and its children from trash then synchronize
        remote.undelete('/Test folder')
        remote.undelete('/Test folder/joe.odt')
        remote.undelete('/Test folder/jack.odt')
        remote.undelete('/Test folder/Sub folder 1')
        remote.undelete('/Test folder/Sub folder 1/sub file 1.txt')
        self._synchronize(syn)
        # Remotely restored documents should be merged with
        # locally modified content which should be unmarked
        # as 'unsynchronized' and therefore synchronized upstream
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        children_info = local.get_children_info('/Test folder')
        self.assertEquals(len(children_info), 6)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        self.assertTrue(local.exists(remote_version.path))
        self.assertEquals(local.get_content(remote_version.path),
                          'Some content')
        self.assertTrue(local.exists(local_version.path))
        self.assertEquals(local.get_content(local_version.path),
                          'Some updated content')
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/new.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 2'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 2/sub file 2.txt'))
        # State check
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/new.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/Sub folder 2',
                               'synchronized')
        self._check_pair_state(session,
                               '/Test folder/Sub folder 2/sub file 2.txt',
                               'synchronized')
        # Remote check
        self.assertTrue(remote.exists('/Test folder'))
        test_folder_uid = remote.get_info('/Test folder').uid
        children_info = remote.get_children_info(test_folder_uid)
        self.assertEquals(len(children_info), 6)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        remote_version_ref_length = (len(remote_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        remote_version_ref = remote_version.path[-remote_version_ref_length:]
        self.assertTrue(remote.exists(remote_version_ref))
        self.assertEquals(remote.get_content(remote_version_ref),
                          'Some content')
        local_version_ref_length = (len(local_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        local_version_ref = local_version.path[-local_version_ref_length:]
        self.assertTrue(remote.exists(local_version_ref))
        self.assertEquals(remote.get_content(local_version_ref),
                          'Some updated content')
        self.assertTrue(remote.exists('/Test folder/jack.odt'))
        self.assertTrue(remote.exists('/Test folder/new.odt'))
        self.assertTrue(remote.exists('/Test folder/Sub folder 1'))
        self.assertTrue(remote.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(remote.exists('/Test folder/Sub folder 2'))
        self.assertTrue(remote.exists(
                    '/Test folder/Sub folder 2/sub file 2.txt'))

        # Delete remote file and update its local content
        # concurrently then synchronize
        remote.delete('/Test folder/jack.odt')
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        local.update_content('/Test folder/jack.odt', 'Some updated content')
        self._synchronize(syn)
        # File should be kept locally and be marked as 'unsynchronized'.
        # Local check
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertEquals(local.get_content('/Test folder/jack.odt'),
                          'Some updated content')
        # Remote check
        self.assertFalse(remote.exists('/Test folder/jack.odt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/jack.odt',
                               'unsynchronized')

        # Remotely restore file from the trash then synchronize
        remote.undelete('/Test folder/jack.odt')
        self._synchronize(syn)
        # Remotely restored file should be merged with locally modified file
        # with a conflict detection and both files should be marked
        # as 'synchronized'
        # Local check
        children_info = local.get_children_info('/Test folder')
        for info in children_info:
            if info.name == 'jack.odt':
                remote_version = info
            elif (info.name.startswith('jack (')
                  and info.name.endswith(').odt')):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        self.assertTrue(local.exists(remote_version.path))
        self.assertEquals(local.get_content(remote_version.path),
                          'Some content')
        self.assertTrue(local.exists(local_version.path))
        self.assertEquals(local.get_content(local_version.path),
                          'Some updated content')
        # Remote check
        self.assertTrue(remote.exists(remote_version.path))
        self.assertEquals(remote.get_content(remote_version.path),
                          'Some content')
        local_version_path = self._truncate_remote_path(local_version.path)
        self.assertTrue(remote.exists(local_version_path))
        self.assertEquals(remote.get_content(local_version_path),
                          'Some updated content')
        # State check
        self._check_pair_state(session, remote_version.path,
                               'synchronized')
        self._check_pair_state(session, local_version.path,
                               'synchronized')

        # Delete remote file and rename it locally
        # concurrently then synchronize
        remote.delete('/Test folder/jack.odt')
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        local.rename('/Test folder/jack.odt', 'jack renamed.odt')
        self._synchronize(syn)
        # File should be kept locally and be marked as 'synchronized'
        # Local check
        self.assertFalse(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/jack renamed.odt'))
        self.assertEquals(local.get_content('/Test folder/jack renamed.odt'),
                          'Some content')
        # Remote check
        self.assertFalse(remote.exists('/Test folder/jack.odt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/jack renamed.odt',
                               'synchronized')

        # Remotely restore file from the trash then synchronize
        remote.undelete('/Test folder/jack.odt')
        self._synchronize(syn)
        # Remotely restored file should be merged with locally renamed file
        # and both files should be marked as 'synchronized'
        # Local check
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertEquals(local.get_content('/Test folder/jack.odt'),
                          'Some content')
        self.assertTrue(local.exists('/Test folder/jack renamed.odt'))
        self.assertEquals(local.get_content('/Test folder/jack renamed.odt'),
                          'Some content')
        # Remote check
        self.assertTrue(remote.exists('/Test folder/jack.odt'))
        self.assertEquals(remote.get_content('/Test folder/jack.odt'),
                          'Some content')
        self.assertTrue(remote.exists('/Test folder/jack renamed.odt'))
        self.assertEquals(remote.get_content('/Test folder/jack renamed.odt'),
                          'Some content')
        # State check
        self._check_pair_state(session, '/Test folder/jack.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/jack renamed.odt',
                               'synchronized')
class TestIntegrationLocalMoveAndRename(IntegrationTestCase):

    # Sets up the following local hierarchy:
    # Nuxeo Drive Test Workspace
    #    |-- Original File 1.txt
    #    |-- Original File 2.txt
    #    |-- Original Folder 1
    #    |       |-- Sub-Folder 1.1
    #    |       |-- Sub-Folder 1.2
    #    |       |-- Original File 1.1.txt
    #    |-- Original Folder 2
    #    |       |-- Original File 3.txt
    def setUp(self):
        super(TestIntegrationLocalMoveAndRename, self).setUp()

        self.sb_1 = self.controller_1.bind_server(self.local_nxdrive_folder_1,
                                                  self.nuxeo_url, self.user_1,
                                                  self.password_1)

        self.controller_1.bind_root(self.local_nxdrive_folder_1,
                                    self.workspace)

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

        self.sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                               self.workspace_title)
        self.local_client_1 = LocalClient(self.sync_root_folder_1)

        self.local_client_1.make_file(
            '/',
            u'Original File 1.txt',
            content=u'Some Content 1'.encode('utf-8'))

        self.local_client_1.make_file(
            '/',
            u'Original File 2.txt',
            content=u'Some Content 2'.encode('utf-8'))

        self.local_client_1.make_folder(u'/', u'Original Folder 1')
        self.local_client_1.make_folder(u'/Original Folder 1',
                                        u'Sub-Folder 1.1')
        self.local_client_1.make_folder(u'/Original Folder 1',
                                        u'Sub-Folder 1.2')
        self.local_client_1.make_file(
            u'/Original Folder 1',
            u'Original File 1.1.txt',
            content=u'Some Content 1'.encode('utf-8'))  # Same content as OF1

        self.local_client_1.make_folder('/', 'Original Folder 2')
        self.local_client_1.make_file(
            '/Original Folder 2',
            u'Original File 3.txt',
            content=u'Some Content 3'.encode('utf-8'))

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

    def test_local_rename_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid
        local_client.rename(u'/Original File 1.txt', u'Renamed File 1.txt')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed File 1.txt'))
        original_file_1_remote_info = remote_client.get_info(
            original_file_1_uid)
        self.assertEquals(original_file_1_remote_info.name,
                          u'Renamed File 1.txt')

        # Rename 'Renamed File 1.txt' to 'Renamed Again File 1.txt'
        # and 'Original File 1.1.txt' to
        # 'Renamed File 1.1.txt' at the same time as they share
        # the same digest but do not live in the same folder
        original_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        local_client.rename(u'/Original Folder 1/Original File 1.1.txt',
                            u'Renamed File 1.1 \xe9.txt')
        self.assertFalse(
            local_client.exists('/Original Folder 1/Original File 1.1.txt'))
        self.assertTrue(
            local_client.exists(
                u'/Original Folder 1/Renamed File 1.1 \xe9.txt'))
        local_client.rename('/Renamed File 1.txt', 'Renamed Again File 1.txt')
        self.assertFalse(local_client.exists(u'/Renamed File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed Again File 1.txt'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 2)
        self.assertFalse(local_client.exists(u'/Renamed File 1.txt'))
        self.assertTrue(local_client.exists(u'/Renamed Again File 1.txt'))
        self.assertFalse(
            local_client.exists(u'/Original Folder 1/Original File 1.1.txt'))
        self.assertTrue(
            local_client.exists(
                u'/Original Folder 1/Renamed File 1.1 \xe9.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Renamed Again File 1.txt')

        # User 1 does not have the rights to see the parent container
        # of the test workspace, hence set fetch_parent_uid=False
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid, fetch_parent_uid=False)
        self.assertEquals(parent_of_file_1_remote_info.name,
                          self.workspace_title)

        file_1_1_remote_info = remote_client.get_info(original_1_1_uid)
        self.assertEquals(file_1_1_remote_info.name,
                          u'Renamed File 1.1 \xe9.txt')

        parent_of_file_1_1_remote_info = remote_client.get_info(
            file_1_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_1_remote_info.name,
                          u'Original Folder 1')

    def test_local_rename_file_uppercase(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt

        # Rename 'Renamed File 1.txt' to 'Renamed Again File 1.txt'
        # and 'Original File 1.1.txt' to
        # 'Renamed File 1.1.txt' at the same time as they share
        # the same digest but do not live in the same folder
        original_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        local_client.rename(u'/Original Folder 1/Original File 1.1.txt',
                            u'original File 1.1.txt')

        ctl.synchronizer.update_synchronize_server(sb)

        file_1_1_remote_info = remote_client.get_info(original_1_1_uid)
        self.assertEquals(file_1_1_remote_info.name, u'original File 1.1.txt')

        parent_of_file_1_1_remote_info = remote_client.get_info(
            file_1_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_1_remote_info.name,
                          u'Original Folder 1')

    def test_local_move_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid
        local_client.move(u'/Original File 1.txt', u'/Original Folder 1')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Original File 1.txt'))

        ctl.synchronizer.update_synchronize_server(sb)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Original File 1.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Original File 1.txt')
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_remote_info.name,
                          u'Original Folder 1')

    def test_local_move_and_rename_file(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Rename /Original File 1.txt to /Renamed File 1.txt
        original_file_1_uid = remote_client.get_info(
            u'/Original File 1.txt').uid

        local_client.rename(u'/Original File 1.txt',
                            u'Renamed File 1 \xe9.txt')
        local_client.move(u'/Renamed File 1 \xe9.txt', u'/Original Folder 1')
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Renamed File 1 \xe9.txt'))

        ctl.synchronizer.update_synchronize_server(sb)
        self.assertFalse(local_client.exists(u'/Original File 1.txt'))
        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Renamed File 1 \xe9.txt'))

        file_1_remote_info = remote_client.get_info(original_file_1_uid)
        self.assertEquals(file_1_remote_info.name, u'Renamed File 1 \xe9.txt')
        parent_of_file_1_remote_info = remote_client.get_info(
            file_1_remote_info.parent_uid)
        self.assertEquals(parent_of_file_1_remote_info.name,
                          u'Original Folder 1')

        # Nothing left to do
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_rename_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to renaming
        original_folder_1_uid = remote_client.get_info(
            u'/Original Folder 1').uid
        original_file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        original_sub_folder_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1').uid

        # Rename a non empty folder with some content
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1 \xe9')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1 \xe9'))

        # Synchronize: only the folder renaming is detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # The server folder has been renamed: the uid stays the same
        new_remote_name = remote_client.get_info(original_folder_1_uid).name
        self.assertEquals(new_remote_name, u"Renamed Folder 1 \xe9")

        # The content of the renamed folder is left unchanged
        file_1_1_info = remote_client.get_info(original_file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, original_folder_1_uid)

        sub_folder_1_1_info = remote_client.get_info(
            original_sub_folder_1_1_uid)
        self.assertEquals(sub_folder_1_1_info.name, u"Sub-Folder 1.1")
        self.assertEquals(sub_folder_1_1_info.parent_uid,
                          original_folder_1_uid)

        # The more things change, the more they remain the same.
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_move_folder(self):
        raise SkipTest("Skipped waiting for"
                       " https://jira.nuxeo.com/browse/NXDRIVE-80 to be fixed")
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to move
        original_folder_1_uid = remote_client.get_info(
            u'/Original Folder 1').uid
        original_folder_2_uid = remote_client.get_info(
            u'/Original Folder 2').uid
        original_file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        original_sub_folder_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1').uid

        # Move a non empty folder with some content
        local_client.move(u'/Original Folder 1', u'/Original Folder 2')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(
            local_client.exists(u'/Original Folder 2/Original Folder 1'))

        # Synchronize: only the folder move is detected: all
        # the descendants are automatically realigned
        ctl.synchronizer.update_synchronize_server(sb)
        # The server folder has been moved: the uid stays the same
        remote_folder_info = remote_client.get_info(original_folder_1_uid)

        # The parent folder is now folder 2
        self.assertEquals(remote_folder_info.parent_uid, original_folder_2_uid)

        # The content of the renamed folder is left unchanged
        file_1_1_info = remote_client.get_info(original_file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, original_folder_1_uid)

        sub_folder_1_1_info = remote_client.get_info(
            original_sub_folder_1_1_uid)
        self.assertEquals(sub_folder_1_1_info.name, u"Sub-Folder 1.1")
        self.assertEquals(sub_folder_1_1_info.parent_uid,
                          original_folder_1_uid)

        # The more things change, the more they remain the same.
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_concurrent_local_rename_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Save the uid of some files and folders prior to renaming
        folder_1_uid = remote_client.get_info(u'/Original Folder 1').uid
        file_1_1_uid = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt').uid
        folder_2_uid = remote_client.get_info(u'/Original Folder 2').uid
        file_3_uid = remote_client.get_info(
            u'/Original Folder 2/Original File 3.txt').uid

        # Rename a non empty folders concurrently
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1')
        local_client.rename(u'/Original Folder 2', u'Renamed Folder 2')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1'))
        self.assertFalse(local_client.exists(u'/Original Folder 2'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 2'))

        # Synchronize: only the folder renamings are detected: all
        # the descendants are automatically realigned
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 2)

        # The server folders have been renamed: the uid stays the same
        folder_1_info = remote_client.get_info(folder_1_uid)
        self.assertEquals(folder_1_info.name, u"Renamed Folder 1")

        folder_2_info = remote_client.get_info(folder_2_uid)
        self.assertEquals(folder_2_info.name, u"Renamed Folder 2")

        # The content of the folder has been left unchanged
        file_1_1_info = remote_client.get_info(file_1_1_uid)
        self.assertEquals(file_1_1_info.name, u"Original File 1.1.txt")
        self.assertEquals(file_1_1_info.parent_uid, folder_1_uid)

        file_3_info = remote_client.get_info(file_3_uid)
        self.assertEquals(file_3_info.name, u"Original File 3.txt")
        self.assertEquals(file_3_info.parent_uid, folder_2_uid)

        # The more things change, the more they remain the same.
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 0)

    def test_local_rename_sync_root_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        # Use the Administrator to be able to introspect the container of the
        # test workspace.
        remote_client = RemoteDocumentClient(
            self.nuxeo_url,
            self.admin_user,
            'nxdrive-test-administrator-device',
            self.version,
            password=self.password,
            base_folder=self.workspace)

        folder_1_uid = remote_client.get_info(u'/Original Folder 1').uid

        # Create new clients to be able to introspect the test sync root
        toplevel_local_client = LocalClient(self.local_nxdrive_folder_1)

        toplevel_local_client.rename('/' + self.workspace_title,
                                     'Renamed Nuxeo Drive Test Workspace')
        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        workspace_info = remote_client.get_info(self.workspace)
        self.assertEquals(workspace_info.name,
                          u"Renamed Nuxeo Drive Test Workspace")

        folder_1_info = remote_client.get_info(folder_1_uid)
        self.assertEquals(folder_1_info.name, u"Original Folder 1")
        self.assertEquals(folder_1_info.parent_uid, self.workspace)

    def test_local_rename_top_level_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = LocalClient(self.local_test_folder_1)
        session = ctl.get_session()

        # Check top level folder
        self.assertTrue(local_client.exists(u'/Nuxeo Drive'))
        top_level_folder_info = local_client.get_info(u'/Nuxeo Drive')
        self.assertEquals(top_level_folder_info.name, u'Nuxeo Drive')
        self.assertEquals(
            top_level_folder_info.filepath,
            os.path.join(self.local_test_folder_1, u'Nuxeo Drive'))
        # Check top level folder state
        top_level_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Nuxeo Drive').one()
        self.assertEquals(top_level_folder_state.local_path, '/')
        self.assertEquals(top_level_folder_state.local_name, u'Nuxeo Drive')

        # Rename top level folder
        local_client.rename(u'/Nuxeo Drive', u'Nuxeo Drive renamed')
        top_level_folder_info = local_client.get_info(u'/Nuxeo Drive renamed')
        self.assertEquals(top_level_folder_info.name, u'Nuxeo Drive renamed')
        self.assertEquals(
            top_level_folder_info.filepath,
            os.path.join(self.local_test_folder_1, u'Nuxeo Drive renamed'))

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # Check deleted server binding
        self.assertRaises(RuntimeError,
                          ctl.get_server_binding,
                          self.local_nxdrive_folder_1,
                          raise_if_missing=True)
        # Check deleted pair state
        self.assertEquals(len(session.query(LastKnownState).all()), 0)

    def test_local_delete_top_level_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = LocalClient(self.local_test_folder_1)
        session = ctl.get_session()

        # Check top level folder
        self.assertTrue(local_client.exists(u'/Nuxeo Drive'))

        # Delete top level folder
        local_client.delete(u'/Nuxeo Drive')
        self.assertRaises(NotFound, local_client.get_info, u'/Nuxeo Drive')

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 1)

        # Check deleted server binding
        self.assertRaises(RuntimeError,
                          ctl.get_server_binding,
                          self.local_nxdrive_folder_1,
                          raise_if_missing=True)
        # Check deleted pair state
        self.assertEquals(len(session.query(LastKnownState).all()), 0)

    def test_local_rename_readonly_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1
        session = ctl.get_session()

        # Check local folder
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertTrue(folder_1_state.remote_can_rename)

        # Set remote folder as readonly for test user
        folder_1_path = self.TEST_WORKSPACE_PATH + u'/Original Folder 1'
        op_input = "doc:" + folder_1_path
        self.root_remote_client.execute("Document.SetACE",
                                        op_input=op_input,
                                        user="******",
                                        permission="Read")
        self.root_remote_client.block_inheritance(folder_1_path,
                                                  overwrite=False)

        # Check can_rename flag in pair state
        folder_1_state.refresh_remote(self.remote_file_system_client_1)
        self.assertFalse(folder_1_state.remote_can_rename)

        # Rename local folder
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1 \xe9')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1 \xe9'))

        ctl.synchronizer.update_synchronize_server(sb)

        # Check remote folder has not been renamed
        folder_1_remote_info = remote_client.get_info(u'/Original Folder 1')
        self.assertEquals(folder_1_remote_info.name, u'Original Folder 1')

        # Check state of local folder and its children
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Renamed Folder 1 \xe9').one()
        self.assertEquals(folder_1_state.remote_name, u'Original Folder 1')

        self.assertTrue(
            local_client.exists(
                u'/Renamed Folder 1 \xe9/Original File 1.1.txt'))
        file_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original File 1.1.txt').one()
        self.assertEquals(file_1_1_state.local_name, u'Original File 1.1.txt')
        self.assertEquals(file_1_1_state.remote_name, u'Original File 1.1.txt')

        self.assertTrue(
            local_client.exists(u'/Renamed Folder 1 \xe9/Sub-Folder 1.1'))
        folder_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.1').one()
        self.assertEquals(folder_1_1_state.local_name, u'Sub-Folder 1.1')
        self.assertEquals(folder_1_1_state.remote_name, u'Sub-Folder 1.1')

        self.assertTrue(
            local_client.exists(u'/Renamed Folder 1 \xe9/Sub-Folder 1.2'))
        folder_1_2_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.2').one()
        self.assertEquals(folder_1_2_state.local_name, u'Sub-Folder 1.2')
        self.assertEquals(folder_1_2_state.remote_name, u'Sub-Folder 1.2')

    def test_local_rename_readonly_folder_with_rollback(self):
        sb, ctl = self.sb_1, self.controller_1

        def watchdog():
            return False

        def rollback():
            return True

        ctl.use_watchdog = watchdog
        ctl.synchronizer.local_full_scan = []
        ctl.local_rollback = rollback
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1
        session = ctl.get_session()

        # Check local folder
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertTrue(folder_1_state.remote_can_rename)

        # Set remote folder as readonly for test user
        folder_1_path = self.TEST_WORKSPACE_PATH + u'/Original Folder 1'
        op_input = "doc:" + folder_1_path
        self.root_remote_client.execute("Document.SetACE",
                                        op_input=op_input,
                                        user="******",
                                        permission="Read")
        self.root_remote_client.block_inheritance(folder_1_path,
                                                  overwrite=False)

        # Check can_rename flag in pair state
        folder_1_state.refresh_remote(self.remote_file_system_client_1)
        self.assertFalse(folder_1_state.remote_can_rename)

        # Rename local folder
        local_client.rename(u'/Original Folder 1', u'Renamed Folder 1 \xe9')
        self.assertFalse(local_client.exists(u'/Original Folder 1'))
        self.assertTrue(local_client.exists(u'/Renamed Folder 1 \xe9'))

        ctl.synchronizer.update_synchronize_server(sb)

        # Check remote folder has not been renamed
        folder_1_remote_info = remote_client.get_info(u'/Original Folder 1')
        self.assertEquals(folder_1_remote_info.name, u'Original Folder 1')

        # Check state of local folder and its children
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertEquals(folder_1_state.remote_name, u'Original Folder 1')

        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Original File 1.1.txt'))
        file_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original File 1.1.txt').one()
        self.assertEquals(file_1_1_state.local_name, u'Original File 1.1.txt')
        self.assertEquals(file_1_1_state.remote_name, u'Original File 1.1.txt')

        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Sub-Folder 1.1'))
        folder_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.1').one()
        self.assertEquals(folder_1_1_state.local_name, u'Sub-Folder 1.1')
        self.assertEquals(folder_1_1_state.remote_name, u'Sub-Folder 1.1')

    def test_local_move_with_remote_error(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1

        # Check local folder
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        remote_client._remote_error = IOError
        local_client.rename(u'/Original Folder 1', u'IOErrorTest')
        ctl.synchronizer.update_synchronize_server(sb)
        remote_client._remote_error = None
        folder_1 = remote_client.get_info(u'/Original Folder 1')
        self.assertTrue(folder_1 is not None, 'Move has happen')
        self.assertTrue(local_client.exists(u'/IOErrorTest'))
        ctl.synchronizer.update_synchronize_server(sb)
        folder_1 = remote_client.get_info(folder_1.uid)
        self.assertEquals(folder_1.name, u'IOErrorTest', 'Move has not happen')
        self.assertTrue(local_client.exists(u'/IOErrorTest'))

    def test_local_delete_readonly_folder(self):
        sb, ctl = self.sb_1, self.controller_1
        local_client = self.local_client_1
        remote_client = self.remote_document_client_1
        session = ctl.get_session()

        # Check local folder
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertTrue(folder_1_state.remote_can_delete)

        # Set remote folder as readonly for test user
        folder_1_path = self.TEST_WORKSPACE_PATH + u'/Original Folder 1'
        op_input = "doc:" + folder_1_path
        self.root_remote_client.execute("Document.SetACE",
                                        op_input=op_input,
                                        user="******",
                                        permission="Read")
        self.root_remote_client.block_inheritance(folder_1_path,
                                                  overwrite=False)

        # Check can_delete flag in pair state
        folder_1_state.refresh_remote(self.remote_file_system_client_1)
        self.assertFalse(folder_1_state.remote_can_delete)

        # Delete local folder
        local_client.delete(u'/Original Folder 1')
        self.assertRaises(NotFound, local_client.get_info,
                          u'/Original Folder 1')

        self.assertEquals(ctl.synchronizer.update_synchronize_server(sb), 4)

        # Check remote folder and its children have not been deleted
        folder_1_remote_info = remote_client.get_info(u'/Original Folder 1')
        self.assertEquals(folder_1_remote_info.name, u'Original Folder 1')

        file_1_1_remote_info = remote_client.get_info(
            u'/Original Folder 1/Original File 1.1.txt')
        self.assertEquals(file_1_1_remote_info.name, u'Original File 1.1.txt')

        folder_1_1_remote_info = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.1')
        self.assertEquals(folder_1_1_remote_info.name, u'Sub-Folder 1.1')

        folder_1_2_remote_info = remote_client.get_info(
            u'/Original Folder 1/Sub-Folder 1.2')
        self.assertEquals(folder_1_2_remote_info.name, u'Sub-Folder 1.2')

        # Check local folder and its children have been re-created
        self.assertTrue(local_client.exists(u'/Original Folder 1'))
        folder_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original Folder 1').one()
        self.assertEquals(folder_1_state.local_name, u'Original Folder 1')
        self.assertEquals(folder_1_state.remote_name, u'Original Folder 1')

        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Original File 1.1.txt'))
        file_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Original File 1.1.txt').one()
        self.assertEquals(file_1_1_state.local_name, u'Original File 1.1.txt')
        self.assertEquals(file_1_1_state.remote_name, u'Original File 1.1.txt')

        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Sub-Folder 1.1'))
        folder_1_1_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.1').one()
        self.assertEquals(folder_1_1_state.local_name, u'Sub-Folder 1.1')
        self.assertEquals(folder_1_1_state.remote_name, u'Sub-Folder 1.1')

        self.assertTrue(
            local_client.exists(u'/Original Folder 1/Sub-Folder 1.2'))
        folder_1_2_state = session.query(LastKnownState).filter_by(
            local_name=u'Sub-Folder 1.2').one()
        self.assertEquals(folder_1_2_state.local_name, u'Sub-Folder 1.2')
        self.assertEquals(folder_1_2_state.remote_name, u'Sub-Folder 1.2')
    def test_delete_local_folder_update_remote_folder_property(self):

        # Get local and remote clients
        local = LocalClient(self.local_nxdrive_folder_1)
        remote = self.remote_document_client_1

        # Bind server and test workspace for nuxeoDriveTestUser_user_1
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        self.wait_audit_change_finder_if_needed()
        self.wait()
        sync = ctl.synchronizer
        sync.loop(delay=0.1, max_loops=1)

        # Test workspace should be created locally
        self.assertTrue(local.exists('/Nuxeo Drive Test Workspace'))

        # Create a local folder in the test workspace and a file inside
        # this folder, then synchronize
        local.make_folder('/Nuxeo Drive Test Workspace', 'Test folder')
        local.make_file('/Nuxeo Drive Test Workspace/Test folder',
                        'test.odt', 'Some content.')

        sync.loop(delay=0.1, max_loops=1)

        # Test folder should be created remotely in the test workspace
        self.assertTrue(remote.exists('/Test folder'))
        self.assertTrue(remote.exists('/Test folder/test.odt'))

        # Delete Test folder locally and remotely update one of its properties
        # concurrently, then synchronize
        local.delete('/Nuxeo Drive Test Workspace/Test folder')
        self.assertFalse(local.exists(
                                    '/Nuxeo Drive Test Workspace/Test folder'))
        test_folder_ref = remote._check_ref('/Test folder')
        # Wait for 1 second to make sure the folder's last modification time
        # will be different from the pair state's last remote update time
        time.sleep(self.REMOTE_MODIFICATION_TIME_RESOLUTION)
        remote.update(test_folder_ref,
                      properties={'dc:description': 'Some description.'})
        test_folder = remote.fetch(test_folder_ref)
        self.assertEqual(test_folder['properties']['dc:description'],
                         'Some description.')

        self.wait_audit_change_finder_if_needed()
        self.wait()
        sync.loop(delay=0.1, max_loops=1)

        # Test folder should be deleted remotely in the test workspace.
        # Even though fetching the remote changes will send a
        # 'documentModified' event for Test folder as a result of its
        # dc:description property update, since the folder will not have been
        # renamed nor moved since last synchronization, its remote pair state
        # will not be marked as 'modified', see Model.update_remote().
        # Thus the pair state will be ('deleted', 'synchronized'), resolved as
        # 'locally_deleted'.
        self.assertFalse(remote.exists('/Test folder'))

        # Check Test folder has not been re-created locally
        self.assertFalse(local.exists(
                                    '/Nuxeo Drive Test Workspace/Test folder'))
    def test_synchronize_paged_delete_detection(self):
        # Initialize a controller with page size = 1 for deleted items
        # detection query
        ctl = Controller(self.nxdrive_conf_folder_1, page_size=1)
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Launch first synchronization
        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn = ctl.synchronizer
        syn.loop(delay=0.1, max_loops=1)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1

        # Create a remote folder with 2 children then synchronize
        remote.make_folder('/', 'Remote folder',)
        remote.make_file('/Remote folder', 'Remote file 1.odt',
                         'Some content.')
        remote.make_file('/Remote folder', 'Remote file 2.odt',
                         'Other content.')

        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(local.exists('/Remote folder'))
        self.assertTrue(local.exists('/Remote folder/Remote file 1.odt'))
        self.assertTrue(local.exists('/Remote folder/Remote file 2.odt'))

        # Delete remote folder then synchronize
        remote.delete('/Remote folder')

        time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        self.assertFalse(local.exists('/Remote folder'))
        self.assertFalse(local.exists('/Remote folder/Remote file 1.odt'))
        self.assertFalse(local.exists('/Remote folder/Remote file 2.odt'))

        # Create a local folder with 2 children then synchronize
        local.make_folder('/', 'Local folder')
        local.make_file('/Local folder', 'Local file 1.odt', 'Some content.')
        local.make_file('/Local folder', 'Local file 2.odt', 'Other content.')

        syn.loop(delay=0.1, max_loops=1)
        self.assertTrue(remote.exists('/Local folder'))
        self.assertTrue(remote.exists('/Local folder/Local file 1.odt'))
        self.assertTrue(remote.exists('/Local folder/Local file 2.odt'))

        # Delete local folder then synchronize
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        local.delete('/Local folder')

        syn.loop(delay=0.1, max_loops=1)
        self.assertFalse(remote.exists('/Local folder'))
        self.assertFalse(remote.exists('/Local folder/Local file 1.odt'))
        self.assertFalse(remote.exists('/Local folder/Local file 2.odt'))

        # Dispose dedicated Controller instantiated for this test
        ctl.dispose()
Exemple #31
0
class TestIntegrationVersioning(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationVersioning, self).setUp()

        self.controller_1.bind_server(self.local_nxdrive_folder_1,
            self.nuxeo_url, self.user_1, self.password_1)
        self.controller_2.bind_server(self.local_nxdrive_folder_2,
            self.nuxeo_url, self.user_2, self.password_2)
        self.controller_1.bind_root(self.local_nxdrive_folder_1,
            self.workspace)
        self.controller_2.bind_root(self.local_nxdrive_folder_2,
            self.workspace)

        self.syn_1 = self.controller_1.synchronizer
        self.syn_2 = self.controller_2.synchronizer
        self.syn_1.loop(delay=0.010, max_loops=1, no_event_init=True)
        self.syn_2.loop(delay=0.010, max_loops=1, no_event_init=True)

        # Fetch server bindings after sync loop as it closes the Session
        self.sb_1 = self.controller_1.get_server_binding(
            self.local_nxdrive_folder_1)
        self.sb_2 = self.controller_2.get_server_binding(
            self.local_nxdrive_folder_2)

        self.remote_client_1 = self.remote_document_client_1
        self.remote_client_2 = self.remote_document_client_2
        sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                       self.workspace_title)
        sync_root_folder_2 = os.path.join(self.local_nxdrive_folder_2,
                                       self.workspace_title)
        self.local_client_1 = LocalClient(sync_root_folder_1)
        self.local_client_2 = LocalClient(sync_root_folder_2)

        # Call the Nuxeo operation to set the versioning delay to 10 seconds
        self.versioning_delay = self.OS_STAT_MTIME_RESOLUTION * 10
        self.root_remote_client.execute(
            "NuxeoDrive.SetVersioningOptions",
            delay=str(self.versioning_delay))

    def test_versioning(self):
        # Create a file as user 1
        self.local_client_1.make_file('/', 'Test versioning.txt',
            "This is version 0")
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 0)

        # Synchronize it for user 2
        self.assertTrue(self.remote_client_2.exists('/Test versioning.txt'))
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1, wait=True)
        self.assertTrue(self.local_client_2.exists('/Test versioning.txt'))

        # Update it as user 2 => should be versioned
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        self.local_client_2.update_content('/Test versioning.txt',
            "Modified content")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 1)

        # Update it as user 2 => should NOT be versioned
        # since the versioning delay (10s) is not passed by
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        self.local_client_2.update_content('/Test versioning.txt',
            "Content twice modified")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 1)

        # Update it as user 2 after 10s => should be versioned
        # since the versioning delay is passed by
        time.sleep(self.versioning_delay + 0.1)
        self.local_client_2.update_content('/Test versioning.txt',
            "Updated again!!")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, 0, 2)

    def test_version_restore(self):
        remote_client = self.remote_client_1
        local_client = self.local_client_1

        # Create a remote doc
        doc = remote_client.make_file(self.workspace,
                                    'Document to restore.txt',
                                    content="Initial content.")
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        self.assertTrue(local_client.exists('/Document to restore.txt'))
        self.assertEquals(local_client.get_content('/Document to restore.txt'),
                          "Initial content.")

        # Create version 1.0, update content, then restore version 1.0
        remote_client.create_version(doc, 'Major')
        # Ensure that modification time is different between the version
        # and the updated live document, otherwise the synchronizer won't
        # consider the restored document (with the modification date of
        # the version) as to be updated
        time.sleep(1.0)
        remote_client.update_content(doc, "Updated content.")
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        self.assertEquals(local_client.get_content('/Document to restore.txt'),
                          "Updated content.")
        version_uid = remote_client.get_versions(doc)[0][0]
        remote_client.restore_version(version_uid)
        self.wait_audit_change_finder_if_needed()
        self.wait()
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        self.assertEquals(local_client.get_content('/Document to restore.txt'),
                          "Initial content.")

    def _synchronize_and_assert(self, synchronizer, server_binding,
        expected_synchronized, wait=False):
        if wait:
            # Wait for audit changes to be detected after the 1 second step
            self.wait_audit_change_finder_if_needed()
            self.wait()
        n_synchronized = synchronizer.update_synchronize_server(server_binding)
        self.assertEqual(n_synchronized, expected_synchronized)

    def _assert_version(self, doc, major, minor):
        self.assertEquals(doc['properties']['uid:major_version'], major)
        self.assertEquals(doc['properties']['uid:minor_version'], minor)
Exemple #32
0
def test_local_scan():
    ctl.bind_server(TEST_SYNCED_FOLDER, 'http://example.com/nuxeo',
                    'username', 'secret')
    ctl.bind_root(TEST_SYNCED_FOLDER, 'folder_1-nuxeo-ref')
    ctl.bind_root(TEST_SYNCED_FOLDER, 'folder_2-nuxeo-ref')
    root_1 = join(TEST_SYNCED_FOLDER, 'Folder 1')
    root_2 = join(TEST_SYNCED_FOLDER, 'Folder 2')

    client_1 = LocalClient(root_1)

    # Folder are registered but empty for now
    assert_equal(ctl.children_states(root_1), [])
    assert_equal(ctl.children_states(root_2), [])

    # Put some content under the first root
    client_1.make_file('/', 'File 1.txt',
                       content="Initial 'File 1.txt' content")
    folder_3 = client_1.make_folder('/', 'Folder 3')
    client_1.make_file(folder_3, 'File 2.txt',
                       content="Initial 'File 2.txt' content")

    # The states have not been updated
    assert_equal(ctl.children_states(root_1), [])
    assert_equal(ctl.children_states(root_2), [])

    # Scanning the other root will not updated the first root states.
    session = ctl.get_session()
    ctl.scan_local(root_2, session)
    assert_equal(ctl.children_states(root_1), [])

    # Scanning root_1 will find the changes
    ctl.scan_local(root_1, session)
    assert_equal(ctl.children_states(root_1), [
        (u'/File 1.txt', u'unknown'),
        (u'/Folder 3', 'children_modified'),
    ])
    folder_3_abs = os.path.join(root_1, 'Folder 3')
    assert_equal(ctl.children_states(folder_3_abs), [
        (u'/Folder 3/File 2.txt', u'unknown'),
    ])

    # Wait a bit for file time stamps to increase enough: on most OS the file
    # modification time resolution is 1s
    time.sleep(1.0)

    # let's do some changes
    client_1.delete('/File 1.txt')
    client_1.make_folder('/Folder 3', 'Folder 3.1')
    client_1.make_file('/Folder 3', 'File 3.txt',
                      content="Initial 'File 3.txt' content")
    client_1.update_content('/Folder 3/File 2.txt',
                            "Updated content for 'File 2.txt'")

    # If we don't do a rescan, the controller is not aware of the changes
    assert_equal(ctl.children_states(root_1), [
        (u'/File 1.txt', u'unknown'),
        (u'/Folder 3', 'children_modified'),
    ])
    folder_3_abs = os.path.join(root_1, 'Folder 3')
    assert_equal(ctl.children_states(folder_3_abs), [
        (u'/Folder 3/File 2.txt', u'unknown'),
    ])

    # Let's scan again
    ctl.scan_local(root_1, session)
    assert_equal(ctl.children_states(root_1), [
        (u'/Folder 3', 'children_modified'),
    ])
    assert_equal(ctl.children_states(folder_3_abs), [
        (u'/Folder 3/File 2.txt', u'locally_modified'),
        (u'/Folder 3/File 3.txt', u'unknown'),
        (u'/Folder 3/Folder 3.1', u'unknown')
    ])

    # Delete the toplevel folder that has not been synchronised to the
    # server
    client_1.delete('/Folder 3')
    ctl.scan_local(root_1, session)
    assert_equal(ctl.children_states(root_1), [])
    assert_equal(ctl.children_states(folder_3_abs), [])
class TestIntegrationLocalRootDeletion(IntegrationTestCase):
    def setUp(self):
        super(TestIntegrationLocalRootDeletion, self).setUp()

        self.sb_1 = self.controller_1.bind_server(self.local_nxdrive_folder_1,
                                                  self.nuxeo_url, self.user_1,
                                                  self.password_1)

        self.controller_1.bind_root(self.local_nxdrive_folder_1,
                                    self.workspace)

        # Deactivate Watchdog as it prevents the Nuxeo Drive folder from being
        # well removed later on by shutil.rmtree, thus re-created by the
        # synchronizer during rollback if activated.
        def no_watchdog():
            return False

        self.controller_1.use_watchdog = no_watchdog

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)

        self.sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                               self.workspace_title)
        self.local_client_1 = LocalClient(self.sync_root_folder_1)

        self.local_client_1.make_file(
            '/',
            u'Original File 1.txt',
            content=u'Some Content 1'.encode('utf-8'))

        self.local_client_1.make_file(
            '/',
            u'Original File 2.txt',
            content=u'Some Content 2'.encode('utf-8'))

        self.local_client_1.make_folder(u'/', u'Original Folder 1')
        self.local_client_1.make_folder(u'/Original Folder 1',
                                        u'Sub-Folder 1.1')
        self.local_client_1.make_folder(u'/Original Folder 1',
                                        u'Sub-Folder 1.2')
        self.local_client_1.make_file(
            u'/Original Folder 1',
            u'Original File 1.1.txt',
            content=u'Some Content 1'.encode('utf-8'))  # Same content as OF1

        self.local_client_1.make_folder('/', 'Original Folder 2')
        self.local_client_1.make_file(
            '/Original Folder 2',
            u'Original File 3.txt',
            content=u'Some Content 3'.encode('utf-8'))

        self.controller_1.synchronizer.update_synchronize_server(self.sb_1)
        self.local_client_1.unlock_path(self.sync_root_folder_1)
        # Force the write mode under Windows
        import stat
        if os.access(self.local_nxdrive_folder_1, os.W_OK):
            os.chmod(
                self.local_nxdrive_folder_1, stat.S_IXUSR | stat.S_IRGRP
                | stat.S_IXGRP | stat.S_IRUSR | stat.S_IWGRP | stat.S_IWUSR)
        shutil.rmtree(self.local_nxdrive_folder_1, False)

    def test_without_rollback(self):
        sb, ctl = self.sb_1, self.controller_1
        ctl.synchronizer.update_synchronize_server(sb)
        self.assertFalse(os.path.exists(self.local_nxdrive_folder_1))
        self.assertFalse(sb in ctl.list_server_bindings())

    def test_with_rollback(self):
        sb, ctl = self.sb_1, self.controller_1

        def rollback():
            return True

        ctl.local_rollback = rollback

        ctl.synchronizer.update_synchronize_server(sb)
        self.assertTrue(os.path.exists(self.local_nxdrive_folder_1))
        sb = ctl.list_server_bindings()[0]
        ctl.synchronizer.update_synchronize_server(sb)
        self.assertTrue(os.path.exists(self.sync_root_folder_1))
    def test_synchronize_denying_read_access_local_modification(self):
        """Test denying Read access with concurrent local modification

        Use cases:
          - Deny Read access on a regular folder and make some
            local and remote changes concurrently.
              => Only locally modified content should be kept
                 and should be marked as 'unsynchronized',
                 other content should be deleted.
                 Remote changes should not be impacted client side.
                 Local changes should not be impacted server side.
          - Grant Read access back.
              => Remote documents should be merged with
                 locally modified content which should be unmarked
                 as 'unsynchronized' and therefore synchronized upstream.

        See TestIntegrationRemoteDeletion
                .test_synchronize_remote_deletion_local_modification
        as the same uses cases are tested.

        Note that we use the .odt extension for test files to make sure
        that they are created as File and not Note documents on the server
        when synchronized upstream, as the current implementation of
        RemoteDocumentClient is File oriented.
        """
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1
        root_remote = self.root_remote_client

        # Create documents in the remote root workspace
        # then synchronize
        remote.make_folder('/', 'Test folder')
        remote.make_file('/Test folder', 'joe.odt', 'Some content')
        remote.make_file('/Test folder', 'jack.odt', 'Some content')
        remote.make_folder('/Test folder', 'Sub folder 1')
        remote.make_file('/Test folder/Sub folder 1', 'sub file 1.txt',
                         'Content')

        syn = ctl.synchronizer
        self._synchronize(syn)
        self.assertTrue(local.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))

        # Remove Read permission for test user on a regular folder
        # and make some local and remote changes concurrently then synchronize
        test_folder_path = self.TEST_WORKSPACE_PATH + '/Test folder'
        self._set_read_permission("nuxeoDriveTestUser_user_1",
                                  test_folder_path, False)
        # Local changes
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        # Create new file
        local.make_file('/Test folder', 'local.odt', 'New local content')
        # Create new folder with files
        local.make_folder('/Test folder', 'Local sub folder 2')
        local.make_file('/Test folder/Local sub folder 2',
                        'local sub file 2.txt', 'Other local content')
        # Update file
        local.update_content('/Test folder/joe.odt',
                             'Some locally updated content')
        # Remote changes
        # Create new file
        root_remote.make_file(test_folder_path, 'remote.odt',
                              'New remote content')
        # Create new folder with files
        root_remote.make_folder(test_folder_path, 'Remote sub folder 2')
        root_remote.make_file(test_folder_path + '/Remote sub folder 2',
                'remote sub file 2.txt', 'Other remote content')
        # Update file
        root_remote.update_content(test_folder_path + '/joe.odt',
                'Some remotely updated content')

        self._synchronize(syn)
        # Only locally modified content should exist
        # and should be marked as 'unsynchronized', other content should
        # have been deleted.
        # Remote changes should not be impacted client side.
        # Local changes should not be impacted server side.
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        self.assertEquals(len(local.get_children_info('/Test folder')), 3)
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertEquals(local.get_content('/Test folder/joe.odt'),
                          'Some locally updated content')
        self.assertTrue(local.exists('/Test folder/local.odt'))
        self.assertTrue(local.exists('/Test folder/Local sub folder 2'))
        self.assertTrue(local.exists(
                    '/Test folder/Local sub folder 2/local sub file 2.txt'))

        self.assertFalse(local.exists('/Test folder/jack.odt'))
        self.assertFalse(local.exists('/Test folder/remote.odt'))
        self.assertFalse(local.exists('/Test folder/Sub folder 1'))
        self.assertFalse(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertFalse(local.exists('/Test folder/Remote sub folder 1'))
        self.assertFalse(local.exists(
                    '/Test folder/Remote sub folder 1/remote sub file 1.txt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'unsynchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/local.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/Local sub folder 2',
                               'unsynchronized')
        self._check_pair_state(session,
                        '/Test folder/Local sub folder 2/local sub file 2.txt',
                        'unsynchronized')
        # Remote check
        test_folder_uid = root_remote.get_info(test_folder_path).uid
        self.assertEquals(len(root_remote.get_children_info(
                                                        test_folder_uid)), 5)
        self.assertTrue(root_remote.exists(test_folder_path + '/joe.odt'))
        self.assertEquals(root_remote.get_content(
                                            test_folder_path + '/joe.odt'),
                                            'Some remotely updated content')
        self.assertTrue(root_remote.exists(test_folder_path + '/jack.odt'))
        self.assertTrue(root_remote.exists(test_folder_path + '/remote.odt'))
        self.assertTrue(root_remote.exists(test_folder_path + '/Sub folder 1'))
        self.assertTrue(root_remote.exists(
            test_folder_path + '/Sub folder 1/sub file 1.txt'))
        self.assertTrue(root_remote.exists(
            test_folder_path + '/Remote sub folder 2'))
        self.assertTrue(root_remote.exists(
            test_folder_path + '/Remote sub folder 2/remote sub file 2.txt'))

        self.assertFalse(root_remote.exists(test_folder_path + '/local.odt'))
        self.assertFalse(root_remote.exists(
            test_folder_path + '/Local sub folder 2'))
        self.assertFalse(root_remote.exists(
            test_folder_path + '/Local sub folder 1/local sub file 2.txt'))

        # Add Read permission back for test user then synchronize
        self._set_read_permission("nuxeoDriveTestUser_user_1",
                                  self.TEST_WORKSPACE_PATH + '/Test folder',
                                  True)
        self._synchronize(syn)
        # Remote documents should be merged with locally modified content
        # which should be unmarked as 'unsynchronized' and therefore
        # synchronized upstream.
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        children_info = local.get_children_info('/Test folder')
        self.assertEquals(len(children_info), 8)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        self.assertTrue(local.exists(remote_version.path))
        self.assertEquals(local.get_content(remote_version.path),
                          'Some remotely updated content')
        self.assertTrue(local.exists(local_version.path))
        self.assertEquals(local.get_content(local_version.path),
                          'Some locally updated content')
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/local.odt'))
        self.assertTrue(local.exists('/Test folder/remote.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(local.exists('/Test folder/Local sub folder 2'))
        self.assertTrue(local.exists(
                    '/Test folder/Local sub folder 2/local sub file 2.txt'))
        self.assertTrue(local.exists('/Test folder/Remote sub folder 2'))
        self.assertTrue(local.exists(
                    '/Test folder/Remote sub folder 2/remote sub file 2.txt'))
        # State check
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/local.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/Local sub folder 2',
                               'synchronized')
        self._check_pair_state(session,
                        '/Test folder/Local sub folder 2/local sub file 2.txt',
                        'synchronized')
        # Remote check
        self.assertTrue(remote.exists('/Test folder'))
        children_info = remote.get_children_info(test_folder_uid)
        self.assertEquals(len(children_info), 8)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        remote_version_ref_length = (len(remote_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        remote_version_ref = remote_version.path[-remote_version_ref_length:]
        self.assertTrue(remote.exists(remote_version_ref))
        self.assertEquals(remote.get_content(remote_version_ref),
                          'Some remotely updated content')
        local_version_ref_length = (len(local_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        local_version_ref = local_version.path[-local_version_ref_length:]
        self.assertTrue(remote.exists(local_version_ref))
        self.assertEquals(remote.get_content(local_version_ref),
                          'Some locally updated content')
        self.assertTrue(remote.exists('/Test folder/jack.odt'))
        self.assertTrue(remote.exists('/Test folder/local.odt'))
        self.assertTrue(remote.exists('/Test folder/remote.odt'))
        self.assertTrue(remote.exists('/Test folder/Sub folder 1'))
        self.assertTrue(remote.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(remote.exists('/Test folder/Local sub folder 2'))
        self.assertTrue(remote.exists(
                    '/Test folder/Local sub folder 2/local sub file 2.txt'))
        self.assertTrue(remote.exists('/Test folder/Remote sub folder 2'))
        self.assertTrue(remote.exists(
                    '/Test folder/Remote sub folder 2/remote sub file 2.txt'))
class TestIntegrationVersioning(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationVersioning, self).setUp()

        self.controller_1.bind_server(self.local_nxdrive_folder_1,
            self.nuxeo_url, self.user_1, self.password_1)
        self.controller_2.bind_server(self.local_nxdrive_folder_2,
            self.nuxeo_url, self.user_2, self.password_2)
        self.sb_1 = self.controller_1.get_server_binding(
            self.local_nxdrive_folder_1)
        self.sb_2 = self.controller_2.get_server_binding(
            self.local_nxdrive_folder_2)
        self.controller_1.bind_root(self.local_nxdrive_folder_1,
            self.workspace)
        self.controller_2.bind_root(self.local_nxdrive_folder_2,
            self.workspace)

        self.syn_1 = self.controller_1.synchronizer
        self.syn_2 = self.controller_2.synchronizer
        self.syn_1.loop(delay=0.010, max_loops=1)
        self.syn_2.loop(delay=0.010, max_loops=1)

        self.remote_client_1 = self.remote_document_client_1
        self.remote_client_2 = self.remote_document_client_2
        sync_root_folder_1 = os.path.join(self.local_nxdrive_folder_1,
                                       self.workspace_title)
        sync_root_folder_2 = os.path.join(self.local_nxdrive_folder_2,
                                       self.workspace_title)
        self.local_client_1 = LocalClient(sync_root_folder_1)
        self.local_client_2 = LocalClient(sync_root_folder_2)

        # Call the Nuxeo operation to set the versioning delay to 3 seconds
        self.versioning_delay = self.OS_STAT_MTIME_RESOLUTION * 3
        self.root_remote_client.execute(
            "NuxeoDrive.SetVersioningOptions",
            delay=str(self.versioning_delay))

    def test_versioning(self):
        # Create a file as user 1
        self.local_client_1.make_file('/', 'Test versioning.txt',
            "This is version 0")
        self._synchronize_and_assert(self.syn_1, self.sb_1, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, '0', '0')

        # Synchronize it for user 2
        self.assertTrue(self.remote_client_2.exists('/Test versioning.txt'))
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1, wait=True)
        self.assertTrue(self.local_client_2.exists('/Test versioning.txt'))

        # Update it as user 2 => should be versioned
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        self.local_client_2.update_content('/Test versioning.txt',
            "Modified content")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, '0', '1')

        # Update it as user 2 => should NOT be versioned
        # since the versioning delay (3s) is not passed by
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        self.local_client_2.update_content('/Test versioning.txt',
            "Content twice modified")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, '0', '1')

        # Update it as user 2 after 3s => should be versioned
        # since the versioning delay is passed by
        time.sleep(self.versioning_delay + 0.1)
        self.local_client_2.update_content('/Test versioning.txt',
            "Updated again!!")
        self._synchronize_and_assert(self.syn_2, self.sb_2, 1)
        doc = self.root_remote_client.fetch(
            self.TEST_WORKSPACE_PATH + '/Test versioning.txt')
        self._assert_version(doc, '0', '2')

    def _synchronize_and_assert(self, synchronizer, server_binding,
        expected_synchronized, wait=False):
        if wait:
            # Wait for audit changes to be detected after the 1 second step
            time.sleep(self.AUDIT_CHANGE_FINDER_TIME_RESOLUTION)
        n_synchronized = synchronizer.update_synchronize_server(server_binding)
        self.assertEqual(n_synchronized, expected_synchronized)

    def _assert_version(self, doc, major, minor):
        self.assertEquals(doc['properties']['uid:major_version'], major)
        self.assertEquals(doc['properties']['uid:minor_version'], minor)
    def test_create_content_in_readonly_area(self):
        raise SkipTest("WIP in https://jira.nuxeo.com/browse/NXDRIVE-170")
        # Let's bind a the server but no root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        syn = ctl.synchronizer
        self.wait()

        syn.loop(delay=0.1, max_loops=1)
        self.assertEquals(ctl.list_pending(), [])

        # Let's create a subfolder of the main readonly folder
        local = LocalClient(self.local_nxdrive_folder_1)
        local.make_folder('/', 'Folder 3')
        local.make_file('/Folder 3', 'File 1.txt', content='Some content.')
        local.make_folder('/Folder 3', 'Sub Folder 1')
        local.make_file('/Folder 3/Sub Folder 1', 'File 2.txt',
                        content='Some other content.')
        syn.loop(delay=0.1, max_loops=1)

        # Pairs have been created for the subfolder and its content,
        # marked as synchronized
        self.assertEquals(self.get_all_states(get_pair_state=True), [
            (u'/', u'synchronized', u'synchronized', u'synchronized'),
            (u'/Folder 3', u'created', u'unknown', u'unsynchronized'),
            (u'/Folder 3/File 1.txt', u'created', u'unknown',
             u'unsynchronized'),
            (u'/Folder 3/Sub Folder 1', u'created', u'unknown',
             u'unsynchronized'),
            (u'/Folder 3/Sub Folder 1/File 2.txt',
             u'created', u'unknown', u'unsynchronized'),
        ])
        self.assertEquals(ctl.list_pending(), [])

        # Let's create a file in the main readonly folder
        local.make_file('/', 'A file in a readonly folder.txt',
            content='Some Content')
        syn.loop(delay=0.1, max_loops=1)

        # A pair has been created, marked as synchronized
        self.assertEquals(self.get_all_states(get_pair_state=True), [
            (u'/', u'synchronized', u'synchronized', u'synchronized'),
            (u'/A file in a readonly folder.txt',
             u'created', u'unknown', u'unsynchronized'),
            (u'/Folder 3', u'created', u'unknown', u'unsynchronized'),
            (u'/Folder 3/File 1.txt', u'created', u'unknown',
             u'unsynchronized'),
            (u'/Folder 3/Sub Folder 1', u'created', u'unknown',
             u'unsynchronized'),
            (u'/Folder 3/Sub Folder 1/File 2.txt',
             u'created', u'unknown', u'unsynchronized'),
        ])
        self.assertEquals(len(ctl.list_pending(ignore_in_error=300)), 0)

        # Let's create a file and a folder in a folder on which the Write
        # permission has been removed. Thanks to NXP-13119, this permission
        # change will be detected server-side, thus fetched by the client
        # in the remote change summary, and the remote_can_create_child flag
        # on which the synchronizer relies to check if creation is allowed
        # will be set to False and no attempt to create the remote file
        # will be made.

        # Bind root workspace, create local folder and synchronize it remotely
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)
        self.wait()
        syn.loop(delay=0.1, max_loops=1)

        local = LocalClient(
            os.path.join(self.local_nxdrive_folder_1, self.workspace_title))
        local.make_folder(u'/', u'Readonly folder')
        syn.loop(delay=0.1, max_loops=1)

        remote = self.remote_document_client_1
        self.assertTrue(remote.exists(u'/Readonly folder'))

        # Check remote_can_create_child flag in pair state
        session = ctl.get_session()
        readonly_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Readonly folder').one()
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Make one sync loop to detect remote folder creation triggered
        # by last synchronization and make sure we get a clean state at
        # next change summary
        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        # Re-fetch folder state as sync loop closes the Session
        readonly_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Readonly folder').one()
        self.assertTrue(readonly_folder_state.remote_can_create_child)

        # Set remote folder as readonly for test user
        readonly_folder_path = TEST_WORKSPACE_PATH + u'/Readonly folder'
        op_input = "doc:" + readonly_folder_path
        self.root_remote_client.execute("Document.SetACE",
            op_input=op_input,
            user="******",
            permission="Read")
        self.root_remote_client.block_inheritance(readonly_folder_path,
                                                  overwrite=False)

        # Wait to make sure permission change is detected.
        self.wait()
        syn.loop(delay=0.1, max_loops=1)
        # Re-fetch folder state as sync loop closes the Session
        readonly_folder_state = session.query(LastKnownState).filter_by(
            local_name=u'Readonly folder').one()
        self.assertFalse(readonly_folder_state.remote_can_create_child)

        # Try to create a local file and folder in the readonly folder,
        # they should not be created remotely.
        local.make_file(u'/Readonly folder', u'File in readonly folder',
                        u"File content")
        local.make_folder(u'/Readonly folder', u'Folder in readonly folder')
        syn.loop(delay=0.1, max_loops=1)
        self.assertFalse(remote.exists(
            u'/Readonly folder/File in readonly folder'))
        self.assertFalse(remote.exists(
            u'/Readonly folder/Folder in readonly folder'))
    def test_synchronize_denying_read_access_local_modification(self):
        """Test denying Read access with concurrent local modification

        Use cases:
          - Deny Read access on a regular folder and make some
            local and remote changes concurrently.
              => Only locally modified content should be kept
                 and should be marked as 'unsynchronized',
                 other content should be deleted.
                 Remote changes should not be impacted client side.
                 Local changes should not be impacted server side.
          - Grant Read access back.
              => Remote documents should be merged with
                 locally modified content which should be unmarked
                 as 'unsynchronized' and therefore synchronized upstream.

        See TestIntegrationRemoteDeletion
                .test_synchronize_remote_deletion_local_modification
        as the same uses cases are tested.

        Note that we use the .odt extension for test files to make sure
        that they are created as File and not Note documents on the server
        when synchronized upstream, as the current implementation of
        RemoteDocumentClient is File oriented.
        """
        # Bind the server and root workspace
        ctl = self.controller_1
        ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                        self.user_1, self.password_1)
        ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        # Get local and remote clients
        local = LocalClient(os.path.join(self.local_nxdrive_folder_1,
                                         self.workspace_title))
        remote = self.remote_document_client_1
        root_remote = self.root_remote_client

        # Create documents in the remote root workspace
        # then synchronize
        remote.make_folder('/', 'Test folder')
        remote.make_file('/Test folder', 'joe.odt', 'Some content')
        remote.make_file('/Test folder', 'jack.odt', 'Some content')
        remote.make_folder('/Test folder', 'Sub folder 1')
        remote.make_file('/Test folder/Sub folder 1', 'sub file 1.txt',
                         'Content')

        syn = ctl.synchronizer
        self._synchronize(syn)
        self.assertTrue(local.exists('/Test folder'))
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))

        # Remove Read permission for test user on a regular folder
        # and make some local and remote changes concurrently then synchronize
        test_folder_path = self.TEST_WORKSPACE_PATH + '/Test folder'
        self._set_read_permission("nuxeoDriveTestUser_user_1",
                                  test_folder_path, False)
        # Local changes
        time.sleep(self.OS_STAT_MTIME_RESOLUTION)
        # Create new file
        local.make_file('/Test folder', 'local.odt', 'New local content')
        # Create new folder with files
        local.make_folder('/Test folder', 'Local sub folder 2')
        local.make_file('/Test folder/Local sub folder 2',
                        'local sub file 2.txt', 'Other local content')
        # Update file
        local.update_content('/Test folder/joe.odt',
                             'Some locally updated content')
        # Remote changes
        # Create new file
        root_remote.make_file(test_folder_path, 'remote.odt',
                              'New remote content')
        # Create new folder with files
        root_remote.make_folder(test_folder_path, 'Remote sub folder 2')
        root_remote.make_file(test_folder_path + '/Remote sub folder 2',
                'remote sub file 2.txt', 'Other remote content')
        # Update file
        root_remote.update_content(test_folder_path + '/joe.odt',
                'Some remotely updated content')

        self._synchronize(syn)
        # Only locally modified content should exist
        # and should be marked as 'unsynchronized', other content should
        # have been deleted.
        # Remote changes should not be impacted client side.
        # Local changes should not be impacted server side.
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        self.assertEquals(len(local.get_children_info('/Test folder')), 3)
        self.assertTrue(local.exists('/Test folder/joe.odt'))
        self.assertEquals(local.get_content('/Test folder/joe.odt'),
                          'Some locally updated content')
        self.assertTrue(local.exists('/Test folder/local.odt'))
        self.assertTrue(local.exists('/Test folder/Local sub folder 2'))
        self.assertTrue(local.exists(
                    '/Test folder/Local sub folder 2/local sub file 2.txt'))

        self.assertFalse(local.exists('/Test folder/jack.odt'))
        self.assertFalse(local.exists('/Test folder/remote.odt'))
        self.assertFalse(local.exists('/Test folder/Sub folder 1'))
        self.assertFalse(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertFalse(local.exists('/Test folder/Remote sub folder 1'))
        self.assertFalse(local.exists(
                    '/Test folder/Remote sub folder 1/remote sub file 1.txt'))
        # State check
        session = ctl.get_session()
        self._check_pair_state(session, '/Test folder', 'unsynchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/local.odt',
                               'unsynchronized')
        self._check_pair_state(session, '/Test folder/Local sub folder 2',
                               'unsynchronized')
        self._check_pair_state(session,
                        '/Test folder/Local sub folder 2/local sub file 2.txt',
                        'unsynchronized')
        # Remote check
        test_folder_uid = root_remote.get_info(test_folder_path).uid
        self.assertEquals(len(root_remote.get_children_info(
                                                        test_folder_uid)), 5)
        self.assertTrue(root_remote.exists(test_folder_path + '/joe.odt'))
        self.assertEquals(root_remote.get_content(
                                            test_folder_path + '/joe.odt'),
                                            'Some remotely updated content')
        self.assertTrue(root_remote.exists(test_folder_path + '/jack.odt'))
        self.assertTrue(root_remote.exists(test_folder_path + '/remote.odt'))
        self.assertTrue(root_remote.exists(test_folder_path + '/Sub folder 1'))
        self.assertTrue(root_remote.exists(
            test_folder_path + '/Sub folder 1/sub file 1.txt'))
        self.assertTrue(root_remote.exists(
            test_folder_path + '/Remote sub folder 2'))
        self.assertTrue(root_remote.exists(
            test_folder_path + '/Remote sub folder 2/remote sub file 2.txt'))

        self.assertFalse(root_remote.exists(test_folder_path + '/local.odt'))
        self.assertFalse(root_remote.exists(
            test_folder_path + '/Local sub folder 2'))
        self.assertFalse(root_remote.exists(
            test_folder_path + '/Local sub folder 1/local sub file 2.txt'))

        # Add Read permission back for test user then synchronize
        self._set_read_permission("nuxeoDriveTestUser_user_1",
                                  self.TEST_WORKSPACE_PATH + '/Test folder',
                                  True)
        self._synchronize(syn)
        # Remote documents should be merged with locally modified content
        # which should be unmarked as 'unsynchronized' and therefore
        # synchronized upstream.
        # Local check
        self.assertTrue(local.exists('/Test folder'))
        children_info = local.get_children_info('/Test folder')
        self.assertEquals(len(children_info), 8)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        self.assertTrue(local.exists(remote_version.path))
        self.assertEquals(local.get_content(remote_version.path),
                          'Some remotely updated content')
        self.assertTrue(local.exists(local_version.path))
        self.assertEquals(local.get_content(local_version.path),
                          'Some locally updated content')
        self.assertTrue(local.exists('/Test folder/jack.odt'))
        self.assertTrue(local.exists('/Test folder/local.odt'))
        self.assertTrue(local.exists('/Test folder/remote.odt'))
        self.assertTrue(local.exists('/Test folder/Sub folder 1'))
        self.assertTrue(local.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(local.exists('/Test folder/Local sub folder 2'))
        self.assertTrue(local.exists(
                    '/Test folder/Local sub folder 2/local sub file 2.txt'))
        self.assertTrue(local.exists('/Test folder/Remote sub folder 2'))
        self.assertTrue(local.exists(
                    '/Test folder/Remote sub folder 2/remote sub file 2.txt'))
        # State check
        self._check_pair_state(session, '/Test folder', 'synchronized')
        self._check_pair_state(session, '/Test folder/joe.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/local.odt',
                               'synchronized')
        self._check_pair_state(session, '/Test folder/Local sub folder 2',
                               'synchronized')
        self._check_pair_state(session,
                        '/Test folder/Local sub folder 2/local sub file 2.txt',
                        'synchronized')
        # Remote check
        self.assertTrue(remote.exists('/Test folder'))
        children_info = remote.get_children_info(test_folder_uid)
        self.assertEquals(len(children_info), 8)
        for info in children_info:
            if info.name == 'joe.odt':
                remote_version = info
            elif info.name.startswith('joe (') and info.name.endswith(').odt'):
                local_version = info
        self.assertTrue(remote_version is not None)
        self.assertTrue(local_version is not None)
        remote_version_ref_length = (len(remote_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        remote_version_ref = remote_version.path[-remote_version_ref_length:]
        self.assertTrue(remote.exists(remote_version_ref))
        self.assertEquals(remote.get_content(remote_version_ref),
                          'Some remotely updated content')
        local_version_ref_length = (len(local_version.path)
                                     - len(self.TEST_WORKSPACE_PATH))
        local_version_ref = local_version.path[-local_version_ref_length:]
        self.assertTrue(remote.exists(local_version_ref))
        self.assertEquals(remote.get_content(local_version_ref),
                          'Some locally updated content')
        self.assertTrue(remote.exists('/Test folder/jack.odt'))
        self.assertTrue(remote.exists('/Test folder/local.odt'))
        self.assertTrue(remote.exists('/Test folder/remote.odt'))
        self.assertTrue(remote.exists('/Test folder/Sub folder 1'))
        self.assertTrue(remote.exists(
                                '/Test folder/Sub folder 1/sub file 1.txt'))
        self.assertTrue(remote.exists('/Test folder/Local sub folder 2'))
        self.assertTrue(remote.exists(
                    '/Test folder/Local sub folder 2/local sub file 2.txt'))
        self.assertTrue(remote.exists('/Test folder/Remote sub folder 2'))
        self.assertTrue(remote.exists(
                    '/Test folder/Remote sub folder 2/remote sub file 2.txt'))
class TestIntegrationEncoding(IntegrationTestCase):
    def setUp(self):
        super(TestIntegrationEncoding, self).setUp()

        self.ctl = self.controller_1
        self.ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
                             self.user_1, self.password_1)
        self.ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        self.syn = self.ctl.synchronizer
        self.syn.loop(delay=0.010, max_loops=1, no_event_init=True)

        # Fetch server binding after sync loop as it closes the Session
        self.sb = self.ctl.get_server_binding(self.local_nxdrive_folder_1)

        self.remote_client = self.remote_document_client_1
        sync_root_folder = os.path.join(self.local_nxdrive_folder_1,
                                        self.workspace_title)
        self.local_client = LocalClient(sync_root_folder)

    def test_filename_with_accents_from_server(self):
        self.remote_client.make_file(self.workspace, u'Nom sans accents.doc',
                                     u"Contenu sans accents.")
        self.remote_client.make_file(self.workspace,
                                     u'Nom avec accents \xe9 \xe8.doc',
                                     u"Contenu sans accents.")

        self._synchronize_and_assert(2, wait=True)

        self.assertEquals(
            self.local_client.get_content(u'/Nom sans accents.doc'),
            u"Contenu sans accents.")
        self.assertEquals(
            self.local_client.get_content(u'/Nom avec accents \xe9 \xe8.doc'),
            u"Contenu sans accents.")

    def test_filename_with_katakana_from_server(self):
        self.remote_client.make_file(self.workspace,
                                     u'Nom sans \u30bc\u30ec accents.doc',
                                     u"Contenu")
        self.local_client.make_file('/', u'Avec accents \u30d7 \u793e.doc',
                                    u"Contenu")

        self._synchronize_and_assert(2, wait=True)

        self.assertEquals(
            self.local_client.get_content(
                u'/Nom sans \u30bc\u30ec accents.doc'), u"Contenu")
        self.assertEquals(
            self.local_client.get_content(u'/Avec accents \u30d7 \u793e.doc'),
            u"Contenu")

    def test_content_with_accents_from_server(self):
        self.remote_client.make_file(
            self.workspace, u'Nom sans accents.txt',
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))
        self._synchronize_and_assert(1, wait=True)
        self.assertEquals(
            self.local_client.get_content(u'/Nom sans accents.txt'),
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))

    def test_filename_with_accents_from_client(self):
        self.local_client.make_file('/', u'Avec accents \xe9 \xe8.doc',
                                    u"Contenu sans accents.")
        self.local_client.make_file('/', u'Sans accents.doc',
                                    u"Contenu sans accents.")
        self._synchronize_and_assert(2)
        self.assertEquals(
            self.remote_client.get_content(u'/Avec accents \xe9 \xe8.doc'),
            u"Contenu sans accents.")
        self.assertEquals(self.remote_client.get_content(u'/Sans accents.doc'),
                          u"Contenu sans accents.")

    def test_content_with_accents_from_client(self):
        self.local_client.make_file(
            '/', u'Nom sans accents',
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))
        self._synchronize_and_assert(1)
        self.assertEquals(
            self.remote_client.get_content(u'/Nom sans accents'),
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))

    def test_name_normalization(self):
        self.local_client.make_file('/', u'espace\xa0 et TM\u2122.doc')
        self._synchronize_and_assert(1)
        self.assertEquals(
            self.remote_client.get_info(u'/espace\xa0 et TM\u2122.doc').name,
            u'espace\xa0 et TM\u2122.doc')

    def _synchronize_and_assert(self, expected_synchronized, wait=False):
        if wait:
            # Wait for audit changes to be detected after the 1 second step
            self.wait_audit_change_finder_if_needed()
            self.wait()
        n_synchronized = self.syn.update_synchronize_server(self.sb)
        self.assertEqual(n_synchronized, expected_synchronized)
class TestIntegrationEncoding(IntegrationTestCase):

    def setUp(self):
        super(TestIntegrationEncoding, self).setUp()

        self.ctl = self.controller_1
        self.ctl.bind_server(self.local_nxdrive_folder_1, self.nuxeo_url,
            self.user_1, self.password_1)
        self.ctl.bind_root(self.local_nxdrive_folder_1, self.workspace)

        self.syn = self.ctl.synchronizer
        self.syn.loop(delay=0.010, max_loops=1, no_event_init=True)

        # Fetch server binding after sync loop as it closes the Session
        self.sb = self.ctl.get_server_binding(self.local_nxdrive_folder_1)

        self.remote_client = self.remote_document_client_1
        sync_root_folder = os.path.join(self.local_nxdrive_folder_1,
            self.workspace_title)
        self.local_client = LocalClient(sync_root_folder)

    def test_filename_with_accents_from_server(self):
        self.remote_client.make_file(self.workspace,
            u'Nom sans accents.doc',
            u"Contenu sans accents.")
        self.remote_client.make_file(self.workspace,
            u'Nom avec accents \xe9 \xe8.doc',
            u"Contenu sans accents.")

        self._synchronize_and_assert(2, wait=True)

        self.assertEquals(self.local_client.get_content(
            u'/Nom sans accents.doc'),
            u"Contenu sans accents.")
        self.assertEquals(self.local_client.get_content(
            u'/Nom avec accents \xe9 \xe8.doc'),
            u"Contenu sans accents.")

    def test_filename_with_katakana_from_server(self):
        self.remote_client.make_file(self.workspace,
            u'Nom sans \u30bc\u30ec accents.doc',
            u"Contenu")
        self.local_client.make_file('/',
            u'Avec accents \u30d7 \u793e.doc',
            u"Contenu")

        self._synchronize_and_assert(2, wait=True)

        self.assertEquals(self.local_client.get_content(
            u'/Nom sans \u30bc\u30ec accents.doc'),
            u"Contenu")
        self.assertEquals(self.local_client.get_content(
            u'/Avec accents \u30d7 \u793e.doc'),
            u"Contenu")

    def test_content_with_accents_from_server(self):
        self.remote_client.make_file(self.workspace,
            u'Nom sans accents.txt',
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))
        self._synchronize_and_assert(1, wait=True)
        self.assertEquals(self.local_client.get_content(
            u'/Nom sans accents.txt'),
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))

    def test_filename_with_accents_from_client(self):
        self.local_client.make_file('/',
            u'Avec accents \xe9 \xe8.doc',
            u"Contenu sans accents.")
        self.local_client.make_file('/',
            u'Sans accents.doc',
            u"Contenu sans accents.")
        self._synchronize_and_assert(2)
        self.assertEquals(self.remote_client.get_content(
            u'/Avec accents \xe9 \xe8.doc'),
            u"Contenu sans accents.")
        self.assertEquals(self.remote_client.get_content(
            u'/Sans accents.doc'),
            u"Contenu sans accents.")

    def test_content_with_accents_from_client(self):
        self.local_client.make_file('/',
            u'Nom sans accents',
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))
        self._synchronize_and_assert(1)
        self.assertEquals(self.remote_client.get_content(
            u'/Nom sans accents'),
            u"Contenu avec caract\xe8res accentu\xe9s.".encode('utf-8'))

    def test_name_normalization(self):
        self.local_client.make_file('/',
            u'espace\xa0 et TM\u2122.doc')
        self._synchronize_and_assert(1)
        self.assertEquals(self.remote_client.get_info(
            u'/espace\xa0 et TM\u2122.doc').name,
            u'espace\xa0 et TM\u2122.doc')

    def _synchronize_and_assert(self, expected_synchronized, wait=False):
        if wait:
            # Wait for audit changes to be detected after the 1 second step
            self.wait_audit_change_finder_if_needed()
            self.wait()
        n_synchronized = self.syn.update_synchronize_server(self.sb)
        self.assertEqual(n_synchronized, expected_synchronized)