def test_update_sources(homedir, mocker): """ Check that: * Existing sources are updated in the local database. * New sources have an entry in the local database. * Local sources not returned by the remote server are deleted from the local database. """ mock_session = mocker.MagicMock() # Some source objects from the API, one of which will exist in the local # database, the other will NOT exist in the local source database (this # will be added to the database) source_update = make_remote_source() source_create = make_remote_source() remote_sources = [source_update, source_create] # Some local source objects. One already exists in the API results (this # will be updated), one does NOT exist in the API results (this will be # deleted from the local database). local_source1 = mocker.MagicMock() local_source1.uuid = source_update.uuid local_source2 = mocker.MagicMock() local_source2.uuid = str(uuid.uuid4()) local_sources = [local_source1, local_source2] update_sources(remote_sources, local_sources, mock_session, homedir) # Check the expected local source object has been updated with values from # the API. assert local_source1.journalist_designation == \ source_update.journalist_designation assert local_source1.is_flagged == source_update.is_flagged assert local_source1.public_key == source_update.key['public'] assert local_source1.interaction_count == source_update.interaction_count assert local_source1.is_starred == source_update.is_starred assert local_source1.last_updated == parse(source_update.last_updated) # Check the expected local source object has been created with values from # the API. assert mock_session.add.call_count == 1 new_source = mock_session.add.call_args_list[0][0][0] assert new_source.uuid == source_create.uuid assert new_source.journalist_designation == \ source_create.journalist_designation assert new_source.is_flagged == source_create.is_flagged assert new_source.public_key == source_create.key['public'] assert new_source.interaction_count == source_create.interaction_count assert new_source.is_starred == source_create.is_starred assert new_source.last_updated == parse(source_create.last_updated) # Ensure the record for the local source that is missing from the results # of the API is deleted. mock_session.delete.assert_called_once_with(local_source2) # Session is committed to database. assert mock_session.commit.call_count == 1
def test_update_sources_deletes_files_associated_with_the_source( homedir, mocker): """ Check that: * Sources are deleted on disk after sync. """ mock_session = mocker.MagicMock() # Test scenario: one source locally, no sources on server. remote_sources = [] # A local source object. To ensure that all submissions/replies from # various stages of processing are cleaned up, we'll add several filenames # associated with each message, document, and reply for each stage of processing. # This simulates if a step failed. msg_server_filename = '1-pericardial-surfacing-msg.gpg' msg_local_filename_decrypted = '1-pericardial-surfacing-msg' file_server_filename = '1-pericardial-surfacing-doc.gz.gpg' file_local_filename_decompressed = '1-pericardial-surfacing-doc' file_local_filename_decrypted = '1-pericardial-surfacing-doc.gz' reply_server_filename = '1-pericardial-surfacing-reply.gpg' reply_local_filename_decrypted = '1-pericardial-surfacing-reply' # Here we're not mocking out the models use so that we can use the collection attribute. local_source = factory.Source() file_submission = db.Submission(source=local_source, uuid="test", size=123, filename=file_server_filename, download_url='http://test/test') msg_submission = db.Submission(source=local_source, uuid="test", size=123, filename=msg_server_filename, download_url='http://test/test') user = db.User('hehe') reply = db.Reply(source=local_source, journalist=user, filename=reply_server_filename, size=1234, uuid='test') local_source.submissions = [file_submission, msg_submission] local_source.replies = [reply] # Make the test files on disk in tmpdir so we can check they get deleted. test_filename_absolute_paths = [] for test_filename in [ msg_server_filename, msg_local_filename_decrypted, file_server_filename, file_local_filename_decompressed, file_local_filename_decrypted, reply_server_filename, reply_local_filename_decrypted ]: abs_server_filename = add_test_file_to_temp_dir(homedir, test_filename) test_filename_absolute_paths.append(abs_server_filename) local_sources = [local_source] update_sources(remote_sources, local_sources, mock_session, homedir) # Ensure the files associated with the reply are deleted on disk. for test_filename in test_filename_absolute_paths: assert not os.path.exists(test_filename) # Ensure the record for the local source is gone, along with its # related files. mock_session.delete.assert_called_with(local_source) # Session is committed to database. assert mock_session.commit.call_count == 1
def test_update_sources(homedir, mocker, session_maker, session): """ Check that: * Existing sources are updated in the local database. * New sources have an entry in the local database. * Local sources not returned by the remote server are deleted from the local database. """ # This remote source exists locally and will be updated. source_update = factory.RemoteSource( journalist_designation="source update") # This remote source does not exist locally and will be created. source_create = factory.RemoteSource( journalist_designation="source create") remote_sources = [source_update, source_create] # This local source already exists in the API results and will be updated. local_source1 = factory.Source( journalist_designation=source_update.journalist_designation, uuid=source_update.uuid, public_key=None, fingerprint=None, ) # This local source does not exist in the API results and will be # deleted from the local database. local_source2 = factory.Source(journalist_designation="beep_boop") session.add(local_source1) session.add(local_source2) session.commit() local_sources = [local_source1, local_source2] file_delete_fcn = mocker.patch( "securedrop_client.storage.delete_source_collection") update_sources(remote_sources, local_sources, session, homedir) # Check the expected local source object has been updated with values from # the API. updated_source = session.query( db.Source).filter_by(uuid=source_update.uuid).one() assert updated_source.journalist_designation == source_update.journalist_designation assert updated_source.is_flagged == source_update.is_flagged assert updated_source.public_key == source_update.key["public"] assert updated_source.fingerprint == source_update.key["fingerprint"] assert updated_source.interaction_count == source_update.interaction_count assert updated_source.is_starred == source_update.is_starred assert updated_source.last_updated == parse(source_update.last_updated) # Check the expected local source object has been created with values from # the API. new_source = session.query( db.Source).filter_by(uuid=source_create.uuid).one() assert new_source.uuid == source_create.uuid assert new_source.journalist_designation == source_create.journalist_designation assert new_source.is_flagged == source_create.is_flagged assert new_source.public_key == source_create.key["public"] assert new_source.fingerprint == source_create.key["fingerprint"] assert new_source.interaction_count == source_create.interaction_count assert new_source.is_starred == source_create.is_starred assert new_source.last_updated == parse(source_create.last_updated) # Check that the local source not present in the API results was deleted. with pytest.raises(NoResultFound): session.query(db.Source).filter_by(uuid=local_source2.uuid).one() # Ensure that we called the method to delete the source collection. # This will delete any content in that source's data directory. assert file_delete_fcn.call_count == 1