def test_Client_call_api(homedir, config, mocker):
    """
    A new thread and APICallRunner is created / setup.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.finish_api_call = mocker.MagicMock()
    mocker.patch('securedrop_client.logic.QThread')
    mocker.patch('securedrop_client.logic.APICallRunner')
    mocker.patch('securedrop_client.logic.QTimer')
    mock_api_call = mocker.MagicMock()
    mock_callback = mocker.MagicMock()
    mock_timeout = mocker.MagicMock()

    cl.call_api(mock_api_call, mock_callback, mock_timeout, 'foo', bar='baz')

    assert len(cl.api_threads) == 1
    thread_info = cl.api_threads[list(cl.api_threads.keys())[0]]
    thread = thread_info['thread']
    runner = thread_info['runner']
    timer = thread_info['timer']
    thread.started.connect.assert_called_once_with(runner.call_api)
    thread.start.assert_called_once_with()
    runner.moveToThread.assert_called_once_with(thread)
    timer.timeout.connect.call_count == 1
    runner.call_finished.connect.call_count == 1
def test_Client_call_api(safe_tmpdir):
    """
    A new thread and APICallRunner is created / setup.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.finish_api_call = mock.MagicMock()
    with mock.patch('securedrop_client.logic.QThread'), \
            mock.patch(
                'securedrop_client.logic.APICallRunner'), \
            mock.patch('securedrop_client.logic.QTimer'):
        mock_api_call = mock.MagicMock()
        mock_callback = mock.MagicMock()
        mock_timeout = mock.MagicMock()
        cl.call_api(mock_api_call,
                    mock_callback,
                    mock_timeout,
                    'foo',
                    bar='baz')
    assert len(cl.api_threads) == 1
    thread_info = cl.api_threads[list(cl.api_threads.keys())[0]]
    thread = thread_info['thread']
    runner = thread_info['runner']
    timer = thread_info['timer']
    thread.started.connect.assert_called_once_with(runner.call_api)
    thread.start.assert_called_once_with()
    runner.moveToThread.assert_called_once_with(thread)
    timer.timeout.connect.call_count == 1
    runner.call_finished.connect.call_count == 1
def test_Client_call_api():
    """
    A new thread and APICallRunner is created / setup.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session)
    cl.finish_api_call = mock.MagicMock()
    with mock.patch('securedrop_client.logic.QThread') as mock_qthread, \
            mock.patch('securedrop_client.logic.APICallRunner') as mock_runner:
        mock_api_call = mock.MagicMock()
        mock_callback = mock.MagicMock()
        mock_timeout = mock.MagicMock()
        cl.call_api(mock_api_call, mock_callback, mock_timeout, 'foo',
                    bar='baz')
        cl.api_thread.started.connect.\
            assert_called_once_with(cl.api_runner.call_api)
        cl.api_thread.finished.connect.\
            assert_called_once_with(cl.call_reset)
        cl.api_thread.start.assert_called_once_with()
        cl.api_runner.moveToThread.assert_called_once_with(cl.api_thread)
        cl.api_runner.call_finished.connect.\
            assert_called_once_with(mock_callback)
        cl.api_runner.timeout.connect.assert_called_once_with(mock_timeout)
        cl.finish_api_call.connect(cl.api_runner.on_cancel_timeout)
def test_Client_call_api_existing_thread():
    """
    The client will ignore attempt to call API if an existing request is in
    progress.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session)
    cl.api_thread = True
    cl.call_api(mock.MagicMock(), mock.MagicMock(), mock.MagicMock())
    assert cl.api_thread is True
def test_Client_on_file_download_Reply(homedir, config, mocker):
    """
    If the handler is passed a reply, check the download_reply
    function is the one called against the API.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    source = factory.Source()
    journalist = db.User('Testy mcTestface')
    reply = db.Reply('reply-uuid', journalist, source, 'my-reply.gpg',
                     123)  # Not a sdclientapi.Submission
    cl.call_api = mocker.MagicMock()
    cl.api = mocker.MagicMock()
    reply_sdk_object = mocker.MagicMock()
    mock_reply = mocker.patch('sdclientapi.Reply')
    mock_reply.return_value = reply_sdk_object
    cl.on_file_download(source, reply)
    cl.call_api.assert_called_once_with(cl.api.download_reply,
                                        cl.on_file_downloaded,
                                        cl.on_download_timeout,
                                        reply_sdk_object,
                                        cl.data_dir,
                                        current_object=reply)
def test_Client_on_file_download_Reply(safe_tmpdir):
    """
    If the handler is passed a reply, check the download_reply
    function is the one called against the API.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    source = models.Source('source-uuid', 'testy-mctestface', False,
                           'mah pub key', 1, False, datetime.now())
    journalist = models.User('Testy mcTestface')
    reply = models.Reply('reply-uuid', journalist, source, 'my-reply.gpg',
                         123)  # Not a sdclientapi.Submission
    cl.call_api = mock.MagicMock()
    cl.api = mock.MagicMock()
    reply_sdk_object = mock.MagicMock()
    with mock.patch('sdclientapi.Reply') as mock_reply:
        mock_reply.return_value = reply_sdk_object
        cl.on_file_download(source, reply)
    cl.call_api.assert_called_once_with(cl.api.download_reply,
                                        cl.on_file_downloaded,
                                        cl.on_download_timeout,
                                        reply_sdk_object,
                                        cl.data_dir,
                                        current_object=reply)
def test_Client_unstars_a_source_if_unstarred(homedir, config, mocker):
    """
    Ensure that the client stars a source if it is unstarred.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    source_db_object = mocker.MagicMock()
    source_db_object.uuid = mocker.MagicMock()
    source_db_object.is_starred = False
    cl.call_api = mocker.MagicMock()
    cl.api = mocker.MagicMock()
    cl.api.add_star = mocker.MagicMock()
    cl.on_update_star_complete = mocker.MagicMock()
    cl.on_sidebar_action_timeout = mocker.MagicMock()
    source_sdk_object = mocker.MagicMock()
    mock_source = mocker.patch('sdclientapi.Source')
    mock_source.return_value = source_sdk_object
    cl.update_star(source_db_object)
    cl.call_api.assert_called_once_with(cl.api.add_star,
                                        cl.on_update_star_complete,
                                        cl.on_sidebar_action_timeout,
                                        source_sdk_object)
    mock_gui.update_error_status.assert_called_once_with("")
def test_Client_sync_api_not_authenticated(safe_tmpdir):
    """
    If the API isn't authenticated, don't sync.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.authenticated = mock.MagicMock(return_value=False)
    cl.call_api = mock.MagicMock()
    cl.sync_api()
    assert cl.call_api.call_count == 0
def test_Client_sync_api(safe_tmpdir):
    """
    Sync the API is authenticated.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.authenticated = mock.MagicMock(return_value=True)
    cl.call_api = mock.MagicMock()
    cl.sync_api()
    cl.call_api.assert_called_once_with(storage.get_remote_data, cl.on_synced,
                                        cl.on_sync_timeout, cl.api)
def test_Client_sync_api_not_authenticated(homedir, config, mocker):
    """
    If the API isn't authenticated, don't sync.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.authenticated = mocker.MagicMock(return_value=False)
    cl.call_api = mocker.MagicMock()
    cl.sync_api()
    assert cl.call_api.call_count == 0
def test_Client_sync_api(homedir, config, mocker):
    """
    Sync the API is authenticated.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.authenticated = mocker.MagicMock(return_value=True)
    cl.call_api = mocker.MagicMock()
    cl.sync_api()
    cl.call_api.assert_called_once_with(storage.get_remote_data, cl.on_synced,
                                        cl.on_sync_timeout, cl.api)
def test_Client_login(safe_tmpdir):
    """
    Ensures the API is called in the expected manner for logging in the user
    given the username, password and 2fa token.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    cl.call_api = mock.MagicMock()
    with mock.patch('securedrop_client.logic.sdclientapi.API') as mock_api:
        cl.login('username', 'password', '123456')
        cl.call_api.assert_called_once_with(mock_api().authenticate,
                                            cl.on_authenticate,
                                            cl.on_login_timeout)
def test_Client_delete_source(homedir, config, mocker):
    '''
    Using the `config` fixture to ensure the config is written to disk.
    '''
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    mock_source = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.call_api = mocker.MagicMock()
    cl.api = mocker.MagicMock()
    cl.delete_source(mock_source)
    cl.call_api.assert_called_with(cl.api.delete_source,
                                   cl._on_delete_source_complete,
                                   cl._on_delete_action_timeout, mock_source)
def test_Client_login(homedir, config, mocker):
    """
    Ensures the API is called in the expected manner for logging in the user
    given the username, password and 2fa token.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    cl.call_api = mocker.MagicMock()
    mock_api = mocker.patch('securedrop_client.logic.sdclientapi.API')
    cl.login('username', 'password', '123456')
    cl.call_api.assert_called_once_with(mock_api().authenticate,
                                        cl.on_authenticate,
                                        cl.on_login_timeout)
def test_Client_on_file_download_Submission(safe_tmpdir):
    """
    If the handler is passed a submission, check the download_submission
    function is the one called against the API.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    source = models.Source('source-uuid', 'testy-mctestface', False,
                           'mah pub key', 1, False, datetime.now())
    submission = models.Submission(source, 'submission-uuid', 1234,
                                   'myfile.doc.gpg', 'http://myserver/myfile')
    cl.call_api = mock.MagicMock()
    cl.api = mock.MagicMock()
    submission_sdk_object = mock.MagicMock()
    with mock.patch('sdclientapi.Submission') as mock_submission:
        mock_submission.return_value = submission_sdk_object
        cl.on_file_download(source, submission)
    cl.call_api.assert_called_once_with(cl.api.download_submission,
                                        cl.on_file_downloaded,
                                        cl.on_download_timeout,
                                        submission_sdk_object,
                                        cl.data_dir,
                                        current_object=submission)
def test_Client_unstars_a_source_if_unstarred(safe_tmpdir):
    """
    Ensure that the client stars a source if it is unstarred.
    """
    mock_gui = mock.MagicMock()
    mock_session = mock.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, str(safe_tmpdir))
    source_db_object = mock.MagicMock()
    source_db_object.uuid = mock.MagicMock()
    source_db_object.is_starred = False
    cl.call_api = mock.MagicMock()
    cl.api = mock.MagicMock()
    cl.api.add_star = mock.MagicMock()
    cl.on_update_star_complete = mock.MagicMock()
    cl.on_sidebar_action_timeout = mock.MagicMock()
    source_sdk_object = mock.MagicMock()
    with mock.patch('sdclientapi.Source') as mock_source:
        mock_source.return_value = source_sdk_object
        cl.update_star(source_db_object)
        cl.call_api.assert_called_once_with(cl.api.add_star,
                                            cl.on_update_star_complete,
                                            cl.on_sidebar_action_timeout,
                                            source_sdk_object)
    mock_gui.update_error_status.assert_called_once_with("")
def test_Client_on_file_download_Submission(homedir, config, mocker):
    """
    If the handler is passed a submission, check the download_submission
    function is the one called against the API.
    Using the `config` fixture to ensure the config is written to disk.
    """
    mock_gui = mocker.MagicMock()
    mock_session = mocker.MagicMock()
    cl = Client('http://localhost', mock_gui, mock_session, homedir)
    source = factory.Source()
    submission = db.Submission(source, 'submission-uuid', 1234,
                               'myfile.doc.gpg', 'http://myserver/myfile')
    cl.call_api = mocker.MagicMock()
    cl.api = mocker.MagicMock()
    submission_sdk_object = mocker.MagicMock()
    mock_submission = mocker.patch('sdclientapi.Submission')
    mock_submission.return_value = submission_sdk_object
    cl.on_file_download(source, submission)
    cl.call_api.assert_called_once_with(cl.api.download_submission,
                                        cl.on_file_downloaded,
                                        cl.on_download_timeout,
                                        submission_sdk_object,
                                        cl.data_dir,
                                        current_object=submission)