def is_version_compatible( version_id: str, version: Version, server: str, has_browser_login: bool, / ) -> bool: """ Check Drive <-> server version compatibility. Try first the min_all and max_all keys that contain all server versions. Fallback on min and max keys that contain only one server version: the oldest supported. """ if not (has_browser_login or version_lt(version_id, "4")): return False # Remove HF and SNAPSHOT base_server = server.split("-")[0] ver_min = ( version.get("min_all", {}).get(base_server) or version.get("min", "") ).upper() if not ver_min or version_lt(server, ver_min): return False ver_max = ( version.get("max_all", {}).get(base_server) or version.get("max", "") ).upper() return not (ver_max and version_lt(ver_max, server))
def test_synchronize_remote_deletion(self): """Test that deleting remote documents is impacted client side Use cases: - Remotely delete a regular folder => Folder should be locally deleted - Remotely restore folder from the trash => Folder should be locally re-created - Remotely delete a synchronization root => Synchronization root should be locally deleted - Remotely restore synchronization root from the trash => Synchronization root should be locally re-created See TestIntegrationSecurityUpdates.test_synchronize_denying_read_access as the same uses cases are tested """ # Bind the server and root workspace self.engine_1.start() # Get local and remote clients local = self.local_1 remote = self.remote_document_client_1 remote_admin = self.root_remote # Create documents in the remote root workspace # then synchronize folder_id = remote.make_folder("/", "Test folder") file_id = remote.make_file("/Test folder", "joe.txt", content=b"Some content") self.wait_sync(wait_for_async=True) assert local.exists("/Test folder") assert local.exists("/Test folder/joe.txt") # Delete remote folder then synchronize remote.delete("/Test folder") self.wait_sync(wait_for_async=True) assert not local.exists("/Test folder") # Restore folder from trash then synchronize remote.undelete(folder_id) if version_lt(remote.client.server_version, "10.2"): remote.undelete(file_id) self.wait_sync(wait_for_async=True) assert local.exists("/Test folder") assert local.exists("/Test folder/joe.txt") # Delete sync root then synchronize remote_admin.delete(self.workspace) self.wait_sync(wait_for_async=True) assert not local.exists("/") # Restore sync root from trash then synchronize remote_admin.undelete(self.workspace) if version_lt(remote.client.server_version, "10.2"): remote_admin.undelete(folder_id) remote_admin.undelete(file_id) self.wait_sync(wait_for_async=True) assert local.exists("/") assert local.exists("/Test folder") assert local.exists("/Test folder/joe.txt")
def test_comments_with_params(server): """Test GET parameters that allow to retrieve partial list of comments.""" if version_lt(server.client.server_version, "10.3"): pytest.skip("Nuxeo 10.3 minimum") doc = Document(name=WORKSPACE_NAME, type="File", properties={"dc:title": "bar.txt"}) doc = server.documents.create(doc, parent_path=WORKSPACE_ROOT) try: # Create a bunch of comments for that document for idx in range(8): doc.comment(f"This is my comment n° {idx}") # Get maximum comments with default values comments = doc.comments() assert len(comments) == 8 # Page 1 comments = doc.comments(pageSize=5, currentPageIndex=0) assert len(comments) == 5 # Page 2 comments = doc.comments(pageSize=5, currentPageIndex=1) assert len(comments) == 3 # Page 3 comments = doc.comments(pageSize=5, currentPageIndex=2) assert len(comments) == 0 finally: doc.delete()
def test_document_comment(server): """Test the Document.comment() method, it is a simple helper.""" if version_lt(server.client.server_version, "10.3"): pytest.skip("Nuxeo 10.3 minimum") doc = Document(name=WORKSPACE_NAME, type="File", properties={"dc:title": "bar.txt"}) doc = server.documents.create(doc, parent_path=WORKSPACE_ROOT) try: # At first, the document has no comment assert not doc.comments() # Create a comment for that document doc.comment("This is my super comment") # There is now 1 comment comments = doc.comments() assert len(comments) == 1 assert comments[0].text == "This is my super comment" # Delete the comment server.comments.delete(comments[0].uid) finally: doc.delete()
def _get_update_status(self) -> None: """ Retrieve available versions and find a possible candidate. """ try: # Fetch all available versions self._fetch_versions() except UpdateError: status, version = UPDATE_STATUS_UNAVAILABLE_SITE, None else: # Special case to test the auto-updater without the need for an account if os.getenv("FORCE_USE_LATEST_VERSION", "0") == "1": version = max(self.versions) if version_lt(self.manager.version, version): log.info( f"FORCE_USE_LATEST_VERSION is set, upgrading to {version!r}" ) self._set_status(UPDATE_STATUS_UPDATE_AVAILABLE, version=version) else: log.info( f"FORCE_USE_LATEST_VERSION is set, but {version!r} not newer than the current version" ) return login_type = Login.NONE for engine in self.manager.engines.copy().values(): url = engine.server_url login_type |= self.manager.get_server_login_type(url, _raise=False) channel = self.manager.get_update_channel() log.info( f"Getting update status for version {self.manager.version!r}" f" (channel={channel}, desired client_version={Options.client_version!r})" f" on server {self.server_ver}") status, version = get_update_status( self.manager.version, self.versions, channel, self.server_ver, login_type, ) log.debug(f"Guessed status {status!r} and version {version!r}.") # Check the digest is available for that version on that OS if version: info = self.versions.get(version, {}) checksums = info.get("checksum", {}) checksum = checksums.get(self.ext, "").lower() if not checksum: log.warning( f"There is no downloadable file for the version {version!r} on that OS." ) return if status and version and self.enable and self.can_update: self._set_status(status, version=version) elif status: self.status = status self.version = ""
def test_add_permission(server): if version_lt(server.client.server_version, "10.10"): pytest.skip("Nuxeo 10.10 minimum") with patch.object(nuxeo.constants, "CHECK_PARAMS", new=True), Doc(server) as doc: # NXPY-84: here we should not fail with KeyError: 'list' in check_params() doc.add_permission({ "permission": "ReadWrite", "users": ["Administrator"] })
def validate_client_version(value: str) -> str: """The minimum version which implements the Centralized channel is 4.2.0, downgrades below this version are not allowed. """ from nuxeo.utils import version_lt if not version_lt(value, "4.2.0"): return value raise ValueError( f"Downgrade to version {value!r} is not possible. It must be >= '4.2.0'." )
def test_reply(server): if version_lt(server.client.server_version, "10.3"): pytest.skip("Nuxeo 10.3 minimum") doc = server.documents.create(document, parent_path=WORKSPACE_ROOT) try: # Create a comment for that document comment = server.comments.create(doc.uid, "This is my comment") assert not comment.has_replies() # Add a 1st reply to that comment reply1 = comment.reply("This is my reply comment") assert isinstance(reply1, Comment) assert comment.has_replies() # Check the comment has 1 reply (refetch it to ensure data is correct) replies = server.comments.get(doc.uid, uid=comment.uid) assert isinstance(replies, Comment) assert replies.numberOfReplies == 1 assert replies.numberOfReplies == comment.numberOfReplies assert replies.lastReplyDate == reply1.creationDate # Add a 2nd reply to that comment reply2 = comment.reply("This is another reply, yeah! ᕦ(ò_óˇ)ᕤ") assert isinstance(reply2, Comment) assert comment.numberOfReplies == 2 assert not reply2.has_replies() # And a reply to that 2nd reply last_reply = reply2.reply( "And a reply of the 2nd reply with \N{SNOWMAN}, boom!") assert isinstance(last_reply, Comment) assert reply2.has_replies() # Check the comment has 2 direct replies replies = server.comments.get(doc.uid, uid=comment.uid) assert replies.numberOfReplies == 2 assert replies.lastReplyDate == reply2.creationDate # Check the 2nd reply has 1 reply replies = server.comments.get(doc.uid, uid=reply2.uid) assert replies.numberOfReplies == 1 assert replies.lastReplyDate == last_reply.creationDate # Test partial list assert len(comment.replies()) == 2 assert len(comment.replies(pageSize=1, currentPageIndex=0)) == 1 assert len(comment.replies(pageSize=1, currentPageIndex=1)) == 1 assert len(comment.replies(pageSize=1, currentPageIndex=2)) == 0 finally: doc.delete()
def test_locking(server): with Doc(server) as doc: assert not doc.fetch_lock_status() assert not doc.is_locked() doc.lock() status = doc.fetch_lock_status() assert status["lockOwner"] == "Administrator" assert "lockCreated" in status assert doc.is_locked() # Double locking with the same user should work if the server has NXP-24359 if not version_lt(server.client.server_version, "11.1"): doc.lock() doc.unlock() assert not doc.is_locked()
def force_downgrade(self) -> None: try: # Fetch all available versions self._fetch_versions() except UpdateError: self._set_status(UPDATE_STATUS_UNAVAILABLE_SITE) else: versions = { version: info for version, info in self.versions.items() if info.get("type", "").lower() in ( self.manager.get_update_channel(), "release") and version_lt(version, "4") } if versions: version = max(versions.keys()) self._set_status(UPDATE_STATUS_INCOMPATIBLE_SERVER, version=version) self.serverIncompatible.emit()
def test_crud(server): if version_lt(server.client.server_version, "10.3"): pytest.skip("Nuxeo 10.3 minimum") doc = server.documents.create(document, parent_path=WORKSPACE_ROOT) try: # At first, the document has no comment assert not doc.comments() # Create a comment for that document comment = server.comments.create(doc.uid, "This is my comment") assert isinstance(comment, Comment) # Check we can retrieve the comment with its ID assert server.comments.get(doc.uid, uid=comment.uid) # There is now 1 comment comments = doc.comments() assert len(comments) == 1 assert isinstance(comments[0], Comment) assert comments[0].text == "This is my comment" # Update that comment comment.text = "Comment modified" comment.save() # Check the text has changed comments = doc.comments() assert isinstance(comments[0], Comment) assert comments[0].text == "Comment modified" assert comments[0].modificationDate is not None # Delete the comment comment.delete() # Check there si no comments for the document assert not doc.comments() finally: doc.delete()
def test_additionnal_params(server): if version_lt(server.client.server_version, "10.2"): pytest.skip("Nuxeo 10.2 minimum (NXP-21078)") func = partial(server.directories.get, "nature") # The number of returned entries is configured by the querySizeLimit parameters on the server (50 by default) # https://github.com/nuxeo/nuxeo/blob/82d0328/nuxeo-distribution/nuxeo-nxr-server/src/main/resources/templates/common/config/default-directories-bundle.xml#L23 total = len(func().entries) # Get only 10 entries assert len(func(pageSize=10).entries) == 10 # Get all entries assert len(func(pageSize=total).entries) == total # Get the last page of entries page_number, count = divmod(total, 10) assert len(func(pageSize=10, currentPageIndex=page_number).entries) == count # Set an invalid/unknown parameter does not raise assert len(func(pageSizesssssssss=10).entries) == total
def test_many_changes(self): """ Objective: The objective is to make a lot of remote changes (including a folder modified) and wait for nuxeo-drive to successfully sync even if network error happens. 1. Configure drive and wait for sync 2. Create 3 folders folder1, folder2 and shared 3. Create files inside the 3 folders: folder1/file1.txt, folder2/file2.txt, shared/readme1.txt, shared/readme2.txt 4. Wait for 3 folders, 4 files to sync to local PC 5. Check the 3 folders and 4 files are synced to local PC 6. Trigger simulation of network error for GetChildren API using the mock (2 successive failures) 7. Do the following changes in DM side in same order: I. Create 'folder1/sample1.txt' II. Delete 'shared' folder, and immediately restore 'shared' folder IV. Restore 'shared/readme1.txt' V. Create 'shared/readme3.txt' VI. Create 'folder2/sample2.txt' 8. Wait for remote changes to sync for unaffected folders folder1 and folder2 9. Check that folder1/sample1.txt, folder2/sample2.txt are synced to local PC 10. Sleep for two remote scan attempts (to compensate for two network failures) 11. Check if two files 'shared/readme1.txt' and 'shared/readme3.txt' are synced to local PC. """ local = self.local_1 remote = self.remote_document_client_1 network_error = 2 self.engine_1.start() self.wait_sync(wait_for_async=True) # create some folders on the server folder1 = remote.make_folder(self.workspace, "folder1") folder2 = remote.make_folder(self.workspace, "folder2") shared = remote.make_folder(self.workspace, "shared") remote.make_file(folder1, "file1.txt", content=b"This is a sample file1") remote.make_file(folder2, "file2.txt", content=b"This is a sample file2") readme1 = remote.make_file(shared, "readme1.txt", content=b"This is a readme file") remote.make_file(shared, "readme2.txt", content=b"This is a readme file") self.wait_sync(wait_for_async=True) assert local.exists("/folder1") assert local.exists("/folder2") assert local.exists("/shared") assert local.exists("/folder1/file1.txt") assert local.exists("/folder2/file2.txt") assert local.exists("/shared/readme1.txt") assert local.exists("/shared/readme2.txt") def get_children_info(self, *args, **kwargs): nonlocal network_error if network_error > 0: network_error -= 1 # Simulate a network error during the call to NuxeoDrive.GetChildren raise ConnectionError( "Network error simulated for NuxeoDrive.GetChildren") return Remote.get_fs_children(self.engine_1.remote, *args, **kwargs) def mock_method_factory(original): def wrapped_method(data): data["canScrollDescendants"] = True return original(data) return wrapped_method with patch.object(remote, "get_children_info", new=get_children_info), patch.object( RemoteFileInfo, "from_dict", wraps=mock_method_factory( RemoteFileInfo.from_dict), ): # Simulate network error for GetChildren API twice # This is to ensure Drive will eventually recover even after multiple # failures of GetChildren API. remote.make_file(folder1, "sample1.txt", content=b"This is a another sample file1") self.remote_2.register_as_root(shared) # Delete folder 'shared' remote.delete(shared) self.wait_sync(wait_for_async=True) # Restore folder 'shared' from trash remote.undelete(shared) if version_lt(remote.client.server_version, "10.2"): remote.undelete(readme1) self.wait_sync(wait_for_async=True) remote.make_file(shared, "readme3.txt", content=b"This is a another shared file") remote.make_file(folder2, "sample2.txt", content=b"This is a another sample file2") self.wait_sync(wait_for_async=True) assert local.exists("/folder2/sample2.txt") assert local.exists("/folder1/sample1.txt") # Although sync failed for one folder, GetChangeSummary will return # zero event in successive calls. We need to wait two remote scans, # so sleep for TEST_DEFAULT_DELAY * 2 sleep(TEST_DEFAULT_DELAY * 2) assert local.exists("/shared/readme1.txt") assert local.exists("/shared/readme3.txt")
def _get_trash_condition(self) -> str: if version_lt(self.client.server_version, "10.2"): return "AND ecm:currentLifeCycleState != 'deleted'" return "AND ecm:isTrashed = 0"
def test_version_lt(x, y): assert version_lt(x, y)