Beispiel #1
0
 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)
Beispiel #2
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)
Beispiel #3
0
 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)
Beispiel #4
0
 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)
Beispiel #5
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)
Beispiel #6
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)
Beispiel #7
0
 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)
Beispiel #8
0
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)
Beispiel #10
0
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())
Beispiel #11
0
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())
Beispiel #12
0
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'))
Beispiel #13
0
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!'))
Beispiel #14
0
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'))
Beispiel #15
0
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')
        ])
Beispiel #16
0
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')
        ])