class TestProtocolManager(unittest.TestCase):
    """Test the protocol finder."""
    def setUp(self):
        self.manager = ProtocolManager()
        self.log_mock = LogMock('friends.utils.base')

    def tearDown(self):
        self.log_mock.stop()

    def test_find_all_protocols(self):
        # The manager can find all the protocol classes.
        self.assertEqual(self.manager.protocols['flickr'], Flickr)
        self.assertEqual(self.manager.protocols['twitter'], Twitter)

    def test_doesnt_find_base(self):
        # Make sure that the base protocol class isn't returned.
        self.assertNotIn('base', self.manager.protocols)
        for protocol_class in self.manager.protocols.values():
            self.assertNotEqual(protocol_class, Base)
class TestAuthentication(unittest.TestCase):
    """Test authentication."""
    def setUp(self):
        self.log_mock = LogMock('friends.utils.authentication')
        self.account = FakeAccount()
        self.account.auth.get_credentials_id = lambda *ignore: 'my id'
        self.account.auth.get_method = lambda *ignore: 'some method'
        self.account.auth.get_parameters = lambda *ignore: 'change me'
        self.account.auth.get_mechanism = lambda *ignore: 'whatever'

    def tearDown(self):
        self.log_mock.stop()

    @mock.patch('friends.utils.authentication.Signon', FakeSignon)
    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    def test_successful_login(self, accounts, manager):
        manager.get_account().list_services.return_value = ['foo']
        # Prevent an error in the callback.
        accounts.AccountService.new().get_auth_data(
        ).get_parameters.return_value = False
        authenticator = Authentication(self.account.id)
        reply = authenticator.login()
        self.assertEqual(reply, dict(AccessToken='auth reply'))
        self.assertEqual(self.log_mock.empty(), '_login_cb completed\n')

    @mock.patch('friends.utils.authentication.Signon', FailingSignon)
    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    def test_missing_access_token(self, accounts, manager):
        manager.get_account().list_services.return_value = ['foo']
        # Prevent an error in the callback.
        self.account.auth.get_parameters = lambda *ignore: False
        authenticator = Authentication(self.account.id)
        self.assertRaises(AuthorizationError, authenticator.login)

    @mock.patch('friends.utils.authentication.Signon', FakeSignon)
    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    def test_failed_login(self, accounts, manager):
        # Trigger an error in the callback.
        class Error:
            message = 'who are you?'

        manager.get_account().list_services.return_value = ['foo']
        accounts.AccountService.new().get_auth_data(
        ).get_parameters.return_value = Error
        authenticator = Authentication(self.account.id)
        self.assertRaises(AuthorizationError, authenticator.login)

    @mock.patch('friends.utils.authentication.Signon', FakeSignon)
    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    def test_exception_correct_thread(self, accounts, manager):
        manager.get_account().list_services.return_value = ['foo']
        authenticator = Authentication(self.account)
        # If this were to raise any exception for any reason, this
        # test will fail. This method can't be allowed to raise
        # exceptions because it doesn't run in the safety of a
        # subthread where those are caught and logged nicely.
        authenticator._login_cb('session', 'reply', 'error', 'data')
        self.assertEqual(authenticator._reply, 'reply')
        self.assertEqual(authenticator._error, 'error')
Exemple #3
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)
Exemple #5
0
class TestDispatcher(unittest.TestCase):
    """Test the dispatcher's ability to dispatch."""
    @mock.patch('dbus.service.BusName')
    @mock.patch('friends.service.dispatcher.find_accounts')
    @mock.patch('dbus.service.Object.__init__')
    def setUp(self, *mocks):
        self.log_mock = LogMock('friends.service.dispatcher',
                                'friends.utils.account')
        self.dispatcher = Dispatcher(mock.Mock(), mock.Mock())
        self.dispatcher.accounts = {}

    def tearDown(self):
        self.log_mock.stop()

    @mock.patch('friends.service.dispatcher.threading')
    def test_refresh(self, threading_mock):
        account = mock.Mock()
        threading_mock.activeCount.return_value = 1
        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.values.return_value = [account]

        self.assertIsNone(self.dispatcher.Refresh())

        self.dispatcher.accounts.values.assert_called_once_with()
        account.protocol.assert_called_once_with('receive')

        self.assertEqual(
            self.log_mock.empty(), 'Clearing timer id: 42\n'
            'Refresh requested\n'
            'Starting new shutdown timer...\n')

    def test_clear_indicators(self):
        self.dispatcher.menu_manager = mock.Mock()
        self.dispatcher.ClearIndicators()
        self.dispatcher.menu_manager.update_unread_count.assert_called_once_with(
            0)

    def test_do(self):
        account = mock.Mock()
        account.id = '345'
        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.get.return_value = account

        self.dispatcher.Do('like', '345', '23346356767354626')
        self.dispatcher.accounts.get.assert_called_once_with(345)
        account.protocol.assert_called_once_with('like',
                                                 '23346356767354626',
                                                 success=STUB,
                                                 failure=STUB)

        self.assertEqual(
            self.log_mock.empty(), 'Clearing timer id: 42\n'
            '345: like 23346356767354626\n'
            'Starting new shutdown timer...\n')

    def test_failing_do(self):
        account = mock.Mock()
        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.get.return_value = None

        self.dispatcher.Do('unlike', '6', '23346356767354626')
        self.dispatcher.accounts.get.assert_called_once_with(6)
        self.assertEqual(account.protocol.call_count, 0)

        self.assertEqual(
            self.log_mock.empty(), 'Clearing timer id: 42\n'
            'Could not find account: 6\n'
            'Starting new shutdown timer...\n')

    def test_send_message(self):
        account1 = mock.Mock()
        account2 = mock.Mock()
        account3 = mock.Mock()
        account2.send_enabled = False

        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.values.return_value = [
            account1,
            account2,
            account3,
        ]

        self.dispatcher.SendMessage('Howdy friends!')
        self.dispatcher.accounts.values.assert_called_once_with()
        account1.protocol.assert_called_once_with('send',
                                                  'Howdy friends!',
                                                  success=STUB,
                                                  failure=STUB)
        account3.protocol.assert_called_once_with('send',
                                                  'Howdy friends!',
                                                  success=STUB,
                                                  failure=STUB)
        self.assertEqual(account2.protocol.call_count, 0)

    def test_send_reply(self):
        account = mock.Mock()
        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.get.return_value = account

        self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]')
        self.dispatcher.accounts.get.assert_called_once_with(2)
        account.protocol.assert_called_once_with('send_thread',
                                                 'objid',
                                                 '[Hilarious Response]',
                                                 success=STUB,
                                                 failure=STUB)

        self.assertEqual(
            self.log_mock.empty(), 'Clearing timer id: 42\n'
            'Replying to 2, objid\n'
            'Starting new shutdown timer...\n')

    def test_send_reply_failed(self):
        account = mock.Mock()
        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.get.return_value = None

        self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]')
        self.dispatcher.accounts.get.assert_called_once_with(2)
        self.assertEqual(account.protocol.call_count, 0)

        self.assertEqual(
            self.log_mock.empty(), 'Clearing timer id: 42\n'
            'Replying to 2, objid\n'
            'Could not find account: 2\n'
            'Starting new shutdown timer...\n')

    def test_upload_async(self):
        account = mock.Mock()
        self.dispatcher.accounts = mock.Mock()
        self.dispatcher.accounts.get.return_value = account

        success = mock.Mock()
        failure = mock.Mock()

        self.dispatcher.Upload('2',
                               'file://path/to/image.png',
                               'A thousand words',
                               success=success,
                               failure=failure)
        self.dispatcher.accounts.get.assert_called_once_with(2)
        account.protocol.assert_called_once_with(
            'upload',
            'file://path/to/image.png',
            'A thousand words',
            success=success,
            failure=failure,
        )

        self.assertEqual(
            self.log_mock.empty(), 'Clearing timer id: 42\n'
            'Uploading file://path/to/image.png to 2\n'
            'Starting new shutdown timer...\n')

    def test_get_features(self):
        self.assertEqual(json.loads(self.dispatcher.GetFeatures('facebook')), [
            'contacts', 'delete', 'delete_contacts', 'home', 'like', 'receive',
            'search', 'send', 'send_thread', 'unlike', 'upload', 'wall'
        ])
        self.assertEqual(json.loads(self.dispatcher.GetFeatures('twitter')), [
            'contacts', 'delete', 'delete_contacts', 'follow', 'home', 'like',
            'list', 'lists', 'mentions', 'private', 'receive', 'retweet',
            'search', 'send', 'send_private', 'send_thread', 'tag', 'unfollow',
            'unlike', 'user'
        ])
        self.assertEqual(json.loads(self.dispatcher.GetFeatures('identica')), [
            'contacts', 'delete', 'delete_contacts', 'follow', 'home', 'like',
            'mentions', 'private', 'receive', 'retweet', 'search', 'send',
            'send_private', 'send_thread', 'unfollow', 'unlike', 'user'
        ])
        self.assertEqual(json.loads(self.dispatcher.GetFeatures('flickr')),
                         ['delete_contacts', 'receive', 'upload'])
        self.assertEqual(json.loads(self.dispatcher.GetFeatures('foursquare')),
                         ['delete_contacts', 'receive'])

    @mock.patch('friends.service.dispatcher.logging')
    def test_urlshorten_already_shortened(self, logging_mock):
        self.assertEqual('http://tinyurl.com/foo',
                         self.dispatcher.URLShorten('http://tinyurl.com/foo'))

    @mock.patch('friends.service.dispatcher.logging')
    @mock.patch('friends.service.dispatcher.Short')
    def test_urlshorten(self, short_mock, logging_mock):
        short_mock().sub.return_value = 'short url'
        short_mock.reset_mock()
        self.dispatcher.settings.get_string.return_value = 'is.gd'
        long_url = 'http://example.com/really/really/long'
        self.assertEqual(self.dispatcher.URLShorten(long_url), 'short url')
        self.dispatcher.settings.get_boolean.assert_called_once_with(
            'shorten-urls')
        short_mock.assert_called_once_with('is.gd')
        short_mock.return_value.sub.assert_called_once_with(long_url)

    @mock.patch('friends.service.dispatcher.GLib')
    def test_manage_timers_clear(self, glib):
        glib.source_remove.reset_mock()
        manager = ManageTimers()
        manager.timers = {1}
        manager.__enter__()
        glib.source_remove.assert_called_once_with(1)
        manager.timers = {1, 2, 3}
        manager.clear_all_timers()
        self.assertEqual(glib.source_remove.call_count, 4)

    @mock.patch('friends.service.dispatcher.GLib')
    def test_manage_timers_set(self, glib):
        glib.timeout_add_seconds.reset_mock()
        manager = ManageTimers()
        manager.timers = set()
        manager.clear_all_timers = mock.Mock()
        manager.__exit__()
        glib.timeout_add_seconds.assert_called_once_with(30, manager.terminate)
        manager.clear_all_timers.assert_called_once_with()
        self.assertEqual(len(manager.timers), 1)

    @mock.patch('friends.service.dispatcher.persist_model')
    @mock.patch('friends.service.dispatcher.threading')
    @mock.patch('friends.service.dispatcher.GLib')
    def test_manage_timers_terminate(self, glib, thread, persist):
        manager = ManageTimers()
        manager.timers = set()
        thread.activeCount.return_value = 1
        manager.terminate()
        thread.activeCount.assert_called_once_with()
        persist.assert_called_once_with()
        glib.idle_add.assert_called_once_with(manager.callback)

    @mock.patch('friends.service.dispatcher.persist_model')
    @mock.patch('friends.service.dispatcher.threading')
    @mock.patch('friends.service.dispatcher.GLib')
    def test_manage_timers_dont_kill_threads(self, glib, thread, persist):
        manager = ManageTimers()
        manager.timers = set()
        manager.set_new_timer = mock.Mock()
        thread.activeCount.return_value = 10
        manager.terminate()
        thread.activeCount.assert_called_once_with()
        manager.set_new_timer.assert_called_once_with()
Exemple #6
0
class TestAccount(unittest.TestCase):
    """Test Account class."""
    def setUp(self):
        self.log_mock = LogMock('friends.utils.account')

        def connect_side_effect(signal, callback, account):
            # The account service provides a .connect method that connects a
            # signal to a callback.  We have to mock a side effect into the
            # connect() method to record this connection, which some of the
            # tests can then call.
            self._callback_signal = signal
            self._callback = callback
            self._callback_account = account

        # Set up the mock to return some useful values in the API expected by
        # the Account constructor.
        self.account_service = mock.Mock(
            **{
                'get_auth_data.return_value':
                mock.Mock(
                    **{
                        'get_credentials_id.return_value': 'fake credentials',
                        'get_method.return_value': 'fake method',
                        'get_mechanism.return_value': 'fake mechanism',
                        'get_parameters.return_value': {
                            'ConsumerKey': 'fake_key',
                            'ConsumerSecret': 'fake_secret'
                        },
                    }),
                'get_account.return_value':
                mock.Mock(
                    **{
                        'get_settings_dict.return_value': dict(
                            send_enabled=True),
                        'id': 'fake_id',
                        'get_provider_name.return_value': 'flickr',
                    }),
                'get_service.return_value':
                mock.Mock(**{
                    'get_name.return_value': 'fake_service',
                }),
                'connect.side_effect':
                connect_side_effect,
            })
        self.account = Account(self.account_service)

    def tearDown(self):
        self.log_mock.stop()

    def test_account_auth(self):
        # Test that the constructor initializes the 'auth' attribute.
        auth = self.account.auth
        self.assertEqual(auth.get_credentials_id(), 'fake credentials')
        self.assertEqual(auth.get_method(), 'fake method')
        self.assertEqual(auth.get_mechanism(), 'fake mechanism')
        self.assertEqual(
            auth.get_parameters(),
            dict(ConsumerKey='fake_key', ConsumerSecret='fake_secret'))

    def test_account_id(self):
        self.assertEqual(self.account.id, 'fake_id')

    def test_account_service(self):
        # The protocol attribute refers directly to the protocol used.
        self.assertIsInstance(self.account.protocol, Flickr)

    def test_account_unsupported(self):
        # Unsupported protocols raise exceptions in the Account constructor.
        mock = self.account_service.get_account()
        mock.get_provider_name.return_value = 'no service'
        with self.assertRaises(UnsupportedProtocolError) as cm:
            Account(self.account_service)
        self.assertEqual(cm.exception.protocol, 'no service')

    def test_on_account_changed(self):
        # Account.on_account_changed() gets called during the Account
        # constructor.  Test that it has the expected original key value.
        self.assertEqual(self.account.send_enabled, True)

    def test_dict_filter(self):
        # The get_settings_dict() filters everything that doesn't start with
        # 'friends/'
        self._callback_account.get_settings_dict.assert_called_with('friends/')

    def test_on_account_changed_signal(self):
        # Test that when the account changes, and a 'changed' signal is
        # received, the callback is called and the account is updated.
        #
        # Start by simulating a change in the account service.
        other_dict = dict(
            send_enabled=False,
            bee='two',
            cat='three',
        )
        adict = self.account_service.get_account().get_settings_dict
        adict.return_value = other_dict
        # Check that the signal has been connected.
        self.assertEqual(self._callback_signal, 'changed')
        # Check that the account is the object we expect it to be.
        self.assertEqual(self._callback_account,
                         self.account_service.get_account())
        # Simulate the signal.
        self._callback(self.account_service, self._callback_account)
        # Have the expected updates occurred?
        self.assertEqual(self.account.send_enabled, False)
        self.assertFalse(hasattr(self.account, 'bee'))
        self.assertFalse(hasattr(self.account, 'cat'))

    @mock.patch('friends.utils.account.Account._on_account_changed')
    @mock.patch('friends.utils.account.protocol_manager')
    def test_account_consumer_key(self, *mocks):
        account_service = mock.Mock()
        account_service.get_auth_data().get_parameters.return_value = (dict(
            ConsumerKey='key', ConsumerSecret='secret'))
        acct = Account(account_service)
        self.assertEqual(acct.consumer_key, 'key')
        self.assertEqual(acct.consumer_secret, 'secret')

    @mock.patch('friends.utils.account.Account._on_account_changed')
    @mock.patch('friends.utils.account.protocol_manager')
    def test_account_client_id_sohu_style(self, *mocks):
        account_service = mock.Mock()
        account_service.get_auth_data().get_parameters.return_value = (dict(
            ClientId='key', ClientSecret='secret'))
        acct = Account(account_service)
        self.assertEqual(acct.consumer_key, 'key')
        self.assertEqual(acct.consumer_secret, 'secret')

    @mock.patch('friends.utils.account.manager')
    @mock.patch('friends.utils.account.Account')
    @mock.patch('friends.utils.account.Accounts')
    def test_find_accounts(self, accts, acct, manager):
        service = mock.Mock()
        get_enabled = manager.get_enabled_account_services
        get_enabled.return_value = [service]
        manager.reset_mock()
        accounts = _find_accounts_uoa()
        get_enabled.assert_called_once_with()
        acct.assert_called_once_with(service)
        self.assertEqual(accounts, {acct().id: acct()})
        self.assertEqual(
            self.log_mock.empty(), 'Flickr (fake_id) got send_enabled: True\n'
            'Accounts found: 1\n')
class TestDownloader(unittest.TestCase):
    """Test the downloading utilities."""
    def setUp(self):
        self.log_mock = LogMock('friends.utils.http')

    def tearDown(self):
        self.log_mock.stop()

    @classmethod
    def setUpClass(cls):
        cls.server = make_server('', 9180, _app, handler_class=_SilentHandler)
        cls.thread = threading.Thread(target=cls.server.serve_forever)
        cls.thread.start()
        # Wait until the server is responding.
        until = datetime.datetime.now() + datetime.timedelta(seconds=30)
        while datetime.datetime.now() < until:
            try:
                with urlopen('http://localhost:9180/ping'):
                    pass
            except URLError:
                time.sleep(0.1)
            else:
                break
        else:
            raise RuntimeError('Server thread did not start up.')

    @classmethod
    def tearDownClass(cls):
        cls.server.shutdown()
        cls.thread.join()

    def test_simple_json_download(self):
        # Test simple downloading of JSON data.
        self.assertEqual(
            Downloader('http://*****:*****@mock.patch('friends.utils.http._soup', mock.Mock())
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'json-utf-8.dat',
                                'utf-8'))
    def test_json_explicit_utf_8(self):
        # RFC 4627 $3 with explicit charset=utf-8.
        self.assertEqual(
            Downloader('http://example.com').get_json(), dict(yes='ÑØ'))

    @mock.patch('friends.utils.http._soup', mock.Mock())
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'json-utf-8.dat', None))
    def test_json_implicit_utf_8(self):
        # RFC 4627 $3 with implicit charset=utf-8.
        self.assertEqual(
            Downloader('http://example.com').get_json(), dict(yes='ÑØ'))

    @mock.patch('friends.utils.http._soup', mock.Mock())
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'json-utf-16le.dat',
                                None))
    def test_json_implicit_utf_16le(self):
        # RFC 4627 $3 with implicit charset=utf-16le.
        self.assertEqual(
            Downloader('http://example.com').get_json(), dict(yes='ÑØ'))

    @mock.patch('friends.utils.http._soup', mock.Mock())
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'json-utf-16be.dat',
                                None))
    def test_json_implicit_utf_16be(self):
        # RFC 4627 $3 with implicit charset=utf-16be.
        self.assertEqual(
            Downloader('http://example.com').get_json(), dict(yes='ÑØ'))

    @mock.patch('friends.utils.http._soup', mock.Mock())
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'json-utf-32le.dat',
                                None))
    def test_json_implicit_utf_32le(self):
        # RFC 4627 $3 with implicit charset=utf-32le.
        self.assertEqual(
            Downloader('http://example.com').get_json(), dict(yes='ÑØ'))

    @mock.patch('friends.utils.http._soup', mock.Mock())
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'json-utf-32be.dat',
                                None))
    def test_json_implicit_utf_32be(self):
        # RFC 4627 $3 with implicit charset=utf-32be.
        self.assertEqual(
            Downloader('http://example.com').get_json(), dict(yes='ÑØ'))

    def test_simple_text_download(self):
        # Test simple downloading of text data.
        self.assertEqual(
            Downloader('http://*****:*****@mock.patch('friends.utils.http.Soup')
    @mock.patch('friends.utils.http._soup')
    def test_upload_happens_only_once(self, _soupmock, Soupmock):
        filename = resource_filename('friends.tests.data', 'ubuntu.png')
        Uploader(
            'http://localhost:9180/mirror',
            'file://' + filename,
            'Great logo!',
            picture_key='source',
            desc_key='message',
            foo='bar',
        ).get_bytes()
        _soupmock.send_message.assert_called_once_with(
            Soupmock.form_request_new_from_multipart())
Exemple #8
0
class TestModel(unittest.TestCase):
    """Test our Dee.SharedModel instance."""
    def setUp(self):
        self.log_mock = LogMock('friends.utils.model')

    def tearDown(self):
        self.log_mock.stop()

    @mock.patch('friends.utils.model.Model')
    def test_persist_model(self, model):
        model.__len__.return_value = 500
        model.is_synchronized.return_value = True
        persist_model()
        model.is_synchronized.assert_called_once_with()
        model.flush_revision_queue.assert_called_once_with()
        self.assertEqual(
            self.log_mock.empty(),
            'Trying to save Dee.SharedModel with 500 rows.\n' +
            'Saving Dee.SharedModel with 500 rows.\n')

    @mock.patch('friends.utils.model.Model')
    @mock.patch('friends.utils.model.persist_model')
    def test_prune_one(self, persist, model):
        model.get_n_rows.return_value = 8001

        def side_effect(arg):
            model.get_n_rows.return_value -= 1

        model.remove.side_effect = side_effect
        prune_model(8000)
        persist.assert_called_once_with()
        model.get_first_iter.assert_called_once_with()
        model.remove.assert_called_once_with(model.get_first_iter())
        self.assertEqual(self.log_mock.empty(),
                         'Deleted 1 rows from Dee.SharedModel.\n')

    @mock.patch('friends.utils.model.Model')
    @mock.patch('friends.utils.model.persist_model')
    def test_prune_one_hundred(self, persist, model):
        model.get_n_rows.return_value = 8100

        def side_effect(arg):
            model.get_n_rows.return_value -= 1

        model.remove.side_effect = side_effect
        prune_model(8000)
        persist.assert_called_once_with()
        self.assertEqual(model.get_first_iter.call_count, 100)
        model.remove.assert_called_with(model.get_first_iter())
        self.assertEqual(model.remove.call_count, 100)
        self.assertEqual(self.log_mock.empty(),
                         'Deleted 100 rows from Dee.SharedModel.\n')

    @mock.patch('friends.utils.model.Model')
    @mock.patch('friends.utils.model.persist_model')
    def test_prune_none(self, persist, model):
        model.get_n_rows.return_value = 100

        def side_effect(arg):
            model.get_n_rows.return_value -= 1

        model.remove.side_effect = side_effect
        prune_model(8000)
        model.get_n_rows.assert_called_once_with()
        self.assertFalse(persist.called)
        self.assertFalse(model.get_first_iter.called)
        self.assertFalse(model.remove.called)
        self.assertEqual(self.log_mock.empty(), '')
class TestTwitter(unittest.TestCase):
    """Test the Twitter API."""
    def setUp(self):
        self._temp_cache = tempfile.mkdtemp()
        self._root = JsonCache._root = os.path.join(self._temp_cache,
                                                    '{}.json')
        TestModel.clear()
        self.account = FakeAccount()
        self.protocol = Twitter(self.account)
        self.log_mock = LogMock('friends.utils.base',
                                'friends.protocols.twitter')

    def tearDown(self):
        # Ensure that any log entries we haven't tested just get consumed so
        # as to isolate out test logger from other tests.
        self.log_mock.stop()
        shutil.rmtree(self._temp_cache)

    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
    @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
    @mock.patch('friends.protocols.twitter.Downloader.get_json',
                return_value=None)
    def test_unsuccessful_authentication(self, dload, login, *mocks):
        self.assertRaises(AuthorizationError, self.protocol._login)
        self.assertIsNone(self.account.user_name)
        self.assertIsNone(self.account.user_id)

    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    @mock.patch('friends.utils.authentication.Authentication.__init__',
                return_value=None)
    @mock.patch('friends.utils.authentication.Authentication.login',
                return_value=dict(AccessToken='some clever fake data',
                                  TokenSecret='sssssshhh!',
                                  UserId='1234',
                                  ScreenName='stephenfry'))
    def test_successful_authentication(self, *mocks):
        self.assertTrue(self.protocol._login())
        self.assertEqual(self.account.user_name, 'stephenfry')
        self.assertEqual(self.account.user_id, '1234')
        self.assertEqual(self.account.access_token, 'some clever fake data')
        self.assertEqual(self.account.secret_token, 'sssssshhh!')

    @mock.patch('friends.protocols.twitter.Downloader')
    @mock.patch('oauthlib.oauth1.rfc5849.generate_nonce',
                lambda: 'once upon a nonce')
    @mock.patch('oauthlib.oauth1.rfc5849.generate_timestamp',
                lambda: '1348690628')
    def test_signatures(self, dload):
        self.account.secret_token = 'alpha'
        self.account.access_token = 'omega'
        self.account.consumer_secret = 'obey'
        self.account.consumer_key = 'consume'
        self.account.auth.get_credentials_id = lambda *ignore: 6
        self.account.auth.get_method = lambda *ignore: 'oauth2'
        self.account.auth.get_mechanism = lambda *ignore: 'HMAC-SHA1'

        result = '''\
OAuth oauth_nonce="once%20upon%20a%20nonce", \
oauth_timestamp="1348690628", \
oauth_version="1.0", \
oauth_signature_method="HMAC-SHA1", \
oauth_consumer_key="consume", \
oauth_token="omega", \
oauth_signature="klnMTp3hH%2Fl3b5%2BmPtBlv%2BCulic%3D"'''

        self.protocol._rate_limiter = 'limits'

        class fake:
            def get_json():
                return None

        dload.return_value = fake
        self.protocol._get_url('http://example.com')
        dload.assert_called_once_with('http://example.com',
                                      headers=dict(Authorization=result),
                                      rate_limiter='limits',
                                      params=None,
                                      method='GET')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'twitter-home.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_home(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(self.protocol.home(), 3)
        self.assertEqual(3, TestModel.get_n_rows())

        # This test data was ripped directly from Twitter's API docs.
        expected = [
            [
                'twitter',
                88,
                '240558470661799936',
                'messages',
                'OAuth Dancer',
                '119476949',
                'oauth_dancer',
                False,
                '2012-08-28T21:16:23Z',
                'just another test',
                'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg',
                'https://twitter.com/oauth_dancer/status/240558470661799936',
                0,
                False,
                '',
                '',
                '',
                '',
                '',
                '',
                '',
                0.0,
                0.0,
            ],
            [
                'twitter',
                88,
                '240556426106372096',
                'images',
                'Raffi Krikorian',
                '8285392',
                'raffi',
                False,
                '2012-08-28T21:08:15Z',
                'lecturing at the "analyzing big data '
                'with twitter" class at <a href="https://twitter.com/Cal">@Cal</a>'
                ' with <a href="https://twitter.com/othman">@othman</a> '
                '<a href="http://twitter.com/yunorno/status/114080493036773378/photo/1">'
                'pic.twitter.com/rJC5Pxsu</a>',
                'https://si0.twimg.com/profile_images/1270234259/'
                'raffi-headshot-casual.png',
                'https://twitter.com/raffi/status/240556426106372096',
                0,
                False,
                'http://p.twimg.com/AZVLmp-CIAAbkyy.jpg',
                '',
                '',
                '',
                '',
                '',
                '',
                0.0,
                0.0,
            ],
            [
                'twitter',
                88,
                '240539141056638977',
                'messages',
                'Taylor Singletary',
                '819797',
                'episod',
                False,
                '2012-08-28T19:59:34Z',
                'You\'d be right more often if you thought you were wrong.',
                'https://si0.twimg.com/profile_images/2546730059/'
                'f6a8zq58mg1hn0ha8vie.jpeg',
                'https://twitter.com/episod/status/240539141056638977',
                0,
                False,
                '',
                '',
                '',
                '',
                '',
                '',
                '',
                0.0,
                0.0,
            ],
        ]
        for i, expected_row in enumerate(expected):
            self.assertEqual(list(TestModel.get_row(i)), expected_row)

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'twitter-home.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_home_since_id(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.assertEqual(self.protocol.home(), 3)

        with open(self._root.format('twitter_ids'), 'r') as fd:
            self.assertEqual(fd.read(), '{"messages": 240558470661799936}')

        get_url = self.protocol._get_url = mock.Mock()
        get_url.return_value = []
        self.assertEqual(self.protocol.home(), 3)
        get_url.assert_called_once_with(
            'https://api.twitter.com/1.1/statuses/' +
            'home_timeline.json?count=50&since_id=240558470661799936')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'twitter-send.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_from_me(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.account.user_name = 'oauth_dancer'
        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(
            self.protocol.send('some message'),
            'https://twitter.com/oauth_dancer/status/240558470661799936')
        self.assertEqual(1, TestModel.get_n_rows())

        # This test data was ripped directly from Twitter's API docs.
        expected_row = [
            'twitter',
            88,
            '240558470661799936',
            'messages',
            'OAuth Dancer',
            '119476949',
            'oauth_dancer',
            True,
            '2012-08-28T21:16:23Z',
            'just another test',
            'https://si0.twimg.com/profile_images/730275945/oauth-dancer.jpg',
            'https://twitter.com/oauth_dancer/status/240558470661799936',
            0,
            False,
            '',
            '',
            '',
            '',
            '',
            '',
            '',
            0.0,
            0.0,
        ]
        self.assertEqual(list(TestModel.get_row(0)), expected_row)

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_home_url(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.home(), 0)

        publish.assert_called_with('tweet')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/statuses/home_timeline.json?count=50')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_mentions(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.mentions(), 0)

        publish.assert_called_with('tweet', stream='mentions')
        get_url.assert_called_with('https://api.twitter.com/1.1/statuses/' +
                                   'mentions_timeline.json?count=50')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_user(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.user(), 0)

        publish.assert_called_with('tweet', stream='messages')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name='
        )

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_list(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.list('some_list_id'), 0)

        publish.assert_called_with('tweet', stream='list/some_list_id')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/lists/statuses.json?list_id=some_list_id'
        )

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_lists(self):
        get_url = self.protocol._get_url = mock.Mock(
            return_value=[dict(id_str='twitlist')])
        publish = self.protocol.list = mock.Mock()

        self.assertEqual(self.protocol.lists(), 0)

        publish.assert_called_with('twitlist')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/lists/list.json')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_private(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.private(), 0)

        publish.assert_called_with('tweet', stream='private')
        self.assertEqual(get_url.mock_calls, [
            mock.call('https://api.twitter.com/1.1/' +
                      'direct_messages.json?count=50'),
            mock.call('https://api.twitter.com/1.1/' +
                      'direct_messages/sent.json?count=50')
        ])

    def test_private_avatars(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=[
            dict(
                created_at='Sun Nov 04 17:14:52 2012',
                text='Does my avatar show up?',
                id_str='1452456',
                sender=dict(
                    screen_name='some_guy',
                    name='Bob',
                    profile_image_url_https='https://example.com/bob.jpg',
                ),
            )
        ])
        publish = self.protocol._publish = mock.Mock()

        self.protocol.private()

        publish.assert_called_with(
            liked=False,
            sender='Bob',
            stream='private',
            url='https://twitter.com/some_guy/status/1452456',
            icon_uri='https://example.com/bob.jpg',
            link_picture='',
            sender_nick='some_guy',
            sender_id='',
            from_me=False,
            timestamp='2012-11-04T17:14:52Z',
            message='Does my avatar show up?',
            message_id='1452456')
        self.assertEqual(get_url.mock_calls, [
            mock.call('https://api.twitter.com/1.1/' +
                      'direct_messages.json?count=50'),
            mock.call('https://api.twitter.com/1.1/' +
                      'direct_messages/sent.json?count=50&since_id=1452456')
        ])

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_send_private(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._publish_tweet = mock.Mock(
            return_value='https://twitter.com/screen_name/status/tweet_id')

        self.assertEqual(
            self.protocol.send_private('pumpichank', 'Are you mocking me?'),
            'https://twitter.com/screen_name/status/tweet_id')

        publish.assert_called_with('tweet', stream='private')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/direct_messages/new.json',
            dict(text='Are you mocking me?', screen_name='pumpichank'))

    def test_failing_send_private(self):
        def fail(*ignore):
            raise HTTPError('url', 403, 'Forbidden', 'Forbidden', mock.Mock())

        with mock.patch.object(self.protocol, '_get_url', side_effect=fail):
            self.assertRaises(
                HTTPError,
                self.protocol.send_private,
                'pumpichank',
                'Are you mocking me?',
            )

    def test_send(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._publish_tweet = mock.Mock(
            return_value='https://twitter.com/u/status/id')

        self.assertEqual(self.protocol.send('Hello, twitterverse!'),
                         'https://twitter.com/u/status/id')

        publish.assert_called_with('tweet')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/statuses/update.json',
            dict(status='Hello, twitterverse!'))

    def test_send_thread(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._publish_tweet = mock.Mock(
            return_value='tweet permalink')

        self.assertEqual(
            self.protocol.send_thread(
                '1234',
                'Why yes, I would love to respond to your tweet @pumpichank!'),
            'tweet permalink')

        publish.assert_called_with('tweet', stream='reply_to/1234')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/statuses/update.json',
            dict(status='Why yes, I would love to respond to your '
                 'tweet @pumpichank!',
                 in_reply_to_status_id='1234'))

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'twitter-home.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_send_thread_prepend_nick(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(self.protocol.home(), 3)
        self.assertEqual(3, TestModel.get_n_rows())

        # If you forgot to @mention in your reply, we add it for you.
        get = self.protocol._get_url = mock.Mock()
        self.protocol._publish_tweet = mock.Mock()
        self.protocol.send_thread('240556426106372096',
                                  'Exciting and original response!')
        get.assert_called_once_with(
            'https://api.twitter.com/1.1/statuses/update.json',
            dict(status='@raffi Exciting and original response!',
                 in_reply_to_status_id='240556426106372096'))

        # If you remembered the @mention, we won't duplicate it.
        get.reset_mock()
        self.protocol.send_thread('240556426106372096',
                                  'You are the greatest, @raffi!')
        get.assert_called_once_with(
            'https://api.twitter.com/1.1/statuses/update.json',
            dict(status='You are the greatest, @raffi!',
                 in_reply_to_status_id='240556426106372096'))

    def test_delete(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._unpublish = mock.Mock()

        self.assertEqual(self.protocol.delete('1234'), '1234')

        publish.assert_called_with('1234')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/statuses/destroy/1234.json',
            dict(trim_user='******'))

    def test_retweet(self):
        tweet = dict(tweet='twit')
        get_url = self.protocol._get_url = mock.Mock(return_value=tweet)
        publish = self.protocol._publish_tweet = mock.Mock(
            return_value='tweet permalink')

        self.assertEqual(self.protocol.retweet('1234'), 'tweet permalink')

        publish.assert_called_with(tweet)
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/statuses/retweet/1234.json',
            dict(trim_user='******'))

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'twitter-retweet.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_retweet_with_data(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.account.user_name = 'therealrobru'
        self.account.auth.parameters = dict(ConsumerKey='key',
                                            ConsumerSecret='secret')
        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(
            self.protocol.retweet('240558470661799936'),
            'https://twitter.com/therealrobru/status/324220250889543682')
        self.assertEqual(1, TestModel.get_n_rows())

        self.maxDiff = None
        expected_row = [
            'twitter',
            88,
            '324220250889543682',
            'messages',
            'Robert Bruce',
            '836242932',
            'therealrobru',
            True,
            '2013-04-16T17:58:26Z',
            'RT <a href="https://twitter.com/tarek_ziade">@tarek_ziade</a>: Just found a "Notification '
            'of Inspection" card in the bottom of my bag. looks like they were '
            'curious about those raspberry-pi :O',
            'https://si0.twimg.com/profile_images/2631306428/'
            '2a509db8a05b4310394b832d34a137a4.png',
            'https://twitter.com/therealrobru/status/324220250889543682',
            0,
            False,
            '',
            '',
            '',
            '',
            '',
            '',
            '',
            0.0,
            0.0,
        ]
        self.assertEqual(list(TestModel.get_row(0)), expected_row)

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data',
                                'twitter-multiple-links.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_multiple_links(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.account.user_name = 'Independent'
        self.account.auth.parameters = dict(ConsumerKey='key',
                                            ConsumerSecret='secret')

        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(
            self.protocol.send('some message'),
            'https://twitter.com/Independent/status/426318539796930560')
        self.assertEqual(1, TestModel.get_n_rows())

        self.maxDiff = None
        expected_row = [
            'twitter',
            88,
            '426318539796930560',
            'messages',
            'The Independent',
            '16973333',
            'Independent',
            True,
            '2014-01-23T11:40:35Z',
            'An old people\'s home has recreated famous movie scenes for a wonderful calendar '
            '<a href="http://ind.pn/1g3wX9q">ind.pn/1g3wX9q</a> '
            '<a href="http://twitter.com/Independent/status/426318539796930560/photo/1">pic.twitter.com/JxQTPG7WLL</a>',
            'https://pbs.twimg.com/profile_images/378800000706113664/d1a957578723e496c025be1e2577d06d.jpeg',
            'https://twitter.com/Independent/status/426318539796930560',
            0,
            False,
            'http://pbs.twimg.com/media/BeqWc_-CIAAhmdc.jpg',
            '',
            '',
            '',
            '',
            '',
            '',
            0.0,
            0.0,
        ]
        self.assertEqual(list(TestModel.get_row(0)), expected_row)

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'twitter-hashtags.dat'))
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_multiple_links(self, *mocks):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        self.account.user_name = 'Independent'
        self.account.auth.parameters = dict(ConsumerKey='key',
                                            ConsumerSecret='secret')

        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(
            self.protocol.send('some message'),
            'https://twitter.com/Kai_Mast/status/424185261375766530')
        self.assertEqual(1, TestModel.get_n_rows())

        self.maxDiff = None
        expected_row = [
            'twitter',
            88,
            '424185261375766530',
            'messages',
            'Kai Mast',
            '17339829',
            'Kai_Mast',
            False,
            '2014-01-17T14:23:41Z',
            'A service that filters food pictures from Instagram. '
            '<a href="https://twitter.com/search?q=%23MillionDollarIdea&src=hash">#MillionDollarIdea</a>',
            'https://pbs.twimg.com/profile_images/424181800701673473/Q6Ggqg7P.png',
            'https://twitter.com/Kai_Mast/status/424185261375766530',
            0,
            False,
            '',
            '',
            '',
            '',
            '',
            '',
            '',
            0.0,
            0.0,
        ]
        self.assertEqual(list(TestModel.get_row(0)), expected_row)

    def test_unfollow(self):
        get_url = self.protocol._get_url = mock.Mock()

        self.assertEqual(self.protocol.unfollow('pumpichank'), 'pumpichank')

        get_url.assert_called_with(
            'https://api.twitter.com/1.1/friendships/destroy.json',
            dict(screen_name='pumpichank'))

    def test_follow(self):
        get_url = self.protocol._get_url = mock.Mock()

        self.assertEqual(self.protocol.follow('pumpichank'), 'pumpichank')

        get_url.assert_called_with(
            'https://api.twitter.com/1.1/friendships/create.json',
            dict(screen_name='pumpichank', follow='true'))

    def test_like(self):
        get_url = self.protocol._get_url = mock.Mock()
        inc_cell = self.protocol._inc_cell = mock.Mock()
        set_cell = self.protocol._set_cell = mock.Mock()

        self.assertEqual(self.protocol.like('1234'), '1234')

        inc_cell.assert_called_once_with('1234', 'likes')
        set_cell.assert_called_once_with('1234', 'liked', True)
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/favorites/create.json',
            dict(id='1234'))

    def test_unlike(self):
        get_url = self.protocol._get_url = mock.Mock()
        dec_cell = self.protocol._dec_cell = mock.Mock()
        set_cell = self.protocol._set_cell = mock.Mock()

        self.assertEqual(self.protocol.unlike('1234'), '1234')

        dec_cell.assert_called_once_with('1234', 'likes')
        set_cell.assert_called_once_with('1234', 'liked', False)
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/favorites/destroy.json',
            dict(id='1234'))

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_tag(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=dict(
            statuses=['tweet']))
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.tag('yegbike'), 0)

        publish.assert_called_with('tweet', stream='search/#yegbike')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/search/tweets.json?q=%23yegbike')

        self.assertEqual(self.protocol.tag('#yegbike'), 0)

        publish.assert_called_with('tweet', stream='search/#yegbike')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/search/tweets.json?q=%23yegbike')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_search(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=dict(
            statuses=['tweet']))
        publish = self.protocol._publish_tweet = mock.Mock()

        self.assertEqual(self.protocol.search('hello'), 0)

        publish.assert_called_with('tweet', stream='search/hello')
        get_url.assert_called_with(
            'https://api.twitter.com/1.1/search/tweets.json?q=hello')

    @mock.patch('friends.protocols.twitter.time.sleep')
    def test_rate_limiter_first_time(self, sleep):
        # The first time we see a URL, there is no rate limiting.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data', 'twitter-home.dat')
        message.new('GET', 'http://example.com/')
        limiter.wait(message)
        sleep.assert_called_with(0)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_second_time(self, time, sleep):
        # The second time we see the URL, we get rate limited.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 1,
                                  })
        limiter.update(message.new('GET', 'http://example.com'))
        limiter.wait(message)
        sleep.assert_called_with(300)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_second_time_with_query(self, time, sleep):
        # A query parameter on the second request is ignored.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 1,
                                  })
        limiter.update(message.new('GET', 'http://example.com/foo?baz=7'))
        limiter.wait(message)
        sleep.assert_called_with(300)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_second_time_with_query_on_request(self, time, sleep):
        # A query parameter on the original request is ignored.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 1,
                                  })
        limiter.update(message.new('GET', 'http://example.com/foo?baz=7'))
        limiter.wait(message)
        sleep.assert_called_with(300)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_maximum(self, time, sleep):
        # With one remaining call this window, we get rate limited to the
        # full amount of the remaining window.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 1,
                                  })
        limiter.update(message.new('GET', 'http://example.com/alpha'))
        limiter.wait(message)
        sleep.assert_called_with(300)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_until_end_of_window(self, time, sleep):
        # With no remaining calls left this window, we wait until the end of
        # the window.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 0,
                                  })
        limiter.update(message.new('GET', 'http://example.com/alpha'))
        limiter.wait(message)
        sleep.assert_called_with(300)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_medium(self, time, sleep):
        # With a few calls remaining this window, we time slice the remaining
        # time evenly between those remaining calls.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 3,
                                  })
        limiter.update(message.new('GET', 'http://example.com/beta'))
        limiter.wait(message)
        sleep.assert_called_with(100.0)

    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_rate_limiter_unlimited(self, time, sleep):
        # With more than 5 calls remaining in this window, we don't rate
        # limit, even if we've already seen this url.
        limiter = RateLimiter()
        message = FakeSoupMessage('friends.tests.data',
                                  'twitter-home.dat',
                                  headers={
                                      'X-Rate-Limit-Reset': 1349382153 + 300,
                                      'X-Rate-Limit-Remaining': 10,
                                  })
        limiter.update(message.new('GET', 'http://example.com/omega'))
        limiter.wait(message)
        sleep.assert_called_with(0)

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.protocols.twitter.Twitter._login', return_value=True)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data',
                                'twitter-home.dat',
                                headers={
                                    'X-Rate-Limit-Reset': 1349382153 + 300,
                                    'X-Rate-Limit-Remaining': 3,
                                }))
    @mock.patch('friends.protocols.twitter.time.sleep')
    @mock.patch('friends.protocols.twitter.time.time', return_value=1349382153)
    def test_protocol_rate_limiting(self, time, sleep, login):
        self.account.access_token = 'access'
        self.account.secret_token = 'secret'
        # Test rate limiting via the Twitter plugin API.
        #
        # The first call doesn't get rate limited.
        self.protocol.home()
        sleep.assert_called_with(0)
        # Second call gets called with the established limit.  Because there
        # are three more calls allowed within the current window, and we're
        # reporting 300 seconds left in the current window, we're saying we'll
        # sleep 100 seconds between each call.
        self.protocol.home()
        sleep.assert_called_with(100.0)

    def test_contacts(self):
        get = self.protocol._get_url = mock.Mock(
            return_value=dict(ids=[1, 2], name='Bob', screen_name='bobby'))
        prev = self.protocol._previously_stored_contact = mock.Mock(
            return_value=False)
        push = self.protocol._push_to_eds = mock.Mock()
        self.assertEqual(self.protocol.contacts(), 2)
        self.assertEqual(get.call_args_list, [
            mock.call('https://api.twitter.com/1.1/friends/ids.json'),
            mock.call(
                url='https://api.twitter.com/1.1/users/show.json?user_id=1'),
            mock.call(
                url='https://api.twitter.com/1.1/users/show.json?user_id=2')
        ])
        self.assertEqual(prev.call_args_list, [mock.call('1'), mock.call('2')])
        self.assertEqual(push.call_args_list, [
            mock.call(link='https://twitter.com/bobby',
                      uid='1',
                      name='Bob',
                      nick='bobby'),
            mock.call(link='https://twitter.com/bobby',
                      uid='2',
                      name='Bob',
                      nick='bobby')
        ])
Exemple #10
0
class TestIdentica(unittest.TestCase):
    """Test the Identica API."""

    def setUp(self):
        self._temp_cache = tempfile.mkdtemp()
        self._root = JsonCache._root = os.path.join(
            self._temp_cache, '{}.json')
        self.account = FakeAccount()
        self.protocol = Identica(self.account)
        self.log_mock = LogMock('friends.utils.base',
                                'friends.protocols.twitter')

    def tearDown(self):
        # Ensure that any log entries we haven't tested just get consumed so
        # as to isolate out test logger from other tests.
        self.log_mock.stop()
        shutil.rmtree(self._temp_cache)

    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
    @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
    @mock.patch('friends.utils.http.Downloader.get_json',
                return_value=None)
    def test_unsuccessful_authentication(self, *mocks):
        self.assertRaises(AuthorizationError, self.protocol._login)
        self.assertIsNone(self.account.user_name)
        self.assertIsNone(self.account.user_id)

    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    @mock.patch('friends.utils.authentication.Authentication.__init__',
                return_value=None)
    @mock.patch('friends.utils.authentication.Authentication.login',
                return_value=dict(AccessToken='some clever fake data',
                                  TokenSecret='sssssshhh!'))
    def test_successful_authentication(self, *mocks):
        get_url = self.protocol._get_url = mock.Mock(
            return_value=dict(id='1234', screen_name='therealrobru'))
        self.assertTrue(self.protocol._login())
        self.assertEqual(self.account.user_name, 'therealrobru')
        self.assertEqual(self.account.user_id, '1234')
        self.assertEqual(self.account.access_token, 'some clever fake data')
        self.assertEqual(self.account.secret_token, 'sssssshhh!')
        get_url.assert_called_once_with('http://identi.ca/api/users/show.json')

    def test_mentions(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.mentions()

        publish.assert_called_with('tweet', stream='mentions')
        get_url.assert_called_with(
            'http://identi.ca/api/statuses/mentions.json?count=50')

    def test_user(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.user()

        publish.assert_called_with('tweet', stream='messages')
        get_url.assert_called_with(
            'http://identi.ca/api/statuses/user_timeline.json?screen_name=')

    def test_list(self):
        self.protocol._get_url = mock.Mock(return_value=['tweet'])
        self.protocol._publish_tweet = mock.Mock()
        self.assertRaises(NotImplementedError,
                          self.protocol.list, 'some_list_id')

    def test_lists(self):
        self.protocol._get_url = mock.Mock(
            return_value=[dict(id_str='twitlist')])
        self.protocol.list = mock.Mock()
        self.assertRaises(NotImplementedError, self.protocol.lists)

    def test_private(self):
        get_url = self.protocol._get_url = mock.Mock(return_value=['tweet'])
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.private()

        publish.assert_called_with('tweet', stream='private')
        self.assertEqual(
            get_url.mock_calls,
            [mock.call('http://identi.ca/api/direct_messages.json?count=50'),
             mock.call('http://identi.ca/api/direct_messages' +
                       '/sent.json?count=50')])

    def test_send_private(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.send_private('pumpichank', 'Are you mocking me?')

        publish.assert_called_with('tweet', stream='private')
        get_url.assert_called_with(
            'http://identi.ca/api/direct_messages/new.json',
            dict(text='Are you mocking me?', screen_name='pumpichank'))

    def test_send(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.send('Hello, twitterverse!')

        publish.assert_called_with('tweet')
        get_url.assert_called_with(
            'http://identi.ca/api/statuses/update.json',
            dict(status='Hello, twitterverse!'))

    def test_send_thread(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.send_thread(
            '1234',
            'Why yes, I would love to respond to your tweet @pumpichank!')

        publish.assert_called_with('tweet', stream='reply_to/1234')
        get_url.assert_called_with(
            'http://identi.ca/api/statuses/update.json',
            dict(status=
                 'Why yes, I would love to respond to your tweet @pumpichank!',
                 in_reply_to_status_id='1234'))

    def test_delete(self):
        get_url = self.protocol._get_url = mock.Mock(return_value='tweet')
        publish = self.protocol._unpublish = mock.Mock()

        self.protocol.delete('1234')

        publish.assert_called_with('1234')
        get_url.assert_called_with(
            'http://identi.ca/api/statuses/destroy/1234.json',
            dict(trim_user='******'))

    def test_retweet(self):
        tweet=dict(tweet='twit')
        get_url = self.protocol._get_url = mock.Mock(return_value=tweet)
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.retweet('1234')

        publish.assert_called_with(tweet)
        get_url.assert_called_with(
            'http://identi.ca/api/statuses/retweet/1234.json',
            dict(trim_user='******'))

    def test_unfollow(self):
        get_url = self.protocol._get_url = mock.Mock()

        self.protocol.unfollow('pumpichank')

        get_url.assert_called_with(
            'http://identi.ca/api/friendships/destroy.json',
            dict(screen_name='pumpichank'))

    def test_follow(self):
        get_url = self.protocol._get_url = mock.Mock()

        self.protocol.follow('pumpichank')

        get_url.assert_called_with(
            'http://identi.ca/api/friendships/create.json',
            dict(screen_name='pumpichank', follow='true'))

    def test_tag(self):
        self.protocol._get_url = mock.Mock(
            return_value=dict(statuses=['tweet']))
        self.protocol._publish_tweet = mock.Mock()
        self.assertRaises(NotImplementedError, self.protocol.tag, 'hashtag')

    def test_search(self):
        get_url = self.protocol._get_url = mock.Mock(
            return_value=dict(results=['tweet']))
        publish = self.protocol._publish_tweet = mock.Mock()

        self.protocol.search('hello')

        publish.assert_called_with('tweet', stream='search/hello')
        get_url.assert_called_with(
            'http://identi.ca/api/search.json?q=hello')

    def test_like(self):
        get_url = self.protocol._get_url = mock.Mock()
        inc_cell = self.protocol._inc_cell = mock.Mock()
        set_cell = self.protocol._set_cell = mock.Mock()

        self.assertEqual(self.protocol.like('1234'), '1234')

        inc_cell.assert_called_once_with('1234', 'likes')
        set_cell.assert_called_once_with('1234', 'liked', True)
        get_url.assert_called_with(
            'http://identi.ca/api/favorites/create/1234.json',
            dict(id='1234'))

    def test_unlike(self):
        get_url = self.protocol._get_url = mock.Mock()
        dec_cell = self.protocol._dec_cell = mock.Mock()
        set_cell = self.protocol._set_cell = mock.Mock()

        self.assertEqual(self.protocol.unlike('1234'), '1234')

        dec_cell.assert_called_once_with('1234', 'likes')
        set_cell.assert_called_once_with('1234', 'liked', False)
        get_url.assert_called_with(
            'http://identi.ca/api/favorites/destroy/1234.json',
            dict(id='1234'))

    def test_contacts(self):
        get = self.protocol._get_url = mock.Mock(
            return_value=dict(ids=[1,2],name='Bob',screen_name='bobby'))
        prev = self.protocol._previously_stored_contact = mock.Mock(return_value=False)
        push = self.protocol._push_to_eds = mock.Mock()
        self.assertEqual(self.protocol.contacts(), 2)
        self.assertEqual(
            get.call_args_list,
            [mock.call('http://identi.ca/api/friends/ids.json'),
             mock.call(url='http://identi.ca/api/users/show.json?user_id=1'),
             mock.call(url='http://identi.ca/api/users/show.json?user_id=2')])
        self.assertEqual(
            prev.call_args_list,
            [mock.call('1'), mock.call('2')])
        self.assertEqual(
            push.call_args_list,
            [mock.call(link='https://identi.ca/bobby', nick='bobby',
                       uid='1', name='Bob'),
             mock.call(link='https://identi.ca/bobby', nick='bobby',
                       uid='2', name='Bob')])
class TestLinkedIn(unittest.TestCase):
    """Test the LinkedIn API."""
    def setUp(self):
        TestModel.clear()
        self.account = FakeAccount()
        self.protocol = LinkedIn(self.account)
        self.log_mock = LogMock('friends.utils.base',
                                'friends.protocols.linkedin')

    def tearDown(self):
        # Ensure that any log entries we haven't tested just get consumed so
        # as to isolate out test logger from other tests.
        self.log_mock.stop()

    def test_name_logic(self):
        self.assertEqual('', make_fullname())
        self.assertEqual('', make_fullname(irrelevant_key='foo'))
        self.assertEqual('Bob', make_fullname(**dict(firstName='Bob')))
        self.assertEqual('LastOnly',
                         make_fullname(**dict(lastName='LastOnly')))
        self.assertEqual(
            'Bob Loblaw',
            make_fullname(**dict(firstName='Bob', lastName='Loblaw')))
        self.assertEqual(
            'Bob Loblaw',
            make_fullname(
                **dict(firstName='Bob', lastName='Loblaw', extra='ignored')))

    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1)
    @mock.patch('friends.utils.authentication.Signon.AuthSession.new')
    @mock.patch('friends.protocols.linkedin.Downloader.get_json',
                return_value=None)
    def test_unsuccessful_authentication(self, dload, login, *mocks):
        self.assertRaises(AuthorizationError, self.protocol._login)
        self.assertIsNone(self.account.user_name)
        self.assertIsNone(self.account.user_id)

    @mock.patch('friends.utils.authentication.manager')
    @mock.patch('friends.utils.authentication.Accounts')
    @mock.patch('friends.utils.authentication.Authentication.__init__',
                return_value=None)
    @mock.patch('friends.utils.authentication.Authentication.login',
                return_value=dict(AccessToken='some clever fake data'))
    @mock.patch('friends.protocols.linkedin.Downloader.get_json',
                return_value=dict(id='blerch',
                                  firstName='Bob',
                                  lastName='Loblaw'))
    def test_successful_authentication(self, *mocks):
        self.assertTrue(self.protocol._login())
        self.assertEqual(self.account.user_name, 'Bob Loblaw')
        self.assertEqual(self.account.user_id, 'blerch')
        self.assertEqual(self.account.access_token, 'some clever fake data')

    @mock.patch('friends.utils.base.Model', TestModel)
    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data', 'linkedin_receive.json'))
    @mock.patch('friends.protocols.linkedin.LinkedIn._login',
                return_value=True)
    @mock.patch('friends.utils.base._seen_ids', {})
    def test_home(self, *mocks):
        self.account.access_token = 'access'
        self.assertEqual(0, TestModel.get_n_rows())
        self.assertEqual(self.protocol.home(), 1)
        self.assertEqual(1, TestModel.get_n_rows())
        self.maxDiff = None

        self.assertEqual(list(TestModel.get_row(0)), [
            'linkedin', 88, 'UNIU-73705-576270369559388-SHARE', 'messages',
            'Hobson L', 'ma0LLid', '', False, '2013-07-16T00:47:06Z',
            'I\'m looking forward to the Udacity Global meetup event here in '
            'Portland: <a href="http://lnkd.in/dh5MQz">http://lnkd.in/dh5MQz'
            '</a>\nGreat way to support the next big thing in c…',
            'http://m.c.lnkd.licdn.com/mpr/mprx/0_mVxsC0BnN52aqc24yWvoyA5haqc2Z'
            'wLCgzLv0EiBGp7n2jTwX-ls_dzgkSVIZu0',
            'https://www.linkedin.com/profile/view?id=7375&authType=name'
            '&authToken=-LNy&trk=api*a26127*s26893*', 1, False, '', '', '', '',
            '', '', '', 0.0, 0.0
        ])

    @mock.patch('friends.utils.http.Soup.Message',
                FakeSoupMessage('friends.tests.data',
                                'linkedin_contacts.json'))
    @mock.patch('friends.protocols.linkedin.LinkedIn._login',
                return_value=True)
    def test_contacts(self, *mocks):
        push = self.protocol._push_to_eds = mock.Mock()
        prev = self.protocol._previously_stored_contact = mock.Mock(
            return_value=False)
        token = self.protocol._get_access_token = mock.Mock(return_value='foo')
        self.protocol._create_contact = lambda arg: arg
        self.assertEqual(self.protocol.contacts(), 4)
        self.assertEqual(push.mock_calls, [
            mock.call(link='https://www.linkedin.com', name='H A', uid='IFDI'),
            mock.call(link='https://www.linkedin.com', name='C A', uid='AefF'),
            mock.call(link='https://www.linkedin.com', name='R A', uid='DFdV'),
            mock.call(link='https://www.linkedin.com', name='A Z', uid='xkBU')
        ])