Skip to content

Commit

Permalink
tests for sending replies
Browse files Browse the repository at this point in the history
  • Loading branch information
heartsucker committed Feb 4, 2019
1 parent d704511 commit 8f3faed
Show file tree
Hide file tree
Showing 2 changed files with 243 additions and 6 deletions.
102 changes: 96 additions & 6 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from securedrop_client import logic
from securedrop_client.gui.widgets import ToolBar, MainView, SourceList, SourceWidget, \
LoginDialog, SpeechBubble, ConversationWidget, MessageWidget, ReplyWidget, FileWidget, \
ConversationView, DeleteSourceMessageBox, DeleteSourceAction, SourceMenu
ConversationView, DeleteSourceMessageBox, DeleteSourceAction, SourceMenu, \
SourceConversationWrapper, ReplyBoxWidget


app = QApplication([])
Expand Down Expand Up @@ -648,15 +649,31 @@ def test_ReplyWidget_init(mocker):
"""
Check the CSS is set as expected.
"""
mock_signal = mocker.Mock()
mock_connected = mocker.Mock()
mock_signal.connect = mock_connected
mock_update_signal = mocker.Mock()
mock_update_connected = mocker.Mock()
mock_update_signal.connect = mock_update_connected

rw = ReplyWidget('mock id', 'hello', mock_signal)
mock_success_signal = mocker.MagicMock()
mock_success_connected = mocker.Mock()
mock_success_signal.connect = mock_success_connected

mock_failure_signal = mocker.MagicMock()
mock_failure_connected = mocker.Mock()
mock_failure_signal.connect = mock_failure_connected

rw = ReplyWidget(
'mock id',
'hello',
mock_update_signal,
mock_success_signal,
mock_failure_signal,
)
ss = rw.styleSheet()

assert 'background-color' in ss
assert mock_connected.called
assert mock_update_connected.called
assert mock_success_connected.called
assert mock_failure_connected.called


def test_FileWidget_init_left(mocker):
Expand Down Expand Up @@ -1026,3 +1043,76 @@ def test_DeleteSource_from_source_widget_when_user_is_loggedout(mocker):
source_widget.setup(mock_controller)
source_widget.delete_source(mock_event)
mock_delete_source_message_box_obj.launch.assert_not_called()


def test_SourceConversationWrapper_send_reply(mocker):
mock_source = mocker.Mock()
mock_source.uuid = 'abc123'
mock_source.collection = []
mock_uuid = '456xyz'
mocker.patch('securedrop_client.gui.widgets.uuid4', return_value=mock_uuid)
mock_controller = mocker.MagicMock()

cw = SourceConversationWrapper(mock_source, 'mock home', mock_controller)
mock_add_reply = mocker.Mock()
cw.conversation.add_reply = mock_add_reply

msg = 'Alles für Alle'
cw.send_reply(msg)

mock_add_reply.assert_called_once_with(mock_uuid, msg)
mock_controller.send_reply.assert_called_once_with(mock_source.uuid, mock_uuid, msg)


def test_ReplyBoxWidget_send_reply(mocker):
mock_conversation = mocker.Mock()
rw = ReplyBoxWidget(mock_conversation)

# when empty, don't sent message
assert not rw.text_edit.toPlainText() # precondition
rw.send_reply()
assert not mock_conversation.send_reply.called

# when only whitespace, don't sent message
rw.text_edit.setText(' \n\n ')
rw.send_reply()
assert not mock_conversation.send_reply.called

# send send send send
msg = 'nein'
rw.text_edit.setText(msg)
rw.send_reply()
mock_conversation.send_reply.assert_called_once_with(msg)


def test_ReplyWidget_success_failure_slots(mocker):
mock_update_signal = mocker.Mock()
mock_success_signal = mocker.Mock()
mock_failure_signal = mocker.Mock()
msg_id = 'abc123'

widget = ReplyWidget(msg_id,
'lol',
mock_update_signal,
mock_success_signal,
mock_failure_signal)

# ensure we have connected the slots
mock_success_signal.connect.assert_called_once_with(widget._on_reply_success)
mock_failure_signal.connect.assert_called_once_with(widget._on_reply_failure)
assert mock_update_signal.connect.called # to ensure no stale mocks

# check the success slog
mock_logger = mocker.patch('securedrop_client.gui.widgets.logger')
widget._on_reply_success(msg_id + "x")
assert not mock_logger.debug.called
widget._on_reply_success(msg_id)
assert mock_logger.debug.called
mock_logger.reset_mock()

# check the failure slot
mock_logger = mocker.patch('securedrop_client.gui.widgets.logger')
widget._on_reply_failure(msg_id + "x")
assert not mock_logger.debug.called
widget._on_reply_failure(msg_id)
assert mock_logger.debug.called
147 changes: 147 additions & 0 deletions tests/test_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import arrow
import os
import pytest
from sdclientapi import sdlocalobjects
from tests import factory
from securedrop_client import storage, db
from securedrop_client.crypto import CryptoError
Expand Down Expand Up @@ -1179,3 +1180,149 @@ def test_Client_delete_source(homedir, config, mocker):
cl._on_delete_action_timeout,
mock_source
)


def test_Client_send_reply_success(homedir, mocker):
'''
Check that the "happy path" of encrypting a message and sending it to the sever behaves as
expected.
'''
mock_gui = mocker.MagicMock()
mock_session = mocker.MagicMock()

cl = Client('http://localhost', mock_gui, mock_session, homedir)

cl.call_api = mocker.Mock()
cl.api = mocker.Mock()
encrypted_reply = 's3kr1t m3ss1dg3'
mock_encrypt = mocker.patch.object(cl.gpg, 'encrypt_to_source', return_value=encrypted_reply)
source_uuid = 'abc123'
msg_uuid = 'xyz456'
msg = 'wat'
mock_sdk_source = mocker.Mock()
mock_source_init = mocker.patch('securedrop_client.logic.sdclientapi.Source',
return_value=mock_sdk_source)

cl.send_reply(source_uuid, msg_uuid, msg)

# ensure message is encrypted
mock_encrypt.assert_called_once_with(source_uuid, msg)

# ensure api is called
cl.call_api.assert_called_once_with(
cl.api.reply_source,
cl._on_reply_complete,
cl._on_reply_timeout,
mock_sdk_source,
encrypted_reply,
msg_uuid,
current_object=(source_uuid, msg_uuid),
)

assert mock_source_init.called # to prevent stale mocks


def test_Client_send_reply_gpg_error(homedir, mocker):
'''
Check that if gpg fails when sending a message, we alert the UI and do *not* call the API.
'''
mock_gui = mocker.MagicMock()
mock_session = mocker.MagicMock()

cl = Client('http://localhost', mock_gui, mock_session, homedir)

cl.call_api = mocker.Mock()
cl.api = mocker.Mock()
mock_encrypt = mocker.patch.object(cl.gpg, 'encrypt_to_source', side_effect=Exception)
source_uuid = 'abc123'
msg_uuid = 'xyz456'
msg = 'wat'
mock_sdk_source = mocker.Mock()
mock_source_init = mocker.patch('securedrop_client.logic.sdclientapi.Source',
return_value=mock_sdk_source)
mock_reply_failed = mocker.patch.object(cl, 'reply_failed')

cl.send_reply(source_uuid, msg_uuid, msg)

# ensure there is an attempt to encrypt the message
mock_encrypt.assert_called_once_with(source_uuid, msg)

# ensure we emit a failure on gpg errors
mock_reply_failed.emit.assert_called_once_with(msg_uuid)

# ensure api not is called after a gpg error
assert not cl.call_api.called

assert mock_source_init.called # to prevent stale mocks


def test_Client_on_reply_complete_success(homedir, mocker):
'''
Check that when the result is a success, the client emits the correct signal.
'''
mock_gui = mocker.MagicMock()
mock_session = mocker.MagicMock()
mock_reply_init = mocker.patch('securedrop_client.logic.db.Reply')

cl = Client('http://localhost', mock_gui, mock_session, homedir)
cl.api = mocker.Mock()
journalist_uuid = 'abc123'
cl.api.token = {'journalist_uuid': journalist_uuid}
mock_reply_succeeded = mocker.patch.object(cl, 'reply_succeeded')
mock_reply_failed = mocker.patch.object(cl, 'reply_failed')

reply = sdlocalobjects.Reply(uuid='xyz456', filename='1-wat.gpg')

source_uuid = 'foo111'
msg_uuid = 'bar222'
current_object = (source_uuid, msg_uuid)
cl._on_reply_complete(reply, current_object)
cl.session.commit.assert_called_once_with()
mock_reply_succeeded.emit.assert_called_once_with(msg_uuid)
assert not mock_reply_failed.emit.called

assert mock_reply_init.called # to prevent stale mocks


def test_Client_on_reply_complete_failure(homedir, mocker):
'''
Check that when the result is a failure, the client emits the correct signal.
'''
mock_gui = mocker.MagicMock()
mock_session = mocker.MagicMock()

cl = Client('http://localhost', mock_gui, mock_session, homedir)
cl.api = mocker.Mock()
journalist_uuid = 'abc123'
cl.api.token = {'journalist_uuid': journalist_uuid}
mock_reply_succeeded = mocker.patch.object(cl, 'reply_succeeded')
mock_reply_failed = mocker.patch.object(cl, 'reply_failed')

source_uuid = 'foo111'
msg_uuid = 'bar222'
current_object = (source_uuid, msg_uuid)
cl._on_reply_complete(Exception, current_object)
mock_reply_failed.emit.assert_called_once_with(msg_uuid)
assert not mock_reply_succeeded.emit.called


def test_Client_on_reply_timeout(homedir, mocker):
'''
Check that when the reply timesout, the correct signal is emitted.
'''
mock_gui = mocker.MagicMock()
mock_session = mocker.MagicMock()

cl = Client('http://localhost', mock_gui, mock_session, homedir)
cl.api = mocker.Mock()
journalist_uuid = 'abc123'
cl.api.token = {'journalist_uuid': journalist_uuid}
mock_reply_succeeded = mocker.patch.object(cl, 'reply_succeeded')
mock_reply_failed = mocker.patch.object(cl, 'reply_failed')

source_uuid = 'foo111'
msg_uuid = 'bar222'
current_object = (source_uuid, msg_uuid)
cl._on_reply_timeout(current_object)
mock_reply_failed.emit.assert_called_once_with(msg_uuid)
assert not mock_reply_succeeded.emit.called

0 comments on commit 8f3faed

Please sign in to comment.