class TestAuthentication(unittest.TestCase): """Test authentication.""" def setUp(self): self.log_mock = LogMock('friends.utils.authentication') self.account = FakeAccount() self.account.auth.get_credentials_id = lambda *ignore: 'my id' self.account.auth.get_method = lambda *ignore: 'some method' self.account.auth.get_parameters = lambda *ignore: 'change me' self.account.auth.get_mechanism = lambda *ignore: 'whatever' def tearDown(self): self.log_mock.stop() @mock.patch('friends.utils.authentication.Signon', FakeSignon) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') def test_successful_login(self, accounts, manager): manager.get_account().list_services.return_value = ['foo'] # Prevent an error in the callback. accounts.AccountService.new().get_auth_data( ).get_parameters.return_value = False authenticator = Authentication(self.account.id) reply = authenticator.login() self.assertEqual(reply, dict(AccessToken='auth reply')) self.assertEqual(self.log_mock.empty(), '_login_cb completed\n') @mock.patch('friends.utils.authentication.Signon', FailingSignon) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') def test_missing_access_token(self, accounts, manager): manager.get_account().list_services.return_value = ['foo'] # Prevent an error in the callback. self.account.auth.get_parameters = lambda *ignore: False authenticator = Authentication(self.account.id) self.assertRaises(AuthorizationError, authenticator.login) @mock.patch('friends.utils.authentication.Signon', FakeSignon) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') def test_failed_login(self, accounts, manager): # Trigger an error in the callback. class Error: message = 'who are you?' manager.get_account().list_services.return_value = ['foo'] accounts.AccountService.new().get_auth_data( ).get_parameters.return_value = Error authenticator = Authentication(self.account.id) self.assertRaises(AuthorizationError, authenticator.login) @mock.patch('friends.utils.authentication.Signon', FakeSignon) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') def test_exception_correct_thread(self, accounts, manager): manager.get_account().list_services.return_value = ['foo'] authenticator = Authentication(self.account) # If this were to raise any exception for any reason, this # test will fail. This method can't be allowed to raise # exceptions because it doesn't run in the safety of a # subthread where those are caught and logged nicely. authenticator._login_cb('session', 'reply', 'error', 'data') self.assertEqual(authenticator._reply, 'reply') self.assertEqual(authenticator._error, 'error')
class TestFlickr(unittest.TestCase): """Test the Flickr API.""" def setUp(self): self.maxDiff = None self.account = FakeAccount() self.protocol = Flickr(self.account) self.protocol._get_oauth_headers = lambda *ignore, **kwignore: {} self.log_mock = LogMock('friends.utils.base', 'friends.protocols.flickr') TestModel.clear() def tearDown(self): self.log_mock.stop() # Reset the database. TestModel.clear() def test_features(self): # The set of public features. self.assertEqual(Flickr.get_features(), ['delete_contacts', 'receive', 'upload']) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) @mock.patch('friends.utils.base.Model', TestModel) def test_already_logged_in(self): # Try to get the data when already logged in. self.account.access_token = 'original token' # There's no data, and no way to test that the user_nsid was actually # used, except for the side effect of not getting an # AuthorizationError. self.protocol.receive() # No error messages. self.assertEqual(self.log_mock.empty(), '') # But also no photos. self.assertEqual(TestModel.get_n_rows(), 0) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) @mock.patch('friends.utils.base.Model', TestModel) def test_successful_login(self): # The user is not already logged in, but the act of logging in # succeeds. def side_effect(): # Perform a successful login. self.account.user_id = 'cate' return True with mock.patch.object(self.protocol, '_login', side_effect=side_effect): self.protocol.receive() # No error message. self.assertEqual(self.log_mock.empty(), '') # But also no photos. self.assertEqual(TestModel.get_n_rows(), 0) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) @mock.patch('friends.utils.authentication.Signon.AuthSession.new') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) def test_login_unsuccessful_authentication_no_callback(self, *mocks): # Logging in required communication with the account service to get an # AccessToken, but this fails. self.assertRaises(AuthorizationError, self.protocol.receive) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) @mock.patch('friends.utils.authentication.Authentication.__init__', return_value=None) @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(username='******', user_nsid='bob', AccessToken='123', TokenSecret='abc')) def test_login_successful_authentication(self, *mocks): # Logging in required communication with the account service to get an # AccessToken, but this fails. self.protocol.receive() # Make sure our account data got properly updated. self.assertEqual(self.account.user_name, 'Bob Dobbs') self.assertEqual(self.account.user_id, 'bob') self.assertEqual(self.account.access_token, '123') self.assertEqual(self.account.secret_token, 'abc') @mock.patch('friends.utils.base.Model', TestModel) def test_get(self): # Make sure that the REST GET url looks right. token = self.protocol._get_access_token = mock.Mock() class fake: def get_json(*ignore): return {} with mock.patch('friends.protocols.flickr.Downloader') as cm: cm.return_value = fake() self.assertEqual(self.protocol.receive(), 0) token.assert_called_once_with() # GET was called once. cm.assert_called_once_with( 'http://api.flickr.com/services/rest', method='GET', params=dict( extras='date_upload,owner_name,icon_server,geo', format='json', nojsoncallback='1', api_key='consume', method='flickr.photos.getContactsPhotos', ), headers={}) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) @mock.patch('friends.utils.base.Model', TestModel) def test_no_photos(self): # The JSON data in response to the GET request returned no photos. with mock.patch.object( self.protocol, '_get_access_token', return_value='token'): # No photos are returned in the JSON data. self.assertEqual(self.protocol.receive(), 0) self.assertEqual(TestModel.get_n_rows(), 0) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'flickr-full.dat')) @mock.patch('friends.utils.base.Model', TestModel) def test_flickr_data(self): # Start by setting up a fake account id. self.account.id = 69 with mock.patch.object(self.protocol, '_get_access_token', return_value='token'): self.assertEqual(self.protocol.receive(), 10) self.assertEqual(TestModel.get_n_rows(), 10) self.assertEqual( list(TestModel.get_row(0)), ['flickr', 69, '8552892154', 'images', 'raise my voice', '47303164@N00', 'raise my voice', True, '2013-03-12T19:51:42Z', 'Chocolate chai #yegcoffee', 'http://farm1.static.flickr.com/93/buddyicons/[email protected]', 'http://www.flickr.com/photos/47303164@N00/8552892154', 0, False, 'http://farm9.static.flickr.com/8378/8552892154_a_m.jpg', '', 'http://www.flickr.com/photos/47303164@N00/8552892154', '', '', 'http://farm9.static.flickr.com/8378/8552892154_a_t.jpg', '', 0.0, 0.0, ]) self.assertEqual( list(TestModel.get_row(4)), ['flickr', 69, '8550829193', 'images', 'Nelson Webb', '27204141@N05', 'Nelson Webb', True, '2013-03-12T13:54:10Z', 'St. Michael - The Archangel', 'http://farm3.static.flickr.com/2047/buddyicons/[email protected]', 'http://www.flickr.com/photos/27204141@N05/8550829193', 0, False, 'http://farm9.static.flickr.com/8246/8550829193_e_m.jpg', '', 'http://www.flickr.com/photos/27204141@N05/8550829193', '', '', 'http://farm9.static.flickr.com/8246/8550829193_e_t.jpg', '', 53.833156, -112.330784, ]) @mock.patch('friends.utils.http.Soup.form_request_new_from_multipart', lambda *ignore: FakeSoupMessage('friends.tests.data', 'flickr-xml.dat')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Gio.File') @mock.patch('friends.protocols.flickr.time.time', lambda: 1361292793) def test_upload(self, gfile): self.account.user_name = 'freddyjimbobjones' gfile.new_for_uri().load_contents.return_value = [True, 'data'.encode()] token = self.protocol._get_access_token = mock.Mock() publish = self.protocol._publish = mock.Mock() avatar = self.protocol._get_avatar = mock.Mock() avatar.return_value = '/path/to/cached/avatar' self.assertEqual( self.protocol.upload( 'file:///path/to/some.jpg', 'Beautiful photograph!'), 'http://www.flickr.com/photos/freddyjimbobjones/8488552823') token.assert_called_with() publish.assert_called_with( message='Beautiful photograph!', timestamp='2013-02-19T16:53:13Z', stream='images', message_id='8488552823', from_me=True, sender=None, sender_nick='freddyjimbobjones', icon_uri='/path/to/cached/avatar', url='http://www.flickr.com/photos/freddyjimbobjones/8488552823', sender_id=None) @mock.patch('friends.utils.http.Soup.form_request_new_from_multipart', lambda *ignore: FakeSoupMessage('friends.tests.data', 'flickr-xml-error.dat')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Gio.File') def test_failing_upload(self, gfile): gfile.new_for_uri().load_contents.return_value = [True, 'data'.encode()] token = self.protocol._get_access_token = mock.Mock() publish = self.protocol._publish = mock.Mock() self.assertRaises( FriendsError, self.protocol.upload, 'file:///path/to/some.jpg', 'Beautiful photograph!') token.assert_called_with() self.assertEqual(publish.call_count, 0)
class TestDispatcher(unittest.TestCase): """Test the dispatcher's ability to dispatch.""" @mock.patch('dbus.service.BusName') @mock.patch('friends.service.dispatcher.find_accounts') @mock.patch('dbus.service.Object.__init__') def setUp(self, *mocks): self.log_mock = LogMock('friends.service.dispatcher', 'friends.utils.account') self.dispatcher = Dispatcher(mock.Mock(), mock.Mock()) self.dispatcher.accounts = {} def tearDown(self): self.log_mock.stop() @mock.patch('friends.service.dispatcher.threading') def test_refresh(self, threading_mock): account = mock.Mock() threading_mock.activeCount.return_value = 1 self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.values.return_value = [account] self.assertIsNone(self.dispatcher.Refresh()) self.dispatcher.accounts.values.assert_called_once_with() account.protocol.assert_called_once_with('receive') self.assertEqual( self.log_mock.empty(), 'Clearing timer id: 42\n' 'Refresh requested\n' 'Starting new shutdown timer...\n') def test_clear_indicators(self): self.dispatcher.menu_manager = mock.Mock() self.dispatcher.ClearIndicators() self.dispatcher.menu_manager.update_unread_count.assert_called_once_with( 0) def test_do(self): account = mock.Mock() account.id = '345' self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.get.return_value = account self.dispatcher.Do('like', '345', '23346356767354626') self.dispatcher.accounts.get.assert_called_once_with(345) account.protocol.assert_called_once_with('like', '23346356767354626', success=STUB, failure=STUB) self.assertEqual( self.log_mock.empty(), 'Clearing timer id: 42\n' '345: like 23346356767354626\n' 'Starting new shutdown timer...\n') def test_failing_do(self): account = mock.Mock() self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.get.return_value = None self.dispatcher.Do('unlike', '6', '23346356767354626') self.dispatcher.accounts.get.assert_called_once_with(6) self.assertEqual(account.protocol.call_count, 0) self.assertEqual( self.log_mock.empty(), 'Clearing timer id: 42\n' 'Could not find account: 6\n' 'Starting new shutdown timer...\n') def test_send_message(self): account1 = mock.Mock() account2 = mock.Mock() account3 = mock.Mock() account2.send_enabled = False self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.values.return_value = [ account1, account2, account3, ] self.dispatcher.SendMessage('Howdy friends!') self.dispatcher.accounts.values.assert_called_once_with() account1.protocol.assert_called_once_with('send', 'Howdy friends!', success=STUB, failure=STUB) account3.protocol.assert_called_once_with('send', 'Howdy friends!', success=STUB, failure=STUB) self.assertEqual(account2.protocol.call_count, 0) def test_send_reply(self): account = mock.Mock() self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.get.return_value = account self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]') self.dispatcher.accounts.get.assert_called_once_with(2) account.protocol.assert_called_once_with('send_thread', 'objid', '[Hilarious Response]', success=STUB, failure=STUB) self.assertEqual( self.log_mock.empty(), 'Clearing timer id: 42\n' 'Replying to 2, objid\n' 'Starting new shutdown timer...\n') def test_send_reply_failed(self): account = mock.Mock() self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.get.return_value = None self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]') self.dispatcher.accounts.get.assert_called_once_with(2) self.assertEqual(account.protocol.call_count, 0) self.assertEqual( self.log_mock.empty(), 'Clearing timer id: 42\n' 'Replying to 2, objid\n' 'Could not find account: 2\n' 'Starting new shutdown timer...\n') def test_upload_async(self): account = mock.Mock() self.dispatcher.accounts = mock.Mock() self.dispatcher.accounts.get.return_value = account success = mock.Mock() failure = mock.Mock() self.dispatcher.Upload('2', 'file://path/to/image.png', 'A thousand words', success=success, failure=failure) self.dispatcher.accounts.get.assert_called_once_with(2) account.protocol.assert_called_once_with( 'upload', 'file://path/to/image.png', 'A thousand words', success=success, failure=failure, ) self.assertEqual( self.log_mock.empty(), 'Clearing timer id: 42\n' 'Uploading file://path/to/image.png to 2\n' 'Starting new shutdown timer...\n') def test_get_features(self): self.assertEqual(json.loads(self.dispatcher.GetFeatures('facebook')), [ 'contacts', 'delete', 'delete_contacts', 'home', 'like', 'receive', 'search', 'send', 'send_thread', 'unlike', 'upload', 'wall' ]) self.assertEqual(json.loads(self.dispatcher.GetFeatures('twitter')), [ 'contacts', 'delete', 'delete_contacts', 'follow', 'home', 'like', 'list', 'lists', 'mentions', 'private', 'receive', 'retweet', 'search', 'send', 'send_private', 'send_thread', 'tag', 'unfollow', 'unlike', 'user' ]) self.assertEqual(json.loads(self.dispatcher.GetFeatures('identica')), [ 'contacts', 'delete', 'delete_contacts', 'follow', 'home', 'like', 'mentions', 'private', 'receive', 'retweet', 'search', 'send', 'send_private', 'send_thread', 'unfollow', 'unlike', 'user' ]) self.assertEqual(json.loads(self.dispatcher.GetFeatures('flickr')), ['delete_contacts', 'receive', 'upload']) self.assertEqual(json.loads(self.dispatcher.GetFeatures('foursquare')), ['delete_contacts', 'receive']) @mock.patch('friends.service.dispatcher.logging') def test_urlshorten_already_shortened(self, logging_mock): self.assertEqual('http://tinyurl.com/foo', self.dispatcher.URLShorten('http://tinyurl.com/foo')) @mock.patch('friends.service.dispatcher.logging') @mock.patch('friends.service.dispatcher.Short') def test_urlshorten(self, short_mock, logging_mock): short_mock().sub.return_value = 'short url' short_mock.reset_mock() self.dispatcher.settings.get_string.return_value = 'is.gd' long_url = 'http://example.com/really/really/long' self.assertEqual(self.dispatcher.URLShorten(long_url), 'short url') self.dispatcher.settings.get_boolean.assert_called_once_with( 'shorten-urls') short_mock.assert_called_once_with('is.gd') short_mock.return_value.sub.assert_called_once_with(long_url) @mock.patch('friends.service.dispatcher.GLib') def test_manage_timers_clear(self, glib): glib.source_remove.reset_mock() manager = ManageTimers() manager.timers = {1} manager.__enter__() glib.source_remove.assert_called_once_with(1) manager.timers = {1, 2, 3} manager.clear_all_timers() self.assertEqual(glib.source_remove.call_count, 4) @mock.patch('friends.service.dispatcher.GLib') def test_manage_timers_set(self, glib): glib.timeout_add_seconds.reset_mock() manager = ManageTimers() manager.timers = set() manager.clear_all_timers = mock.Mock() manager.__exit__() glib.timeout_add_seconds.assert_called_once_with(30, manager.terminate) manager.clear_all_timers.assert_called_once_with() self.assertEqual(len(manager.timers), 1) @mock.patch('friends.service.dispatcher.persist_model') @mock.patch('friends.service.dispatcher.threading') @mock.patch('friends.service.dispatcher.GLib') def test_manage_timers_terminate(self, glib, thread, persist): manager = ManageTimers() manager.timers = set() thread.activeCount.return_value = 1 manager.terminate() thread.activeCount.assert_called_once_with() persist.assert_called_once_with() glib.idle_add.assert_called_once_with(manager.callback) @mock.patch('friends.service.dispatcher.persist_model') @mock.patch('friends.service.dispatcher.threading') @mock.patch('friends.service.dispatcher.GLib') def test_manage_timers_dont_kill_threads(self, glib, thread, persist): manager = ManageTimers() manager.timers = set() manager.set_new_timer = mock.Mock() thread.activeCount.return_value = 10 manager.terminate() thread.activeCount.assert_called_once_with() manager.set_new_timer.assert_called_once_with()
class TestAccount(unittest.TestCase): """Test Account class.""" def setUp(self): self.log_mock = LogMock('friends.utils.account') def connect_side_effect(signal, callback, account): # The account service provides a .connect method that connects a # signal to a callback. We have to mock a side effect into the # connect() method to record this connection, which some of the # tests can then call. self._callback_signal = signal self._callback = callback self._callback_account = account # Set up the mock to return some useful values in the API expected by # the Account constructor. self.account_service = mock.Mock( **{ 'get_auth_data.return_value': mock.Mock( **{ 'get_credentials_id.return_value': 'fake credentials', 'get_method.return_value': 'fake method', 'get_mechanism.return_value': 'fake mechanism', 'get_parameters.return_value': { 'ConsumerKey': 'fake_key', 'ConsumerSecret': 'fake_secret' }, }), 'get_account.return_value': mock.Mock( **{ 'get_settings_dict.return_value': dict( send_enabled=True), 'id': 'fake_id', 'get_provider_name.return_value': 'flickr', }), 'get_service.return_value': mock.Mock(**{ 'get_name.return_value': 'fake_service', }), 'connect.side_effect': connect_side_effect, }) self.account = Account(self.account_service) def tearDown(self): self.log_mock.stop() def test_account_auth(self): # Test that the constructor initializes the 'auth' attribute. auth = self.account.auth self.assertEqual(auth.get_credentials_id(), 'fake credentials') self.assertEqual(auth.get_method(), 'fake method') self.assertEqual(auth.get_mechanism(), 'fake mechanism') self.assertEqual( auth.get_parameters(), dict(ConsumerKey='fake_key', ConsumerSecret='fake_secret')) def test_account_id(self): self.assertEqual(self.account.id, 'fake_id') def test_account_service(self): # The protocol attribute refers directly to the protocol used. self.assertIsInstance(self.account.protocol, Flickr) def test_account_unsupported(self): # Unsupported protocols raise exceptions in the Account constructor. mock = self.account_service.get_account() mock.get_provider_name.return_value = 'no service' with self.assertRaises(UnsupportedProtocolError) as cm: Account(self.account_service) self.assertEqual(cm.exception.protocol, 'no service') def test_on_account_changed(self): # Account.on_account_changed() gets called during the Account # constructor. Test that it has the expected original key value. self.assertEqual(self.account.send_enabled, True) def test_dict_filter(self): # The get_settings_dict() filters everything that doesn't start with # 'friends/' self._callback_account.get_settings_dict.assert_called_with('friends/') def test_on_account_changed_signal(self): # Test that when the account changes, and a 'changed' signal is # received, the callback is called and the account is updated. # # Start by simulating a change in the account service. other_dict = dict( send_enabled=False, bee='two', cat='three', ) adict = self.account_service.get_account().get_settings_dict adict.return_value = other_dict # Check that the signal has been connected. self.assertEqual(self._callback_signal, 'changed') # Check that the account is the object we expect it to be. self.assertEqual(self._callback_account, self.account_service.get_account()) # Simulate the signal. self._callback(self.account_service, self._callback_account) # Have the expected updates occurred? self.assertEqual(self.account.send_enabled, False) self.assertFalse(hasattr(self.account, 'bee')) self.assertFalse(hasattr(self.account, 'cat')) @mock.patch('friends.utils.account.Account._on_account_changed') @mock.patch('friends.utils.account.protocol_manager') def test_account_consumer_key(self, *mocks): account_service = mock.Mock() account_service.get_auth_data().get_parameters.return_value = (dict( ConsumerKey='key', ConsumerSecret='secret')) acct = Account(account_service) self.assertEqual(acct.consumer_key, 'key') self.assertEqual(acct.consumer_secret, 'secret') @mock.patch('friends.utils.account.Account._on_account_changed') @mock.patch('friends.utils.account.protocol_manager') def test_account_client_id_sohu_style(self, *mocks): account_service = mock.Mock() account_service.get_auth_data().get_parameters.return_value = (dict( ClientId='key', ClientSecret='secret')) acct = Account(account_service) self.assertEqual(acct.consumer_key, 'key') self.assertEqual(acct.consumer_secret, 'secret') @mock.patch('friends.utils.account.manager') @mock.patch('friends.utils.account.Account') @mock.patch('friends.utils.account.Accounts') def test_find_accounts(self, accts, acct, manager): service = mock.Mock() get_enabled = manager.get_enabled_account_services get_enabled.return_value = [service] manager.reset_mock() accounts = _find_accounts_uoa() get_enabled.assert_called_once_with() acct.assert_called_once_with(service) self.assertEqual(accounts, {acct().id: acct()}) self.assertEqual( self.log_mock.empty(), 'Flickr (fake_id) got send_enabled: True\n' 'Accounts found: 1\n')
class TestModel(unittest.TestCase): """Test our Dee.SharedModel instance.""" def setUp(self): self.log_mock = LogMock('friends.utils.model') def tearDown(self): self.log_mock.stop() @mock.patch('friends.utils.model.Model') def test_persist_model(self, model): model.__len__.return_value = 500 model.is_synchronized.return_value = True persist_model() model.is_synchronized.assert_called_once_with() model.flush_revision_queue.assert_called_once_with() self.assertEqual( self.log_mock.empty(), 'Trying to save Dee.SharedModel with 500 rows.\n' + 'Saving Dee.SharedModel with 500 rows.\n') @mock.patch('friends.utils.model.Model') @mock.patch('friends.utils.model.persist_model') def test_prune_one(self, persist, model): model.get_n_rows.return_value = 8001 def side_effect(arg): model.get_n_rows.return_value -= 1 model.remove.side_effect = side_effect prune_model(8000) persist.assert_called_once_with() model.get_first_iter.assert_called_once_with() model.remove.assert_called_once_with(model.get_first_iter()) self.assertEqual(self.log_mock.empty(), 'Deleted 1 rows from Dee.SharedModel.\n') @mock.patch('friends.utils.model.Model') @mock.patch('friends.utils.model.persist_model') def test_prune_one_hundred(self, persist, model): model.get_n_rows.return_value = 8100 def side_effect(arg): model.get_n_rows.return_value -= 1 model.remove.side_effect = side_effect prune_model(8000) persist.assert_called_once_with() self.assertEqual(model.get_first_iter.call_count, 100) model.remove.assert_called_with(model.get_first_iter()) self.assertEqual(model.remove.call_count, 100) self.assertEqual(self.log_mock.empty(), 'Deleted 100 rows from Dee.SharedModel.\n') @mock.patch('friends.utils.model.Model') @mock.patch('friends.utils.model.persist_model') def test_prune_none(self, persist, model): model.get_n_rows.return_value = 100 def side_effect(arg): model.get_n_rows.return_value -= 1 model.remove.side_effect = side_effect prune_model(8000) model.get_n_rows.assert_called_once_with() self.assertFalse(persist.called) self.assertFalse(model.get_first_iter.called) self.assertFalse(model.remove.called) self.assertEqual(self.log_mock.empty(), '')