def test_invalid_credentials(self): # Perform first scan and sync self.engine_1.start() self.wait_sync(wait_for_async=True) # Simulate bad responses remote_orig = self.engine_1.remote self.engine_1.remote = RemoteTest( pytest.nuxeo_url, self.user_1, "nxdrive-test-administrator-device", pytest.version, password=self.password_1, ) errors = [ HTTPError(status=401, message="Mock"), HTTPError(status=403, message="Mock"), ] remote = self.engine_1.remote remote.request_token() for error in errors: remote.make_server_call_raise(error) self.wait_sync(wait_for_async=True, fail_if_timeout=False) assert self.engine_1.is_offline() self.engine_1.set_offline(value=False) self.engine_1.set_invalid_credentials(value=False) self.engine_1.resume() # Re-enable network remote.make_server_call_raise(None) self.engine_1.remote = remote_orig
def unlock(*_, **__): """ Patch Remote.unlock() so that it raises a specific HTTPError. """ err = f"(Mock'ed) Document already locked by {self.user_1}" raise HTTPError(status=500, message=err)
def test_local_move_with_remote_error(self): local = self.local_1 remote = self.remote_document_client_1 # Check local folder assert local.exists("/Original Folder 1") # Simulate server error bad_remote = self.get_bad_remote() error = HTTPError(status=500, message="Mock server error") bad_remote.make_server_call_raise(error) with patch.object(self.engine_1, "remote", new=bad_remote): local.rename("/Original Folder 1", "OSErrorTest") self.wait_sync(timeout=5, fail_if_timeout=False) folder_1 = remote.get_info("/Original Folder 1") assert folder_1.name == "Original Folder 1" assert local.exists("/OSErrorTest") # Set engine online as starting from here the behavior is restored self.engine_1.set_offline(value=False) self.wait_sync() folder_1 = remote.get_info(folder_1.uid) assert folder_1.name == "OSErrorTest" assert local.exists("/OSErrorTest") assert len(local.get_children_info("/OSErrorTest")) == 3 assert len(remote.get_children_info(folder_1.uid)) == 3 assert len(local.get_children_info("/")) == 4 assert len(remote.get_children_info(self.workspace)) == 4
def test_synchronize_error_remote(self): path = Path(f"/{self.workspace_title}") / "test.odt" remote = self.remote_document_client_1 dao = self.engine_1.dao bad_remote = self.get_bad_remote() error = HTTPError(status=400, message="Mock") bad_remote.make_download_raise(error) with patch.object(self.engine_1, "remote", new=bad_remote): remote.make_file("/", "test.odt", content=b"Some content.") self.engine_1.start() self.wait_sync(wait_for_async=True) self.engine_1.stop() pair = dao.get_state_from_local(path) assert pair is not None assert pair.error_count assert pair.pair_state == "remotely_created" self.engine_1.start() self.wait_sync() pair = dao.get_state_from_local(path) assert pair.error_count == 4 assert pair.pair_state == "remotely_created" # Requeue errors self.engine_1.retry_pair(pair.id) self.wait_sync() pair = dao.get_state_from_local(path) assert not pair.error_count assert pair.pair_state == "synchronized"
def test_416_range_past_eof(self): """ Test wrong bytes range during download. """ remote = self.remote_document_client_1 local = self.local_1 engine = self.engine_1 engine.start() self.wait_sync(wait_for_async=True) assert local.exists("/") remote.make_file("/", "test.bin", content=b"42") # Simulate a requested range not satisfiable on file download bad_remote = self.get_bad_remote() error = HTTPError(status=416, message="Mock Requested Range Not Satisfiable") bad_remote.make_download_raise(error) with patch.object(self.engine_1, "remote", new=bad_remote): self.wait_sync(fail_if_timeout=False) # Checks assert engine.dao.queue_manager.get_errors_count() == 1 # Starting here, default behavior is restored self.wait_sync() # Checks assert not engine.dao.get_errors() assert local.exists("/test.bin")
def test_synchronize_error_remote(self): path = "/" + self.workspace_title + "/test.odt" remote = self.remote_document_client_1 self.engine_1.remote = RemoteTest( pytest.nuxeo_url, self.user_1, "nxdrive-test-administrator-device", pytest.version, password=self.password_1, ) self.engine_1.remote.make_download_raise(HTTPError(status=400, message="Mock")) remote.make_file("/", "test.odt", content=b"Some content.") self.engine_1.start() self.wait_sync(wait_for_async=True, fail_if_timeout=False) self.engine_1.stop() pair = self.engine_1.get_dao().get_state_from_local(path) assert pair.error_count == 4 assert pair.pair_state == "remotely_created" self.engine_1.start() self.wait_sync(fail_if_timeout=False) pair = self.engine_1.get_dao().get_state_from_local(path) assert pair.error_count == 4 assert pair.pair_state == "remotely_created" self.engine_1.remote.make_download_raise(None) # Requeue errors self.engine_1.retry_pair(pair.id) self.wait_sync(fail_if_timeout=False) pair = self.engine_1.get_dao().get_state_from_local(path) assert not pair.error_count assert pair.pair_state == "synchronized"
def test_direct_edit_max_error(self): """ When uploading changes to the server, recurrent errors can happen. The upload must be retried maximum *Options.max_errors* times before being dropped. """ filename = "error.txt" doc_id = self.remote.make_file_with_blob("/", filename, b"Initial content.") local_path = f"/{doc_id}_file-content/{filename}" with patch.object(self.manager_1, "open_local_file", new=open_local_file): self.direct_edit._prepare_edit(self.nuxeo_url, doc_id) self.wait_sync(timeout=2, fail_if_timeout=False) # Simulate server error bad_remote = self.get_bad_remote() bad_remote.make_upload_raise( HTTPError(status=404, message="Mock'ed Client Error: Not Found")) with patch.object(self.engine_1, "remote", new=bad_remote): # Update file content self.local.update_content(local_path, b"Updated") self.wait_sync(timeout=30) # The file should _not_ be updated on the server content = self.remote.get_blob(self.remote.get_info(doc_id)) assert content == b"Initial content." # There must be no error has the upload has been dropped. assert self.direct_edit._error_queue.empty() # Upload errors dict must be empty assert not self.direct_edit._upload_errors
def test_synchronization_offline(self): # Bound root but nothing is synchronized yet local = self.local_1 assert not local.exists("/") # Perform first scan and sync self.engine_1.start() self.wait_sync(wait_for_async=True) assert local.exists("/") self.engine_1.stop() # Let's create some documents on the client and the server local.make_folder("/", "Folder 3") self.make_server_tree(deep=False) # Find various ways to simulate a network failure self.engine_1.remote = RemoteTest( pytest.nuxeo_url, self.user_1, "nxdrive-test-administrator-device", pytest.version, password=self.password_1, ) errors = [ ConnectionError("Mock connection error"), socket.error("Mock socket error"), HTTPError(status=503, message="Mock"), ] engine_started = False for error in errors: self.engine_1.remote.make_server_call_raise(error) if not engine_started: self.engine_1.start() engine_started = True # Synchronization doesn't occur but does not fail either. # - one 'locally_created' error is registered for Folder 3 # - no states are inserted for the remote documents self.wait_sync(wait_for_async=True, fail_if_timeout=False) workspace_children = self.engine_1.get_dao().get_states_from_partial_local( "/" + self.workspace_title + "/" ) assert len(workspace_children) == 1 assert workspace_children[0].pair_state != "synchronized" assert not self.engine_1.is_offline() # Re-enable network self.engine_1.remote.make_server_call_raise(None) # Verify that everything now gets synchronized self.wait_sync(wait_for_async=True) assert not self.engine_1.is_offline() assert not self.engine_1.get_dao().get_errors(limit=0) workspace_children = self.engine_1.get_dao().get_states_from_partial_local( "/" + self.workspace_title + "/" ) assert len(workspace_children) == 4 for state in workspace_children: assert state.pair_state == "synchronized"
def test_crafted_httperror_parse(): cls = HTTPError() exc = cls.parse({ "message": "oups", "stacktrace": "NulPointerException: bla*3" }) assert exc.status == -1 assert exc.message == "oups" assert exc.stacktrace == "NulPointerException: bla*3"
def test_409_conflict(self): """ Test concurrent upload with files having the same first characters. """ remote = self.remote_document_client_1 local = self.local_1 engine = self.engine_1 engine.start() self.wait_sync(wait_for_async=True) def _raise_for_second_file_only(*args, **kwargs): return kwargs.get("filename").endswith("2.txt") # Simulate a server conflict on file upload engine.remote = RemoteTest( pytest.nuxeo_url, self.user_1, "nxdrive-test-administrator-device", pytest.version, password=self.password_1, ) error = HTTPError(status=409, message="Mock Conflict") engine.remote.make_upload_raise(error) engine.remote.raise_on = _raise_for_second_file_only # Create 2 files locally base = "A" * 40 file1 = base + "1.txt" file2 = base + "2.txt" local.make_file("/", file1, content=b"foo") local.make_file("/", file2, content=b"bar") self.wait_sync(fail_if_timeout=False) # Checks assert engine.get_dao()._queue_manager.get_errors_count() == 1 children = remote.get_children_info(self.workspace) assert len(children) == 1 assert children[0].name == file1 # Re-enable default behavior engine.remote.reset_errors() self.wait_sync() # Checks children = remote.get_children_info(self.workspace) assert len(children) == 2 assert children[0].name == file1 assert children[1].name == file2
def test_synchronization_give_up(self): # Override error threshold to 1 instead of 3 test_error_threshold = 1 self.queue_manager_1._error_threshold = test_error_threshold # Bound root but nothing is synchronized yet local = self.local_1 dao = self.engine_1.dao workspace_path = Path(self.workspace_title) assert not local.exists("/") # Perform first scan and sync self.engine_1.start() self.wait_sync(wait_for_async=True) assert local.exists("/") self.engine_1.stop() # Let's create some documents on the client and the server local.make_folder("/", "Folder 3") self.make_server_tree(deep=False) # Simulate a server failure on file download bad_remote = self.get_bad_remote() error = HTTPError(status=500, message="Mock download error") bad_remote.make_download_raise(error) # File is not synchronized but synchronization does not fail either, # errors are handled and queue manager has given up on them with patch.object(self.engine_1, "remote", new=bad_remote): self.engine_1.start() self.wait_sync(wait_for_async=True, timeout=60) states_in_error = dao.get_errors(limit=test_error_threshold) assert len(states_in_error) == 1 children = dao.get_states_from_partial_local(workspace_path) assert len(children) == 4 for state in children: if state.folderish: assert state.pair_state == "synchronized" else: assert state.pair_state != "synchronized" # Reset errors for state in states_in_error: dao.reset_error(state) # Verify that everything now gets synchronized self.wait_sync() assert not dao.get_errors(limit=test_error_threshold) children = dao.get_states_from_partial_local(workspace_path) assert len(children) == 4 for child in children: assert child.pair_state == "synchronized"
def test_synchronization_offline(self): # Bound root but nothing is synchronized yet local = self.local_1 dao = self.engine_1.dao workspace_path = Path(self.workspace_title) assert not local.exists("/") # Perform first scan and sync self.engine_1.start() self.wait_sync(wait_for_async=True) assert local.exists("/") self.engine_1.stop() # Let's create some documents on the client and the server local.make_folder("/", "Folder 3") self.make_server_tree(deep=False) # Find various ways to simulate a network failure bad_remote = self.get_bad_remote() errors = [ ConnectionError("Mock connection error"), OSError("Mock socket error"), # Old socket.error HTTPError(status=503, message="Mock"), ] engine_started = False with patch.object(self.engine_1, "remote", new=bad_remote): for error in errors: self.engine_1.remote.make_server_call_raise(error) if not engine_started: self.engine_1.start() engine_started = True # Synchronization doesn't occur but does not fail either. # - one 'locally_created' error is registered for Folder 3 # - no states are inserted for the remote documents self.wait_sync(wait_for_async=True, fail_if_timeout=False) children = dao.get_states_from_partial_local(workspace_path) assert len(children) == 1 assert children[0].pair_state != "synchronized" assert not self.engine_1.is_offline() # Starting here, the network is re-enable # Verify that everything now gets synchronized self.wait_sync(wait_for_async=True) assert not self.engine_1.is_offline() assert not dao.get_errors(limit=0) children = dao.get_states_from_partial_local(workspace_path) assert len(children) == 4 for state in children: assert state.pair_state == "synchronized"
def test_409_conflict(self): """ Test concurrent upload with files having the same first characters. """ remote = self.remote_document_client_1 local = self.local_1 engine = self.engine_1 engine.start() self.wait_sync(wait_for_async=True) assert local.exists("/") def _raise_for_second_file_only(*args, **kwargs): return kwargs.get("filename").endswith("2.txt") # Simulate a server conflict on file upload bad_remote = self.get_bad_remote() error = HTTPError(status=409, message="Mock Conflict") bad_remote.make_upload_raise(error) bad_remote.raise_on = _raise_for_second_file_only with patch.object(self.engine_1, "remote", new=bad_remote): # Create 2 files locally base = "A" * 40 file1 = base + "1.txt" file2 = base + "2.txt" local.make_file("/", file1, content=b"foo") local.make_file("/", file2, content=b"bar") self.wait_sync(fail_if_timeout=False) # Checks assert engine.dao.queue_manager.get_errors_count() == 1 children = remote.get_children_info(self.workspace) assert len(children) == 1 assert children[0].name == file1 # Starting here, default behavior is restored self.wait_sync() # Checks children = remote.get_children_info(self.workspace) assert len(children) == 2 assert children[0].name == file1 assert children[1].name == file2
def link_blob_to_doc(self, *args, **kwargs): # Call the original method to effectively end the upload process super().link_blob_to_doc(*args, **kwargs) # The file should be present on the server # assert self.remote.exists(f"/{file}") # There should be 1 upload with DONE transfer status uploads = list(dao.get_uploads()) assert len(uploads) == 1 upload = uploads[0] assert upload.status == TransferStatus.DONE # And throw an error stack = "The proxy server received an invalid response from an upstream server." raise HTTPError(status=502, message="Mocked Proxy Error", stacktrace=stack)
def test_local_move_with_remote_error(self): local = self.local_1 remote = self.remote_document_client_1 # Check local folder assert local.exists("/Original Folder 1") # Simulate server error self.engine_1.remote = RemoteTest( pytest.nuxeo_url, self.user_1, "nxdrive-test-administrator-device", pytest.version, password=self.password_1, ) error = HTTPError(status=500, message="Mock server error") self.engine_1.remote.make_server_call_raise(error) local.rename("/Original Folder 1", "OSErrorTest") self.wait_sync(timeout=5, fail_if_timeout=False) folder_1 = remote.get_info("/Original Folder 1") assert folder_1.name == "Original Folder 1" assert local.exists("/OSErrorTest") # Remove faulty client and set engine online self.engine_1.remote.make_server_call_raise(None) self.engine_1.set_offline(value=False) self.wait_sync() folder_1 = remote.get_info(folder_1.uid) assert folder_1.name == "OSErrorTest" assert local.exists("/OSErrorTest") assert len(local.get_children_info("/OSErrorTest")) == 3 assert len(remote.get_children_info(folder_1.uid)) == 3 assert len(local.get_children_info("/")) == 4 assert len(remote.get_children_info(self.workspace_1)) == 4
def upload_part(*args, **kwargs): raise HTTPError(409, "Conflict", "Mock'ed error")
def link_blob_to_doc(self, *args, **kwargs): """Throw an network error.""" raise HTTPError(status=504, message="Mocked Gateway timeout")
def bad_request(*args, **kwargs): raise HTTPError(500, "Server Error", "Mock'ed error")
def test_crafted_httperror(): exc = HTTPError() assert exc.status == -1 assert exc.message is None assert exc.stacktrace is None
def test_crafted_httperror_with_message(): exc = HTTPError(message="oups") assert exc.status == -1 assert exc.message == "oups" assert exc.stacktrace is None
def test_crafted_httperror_with_message_and_stacktrace(): exc = HTTPError(message="oups", stacktrace="NulPointerException: bla*3") assert exc.status == -1 assert exc.message == "oups" assert exc.stacktrace == "NulPointerException: bla*3"
def get(*args, **kwargs): """Mimic the error message when a converter is not available.""" raise HTTPError(message=f"{converter} is not available")
def upload(*_, **__): """Mocked remote.upload method that raises a HTTPError 413""" raise HTTPError( status=413, message="Mock'ed Client Error: Request Entity Too Large")
def test_synchronization_give_up(self): # Override error threshold to 1 instead of 3 test_error_threshold = 1 self.queue_manager_1._error_threshold = test_error_threshold # Bound root but nothing is synchronized yet local = self.local_1 assert not local.exists("/") # Perform first scan and sync self.engine_1.start() self.wait_sync(wait_for_async=True) assert local.exists("/") self.engine_1.stop() # Let's create some documents on the client and the server local.make_folder("/", "Folder 3") self.make_server_tree(deep=False) # Simulate a server failure on file download self.engine_1.remote = RemoteTest( pytest.nuxeo_url, self.user_1, "nxdrive-test-administrator-device", pytest.version, password=self.password_1, ) error = HTTPError(status=500, message="Mock download error") self.engine_1.remote.make_download_raise(error) # File is not synchronized but synchronization does not fail either, # errors are handled and queue manager has given up on them self.engine_1.start() self.wait_sync(wait_for_async=True, timeout=60) states_in_error = self.engine_1.get_dao().get_errors(limit=test_error_threshold) assert len(states_in_error) == 1 workspace_children = self.engine_1.get_dao().get_states_from_partial_local( "/" + self.workspace_title + "/" ) assert len(workspace_children) == 4 for state in workspace_children: if state.folderish: assert state.pair_state == "synchronized" else: assert state.pair_state != "synchronized" # Remove faulty client and reset errors self.engine_1.remote.make_download_raise(None) for state in states_in_error: self.engine_1.get_dao().reset_error(state) # Verify that everything now gets synchronized self.wait_sync() states_in_error = self.engine_1.get_dao().get_errors(limit=test_error_threshold) assert not states_in_error workspace_children = self.engine_1.get_dao().get_states_from_partial_local( "/" + self.workspace_title + "/" ) assert len(workspace_children) == 4 for state in workspace_children: assert state.pair_state == "synchronized"
def put_object(*args, **kwargs): raise HTTPError(409, "Conflict", "Mock'ed error")