class TestProtocolManager(unittest.TestCase): """Test the protocol finder.""" def setUp(self): self.manager = ProtocolManager() self.log_mock = LogMock('friends.utils.base') def tearDown(self): self.log_mock.stop() def test_find_all_protocols(self): # The manager can find all the protocol classes. self.assertEqual(self.manager.protocols['flickr'], Flickr) self.assertEqual(self.manager.protocols['twitter'], Twitter) def test_doesnt_find_base(self): # Make sure that the base protocol class isn't returned. self.assertNotIn('base', self.manager.protocols) for protocol_class in self.manager.protocols.values(): self.assertNotEqual(protocol_class, Base)
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 TestFourSquare(unittest.TestCase): """Test the FourSquare API.""" def setUp(self): self.account = FakeAccount() self.protocol = FourSquare(self.account) self.log_mock = LogMock('friends.utils.base', 'friends.protocols.foursquare') def tearDown(self): # Ensure that any log entries we haven't tested just get consumed so # as to isolate out test logger from other tests. self.log_mock.stop() # Reset the database. TestModel.clear() def test_features(self): # The set of public features. self.assertEqual(FourSquare.get_features(), ['delete_contacts', 'receive']) @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.Downloader.get_json', return_value=None) def test_unsuccessful_authentication(self, *mocks): self.assertRaises(AuthorizationError, self.protocol._login) self.assertIsNone(self.account.user_name) self.assertIsNone(self.account.user_id) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(AccessToken='tokeny goodness')) @mock.patch('friends.utils.authentication.Authentication.__init__', return_value=None) @mock.patch( 'friends.protocols.foursquare.Downloader.get_json', return_value=dict(response=dict( user=dict(firstName='Bob', lastName='Loblaw', id='1234567')))) def test_successful_authentication(self, *mocks): self.assertTrue(self.protocol._login()) self.assertEqual(self.account.user_name, 'Bob Loblaw') self.assertEqual(self.account.user_id, '1234567') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'foursquare-full.dat')) @mock.patch('friends.protocols.foursquare.FourSquare._login', return_value=True) def test_receive(self, *mocks): self.account.access_token = 'tokeny goodness' self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual(self.protocol.receive(), 1) self.assertEqual(1, TestModel.get_n_rows()) expected = [ 'foursquare', 88, '50574c9ce4b0a9a6e84433a0', 'messages', 'Jimbob Smith', '', '', True, '2012-09-17T19:15:24Z', "Working on friends's foursquare plugin.", 'https://irs0.4sqi.net/img/user/100x100/5IEW3VIX55BBEXAO.jpg', '', 0, False, '', '', '', '', '', '', 'Pop Soda\'s Coffee House & Gallery', 49.88873164336725, -97.158043384552, ] self.assertEqual(list(TestModel.get_row(0)), expected)
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 TestDownloader(unittest.TestCase): """Test the downloading utilities.""" def setUp(self): self.log_mock = LogMock('friends.utils.http') def tearDown(self): self.log_mock.stop() @classmethod def setUpClass(cls): cls.server = make_server('', 9180, _app, handler_class=_SilentHandler) cls.thread = threading.Thread(target=cls.server.serve_forever) cls.thread.start() # Wait until the server is responding. until = datetime.datetime.now() + datetime.timedelta(seconds=30) while datetime.datetime.now() < until: try: with urlopen('http://localhost:9180/ping'): pass except URLError: time.sleep(0.1) else: break else: raise RuntimeError('Server thread did not start up.') @classmethod def tearDownClass(cls): cls.server.shutdown() cls.thread.join() def test_simple_json_download(self): # Test simple downloading of JSON data. self.assertEqual( Downloader('http://*****:*****@mock.patch('friends.utils.http._soup', mock.Mock()) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'json-utf-8.dat', 'utf-8')) def test_json_explicit_utf_8(self): # RFC 4627 $3 with explicit charset=utf-8. self.assertEqual( Downloader('http://example.com').get_json(), dict(yes='ÑØ')) @mock.patch('friends.utils.http._soup', mock.Mock()) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'json-utf-8.dat', None)) def test_json_implicit_utf_8(self): # RFC 4627 $3 with implicit charset=utf-8. self.assertEqual( Downloader('http://example.com').get_json(), dict(yes='ÑØ')) @mock.patch('friends.utils.http._soup', mock.Mock()) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'json-utf-16le.dat', None)) def test_json_implicit_utf_16le(self): # RFC 4627 $3 with implicit charset=utf-16le. self.assertEqual( Downloader('http://example.com').get_json(), dict(yes='ÑØ')) @mock.patch('friends.utils.http._soup', mock.Mock()) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'json-utf-16be.dat', None)) def test_json_implicit_utf_16be(self): # RFC 4627 $3 with implicit charset=utf-16be. self.assertEqual( Downloader('http://example.com').get_json(), dict(yes='ÑØ')) @mock.patch('friends.utils.http._soup', mock.Mock()) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'json-utf-32le.dat', None)) def test_json_implicit_utf_32le(self): # RFC 4627 $3 with implicit charset=utf-32le. self.assertEqual( Downloader('http://example.com').get_json(), dict(yes='ÑØ')) @mock.patch('friends.utils.http._soup', mock.Mock()) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'json-utf-32be.dat', None)) def test_json_implicit_utf_32be(self): # RFC 4627 $3 with implicit charset=utf-32be. self.assertEqual( Downloader('http://example.com').get_json(), dict(yes='ÑØ')) def test_simple_text_download(self): # Test simple downloading of text data. self.assertEqual( Downloader('http://*****:*****@mock.patch('friends.utils.http.Soup') @mock.patch('friends.utils.http._soup') def test_upload_happens_only_once(self, _soupmock, Soupmock): filename = resource_filename('friends.tests.data', 'ubuntu.png') Uploader( 'http://localhost:9180/mirror', 'file://' + filename, 'Great logo!', picture_key='source', desc_key='message', foo='bar', ).get_bytes() _soupmock.send_message.assert_called_once_with( Soupmock.form_request_new_from_multipart())
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(), '')
class TestTwitter(unittest.TestCase): """Test the Twitter API.""" def setUp(self): self._temp_cache = tempfile.mkdtemp() self._root = JsonCache._root = os.path.join(self._temp_cache, '{}.json') TestModel.clear() self.account = FakeAccount() self.protocol = Twitter(self.account) self.log_mock = LogMock('friends.utils.base', 'friends.protocols.twitter') def tearDown(self): # Ensure that any log entries we haven't tested just get consumed so # as to isolate out test logger from other tests. self.log_mock.stop() shutil.rmtree(self._temp_cache) @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.protocols.twitter.Downloader.get_json', return_value=None) def test_unsuccessful_authentication(self, dload, login, *mocks): self.assertRaises(AuthorizationError, self.protocol._login) self.assertIsNone(self.account.user_name) self.assertIsNone(self.account.user_id) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.authentication.Authentication.__init__', return_value=None) @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(AccessToken='some clever fake data', TokenSecret='sssssshhh!', UserId='1234', ScreenName='stephenfry')) def test_successful_authentication(self, *mocks): self.assertTrue(self.protocol._login()) self.assertEqual(self.account.user_name, 'stephenfry') self.assertEqual(self.account.user_id, '1234') self.assertEqual(self.account.access_token, 'some clever fake data') self.assertEqual(self.account.secret_token, 'sssssshhh!') @mock.patch('friends.protocols.twitter.Downloader') @mock.patch('oauthlib.oauth1.rfc5849.generate_nonce', lambda: 'once upon a nonce') @mock.patch('oauthlib.oauth1.rfc5849.generate_timestamp', lambda: '1348690628') def test_signatures(self, dload): self.account.secret_token = 'alpha' self.account.access_token = 'omega' self.account.consumer_secret = 'obey' self.account.consumer_key = 'consume' self.account.auth.get_credentials_id = lambda *ignore: 6 self.account.auth.get_method = lambda *ignore: 'oauth2' self.account.auth.get_mechanism = lambda *ignore: 'HMAC-SHA1' result = '''\ OAuth oauth_nonce="once%20upon%20a%20nonce", \ oauth_timestamp="1348690628", \ oauth_version="1.0", \ oauth_signature_method="HMAC-SHA1", \ oauth_consumer_key="consume", \ oauth_token="omega", \ oauth_signature="klnMTp3hH%2Fl3b5%2BmPtBlv%2BCulic%3D"''' self.protocol._rate_limiter = 'limits' class fake: def get_json(): return None dload.return_value = fake self.protocol._get_url('http://example.com') dload.assert_called_once_with('http://example.com', headers=dict(Authorization=result), rate_limiter='limits', params=None, method='GET') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-home.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_home(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual(self.protocol.home(), 3) self.assertEqual(3, TestModel.get_n_rows()) # This test data was ripped directly from Twitter's API docs. expected = [ [ 'twitter', 88, '240558470661799936', 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', False, '2012-08-28T21:16:23Z', 'just another test', 'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg', 'https://twitter.com/oauth_dancer/status/240558470661799936', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ], [ 'twitter', 88, '240556426106372096', 'images', 'Raffi Krikorian', '8285392', 'raffi', False, '2012-08-28T21:08:15Z', 'lecturing at the "analyzing big data ' 'with twitter" class at <a href="https://twitter.com/Cal">@Cal</a>' ' with <a href="https://twitter.com/othman">@othman</a> ' '<a href="http://twitter.com/yunorno/status/114080493036773378/photo/1">' 'pic.twitter.com/rJC5Pxsu</a>', 'https://si0.twimg.com/profile_images/1270234259/' 'raffi-headshot-casual.png', 'https://twitter.com/raffi/status/240556426106372096', 0, False, 'http://p.twimg.com/AZVLmp-CIAAbkyy.jpg', '', '', '', '', '', '', 0.0, 0.0, ], [ 'twitter', 88, '240539141056638977', 'messages', 'Taylor Singletary', '819797', 'episod', False, '2012-08-28T19:59:34Z', 'You\'d be right more often if you thought you were wrong.', 'https://si0.twimg.com/profile_images/2546730059/' 'f6a8zq58mg1hn0ha8vie.jpeg', 'https://twitter.com/episod/status/240539141056638977', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ], ] for i, expected_row in enumerate(expected): self.assertEqual(list(TestModel.get_row(i)), expected_row) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-home.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_home_since_id(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.assertEqual(self.protocol.home(), 3) with open(self._root.format('twitter_ids'), 'r') as fd: self.assertEqual(fd.read(), '{"messages": 240558470661799936}') get_url = self.protocol._get_url = mock.Mock() get_url.return_value = [] self.assertEqual(self.protocol.home(), 3) get_url.assert_called_once_with( 'https://api.twitter.com/1.1/statuses/' + 'home_timeline.json?count=50&since_id=240558470661799936') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-send.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_from_me(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.account.user_name = 'oauth_dancer' self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual( self.protocol.send('some message'), 'https://twitter.com/oauth_dancer/status/240558470661799936') self.assertEqual(1, TestModel.get_n_rows()) # This test data was ripped directly from Twitter's API docs. expected_row = [ 'twitter', 88, '240558470661799936', 'messages', 'OAuth Dancer', '119476949', 'oauth_dancer', True, '2012-08-28T21:16:23Z', 'just another test', 'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg', 'https://twitter.com/oauth_dancer/status/240558470661799936', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ] self.assertEqual(list(TestModel.get_row(0)), expected_row) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_home_url(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.home(), 0) publish.assert_called_with('tweet') get_url.assert_called_with( 'https://api.twitter.com/1.1/statuses/home_timeline.json?count=50') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_mentions(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.mentions(), 0) publish.assert_called_with('tweet', stream='mentions') get_url.assert_called_with('https://api.twitter.com/1.1/statuses/' + 'mentions_timeline.json?count=50') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_user(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.user(), 0) publish.assert_called_with('tweet', stream='messages') get_url.assert_called_with( 'https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=' ) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_list(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.list('some_list_id'), 0) publish.assert_called_with('tweet', stream='list/some_list_id') get_url.assert_called_with( 'https://api.twitter.com/1.1/lists/statuses.json?list_id=some_list_id' ) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_lists(self): get_url = self.protocol._get_url = mock.Mock( return_value=[dict(id_str='twitlist')]) publish = self.protocol.list = mock.Mock() self.assertEqual(self.protocol.lists(), 0) publish.assert_called_with('twitlist') get_url.assert_called_with( 'https://api.twitter.com/1.1/lists/list.json') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_private(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.private(), 0) publish.assert_called_with('tweet', stream='private') self.assertEqual(get_url.mock_calls, [ mock.call('https://api.twitter.com/1.1/' + 'direct_messages.json?count=50'), mock.call('https://api.twitter.com/1.1/' + 'direct_messages/sent.json?count=50') ]) def test_private_avatars(self): get_url = self.protocol._get_url = mock.Mock(return_value=[ dict( created_at='Sun Nov 04 17:14:52 2012', text='Does my avatar show up?', id_str='1452456', sender=dict( screen_name='some_guy', name='Bob', profile_image_url_https='https://example.com/bob.jpg', ), ) ]) publish = self.protocol._publish = mock.Mock() self.protocol.private() publish.assert_called_with( liked=False, sender='Bob', stream='private', url='https://twitter.com/some_guy/status/1452456', icon_uri='https://example.com/bob.jpg', link_picture='', sender_nick='some_guy', sender_id='', from_me=False, timestamp='2012-11-04T17:14:52Z', message='Does my avatar show up?', message_id='1452456') self.assertEqual(get_url.mock_calls, [ mock.call('https://api.twitter.com/1.1/' + 'direct_messages.json?count=50'), mock.call('https://api.twitter.com/1.1/' + 'direct_messages/sent.json?count=50&since_id=1452456') ]) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_send_private(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._publish_tweet = mock.Mock( return_value='https://twitter.com/screen_name/status/tweet_id') self.assertEqual( self.protocol.send_private('pumpichank', 'Are you mocking me?'), 'https://twitter.com/screen_name/status/tweet_id') publish.assert_called_with('tweet', stream='private') get_url.assert_called_with( 'https://api.twitter.com/1.1/direct_messages/new.json', dict(text='Are you mocking me?', screen_name='pumpichank')) def test_failing_send_private(self): def fail(*ignore): raise HTTPError('url', 403, 'Forbidden', 'Forbidden', mock.Mock()) with mock.patch.object(self.protocol, '_get_url', side_effect=fail): self.assertRaises( HTTPError, self.protocol.send_private, 'pumpichank', 'Are you mocking me?', ) def test_send(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._publish_tweet = mock.Mock( return_value='https://twitter.com/u/status/id') self.assertEqual(self.protocol.send('Hello, twitterverse!'), 'https://twitter.com/u/status/id') publish.assert_called_with('tweet') get_url.assert_called_with( 'https://api.twitter.com/1.1/statuses/update.json', dict(status='Hello, twitterverse!')) def test_send_thread(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._publish_tweet = mock.Mock( return_value='tweet permalink') self.assertEqual( self.protocol.send_thread( '1234', 'Why yes, I would love to respond to your tweet @pumpichank!'), 'tweet permalink') publish.assert_called_with('tweet', stream='reply_to/1234') get_url.assert_called_with( 'https://api.twitter.com/1.1/statuses/update.json', dict(status='Why yes, I would love to respond to your ' 'tweet @pumpichank!', in_reply_to_status_id='1234')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-home.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_send_thread_prepend_nick(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual(self.protocol.home(), 3) self.assertEqual(3, TestModel.get_n_rows()) # If you forgot to @mention in your reply, we add it for you. get = self.protocol._get_url = mock.Mock() self.protocol._publish_tweet = mock.Mock() self.protocol.send_thread('240556426106372096', 'Exciting and original response!') get.assert_called_once_with( 'https://api.twitter.com/1.1/statuses/update.json', dict(status='@raffi Exciting and original response!', in_reply_to_status_id='240556426106372096')) # If you remembered the @mention, we won't duplicate it. get.reset_mock() self.protocol.send_thread('240556426106372096', 'You are the greatest, @raffi!') get.assert_called_once_with( 'https://api.twitter.com/1.1/statuses/update.json', dict(status='You are the greatest, @raffi!', in_reply_to_status_id='240556426106372096')) def test_delete(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._unpublish = mock.Mock() self.assertEqual(self.protocol.delete('1234'), '1234') publish.assert_called_with('1234') get_url.assert_called_with( 'https://api.twitter.com/1.1/statuses/destroy/1234.json', dict(trim_user='******')) def test_retweet(self): tweet = dict(tweet='twit') get_url = self.protocol._get_url = mock.Mock(return_value=tweet) publish = self.protocol._publish_tweet = mock.Mock( return_value='tweet permalink') self.assertEqual(self.protocol.retweet('1234'), 'tweet permalink') publish.assert_called_with(tweet) get_url.assert_called_with( 'https://api.twitter.com/1.1/statuses/retweet/1234.json', dict(trim_user='******')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-retweet.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_retweet_with_data(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.account.user_name = 'therealrobru' self.account.auth.parameters = dict(ConsumerKey='key', ConsumerSecret='secret') self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual( self.protocol.retweet('240558470661799936'), 'https://twitter.com/therealrobru/status/324220250889543682') self.assertEqual(1, TestModel.get_n_rows()) self.maxDiff = None expected_row = [ 'twitter', 88, '324220250889543682', 'messages', 'Robert Bruce', '836242932', 'therealrobru', True, '2013-04-16T17:58:26Z', 'RT <a href="https://twitter.com/tarek_ziade">@tarek_ziade</a>: Just found a "Notification ' 'of Inspection" card in the bottom of my bag. looks like they were ' 'curious about those raspberry-pi :O', 'https://si0.twimg.com/profile_images/2631306428/' '2a509db8a05b4310394b832d34a137a4.png', 'https://twitter.com/therealrobru/status/324220250889543682', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ] self.assertEqual(list(TestModel.get_row(0)), expected_row) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-multiple-links.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_multiple_links(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.account.user_name = 'Independent' self.account.auth.parameters = dict(ConsumerKey='key', ConsumerSecret='secret') self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual( self.protocol.send('some message'), 'https://twitter.com/Independent/status/426318539796930560') self.assertEqual(1, TestModel.get_n_rows()) self.maxDiff = None expected_row = [ 'twitter', 88, '426318539796930560', 'messages', 'The Independent', '16973333', 'Independent', True, '2014-01-23T11:40:35Z', 'An old people\'s home has recreated famous movie scenes for a wonderful calendar ' '<a href="http://ind.pn/1g3wX9q">ind.pn/1g3wX9q</a> ' '<a href="http://twitter.com/Independent/status/426318539796930560/photo/1">pic.twitter.com/JxQTPG7WLL</a>', 'https://pbs.twimg.com/profile_images/378800000706113664/d1a957578723e496c025be1e2577d06d.jpeg', 'https://twitter.com/Independent/status/426318539796930560', 0, False, 'http://pbs.twimg.com/media/BeqWc_-CIAAhmdc.jpg', '', '', '', '', '', '', 0.0, 0.0, ] self.assertEqual(list(TestModel.get_row(0)), expected_row) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-hashtags.dat')) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_multiple_links(self, *mocks): self.account.access_token = 'access' self.account.secret_token = 'secret' self.account.user_name = 'Independent' self.account.auth.parameters = dict(ConsumerKey='key', ConsumerSecret='secret') self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual( self.protocol.send('some message'), 'https://twitter.com/Kai_Mast/status/424185261375766530') self.assertEqual(1, TestModel.get_n_rows()) self.maxDiff = None expected_row = [ 'twitter', 88, '424185261375766530', 'messages', 'Kai Mast', '17339829', 'Kai_Mast', False, '2014-01-17T14:23:41Z', 'A service that filters food pictures from Instagram. ' '<a href="https://twitter.com/search?q=%23MillionDollarIdea&src=hash">#MillionDollarIdea</a>', 'https://pbs.twimg.com/profile_images/424181800701673473/Q6Ggqg7P.png', 'https://twitter.com/Kai_Mast/status/424185261375766530', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ] self.assertEqual(list(TestModel.get_row(0)), expected_row) def test_unfollow(self): get_url = self.protocol._get_url = mock.Mock() self.assertEqual(self.protocol.unfollow('pumpichank'), 'pumpichank') get_url.assert_called_with( 'https://api.twitter.com/1.1/friendships/destroy.json', dict(screen_name='pumpichank')) def test_follow(self): get_url = self.protocol._get_url = mock.Mock() self.assertEqual(self.protocol.follow('pumpichank'), 'pumpichank') get_url.assert_called_with( 'https://api.twitter.com/1.1/friendships/create.json', dict(screen_name='pumpichank', follow='true')) def test_like(self): get_url = self.protocol._get_url = mock.Mock() inc_cell = self.protocol._inc_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.like('1234'), '1234') inc_cell.assert_called_once_with('1234', 'likes') set_cell.assert_called_once_with('1234', 'liked', True) get_url.assert_called_with( 'https://api.twitter.com/1.1/favorites/create.json', dict(id='1234')) def test_unlike(self): get_url = self.protocol._get_url = mock.Mock() dec_cell = self.protocol._dec_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.unlike('1234'), '1234') dec_cell.assert_called_once_with('1234', 'likes') set_cell.assert_called_once_with('1234', 'liked', False) get_url.assert_called_with( 'https://api.twitter.com/1.1/favorites/destroy.json', dict(id='1234')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_tag(self): get_url = self.protocol._get_url = mock.Mock(return_value=dict( statuses=['tweet'])) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.tag('yegbike'), 0) publish.assert_called_with('tweet', stream='search/#yegbike') get_url.assert_called_with( 'https://api.twitter.com/1.1/search/tweets.json?q=%23yegbike') self.assertEqual(self.protocol.tag('#yegbike'), 0) publish.assert_called_with('tweet', stream='search/#yegbike') get_url.assert_called_with( 'https://api.twitter.com/1.1/search/tweets.json?q=%23yegbike') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.base._seen_ids', {}) def test_search(self): get_url = self.protocol._get_url = mock.Mock(return_value=dict( statuses=['tweet'])) publish = self.protocol._publish_tweet = mock.Mock() self.assertEqual(self.protocol.search('hello'), 0) publish.assert_called_with('tweet', stream='search/hello') get_url.assert_called_with( 'https://api.twitter.com/1.1/search/tweets.json?q=hello') @mock.patch('friends.protocols.twitter.time.sleep') def test_rate_limiter_first_time(self, sleep): # The first time we see a URL, there is no rate limiting. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat') message.new('GET', 'http://example.com/') limiter.wait(message) sleep.assert_called_with(0) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_second_time(self, time, sleep): # The second time we see the URL, we get rate limited. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 1, }) limiter.update(message.new('GET', 'http://example.com')) limiter.wait(message) sleep.assert_called_with(300) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_second_time_with_query(self, time, sleep): # A query parameter on the second request is ignored. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 1, }) limiter.update(message.new('GET', 'http://example.com/foo?baz=7')) limiter.wait(message) sleep.assert_called_with(300) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_second_time_with_query_on_request(self, time, sleep): # A query parameter on the original request is ignored. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 1, }) limiter.update(message.new('GET', 'http://example.com/foo?baz=7')) limiter.wait(message) sleep.assert_called_with(300) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_maximum(self, time, sleep): # With one remaining call this window, we get rate limited to the # full amount of the remaining window. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 1, }) limiter.update(message.new('GET', 'http://example.com/alpha')) limiter.wait(message) sleep.assert_called_with(300) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_until_end_of_window(self, time, sleep): # With no remaining calls left this window, we wait until the end of # the window. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 0, }) limiter.update(message.new('GET', 'http://example.com/alpha')) limiter.wait(message) sleep.assert_called_with(300) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_medium(self, time, sleep): # With a few calls remaining this window, we time slice the remaining # time evenly between those remaining calls. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 3, }) limiter.update(message.new('GET', 'http://example.com/beta')) limiter.wait(message) sleep.assert_called_with(100.0) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_rate_limiter_unlimited(self, time, sleep): # With more than 5 calls remaining in this window, we don't rate # limit, even if we've already seen this url. limiter = RateLimiter() message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 10, }) limiter.update(message.new('GET', 'http://example.com/omega')) limiter.wait(message) sleep.assert_called_with(0) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'twitter-home.dat', headers={ 'X-Rate-Limit-Reset': 1349382153 + 300, 'X-Rate-Limit-Remaining': 3, })) @mock.patch('friends.protocols.twitter.time.sleep') @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153) def test_protocol_rate_limiting(self, time, sleep, login): self.account.access_token = 'access' self.account.secret_token = 'secret' # Test rate limiting via the Twitter plugin API. # # The first call doesn't get rate limited. self.protocol.home() sleep.assert_called_with(0) # Second call gets called with the established limit. Because there # are three more calls allowed within the current window, and we're # reporting 300 seconds left in the current window, we're saying we'll # sleep 100 seconds between each call. self.protocol.home() sleep.assert_called_with(100.0) def test_contacts(self): get = self.protocol._get_url = mock.Mock( return_value=dict(ids=[1, 2], name='Bob', screen_name='bobby')) prev = self.protocol._previously_stored_contact = mock.Mock( return_value=False) push = self.protocol._push_to_eds = mock.Mock() self.assertEqual(self.protocol.contacts(), 2) self.assertEqual(get.call_args_list, [ mock.call('https://api.twitter.com/1.1/friends/ids.json'), mock.call( url='https://api.twitter.com/1.1/users/show.json?user_id=1'), mock.call( url='https://api.twitter.com/1.1/users/show.json?user_id=2') ]) self.assertEqual(prev.call_args_list, [mock.call('1'), mock.call('2')]) self.assertEqual(push.call_args_list, [ mock.call(link='https://twitter.com/bobby', uid='1', name='Bob', nick='bobby'), mock.call(link='https://twitter.com/bobby', uid='2', name='Bob', nick='bobby') ])
class TestIdentica(unittest.TestCase): """Test the Identica API.""" def setUp(self): self._temp_cache = tempfile.mkdtemp() self._root = JsonCache._root = os.path.join( self._temp_cache, '{}.json') self.account = FakeAccount() self.protocol = Identica(self.account) self.log_mock = LogMock('friends.utils.base', 'friends.protocols.twitter') def tearDown(self): # Ensure that any log entries we haven't tested just get consumed so # as to isolate out test logger from other tests. self.log_mock.stop() shutil.rmtree(self._temp_cache) @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.Downloader.get_json', return_value=None) def test_unsuccessful_authentication(self, *mocks): self.assertRaises(AuthorizationError, self.protocol._login) self.assertIsNone(self.account.user_name) self.assertIsNone(self.account.user_id) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.authentication.Authentication.__init__', return_value=None) @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(AccessToken='some clever fake data', TokenSecret='sssssshhh!')) def test_successful_authentication(self, *mocks): get_url = self.protocol._get_url = mock.Mock( return_value=dict(id='1234', screen_name='therealrobru')) self.assertTrue(self.protocol._login()) self.assertEqual(self.account.user_name, 'therealrobru') self.assertEqual(self.account.user_id, '1234') self.assertEqual(self.account.access_token, 'some clever fake data') self.assertEqual(self.account.secret_token, 'sssssshhh!') get_url.assert_called_once_with('http://identi.ca/api/users/show.json') def test_mentions(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.protocol.mentions() publish.assert_called_with('tweet', stream='mentions') get_url.assert_called_with( 'http://identi.ca/api/statuses/mentions.json?count=50') def test_user(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.protocol.user() publish.assert_called_with('tweet', stream='messages') get_url.assert_called_with( 'http://identi.ca/api/statuses/user_timeline.json?screen_name=') def test_list(self): self.protocol._get_url = mock.Mock(return_value=['tweet']) self.protocol._publish_tweet = mock.Mock() self.assertRaises(NotImplementedError, self.protocol.list, 'some_list_id') def test_lists(self): self.protocol._get_url = mock.Mock( return_value=[dict(id_str='twitlist')]) self.protocol.list = mock.Mock() self.assertRaises(NotImplementedError, self.protocol.lists) def test_private(self): get_url = self.protocol._get_url = mock.Mock(return_value=['tweet']) publish = self.protocol._publish_tweet = mock.Mock() self.protocol.private() publish.assert_called_with('tweet', stream='private') self.assertEqual( get_url.mock_calls, [mock.call('http://identi.ca/api/direct_messages.json?count=50'), mock.call('http://identi.ca/api/direct_messages' + '/sent.json?count=50')]) def test_send_private(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._publish_tweet = mock.Mock() self.protocol.send_private('pumpichank', 'Are you mocking me?') publish.assert_called_with('tweet', stream='private') get_url.assert_called_with( 'http://identi.ca/api/direct_messages/new.json', dict(text='Are you mocking me?', screen_name='pumpichank')) def test_send(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._publish_tweet = mock.Mock() self.protocol.send('Hello, twitterverse!') publish.assert_called_with('tweet') get_url.assert_called_with( 'http://identi.ca/api/statuses/update.json', dict(status='Hello, twitterverse!')) def test_send_thread(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._publish_tweet = mock.Mock() self.protocol.send_thread( '1234', 'Why yes, I would love to respond to your tweet @pumpichank!') publish.assert_called_with('tweet', stream='reply_to/1234') get_url.assert_called_with( 'http://identi.ca/api/statuses/update.json', dict(status= 'Why yes, I would love to respond to your tweet @pumpichank!', in_reply_to_status_id='1234')) def test_delete(self): get_url = self.protocol._get_url = mock.Mock(return_value='tweet') publish = self.protocol._unpublish = mock.Mock() self.protocol.delete('1234') publish.assert_called_with('1234') get_url.assert_called_with( 'http://identi.ca/api/statuses/destroy/1234.json', dict(trim_user='******')) def test_retweet(self): tweet=dict(tweet='twit') get_url = self.protocol._get_url = mock.Mock(return_value=tweet) publish = self.protocol._publish_tweet = mock.Mock() self.protocol.retweet('1234') publish.assert_called_with(tweet) get_url.assert_called_with( 'http://identi.ca/api/statuses/retweet/1234.json', dict(trim_user='******')) def test_unfollow(self): get_url = self.protocol._get_url = mock.Mock() self.protocol.unfollow('pumpichank') get_url.assert_called_with( 'http://identi.ca/api/friendships/destroy.json', dict(screen_name='pumpichank')) def test_follow(self): get_url = self.protocol._get_url = mock.Mock() self.protocol.follow('pumpichank') get_url.assert_called_with( 'http://identi.ca/api/friendships/create.json', dict(screen_name='pumpichank', follow='true')) def test_tag(self): self.protocol._get_url = mock.Mock( return_value=dict(statuses=['tweet'])) self.protocol._publish_tweet = mock.Mock() self.assertRaises(NotImplementedError, self.protocol.tag, 'hashtag') def test_search(self): get_url = self.protocol._get_url = mock.Mock( return_value=dict(results=['tweet'])) publish = self.protocol._publish_tweet = mock.Mock() self.protocol.search('hello') publish.assert_called_with('tweet', stream='search/hello') get_url.assert_called_with( 'http://identi.ca/api/search.json?q=hello') def test_like(self): get_url = self.protocol._get_url = mock.Mock() inc_cell = self.protocol._inc_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.like('1234'), '1234') inc_cell.assert_called_once_with('1234', 'likes') set_cell.assert_called_once_with('1234', 'liked', True) get_url.assert_called_with( 'http://identi.ca/api/favorites/create/1234.json', dict(id='1234')) def test_unlike(self): get_url = self.protocol._get_url = mock.Mock() dec_cell = self.protocol._dec_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.unlike('1234'), '1234') dec_cell.assert_called_once_with('1234', 'likes') set_cell.assert_called_once_with('1234', 'liked', False) get_url.assert_called_with( 'http://identi.ca/api/favorites/destroy/1234.json', dict(id='1234')) def test_contacts(self): get = self.protocol._get_url = mock.Mock( return_value=dict(ids=[1,2],name='Bob',screen_name='bobby')) prev = self.protocol._previously_stored_contact = mock.Mock(return_value=False) push = self.protocol._push_to_eds = mock.Mock() self.assertEqual(self.protocol.contacts(), 2) self.assertEqual( get.call_args_list, [mock.call('http://identi.ca/api/friends/ids.json'), mock.call(url='http://identi.ca/api/users/show.json?user_id=1'), mock.call(url='http://identi.ca/api/users/show.json?user_id=2')]) self.assertEqual( prev.call_args_list, [mock.call('1'), mock.call('2')]) self.assertEqual( push.call_args_list, [mock.call(link='https://identi.ca/bobby', nick='bobby', uid='1', name='Bob'), mock.call(link='https://identi.ca/bobby', nick='bobby', uid='2', name='Bob')])
class TestLinkedIn(unittest.TestCase): """Test the LinkedIn API.""" def setUp(self): TestModel.clear() self.account = FakeAccount() self.protocol = LinkedIn(self.account) self.log_mock = LogMock('friends.utils.base', 'friends.protocols.linkedin') def tearDown(self): # Ensure that any log entries we haven't tested just get consumed so # as to isolate out test logger from other tests. self.log_mock.stop() def test_name_logic(self): self.assertEqual('', make_fullname()) self.assertEqual('', make_fullname(irrelevant_key='foo')) self.assertEqual('Bob', make_fullname(**dict(firstName='Bob'))) self.assertEqual('LastOnly', make_fullname(**dict(lastName='LastOnly'))) self.assertEqual( 'Bob Loblaw', make_fullname(**dict(firstName='Bob', lastName='Loblaw'))) self.assertEqual( 'Bob Loblaw', make_fullname( **dict(firstName='Bob', lastName='Loblaw', extra='ignored'))) @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.protocols.linkedin.Downloader.get_json', return_value=None) def test_unsuccessful_authentication(self, dload, login, *mocks): self.assertRaises(AuthorizationError, self.protocol._login) self.assertIsNone(self.account.user_name) self.assertIsNone(self.account.user_id) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.authentication.Authentication.__init__', return_value=None) @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(AccessToken='some clever fake data')) @mock.patch('friends.protocols.linkedin.Downloader.get_json', return_value=dict(id='blerch', firstName='Bob', lastName='Loblaw')) def test_successful_authentication(self, *mocks): self.assertTrue(self.protocol._login()) self.assertEqual(self.account.user_name, 'Bob Loblaw') self.assertEqual(self.account.user_id, 'blerch') self.assertEqual(self.account.access_token, 'some clever fake data') @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'linkedin_receive.json')) @mock.patch('friends.protocols.linkedin.LinkedIn._login', return_value=True) @mock.patch('friends.utils.base._seen_ids', {}) def test_home(self, *mocks): self.account.access_token = 'access' self.assertEqual(0, TestModel.get_n_rows()) self.assertEqual(self.protocol.home(), 1) self.assertEqual(1, TestModel.get_n_rows()) self.maxDiff = None self.assertEqual(list(TestModel.get_row(0)), [ 'linkedin', 88, 'UNIU-73705-576270369559388-SHARE', 'messages', 'Hobson L', 'ma0LLid', '', False, '2013-07-16T00:47:06Z', 'I\'m looking forward to the Udacity Global meetup event here in ' 'Portland: <a href="http://lnkd.in/dh5MQz">http://lnkd.in/dh5MQz' '</a>\nGreat way to support the next big thing in c…', 'http://m.c.lnkd.licdn.com/mpr/mprx/0_mVxsC0BnN52aqc24yWvoyA5haqc2Z' 'wLCgzLv0EiBGp7n2jTwX-ls_dzgkSVIZu0', 'https://www.linkedin.com/profile/view?id=7375&authType=name' '&authToken=-LNy&trk=api*a26127*s26893*', 1, False, '', '', '', '', '', '', '', 0.0, 0.0 ]) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'linkedin_contacts.json')) @mock.patch('friends.protocols.linkedin.LinkedIn._login', return_value=True) def test_contacts(self, *mocks): push = self.protocol._push_to_eds = mock.Mock() prev = self.protocol._previously_stored_contact = mock.Mock( return_value=False) token = self.protocol._get_access_token = mock.Mock(return_value='foo') self.protocol._create_contact = lambda arg: arg self.assertEqual(self.protocol.contacts(), 4) self.assertEqual(push.mock_calls, [ mock.call(link='https://www.linkedin.com', name='H A', uid='IFDI'), mock.call(link='https://www.linkedin.com', name='C A', uid='AefF'), mock.call(link='https://www.linkedin.com', name='R A', uid='DFdV'), mock.call(link='https://www.linkedin.com', name='A Z', uid='xkBU') ])