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)
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)
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)
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)
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)
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)
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)
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 TestFacebook(unittest.TestCase): """Test the Facebook 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 = Facebook(self.account) self.protocol.source_registry = EDSRegistry() def tearDown(self): TestModel.clear() shutil.rmtree(self._temp_cache) def test_features(self): # The set of public features. self.assertEqual(Facebook.get_features(), ['contacts', 'delete', 'delete_contacts', 'home', 'like', 'receive', 'search', 'send', 'send_thread', 'unlike', 'upload', 'wall']) @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='abc')) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'facebook-login.dat')) def test_successful_login(self, *mocks): # Test that a successful response from graph.facebook.com returning # the user's data, sets up the account dict correctly. self.protocol._login() self.assertEqual(self.account.access_token, 'abc') self.assertEqual(self.account.user_name, 'Bart Person') self.assertEqual(self.account.user_id, '801') @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') def test_login_unsuccessful_authentication(self, *mocks): # The user is not already logged in, but the act of logging in fails. self.assertRaises(AuthorizationError, self.protocol._login) self.assertIsNone(self.account.access_token) self.assertIsNone(self.account.user_name) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(AccessToken='abc')) @mock.patch('friends.protocols.facebook.Downloader.get_json', return_value=dict( error=dict(message='Bad access token', type='OAuthException', code=190))) def test_error_response(self, *mocks): with LogMock('friends.utils.base', 'friends.protocols.facebook') as log_mock: self.assertRaises( FriendsError, self.protocol.home, ) contents = log_mock.empty(trim=False) self.assertEqual(contents, 'Logging in to Facebook\n') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'facebook-full.dat')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.protocols.facebook.Facebook._login', return_value=True) def test_receive(self, *mocks): # Receive the wall feed for a user. self.maxDiff = None self.account.access_token = 'abc' self.assertEqual(self.protocol.receive(), 12) self.assertEqual(TestModel.get_n_rows(), 12) self.assertEqual(list(TestModel.get_row(0)), [ 'facebook', 88, 'userid_postid1', 'mentions', 'Yours Truly', '56789', 'Yours Truly', False, '2013-03-13T23:29:07Z', 'Writing code that supports geotagging data from facebook. ' + 'If y\'all could make some geotagged facebook posts for me ' + 'to test with, that\'d be super.', 'https://graph.facebook.com/56789/picture?width=840&height=840', 'https://www.facebook.com/56789/posts/postid1', 1, False, '', '', '', '', '', '', 'Victoria, British Columbia', 48.4333, -123.35, ]) self.assertEqual(list(TestModel.get_row(2)), [ 'facebook', 88, 'postid1_commentid2', 'reply_to/userid_postid1', 'Father', '234', 'Father', False, '2013-03-12T23:29:45Z', 'don\'t know how', 'https://graph.facebook.com/234/picture?width=840&height=840', 'https://www.facebook.com/234/posts/commentid2', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ]) self.assertEqual(list(TestModel.get_row(6)), [ 'facebook', 88, '161247843901324_629147610444676', 'images', 'Best Western Denver Southwest', '161247843901324', 'Best Western Denver Southwest', False, '2013-03-11T23:51:25Z', 'Today only -- Come meet Caroline and Meredith and Stanley the ' + 'Stegosaurus (& Greg & Joe, too!) at the TechZulu Trend Lounge, ' + 'Hilton Garden Inn 18th floor, 500 N Interstate 35, Austin, ' + 'Texas. Monday, March 11th, 4:00pm to 7:00 pm. Also here ' + 'Hannah Hart (My Drunk Kitchen) and Angry Video Game Nerd ' + 'producer, Sean Keegan. Stanley is in the lobby.', 'https://graph.facebook.com/161247843901324/picture?width=840&height=840', 'https://www.facebook.com/161247843901324/posts/629147610444676', 84, False, 'http://graph.facebook.com/629147587111345/picture?type=normal', '', 'https://www.facebook.com/photo.php?fbid=629147587111345&set=a.173256162700492.47377.161247843901324&type=1&relevant_count=1', '', '', '', 'Hilton Garden Inn Austin Downtown/Convention Center', 30.265384957204, -97.735604602521, ]) self.assertEqual(list(TestModel.get_row(9)), [ 'facebook', 88, '104443_100085049977', 'mentions', 'Guy Frenchie', '1244414', 'Guy Frenchie', False, '2013-03-15T19:57:14Z', 'Guy Frenchie did some things with some stuff.', 'https://graph.facebook.com/1244414/picture?width=840&height=840', 'https://www.facebook.com/1244414/posts/100085049977', 3, False, '', '', '', '', '', '', '', 0.0, 0.0, ]) # XXX We really need full coverage of the receive() method, including # cases where some data is missing, or can't be converted # (e.g. timestamps), and paginations. @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'facebook-full.dat')) @mock.patch('friends.protocols.facebook.Facebook._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(), 12) with open(self._root.format('facebook_ids'), 'r') as fd: self.assertEqual(fd.read(), '{"messages": "2013-03-15T19:57:14Z"}') follow = self.protocol._follow_pagination = mock.Mock() follow.return_value = [] self.assertEqual(self.protocol.home(), 12) follow.assert_called_once_with( 'https://graph.facebook.com/me/home', dict(limit=50, since='2013-03-15T19:57:14Z', access_token='access', ) ) @mock.patch('friends.protocols.facebook.Downloader') def test_send_to_my_wall(self, dload): dload().get_json.return_value = dict(id='post_id') token = self.protocol._get_access_token = mock.Mock( return_value='face') publish = self.protocol._publish_entry = mock.Mock( return_value='http://facebook.com/post_id') self.assertEqual( self.protocol.send('I can see the writing on my wall.'), 'http://facebook.com/post_id') token.assert_called_once_with() publish.assert_called_with(entry={'id': 'post_id'}, stream='messages') self.assertEqual( dload.mock_calls, [mock.call(), mock.call('https://graph.facebook.com/me/feed', method='POST', params=dict( access_token='face', message='I can see the writing on my wall.')), mock.call().get_json(), mock.call('https://graph.facebook.com/post_id', params=dict(access_token='face')), mock.call().get_json() ]) @mock.patch('friends.protocols.facebook.Downloader') def test_send_to_my_friends_wall(self, dload): dload().get_json.return_value = dict(id='post_id') token = self.protocol._get_access_token = mock.Mock( return_value='face') publish = self.protocol._publish_entry = mock.Mock( return_value='http://facebook.com/new_post_id') self.assertEqual( self.protocol.send('I can see the writing on my friend\'s wall.', 'friend_id'), 'http://facebook.com/new_post_id') token.assert_called_once_with() publish.assert_called_with(entry={'id': 'post_id'}, stream='messages') self.assertEqual( dload.mock_calls, [mock.call(), mock.call( 'https://graph.facebook.com/friend_id/feed', method='POST', params=dict( access_token='face', message='I can see the writing on my friend\'s wall.')), mock.call().get_json(), mock.call('https://graph.facebook.com/post_id', params=dict(access_token='face')), mock.call().get_json(), ]) @mock.patch('friends.protocols.facebook.Downloader') def test_send_thread(self, dload): dload().get_json.return_value = dict(id='comment_id') token = self.protocol._get_access_token = mock.Mock( return_value='face') publish = self.protocol._publish_entry = mock.Mock( return_value='http://facebook.com/private_message_id') self.assertEqual( self.protocol.send_thread('post_id', 'Some witty response!'), 'http://facebook.com/private_message_id') token.assert_called_once_with() publish.assert_called_with(entry={'id': 'comment_id'}, stream='reply_to/post_id') self.assertEqual( dload.mock_calls, [mock.call(), mock.call( 'https://graph.facebook.com/post_id/comments', method='POST', params=dict( access_token='face', message='Some witty response!')), mock.call().get_json(), mock.call('https://graph.facebook.com/comment_id', params=dict(access_token='face')), mock.call().get_json(), ]) @mock.patch('friends.protocols.facebook.Uploader.get_json', return_value=dict(post_id='234125')) @mock.patch('friends.protocols.facebook.time.time', return_value=1352209748.1254) def test_upload_local(self, *mocks): token = self.protocol._get_access_token = mock.Mock( return_value='face') publish = self.protocol._publish = mock.Mock() src = 'file://' + resource_filename('friends.tests.data', 'ubuntu.png') self.assertEqual(self.protocol.upload(src, 'This is Ubuntu!'), 'https://www.facebook.com/234125') token.assert_called_once_with() publish.assert_called_once_with( sender_nick=None, stream='images', url='https://www.facebook.com/234125', timestamp='2012-11-06T13:49:08Z', sender_id=None, from_me=True, icon_uri='https://graph.facebook.com/None/picture?type=large', message='This is Ubuntu!', message_id='234125', sender=None) @mock.patch('friends.utils.http._soup') @mock.patch('friends.protocols.facebook.Uploader._build_request', return_value=None) @mock.patch('friends.protocols.facebook.time.time', return_value=1352209748.1254) def test_upload_missing(self, *mocks): token = self.protocol._get_access_token = mock.Mock( return_value='face') publish = self.protocol._publish = mock.Mock() src = 'file:///tmp/a/non-existant/path' self.assertRaises( ValueError, self.protocol.upload, src, 'There is no spoon', ) token.assert_called_once_with() self.assertFalse(publish.called) @mock.patch('friends.utils.http._soup') def test_upload_not_uri(self, *mocks): token = self.protocol._get_access_token = mock.Mock( return_value='face') publish = self.protocol._publish = mock.Mock() src = resource_filename('friends.tests.data', 'ubuntu.png') self.assertRaises( GLib.GError, self.protocol.upload, src, 'There is no spoon', ) token.assert_called_once_with() self.assertFalse(publish.called) def test_search(self): self.protocol._get_access_token = lambda: '12345' get_pages = self.protocol._follow_pagination = mock.Mock( return_value=['search results']) publish = self.protocol._publish_entry = mock.Mock() self.assertEqual(self.protocol.search('hello'), 1) publish.assert_called_with('search results', 'search/hello') get_pages.assert_called_with( 'https://graph.facebook.com/search', dict(q='hello', access_token='12345')) @mock.patch('friends.protocols.facebook.Downloader') def test_like(self, dload): dload().get_json.return_value = True token = self.protocol._get_access_token = mock.Mock( return_value='face') inc_cell = self.protocol._inc_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.like('post_id'), 'post_id') inc_cell.assert_called_once_with('post_id', 'likes') set_cell.assert_called_once_with('post_id', 'liked', True) token.assert_called_once_with() dload.assert_called_with( 'https://graph.facebook.com/post_id/likes', method='POST', params=dict(access_token='face')) @mock.patch('friends.protocols.facebook.Downloader') def test_unlike(self, dload): dload.get_json.return_value = True token = self.protocol._get_access_token = mock.Mock( return_value='face') dec_cell = self.protocol._dec_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.unlike('post_id'), 'post_id') dec_cell.assert_called_once_with('post_id', 'likes') set_cell.assert_called_once_with('post_id', 'liked', False) token.assert_called_once_with() dload.assert_called_once_with( 'https://graph.facebook.com/post_id/likes', method='DELETE', params=dict(access_token='face')) @mock.patch('friends.protocols.facebook.Downloader') def test_delete(self, dload): dload().get_json.return_value = True token = self.protocol._get_access_token = mock.Mock( return_value='face') unpublish = self.protocol._unpublish = mock.Mock() self.assertEqual(self.protocol.delete('post_id'), 'post_id') token.assert_called_once_with() dload.assert_called_with( 'https://graph.facebook.com/post_id', method='DELETE', params=dict(access_token='face')) unpublish.assert_called_once_with('post_id') @mock.patch('friends.protocols.facebook.Downloader') def test_contacts(self, downloader): downloader().get_json.return_value = dict( name='Joe Blow', username='******', link='example.com', gender='male') downloader.reset_mock() self.protocol._get_access_token = mock.Mock(return_value='broken') follow = self.protocol._follow_pagination = mock.Mock( return_value=[dict(id='contact1'), dict(id='contact2')]) 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) follow.assert_called_once_with( params={'access_token': 'broken', 'limit': 1000}, url='https://graph.facebook.com/me/friends', limit=1000) self.assertEqual( prev.call_args_list, [mock.call('contact1'), mock.call('contact2')]) self.assertEqual( downloader.call_args_list, [mock.call(url='https://graph.facebook.com/contact1', params={'access_token': 'broken'}), mock.call(url='https://graph.facebook.com/contact2', params={'access_token': 'broken'})]) self.assertEqual( push.call_args_list, [mock.call(gender='male', jabber='*****@*****.**', nick='jblow', link='example.com', name='Joe Blow', uid='contact1'), mock.call(gender='male', jabber='*****@*****.**', nick='jblow', link='example.com', name='Joe Blow', uid='contact2')]) def test_create_contact(self, *mocks): # Receive the users friends. eds_contact = self.protocol._create_contact( uid='555555555', name='Lucy Baron', nick='lucy.baron5', gender='female', link='http:www.facebook.com/lucy.baron5', jabber='*****@*****.**') facebook_id_attr = eds_contact.get_attribute('facebook-id') self.assertEqual(facebook_id_attr.get_value(), '555555555') web_service_addrs = eds_contact.get_attribute('X-FOLKS-WEB-SERVICES-IDS') params= web_service_addrs.get_params() self.assertEqual(len(params), 5) # Can't compare the vcard string directly because it is sorted randomly... vcard = eds_contact.to_string( EBookContacts.VCardFormat(1)).replace('\r\n ', '') self.assertIn( 'social-networking-attributes.X-URIS:http:www.facebook.com/lucy.baron5', vcard) self.assertIn( 'social-networking-attributes.X-GENDER:female', vcard) self.assertIn( 'social-networking-attributes.facebook-id:555555555', vcard) self.assertIn( 'FN:Lucy Baron', vcard) self.assertIn( 'NICKNAME:lucy.baron5', vcard) self.assertIn( 'social-networking-attributes.X-FOLKS-WEB-SERVICES-IDS;', vcard) self.assertIn( 'remote-full-name="Lucy Baron"', vcard) self.assertIn( 'facebook-id=555555555', vcard) self.assertIn( 'jabber="*****@*****.**"', vcard) self.assertIn( 'facebook-nick="lucy.baron5"', vcard) @mock.patch('friends.utils.base.Base._prepare_eds_connections', return_value=True) @mock.patch('gi.repository.EBook.BookClient.new', return_value=EDSBookClientMock()) def test_successfull_push_to_eds(self, *mocks): bare_contact = {'name': 'Lucy Baron', 'uid': '555555555', 'nick': 'lucy.baron5', 'link': 'http:www.facebook.com/lucy.baron5'} self.protocol._address_book = 'test-address-book' client = self.protocol._book_client = mock.Mock() client.add_contact_sync.return_value = True # Implicitely fail test if the following raises any exceptions self.protocol._push_to_eds(**bare_contact) @mock.patch('friends.utils.base.Base._prepare_eds_connections', return_value=None) def test_unsuccessfull_push_to_eds(self, *mocks): bare_contact = {'name': 'Lucy Baron', 'uid': '555555555', 'nick': 'lucy.baron5', 'link': 'http:www.facebook.com/lucy.baron5'} self.protocol._address_book = 'test-address-book' client = self.protocol._book_client = mock.Mock() client.add_contact_sync.return_value = False self.assertRaises( ContactsError, self.protocol._push_to_eds, **bare_contact ) @mock.patch('gi.repository.EBook.BookClient.connect_sync', return_value=EDSBookClientMock()) @mock.patch('gi.repository.EDataServer.SourceRegistry.new_sync', return_value=EDSRegistry()) def test_successful_previously_stored_contact(self, *mocks): result = self.protocol._previously_stored_contact('11111') self.assertEqual(result, True) @mock.patch('gi.repository.EBook.BookClient.connect_sync', return_value=EDSBookClientMock()) @mock.patch('gi.repository.EDataServer.SourceRegistry.new_sync', return_value=EDSRegistry()) def test_first_run_prepare_eds_connections(self, *mocks): self.protocol._name = 'testsuite' self.assertIsNone(self.protocol._address_book_name) self.assertIsNone(self.protocol._eds_source_registry) self.assertIsNone(self.protocol._eds_source) self.assertIsNone(self.protocol._book_client) self.protocol._prepare_eds_connections() self.assertEqual(self.protocol._address_book_name, 'friends-testsuite-contacts') self.assertEqual(self.protocol._eds_source.get_display_name(), 'friends-testsuite-contacts') self.assertEqual(self.protocol._eds_source.get_uid(), 'friends-testsuite-contacts') self.protocol.delete_contacts() @mock.patch('gi.repository.EDataServer.SourceRegistry') @mock.patch('gi.repository.EDataServer.Source') @mock.patch('gi.repository.EBook.BookClient') def test_mocked_prepare_eds_connections(self, client, source, registry): self.protocol._name = 'testsuite' self.assertIsNone(self.protocol._address_book_name) self.protocol._prepare_eds_connections() self.protocol._prepare_eds_connections() # Second time harmlessly ignored self.assertEqual(self.protocol._address_book_name, 'friends-testsuite-contacts') registry.new_sync.assert_called_once_with(None) self.assertEqual(self.protocol._eds_source_registry, registry.new_sync()) registry.new_sync().ref_source.assert_called_once_with( 'friends-testsuite-contacts') self.assertEqual(self.protocol._eds_source, registry.new_sync().ref_source()) client.connect_sync.assert_called_once_with( registry.new_sync().ref_source(), None) self.assertEqual(self.protocol._book_client, client.connect_sync()) @mock.patch('gi.repository.EDataServer.SourceRegistry') @mock.patch('gi.repository.EDataServer.Source') @mock.patch('gi.repository.EBook.BookClient') def test_create_new_eds_book(self, client, source, registry): self.protocol._name = 'testsuite' self.assertIsNone(self.protocol._address_book_name) registry.new_sync().ref_source.return_value = None registry.reset_mock() self.protocol._prepare_eds_connections() self.protocol._prepare_eds_connections() # Second time harmlessly ignored self.assertEqual(self.protocol._address_book_name, 'friends-testsuite-contacts') registry.new_sync.assert_called_once_with(None) self.assertEqual(self.protocol._eds_source_registry, registry.new_sync()) registry.new_sync().ref_source.assert_called_once_with( 'friends-testsuite-contacts') source.new_with_uid.assert_called_once_with( 'friends-testsuite-contacts', None) self.assertEqual(self.protocol._eds_source, source.new_with_uid()) source.new_with_uid().set_display_name.assert_called_once_with( 'friends-testsuite-contacts') source.new_with_uid().set_parent.assert_called_once_with('local-stub') source.new_with_uid().get_extension.assert_called_once_with( EDataServer.SOURCE_EXTENSION_ADDRESS_BOOK) registry.new_sync().commit_source_sync.assert_called_once_with( source.new_with_uid(), None) client.connect_sync.assert_called_once_with( source.new_with_uid(), None) self.assertEqual(self.protocol._book_client, client.connect_sync())
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 TestInstagram(unittest.TestCase): """Test the Instagram 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 = Instagram(self.account) self.protocol.source_registry = EDSRegistry() def tearDown(self): TestModel.clear() shutil.rmtree(self._temp_cache) def test_features(self): # The set of public features. self.assertEqual(Instagram.get_features(), [ 'delete_contacts', 'home', 'like', 'receive', 'send_thread', 'unlike' ]) @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='abc')) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'instagram-login.dat')) def test_successful_login(self, *mock): # Test that a successful response from instagram.com returning # the user's data, sets up the account dict correctly. self.protocol._login() self.assertEqual(self.account.access_token, 'abc') self.assertEqual(self.account.user_name, 'bpersons') self.assertEqual(self.account.user_id, '801') @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') def test_login_unsuccessful_authentication(self, *mock): # The user is not already logged in, but the act of logging in fails. self.assertRaises(AuthorizationError, self.protocol._login) self.assertIsNone(self.account.access_token) self.assertIsNone(self.account.user_name) @mock.patch('friends.utils.authentication.manager') @mock.patch('friends.utils.authentication.Accounts') @mock.patch('friends.utils.authentication.Authentication.login', return_value=dict(AccessToken='abc')) @mock.patch( 'friends.protocols.instagram.Downloader.get_json', return_value=dict(error=dict( message='Bad access token', type='OAuthException', code=190))) def test_error_response(self, *mocks): with LogMock('friends.utils.base', 'friends.protocols.instagram') as log_mock: self.assertRaises( FriendsError, self.protocol.home, ) contents = log_mock.empty(trim=False) self.assertEqual(contents, 'Logging in to Instagram\n') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'instagram-full.dat')) @mock.patch('friends.utils.base.Model', TestModel) @mock.patch('friends.protocols.instagram.Instagram._login', return_value=True) def test_receive(self, *mocks): # Receive the feed for a user. self.maxDiff = None self.account.access_token = 'abc' self.assertEqual(self.protocol.receive(), 14) self.assertEqual(TestModel.get_n_rows(), 14) self.assertEqual(list(TestModel.get_row(0)), [ 'instagram', 88, '431474591469914097_223207800', 'messages', 'Josh', '223207800', 'joshwolp', False, '2013-04-11T04:50:01Z', 'joshwolp shared a picture on Instagram.', 'http://images.ak.instagram.com/profiles/profile_223207800_75sq_1347753109.jpg', 'http://instagram.com/joshwolp', 8, False, 'http://distilleryimage9.s3.amazonaws.com/44ad8486a26311e2872722000a1fd26f_5.jpg', '', 'http://instagram.com/p/X859raK8fx/', '', '', '', '', 0.0, 0.0, ]) self.assertEqual(list(TestModel.get_row(3)), [ 'instagram', 88, '431462132263145102', 'reply_to/431438012683111856_5891266', 'Syd', '5917696', 'squidneylol', False, '2013-04-11T04:25:15Z', 'I remember pushing that little guy of the swings a few times....', 'http://images.ak.instagram.com/profiles/profile_5917696_75sq_1336705905.jpg', '', 0, False, '', '', '', '', '', '', '', 0.0, 0.0, ]) @mock.patch('friends.protocols.instagram.Downloader') def test_send_thread(self, dload): dload().get_json.return_value = dict(id='comment_id') token = self.protocol._get_access_token = mock.Mock(return_value='abc') publish = self.protocol._publish_entry = mock.Mock( return_value='http://instagram.com/p/post_id') self.assertEqual( self.protocol.send_thread('post_id', 'Some witty response!'), 'http://instagram.com/p/post_id') token.assert_called_once_with() publish.assert_called_with(entry={'id': 'comment_id'}, stream='reply_to/post_id') self.assertEqual(dload.mock_calls, [ mock.call(), mock.call( 'https://api.instagram.com/v1/media/post_id/comments?access_token=abc', method='POST', params=dict(access_token='abc', text='Some witty response!')), mock.call().get_json(), mock.call( 'https://api.instagram.com/v1/media/post_id/comments?access_token=abc', params=dict(access_token='abc')), mock.call().get_json(), ]) @mock.patch('friends.protocols.instagram.Downloader') def test_like(self, dload): dload().get_json.return_value = True token = self.protocol._get_access_token = mock.Mock( return_value='insta') inc_cell = self.protocol._inc_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.like('post_id'), 'post_id') inc_cell.assert_called_once_with('post_id', 'likes') set_cell.assert_called_once_with('post_id', 'liked', True) token.assert_called_once_with() dload.assert_called_with( 'https://api.instagram.com/v1/media/post_id/likes?access_token=insta', method='POST', params=dict(access_token='insta')) @mock.patch('friends.protocols.instagram.Downloader') def test_unlike(self, dload): dload.get_json.return_value = True token = self.protocol._get_access_token = mock.Mock( return_value='insta') dec_cell = self.protocol._dec_cell = mock.Mock() set_cell = self.protocol._set_cell = mock.Mock() self.assertEqual(self.protocol.unlike('post_id'), 'post_id') dec_cell.assert_called_once_with('post_id', 'likes') set_cell.assert_called_once_with('post_id', 'liked', False) token.assert_called_once_with() dload.assert_called_once_with( 'https://api.instagram.com/v1/media/post_id/likes?access_token=insta', method='DELETE', params=dict(access_token='insta'))
class TestShorteners(unittest.TestCase): """Test the various shorteners, albeit via mocks.""" @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'short.dat')) def test_isgd(self): self.assertEqual( Short('is.gd').make('http://www.python.org'), 'http://sho.rt/') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'short.dat')) def test_ougd(self): self.assertEqual( Short('ou.gd').make('http://www.python.org'), 'http://sho.rt/') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'short.dat')) def test_linkeecom(self): self.assertEqual( Short('linkee.com').make('http://www.python.org'), 'http://sho.rt/') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'short.dat')) def test_tinyurlcom(self): self.assertEqual( Short('tinyurl.com').make('http://www.python.org'), 'http://sho.rt/') @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'durlme.dat')) def test_durlme(self): self.assertEqual( Short('durl.me').make('http://www.python.org'), 'http://durl.me/5o') def test_missing_or_disabled_lookup(self): # Looking up a non-existent or disabled shortener gives you one that # returns the original url back unchanged. self.assertEqual( Short('nonexistant').make('http://www.python.org'), 'http://www.python.org') self.assertEqual(Short().make('http://www.python.org'), 'http://www.python.org') def test_is_shortened(self): # Test URLs that have been shortened. self.assertTrue(Short.already('http://tinyurl.com/foo')) self.assertTrue(Short.already('http://is.gd/foo')) self.assertTrue(Short.already('http://linkee.com/foo')) self.assertTrue(Short.already('http://ou.gd/foo')) self.assertTrue(Short.already('http://durl.me/foo')) def test_is_not_shortened(self): # Test a URL that has not been shortened. self.assertFalse(Short.already('http://www.python.org/bar')) @mock.patch('friends.utils.shorteners.Downloader') def test_isgd_quoted_properly(self, dl_mock): Short('is.gd').make('http://example.com/~user/stuff/+things') dl_mock.assert_called_once_with( 'http://is.gd/api.php?longurl=http%3A%2F%2Fexample.com' '%2F%7Euser%2Fstuff%2F%2Bthings') @mock.patch('friends.utils.shorteners.Downloader') def test_ougd_quoted_properly(self, dl_mock): Short('ou.gd').make('http://example.com/~user/stuff/+things') dl_mock.assert_called_once_with( 'http://ou.gd/api.php?format=simple&action=shorturl&url=' 'http%3A%2F%2Fexample.com%2F%7Euser%2Fstuff%2F%2Bthings') @mock.patch('friends.utils.shorteners.Downloader') def test_linkeecom_quoted_properly(self, dl_mock): Short('linkee.com').make('http://example.com/~user/stuff/+things') dl_mock.assert_called_once_with( 'http://api.linkee.com/1.0/shorten?format=text&input=' 'http%3A%2F%2Fexample.com%2F%7Euser%2Fstuff%2F%2Bthings') @mock.patch('friends.utils.shorteners.Downloader') def test_tinyurl_quoted_properly(self, dl_mock): Short('tinyurl.com').make('http://example.com/~user/stuff/+things') dl_mock.assert_called_once_with( 'http://tinyurl.com/api-create.php?url=http%3A%2F%2Fexample.com' '%2F%7Euser%2Fstuff%2F%2Bthings') @mock.patch('friends.utils.shorteners.Downloader') def test_durlme_quoted_properly(self, dl_mock): dl_mock().get_string().strip.return_value = '' dl_mock.reset_mock() Short('durl.me').make('http://example.com/~user/stuff/+things') dl_mock.assert_called_once_with( 'http://durl.me/api/Create.do?type=json&longurl=' 'http%3A%2F%2Fexample.com%2F%7Euser%2Fstuff%2F%2Bthings') @mock.patch('friends.utils.shorteners.Downloader') def test_dont_over_shorten(self, dl_mock): Short('tinyurl.com').make('http://tinyurl.com/page_id') Short('linkee.com').make('http://ou.gd/page_id') Short('is.gd').make('http://is.gd/page_id') Short('ou.gd').make('http://linkee.com/page_id') self.assertEqual(dl_mock.call_count, 0) def test_find_all_in_string(self): shorter = Short() shorter.make = lambda url: 'zombo.com' self.assertEqual( 'Welcome to zombo.com, anything is possible. ' 'You can do anything at zombo.com!', shorter.sub( 'Welcome to http://example.com/really/really/long/url, ' 'anything is possible. You can do anything at ' 'http://example.com!'))
class TestAvatars(unittest.TestCase): """Test Avatar logic.""" def setUp(self): # Create a temporary cache directory for storing the avatar image # files. This ensures that the user's operational environment can't # possibly interfere. self._temp_cache = tempfile.mkdtemp() self._avatar_cache = os.path.join(self._temp_cache, 'friends', 'avatars') def tearDown(self): # Clean up the temporary cache directory. shutil.rmtree(self._temp_cache) def test_noop(self): # If a tweet is missing a profile image, silently ignore it. self.assertEqual(Avatar.get_image(''), '') def test_hashing(self): # Check that the path hashing algorithm return a hash based on the # download url. with mock.patch('friends.utils.avatar.CACHE_DIR', self._avatar_cache): path = Avatar.get_path('fake_url') self.assertEqual( path.split(os.sep)[-3:], [ 'friends', 'avatars', # hashlib.sha1('fake_url'.encode('utf-8')).hexdigest() '4f37e5dc9d38391db1728048344c3ab5ff8cecb2' ]) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'ubuntu.png')) def test_cache_filled_on_miss(self): # When the cache is empty, downloading an avatar from a given url # fills the cache with the image data. with mock.patch('friends.utils.avatar.CACHE_DIR', self._avatar_cache) as cache_dir: # The file has not yet been downloaded because the directory does # not yet exist. It is created on demand. self.assertFalse(os.path.isdir(cache_dir)) os.makedirs(cache_dir) Avatar.get_image('http://example.com') # Soup.Message() was called once. Get the mock and check it. from friends.utils.http import Soup self.assertEqual(Soup.Message.call_count, 1) # Now the file is there. self.assertEqual( sorted(os.listdir(cache_dir)), # hashlib.sha1('http://example.com' # .encode('utf-8')).hexdigest() sorted([ '89dce6a446a69d6b9bdc01ac75251e4c322bcdff', '89dce6a446a69d6b9bdc01ac75251e4c322bcdff.100px' ])) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'ubuntu.png')) def test_cache_used_on_hit(self): # When the cache already contains the file, it is not downloaded. with mock.patch('friends.utils.avatar.CACHE_DIR', self._avatar_cache) as cache_dir: os.makedirs(cache_dir) src = resource_filename('friends.tests.data', 'ubuntu.png') dst = os.path.join(cache_dir, '89dce6a446a69d6b9bdc01ac75251e4c322bcdff') shutil.copyfile(src, dst) # Get the image, resulting in a cache hit. path = Avatar.get_image('http://example.com') # No download occurred. Check the mock. from friends.utils.http import Soup self.assertEqual(Soup.Message.call_count, 0) # Confirm that the resulting cache image is actually a PNG. with open(path, 'rb') as raw: # This is the PNG file format magic number, living in the first 8 # bytes of the file. self.assertEqual(raw.read(8), bytes.fromhex('89504E470D0A1A0A')) @mock.patch('friends.utils.http.Soup.Message', FakeSoupMessage('friends.tests.data', 'ubuntu.png')) def test_cache_file_contains_image(self): # The image is preserved in the cache file. with mock.patch('friends.utils.avatar.CACHE_DIR', self._avatar_cache) as cache_dir: os.makedirs(cache_dir) path = Avatar.get_image('http://example.com') # The image must have been downloaded at least once. pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) self.assertEqual(pixbuf.get_height(), 285) self.assertEqual(pixbuf.get_width(), 285) pixbuf = GdkPixbuf.Pixbuf.new_from_file(path + '.100px') self.assertEqual(pixbuf.get_height(), 100) self.assertEqual(pixbuf.get_width(), 100) # Confirm that the resulting cache image is actually a PNG. with open(path, 'rb') as raw: # This is the PNG file format magic number, living in the first 8 # bytes of the file. self.assertEqual(raw.read(8), bytes.fromhex('89504E470D0A1A0A'))
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 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') ])