Merge lp:~jpds/friends/linkedin-protocol into lp:friends
- linkedin-protocol
- Merge into trunk
Proposed by
Jonathan Davies
Status: | Superseded |
---|---|
Proposed branch: | lp:~jpds/friends/linkedin-protocol |
Merge into: | lp:friends |
Diff against target: |
1291 lines (+357/-306) (has conflicts) 20 files modified
Makefile (+1/-1) debian/changelog (+15/-2) debian/control (+6/-0) debian/friends-linkedin.install (+1/-0) friends/protocols/flickr.py (+3/-3) friends/protocols/linkedin.py (+133/-0) friends/service/dispatcher.py (+9/-8) friends/tests/mocks.py (+9/-4) friends/tests/test_account.py (+29/-107) friends/tests/test_authentication.py (+33/-39) friends/tests/test_dispatcher.py (+23/-24) friends/tests/test_facebook.py (+8/-5) friends/tests/test_flickr.py (+6/-2) friends/tests/test_foursquare.py (+4/-0) friends/tests/test_identica.py (+4/-0) friends/tests/test_twitter.py (+11/-21) friends/utils/account.py (+32/-71) friends/utils/authentication.py (+23/-10) friends/utils/base.py (+3/-3) tools/debug_live.py (+4/-6) Text conflict in debian/changelog |
To merge this branch: | bzr merge lp:~jpds/friends/linkedin-protocol |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Bruce Park | Needs Resubmitting | ||
Review via email: mp+155194@code.launchpad.net |
This proposal has been superseded by a proposal from 2013-03-25.
Commit message
Implemented a base LinkedIn protocol for friends.
Description of the change
Implemented a base LinkedIn protocol for friends.
To post a comment you must log in.
- 197. By Jonathan Davies
-
Gave url .get() a default value for robustness.
- 198. By Jonathan Davies
-
Save LinkedIn connections to EDS with _push_to_eds().
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2013-02-05 01:11:35 +0000 | |||
3 | +++ Makefile 2013-03-25 10:33:12 +0000 | |||
4 | @@ -14,7 +14,7 @@ | |||
5 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
6 | 15 | 15 | ||
7 | 16 | check: | 16 | check: |
9 | 17 | python3 -m unittest discover -vv | 17 | python3 -m unittest discover |
10 | 18 | 18 | ||
11 | 19 | install: | 19 | install: |
12 | 20 | python3 setup.py install | 20 | python3 setup.py install |
13 | 21 | 21 | ||
14 | === modified file 'debian/changelog' | |||
15 | --- debian/changelog 2013-03-22 05:02:25 +0000 | |||
16 | +++ debian/changelog 2013-03-25 10:33:12 +0000 | |||
17 | @@ -1,3 +1,4 @@ | |||
18 | 1 | <<<<<<< TREE | ||
19 | 1 | friends (0.1.3daily13.03.22-0ubuntu1) raring; urgency=low | 2 | friends (0.1.3daily13.03.22-0ubuntu1) raring; urgency=low |
20 | 2 | 3 | ||
21 | 3 | * Automatic snapshot from revision 168 | 4 | * Automatic snapshot from revision 168 |
22 | @@ -14,18 +15,30 @@ | |||
23 | 14 | 15 | ||
24 | 15 | -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 21 Mar 2013 05:02:17 +0000 | 16 | -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 21 Mar 2013 05:02:17 +0000 |
25 | 16 | 17 | ||
26 | 18 | ======= | ||
27 | 19 | friends (0.2.0-0ubuntu2) UNRELEASED; urgency=low | ||
28 | 20 | |||
29 | 21 | [ Robert Bruce Park ] | ||
30 | 22 | * Version bump for the next development series. | ||
31 | 23 | |||
32 | 24 | [ Jonathan Davies ] | ||
33 | 25 | * Added friends-linkedin. | ||
34 | 26 | |||
35 | 27 | -- Jonathan Davies <jonathan.davies@canonical.com> Thu, 21 Mar 2013 22:52:07 +0000 | ||
36 | 28 | |||
37 | 29 | >>>>>>> MERGE-SOURCE | ||
38 | 17 | friends (0.1.3daily13.03.20-0ubuntu1) raring; urgency=low | 30 | friends (0.1.3daily13.03.20-0ubuntu1) raring; urgency=low |
39 | 18 | 31 | ||
40 | 19 | [ Robert Bruce Park ] | 32 | [ Robert Bruce Park ] |
41 | 20 | * Keep the Dispatcher alive for 30s beyond the return of the final | 33 | * Keep the Dispatcher alive for 30s beyond the return of the final |
42 | 21 | method invocation. | 34 | method invocation. |
44 | 22 | * Stop deduplicating messages across protocols, simplifying model | 35 | * Stop deduplicating messages across protocols, simplifying model |
45 | 23 | schema (LP: #1156941) | 36 | schema (LP: #1156941) |
46 | 24 | * Add schema columns for latitude, longitude, and location name. | 37 | * Add schema columns for latitude, longitude, and location name. |
47 | 25 | * Fix 'likes' column from gdouble to guint64. | 38 | * Fix 'likes' column from gdouble to guint64. |
48 | 26 | * Add geotagging support from foursquare, facebook, flickr. | 39 | * Add geotagging support from foursquare, facebook, flickr. |
49 | 27 | * Implement since= for Facebook, reducing bandwidth usage. | 40 | * Implement since= for Facebook, reducing bandwidth usage. |
51 | 28 | * Automatically prepend the required @mention to Twitter | 41 | * Automatically prepend the required @mention to Twitter |
52 | 29 | replies (LP: #1156829) | 42 | replies (LP: #1156829) |
53 | 30 | * Automatically linkify URLs that get published to the model. | 43 | * Automatically linkify URLs that get published to the model. |
54 | 31 | * Fix the publishing of Facebook Stories (LP: #1155785) | 44 | * Fix the publishing of Facebook Stories (LP: #1155785) |
55 | 32 | 45 | ||
56 | === modified file 'debian/control' | |||
57 | --- debian/control 2013-02-22 00:06:42 +0000 | |||
58 | +++ debian/control 2013-03-25 10:33:12 +0000 | |||
59 | @@ -103,3 +103,9 @@ | |||
60 | 103 | Depends: friends, ${misc:Depends}, ${python3:Depends} | 103 | Depends: friends, ${misc:Depends}, ${python3:Depends} |
61 | 104 | Description: Social integration with the desktop - Flickr | 104 | Description: Social integration with the desktop - Flickr |
62 | 105 | Provides social networking integration with the desktop | 105 | Provides social networking integration with the desktop |
63 | 106 | |||
64 | 107 | Package: friends-linkedin | ||
65 | 108 | Architecture: all | ||
66 | 109 | Depends: friends, ${misc:Depends}, ${python3:Depends} | ||
67 | 110 | Description: Social integration with the desktop - LinkedIn | ||
68 | 111 | Provides social networking integration with the desktop | ||
69 | 106 | 112 | ||
70 | === added file 'debian/friends-linkedin.install' | |||
71 | --- debian/friends-linkedin.install 1970-01-01 00:00:00 +0000 | |||
72 | +++ debian/friends-linkedin.install 2013-03-25 10:33:12 +0000 | |||
73 | @@ -0,0 +1,1 @@ | |||
74 | 1 | usr/lib/python3/dist-packages/friends/protocols/linkedin* | ||
75 | 0 | 2 | ||
76 | === modified file 'friends/protocols/flickr.py' | |||
77 | --- friends/protocols/flickr.py 2013-03-12 21:08:21 +0000 | |||
78 | +++ friends/protocols/flickr.py 2013-03-25 10:33:12 +0000 | |||
79 | @@ -82,7 +82,7 @@ | |||
80 | 82 | # http://www.flickr.com/services/api/flickr.people.getInfo.html | 82 | # http://www.flickr.com/services/api/flickr.people.getInfo.html |
81 | 83 | def _get_avatar(self, nsid): | 83 | def _get_avatar(self, nsid): |
82 | 84 | args = dict( | 84 | args = dict( |
84 | 85 | api_key=self._account.auth.parameters.get('ConsumerKey'), | 85 | api_key=self._account.consumer_key, |
85 | 86 | method='flickr.people.getInfo', | 86 | method='flickr.people.getInfo', |
86 | 87 | format='json', | 87 | format='json', |
87 | 88 | nojsoncallback='1', | 88 | nojsoncallback='1', |
88 | @@ -109,7 +109,7 @@ | |||
89 | 109 | self._get_access_token() | 109 | self._get_access_token() |
90 | 110 | 110 | ||
91 | 111 | args = dict( | 111 | args = dict( |
93 | 112 | api_key=self._account.auth.parameters.get('ConsumerKey'), | 112 | api_key=self._account.consumer_key, |
94 | 113 | method='flickr.photos.getContactsPhotos', | 113 | method='flickr.photos.getContactsPhotos', |
95 | 114 | format='json', | 114 | format='json', |
96 | 115 | nojsoncallback='1', | 115 | nojsoncallback='1', |
97 | @@ -179,7 +179,7 @@ | |||
98 | 179 | self._get_access_token() | 179 | self._get_access_token() |
99 | 180 | 180 | ||
100 | 181 | args = dict( | 181 | args = dict( |
102 | 182 | api_key=self._account.auth.parameters.get('ConsumerKey'), | 182 | api_key=self._account.consumer_key, |
103 | 183 | title=title, | 183 | title=title, |
104 | 184 | ) | 184 | ) |
105 | 185 | 185 | ||
106 | 186 | 186 | ||
107 | === added file 'friends/protocols/linkedin.py' | |||
108 | --- friends/protocols/linkedin.py 1970-01-01 00:00:00 +0000 | |||
109 | +++ friends/protocols/linkedin.py 2013-03-25 10:33:12 +0000 | |||
110 | @@ -0,0 +1,133 @@ | |||
111 | 1 | # friends-dispatcher -- send & receive messages from any social network | ||
112 | 2 | # Copyright (C) 2013 Canonical Ltd | ||
113 | 3 | # | ||
114 | 4 | # This program is free software: you can redistribute it and/or modify | ||
115 | 5 | # it under the terms of the GNU General Public License as published by | ||
116 | 6 | # the Free Software Foundation, version 3 of the License. | ||
117 | 7 | # | ||
118 | 8 | # This program is distributed in the hope that it will be useful, | ||
119 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
120 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
121 | 11 | # GNU General Public License for more details. | ||
122 | 12 | # | ||
123 | 13 | # You should have received a copy of the GNU General Public License | ||
124 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
125 | 15 | |||
126 | 16 | """The LinkedIn protocol plugin.""" | ||
127 | 17 | |||
128 | 18 | |||
129 | 19 | __all__ = [ | ||
130 | 20 | 'LinkedIn', | ||
131 | 21 | ] | ||
132 | 22 | |||
133 | 23 | import time | ||
134 | 24 | import logging | ||
135 | 25 | |||
136 | 26 | from friends.utils.avatar import Avatar | ||
137 | 27 | from friends.utils.base import Base, feature | ||
138 | 28 | from friends.utils.cache import JsonCache | ||
139 | 29 | from friends.utils.http import Downloader, Uploader | ||
140 | 30 | from friends.utils.time import parsetime, iso8601utc | ||
141 | 31 | from friends.errors import FriendsError | ||
142 | 32 | |||
143 | 33 | log = logging.getLogger(__name__) | ||
144 | 34 | |||
145 | 35 | class LinkedIn(Base): | ||
146 | 36 | _api_base = 'https://api.linkedin.com/v1/{endpoint}?format=json&secure-urls=true&oauth2_access_token={token}' | ||
147 | 37 | |||
148 | 38 | def _whoami(self, authdata): | ||
149 | 39 | """Identify the authenticating user.""" | ||
150 | 40 | # http://developer.linkedin.com/documents/profile-fields | ||
151 | 41 | url = self._api_base.format( | ||
152 | 42 | endpoint='people/~:(id,first-name,last-name)', | ||
153 | 43 | token=self._get_access_token()) | ||
154 | 44 | result = Downloader(url).get_json() | ||
155 | 45 | self._account.user_id = result.get('id') | ||
156 | 46 | self._account.user_full_name = '{firstName} {lastName}'.format(**result) | ||
157 | 47 | |||
158 | 48 | def _publish_entry(self, entry, stream='messages'): | ||
159 | 49 | """Publish a single update into the Dee.SharedModel.""" | ||
160 | 50 | message_id = entry.get('updateKey') | ||
161 | 51 | |||
162 | 52 | if message_id is None: | ||
163 | 53 | # We can't do much with this entry. | ||
164 | 54 | return | ||
165 | 55 | |||
166 | 56 | content = entry.get('updateContent') | ||
167 | 57 | person = content.get('person') | ||
168 | 58 | name = '{firstName} {lastName}'.format(**person) | ||
169 | 59 | person_id = person.get('id') | ||
170 | 60 | status = person.get('currentStatus') | ||
171 | 61 | picture = person.get('pictureUrl', '') | ||
172 | 62 | url = person.get('siteStandardProfileRequest').get('url') | ||
173 | 63 | timestamp = entry.get('timestamp') | ||
174 | 64 | # We need to divide by 1000 here, as LinkedIn's timestamps have | ||
175 | 65 | # milliseconds. | ||
176 | 66 | iso_time = iso8601utc(int(timestamp/1000)) | ||
177 | 67 | |||
178 | 68 | # Posts gives us a likes dict, while replies give us an int. | ||
179 | 69 | likes = entry.get('numLikes', 0) | ||
180 | 70 | |||
181 | 71 | args = dict( | ||
182 | 72 | message_id=message_id, | ||
183 | 73 | stream=stream, | ||
184 | 74 | message=status, | ||
185 | 75 | likes=likes, | ||
186 | 76 | sender_id=person_id, | ||
187 | 77 | sender=name, | ||
188 | 78 | icon_uri=picture, | ||
189 | 79 | link_url=url, | ||
190 | 80 | timestamp=iso_time | ||
191 | 81 | ) | ||
192 | 82 | |||
193 | 83 | self._publish(**args) | ||
194 | 84 | |||
195 | 85 | @feature | ||
196 | 86 | def home(self): | ||
197 | 87 | """Gather and publish public timeline messages.""" | ||
198 | 88 | url = self._api_base.format( | ||
199 | 89 | endpoint='people/~/network/updates', | ||
200 | 90 | token=self._get_access_token()) + '&type=STAT' | ||
201 | 91 | result = Downloader(url).get_json() | ||
202 | 92 | values = result.get('values') | ||
203 | 93 | for update in values: | ||
204 | 94 | self._publish_entry(update) | ||
205 | 95 | |||
206 | 96 | @feature | ||
207 | 97 | def receive(self): | ||
208 | 98 | """Gather and publish all incoming messages.""" | ||
209 | 99 | self.home() | ||
210 | 100 | return self._get_n_rows() | ||
211 | 101 | |||
212 | 102 | def _create_contact(self, connection_json): | ||
213 | 103 | """Build a VCard based on a dict representation of a contact.""" | ||
214 | 104 | user_id = connection_json.get('id') | ||
215 | 105 | |||
216 | 106 | user_fullname = '{firstName} {lastName}'.format(**connection_json) | ||
217 | 107 | user_link = connection_json.get('siteStandardProfileRequest').get('url') | ||
218 | 108 | |||
219 | 109 | attrs = {} | ||
220 | 110 | attrs['linkined-id'] = user_id | ||
221 | 111 | attrs['linkedin-name'] = user_fullname | ||
222 | 112 | attrs['X-URIS'] = user_link | ||
223 | 113 | |||
224 | 114 | contact = Base._create_contact( | ||
225 | 115 | self, user_fullname, None, attrs) | ||
226 | 116 | |||
227 | 117 | return contact | ||
228 | 118 | |||
229 | 119 | @feature | ||
230 | 120 | def contacts(self): | ||
231 | 121 | """Retrieve a list of up to 500 LinkedIn connections.""" | ||
232 | 122 | # http://developer.linkedin.com/documents/connections-api | ||
233 | 123 | url = self._api_base.format( | ||
234 | 124 | endpoint='people/~/connections', | ||
235 | 125 | token=self._get_access_token()) | ||
236 | 126 | result = Downloader(url).get_json() | ||
237 | 127 | connections = result.get('values') | ||
238 | 128 | |||
239 | 129 | for connection in connections: | ||
240 | 130 | if connection.get('id') != 'private': | ||
241 | 131 | # We cannot access information on profiles that are set to | ||
242 | 132 | # private. | ||
243 | 133 | self._create_contact(connection) | ||
244 | 0 | 134 | ||
245 | === modified file 'friends/service/dispatcher.py' | |||
246 | --- friends/service/dispatcher.py 2013-03-20 01:19:39 +0000 | |||
247 | +++ friends/service/dispatcher.py 2013-03-25 10:33:12 +0000 | |||
248 | @@ -31,7 +31,7 @@ | |||
249 | 31 | from contextlib import ContextDecorator | 31 | from contextlib import ContextDecorator |
250 | 32 | 32 | ||
251 | 33 | from friends.utils.avatar import Avatar | 33 | from friends.utils.avatar import Avatar |
253 | 34 | from friends.utils.account import AccountManager | 34 | from friends.utils.account import find_accounts |
254 | 35 | from friends.utils.manager import protocol_manager | 35 | from friends.utils.manager import protocol_manager |
255 | 36 | from friends.utils.menus import MenuManager | 36 | from friends.utils.menus import MenuManager |
256 | 37 | from friends.utils.model import Model, persist_model | 37 | from friends.utils.model import Model, persist_model |
257 | @@ -96,7 +96,8 @@ | |||
258 | 96 | bus_name = dbus.service.BusName(DBUS_INTERFACE, bus=self.bus) | 96 | bus_name = dbus.service.BusName(DBUS_INTERFACE, bus=self.bus) |
259 | 97 | super().__init__(bus_name, self.__dbus_object_path__) | 97 | super().__init__(bus_name, self.__dbus_object_path__) |
260 | 98 | self.mainloop = mainloop | 98 | self.mainloop = mainloop |
262 | 99 | self.account_manager = AccountManager() | 99 | |
263 | 100 | self.accounts = find_accounts() | ||
264 | 100 | 101 | ||
265 | 101 | self._unread_count = 0 | 102 | self._unread_count = 0 |
266 | 102 | self.menu_manager = MenuManager(self.Refresh, self.mainloop.quit) | 103 | self.menu_manager = MenuManager(self.Refresh, self.mainloop.quit) |
267 | @@ -119,7 +120,7 @@ | |||
268 | 119 | # account.protocol() starts a new thread and then returns | 120 | # account.protocol() starts a new thread and then returns |
269 | 120 | # immediately, so there is no delay or blocking during the | 121 | # immediately, so there is no delay or blocking during the |
270 | 121 | # execution of this method. | 122 | # execution of this method. |
272 | 122 | for account in self.account_manager.get_all(): | 123 | for account in self.accounts.values(): |
273 | 123 | try: | 124 | try: |
274 | 124 | account.protocol('receive') | 125 | account.protocol('receive') |
275 | 125 | except NotImplementedError: | 126 | except NotImplementedError: |
276 | @@ -163,14 +164,14 @@ | |||
277 | 163 | service.Do('list', '6', 'list_id') # Fetch a single list. | 164 | service.Do('list', '6', 'list_id') # Fetch a single list. |
278 | 164 | """ | 165 | """ |
279 | 165 | if account_id: | 166 | if account_id: |
281 | 166 | accounts = [self.account_manager.get(account_id)] | 167 | accounts = [self.accounts.get(int(account_id))] |
282 | 167 | if None in accounts: | 168 | if None in accounts: |
283 | 168 | message = 'Could not find account: {}'.format(account_id) | 169 | message = 'Could not find account: {}'.format(account_id) |
284 | 169 | failure(message) | 170 | failure(message) |
285 | 170 | log.error(message) | 171 | log.error(message) |
286 | 171 | return | 172 | return |
287 | 172 | else: | 173 | else: |
289 | 173 | accounts = list(self.account_manager.get_all()) | 174 | accounts = list(self.accounts.values()) |
290 | 174 | 175 | ||
291 | 175 | called = False | 176 | called = False |
292 | 176 | for account in accounts: | 177 | for account in accounts: |
293 | @@ -205,7 +206,7 @@ | |||
294 | 205 | service.SendMessage('Your message') | 206 | service.SendMessage('Your message') |
295 | 206 | """ | 207 | """ |
296 | 207 | sent = False | 208 | sent = False |
298 | 208 | for account in self.account_manager.get_all(): | 209 | for account in self.accounts.values(): |
299 | 209 | if account.send_enabled: | 210 | if account.send_enabled: |
300 | 210 | sent = True | 211 | sent = True |
301 | 211 | log.debug( | 212 | log.debug( |
302 | @@ -240,7 +241,7 @@ | |||
303 | 240 | service.SendReply('6', '34245645347345626', 'Your reply') | 241 | service.SendReply('6', '34245645347345626', 'Your reply') |
304 | 241 | """ | 242 | """ |
305 | 242 | log.debug('Replying to {}, {}'.format(account_id, message_id)) | 243 | log.debug('Replying to {}, {}'.format(account_id, message_id)) |
307 | 243 | account = self.account_manager.get(account_id) | 244 | account = self.accounts.get(int(account_id)) |
308 | 244 | if account is not None: | 245 | if account is not None: |
309 | 245 | account.protocol( | 246 | account.protocol( |
310 | 246 | 'send_thread', | 247 | 'send_thread', |
311 | @@ -298,7 +299,7 @@ | |||
312 | 298 | free to ignore error conditions at your peril. | 299 | free to ignore error conditions at your peril. |
313 | 299 | """ | 300 | """ |
314 | 300 | log.debug('Uploading {} to {}'.format(uri, account_id)) | 301 | log.debug('Uploading {} to {}'.format(uri, account_id)) |
316 | 301 | account = self.account_manager.get(account_id) | 302 | account = self.accounts.get(int(account_id)) |
317 | 302 | if account is not None: | 303 | if account is not None: |
318 | 303 | account.protocol( | 304 | account.protocol( |
319 | 304 | 'upload', | 305 | 'upload', |
320 | 305 | 306 | ||
321 | === modified file 'friends/tests/mocks.py' | |||
322 | --- friends/tests/mocks.py 2013-03-14 19:44:13 +0000 | |||
323 | +++ friends/tests/mocks.py 2013-03-25 10:33:12 +0000 | |||
324 | @@ -100,16 +100,21 @@ | |||
325 | 100 | 100 | ||
326 | 101 | 101 | ||
327 | 102 | class FakeAuth: | 102 | class FakeAuth: |
332 | 103 | id = 'fakeauth id' | 103 | get_credentials_id = lambda *ignore: 'fakeauth id' |
333 | 104 | method = 'fakeauth method' | 104 | get_method = lambda *ignore: 'fakeauth method' |
334 | 105 | parameters = {'ConsumerKey': 'fake', 'ConsumerSecret': 'alsofake'} | 105 | get_mechanism = lambda *ignore: 'fakeauth mechanism' |
335 | 106 | mechanism = 'fakeauth mechanism' | 106 | get_parameters = lambda *ignore: { |
336 | 107 | 'ConsumerKey': 'fake', | ||
337 | 108 | 'ConsumerSecret': 'alsofake', | ||
338 | 109 | } | ||
339 | 107 | 110 | ||
340 | 108 | 111 | ||
341 | 109 | class FakeAccount: | 112 | class FakeAccount: |
342 | 110 | """A fake account object for testing purposes.""" | 113 | """A fake account object for testing purposes.""" |
343 | 111 | 114 | ||
344 | 112 | def __init__(self, service=None, account_id=88): | 115 | def __init__(self, service=None, account_id=88): |
345 | 116 | self.consumer_secret = 'secret' | ||
346 | 117 | self.consumer_key = 'consume' | ||
347 | 113 | self.access_token = None | 118 | self.access_token = None |
348 | 114 | self.secret_token = None | 119 | self.secret_token = None |
349 | 115 | self.user_full_name = None | 120 | self.user_full_name = None |
350 | 116 | 121 | ||
351 | === modified file 'friends/tests/test_account.py' | |||
352 | --- friends/tests/test_account.py 2013-03-14 19:14:03 +0000 | |||
353 | +++ friends/tests/test_account.py 2013-03-25 10:33:12 +0000 | |||
354 | @@ -17,7 +17,6 @@ | |||
355 | 17 | 17 | ||
356 | 18 | __all__ = [ | 18 | __all__ = [ |
357 | 19 | 'TestAccount', | 19 | 'TestAccount', |
358 | 20 | 'TestAccountManager', | ||
359 | 21 | ] | 20 | ] |
360 | 22 | 21 | ||
361 | 23 | 22 | ||
362 | @@ -26,14 +25,15 @@ | |||
363 | 26 | from friends.errors import UnsupportedProtocolError | 25 | from friends.errors import UnsupportedProtocolError |
364 | 27 | from friends.protocols.flickr import Flickr | 26 | from friends.protocols.flickr import Flickr |
365 | 28 | from friends.tests.mocks import FakeAccount, LogMock, SettingsIterMock | 27 | from friends.tests.mocks import FakeAccount, LogMock, SettingsIterMock |
368 | 29 | from friends.tests.mocks import TestModel, mock | 28 | from friends.tests.mocks import TestModel, LogMock, mock |
369 | 30 | from friends.utils.account import Account, AccountManager | 29 | from friends.utils.account import Account, _find_accounts_uoa |
370 | 31 | 30 | ||
371 | 32 | 31 | ||
372 | 33 | class TestAccount(unittest.TestCase): | 32 | class TestAccount(unittest.TestCase): |
373 | 34 | """Test Account class.""" | 33 | """Test Account class.""" |
374 | 35 | 34 | ||
375 | 36 | def setUp(self): | 35 | def setUp(self): |
376 | 36 | self.log_mock = LogMock('friends.utils.account') | ||
377 | 37 | def connect_side_effect(signal, callback, account): | 37 | def connect_side_effect(signal, callback, account): |
378 | 38 | # The account service provides a .connect method that connects a | 38 | # The account service provides a .connect method that connects a |
379 | 39 | # signal to a callback. We have to mock a side effect into the | 39 | # signal to a callback. We have to mock a side effect into the |
380 | @@ -49,7 +49,9 @@ | |||
381 | 49 | 'get_credentials_id.return_value': 'fake credentials', | 49 | 'get_credentials_id.return_value': 'fake credentials', |
382 | 50 | 'get_method.return_value': 'fake method', | 50 | 'get_method.return_value': 'fake method', |
383 | 51 | 'get_mechanism.return_value': 'fake mechanism', | 51 | 'get_mechanism.return_value': 'fake mechanism', |
385 | 52 | 'get_parameters.return_value': 'fake parameters', | 52 | 'get_parameters.return_value': { |
386 | 53 | 'ConsumerKey': 'fake_key', | ||
387 | 54 | 'ConsumerSecret': 'fake_secret'}, | ||
388 | 53 | }), | 55 | }), |
389 | 54 | 'get_account.return_value': mock.Mock(**{ | 56 | 'get_account.return_value': mock.Mock(**{ |
390 | 55 | 'get_settings_iter.return_value': SettingsIterMock(), | 57 | 'get_settings_iter.return_value': SettingsIterMock(), |
391 | @@ -63,17 +65,21 @@ | |||
392 | 63 | }) | 65 | }) |
393 | 64 | self.account = Account(self.account_service) | 66 | self.account = Account(self.account_service) |
394 | 65 | 67 | ||
395 | 68 | def tearDown(self): | ||
396 | 69 | self.log_mock.stop() | ||
397 | 70 | |||
398 | 66 | def test_account_auth(self): | 71 | def test_account_auth(self): |
399 | 67 | # Test that the constructor initializes the 'auth' attribute. | 72 | # Test that the constructor initializes the 'auth' attribute. |
400 | 68 | auth = self.account.auth | 73 | auth = self.account.auth |
405 | 69 | self.assertEqual(auth.id, 'fake credentials') | 74 | self.assertEqual(auth.get_credentials_id(), 'fake credentials') |
406 | 70 | self.assertEqual(auth.method, 'fake method') | 75 | self.assertEqual(auth.get_method(), 'fake method') |
407 | 71 | self.assertEqual(auth.mechanism, 'fake mechanism') | 76 | self.assertEqual(auth.get_mechanism(), 'fake mechanism') |
408 | 72 | self.assertEqual(auth.parameters, 'fake parameters') | 77 | self.assertEqual(auth.get_parameters(), |
409 | 78 | dict(ConsumerKey='fake_key', | ||
410 | 79 | ConsumerSecret='fake_secret')) | ||
411 | 73 | 80 | ||
412 | 74 | def test_account_id(self): | 81 | def test_account_id(self): |
413 | 75 | self.assertEqual(self.account.id, 'fake_id') | 82 | self.assertEqual(self.account.id, 'fake_id') |
414 | 76 | self.assertEqual(self.account.protocol_name, 'flickr') | ||
415 | 77 | 83 | ||
416 | 78 | def test_account_service(self): | 84 | def test_account_service(self): |
417 | 79 | # The protocol attribute refers directly to the protocol used. | 85 | # The protocol attribute refers directly to the protocol used. |
418 | @@ -122,102 +128,18 @@ | |||
419 | 122 | self.assertFalse(hasattr(self.account, 'bee')) | 128 | self.assertFalse(hasattr(self.account, 'bee')) |
420 | 123 | self.assertFalse(hasattr(self.account, 'cat')) | 129 | self.assertFalse(hasattr(self.account, 'cat')) |
421 | 124 | 130 | ||
465 | 125 | def test_enabled(self): | 131 | @mock.patch('friends.utils.account.manager') |
466 | 126 | # .enabled() just passes through from the account service. | 132 | @mock.patch('friends.utils.account.Account') |
424 | 127 | self.account_service.get_enabled.return_value = True | ||
425 | 128 | self.assertTrue(self.account.enabled) | ||
426 | 129 | self.account_service.get_enabled.return_value = False | ||
427 | 130 | self.assertFalse(self.account.enabled) | ||
428 | 131 | |||
429 | 132 | def test_equal(self): | ||
430 | 133 | # Two accounts are equal if their account services are equal. | ||
431 | 134 | other = Account(self.account_service) | ||
432 | 135 | self.assertEqual(self.account, other) | ||
433 | 136 | assert not self.account == None | ||
434 | 137 | |||
435 | 138 | def test_unequal(self): | ||
436 | 139 | # Two accounts are unequal if their account services are unequal. The | ||
437 | 140 | # other mock service has to at least support the basic required API. | ||
438 | 141 | other = Account(mock.Mock(**{ | ||
439 | 142 | 'get_account.return_value': mock.Mock(**{ | ||
440 | 143 | 'get_settings_iter.return_value': SettingsIterMock(), | ||
441 | 144 | # It's okay if the provider names are the same; the test | ||
442 | 145 | # is for whether the account services are the same or not, | ||
443 | 146 | # and in this test, they'll be different mock instances. | ||
444 | 147 | 'get_provider_name.return_value': 'flickr', | ||
445 | 148 | }), | ||
446 | 149 | })) | ||
447 | 150 | self.assertNotEqual(self.account, other) | ||
448 | 151 | assert self.account != None | ||
449 | 152 | |||
450 | 153 | |||
451 | 154 | accounts_manager = mock.Mock() | ||
452 | 155 | accounts_manager.new_for_service_type( | ||
453 | 156 | 'microblogging').get_enabled_account_services.return_value = [] | ||
454 | 157 | |||
455 | 158 | |||
456 | 159 | @mock.patch('gi.repository.Accounts.Manager', accounts_manager) | ||
457 | 160 | @mock.patch('friends.utils.account.Account', FakeAccount) | ||
458 | 161 | class TestAccountManager(unittest.TestCase): | ||
459 | 162 | """Test the AccountManager API.""" | ||
460 | 163 | |||
461 | 164 | def setUp(self): | ||
462 | 165 | TestModel.clear() | ||
463 | 166 | self.account_service = mock.Mock() | ||
464 | 167 | |||
467 | 168 | @mock.patch('friends.utils.account.Accounts') | 133 | @mock.patch('friends.utils.account.Accounts') |
523 | 169 | def test_get_service(self, accounts_mock): | 134 | def test_find_accounts(self, accts, acct, manager): |
524 | 170 | manager = AccountManager() | 135 | service = mock.Mock() |
525 | 171 | manager_mock = mock.Mock() | 136 | get_enabled = manager.get_enabled_account_services |
526 | 172 | account_mock = mock.Mock() | 137 | get_enabled.return_value = [service] |
527 | 173 | service_mock = mock.Mock() | 138 | manager.reset_mock() |
528 | 174 | manager_mock.get_account.return_value = account_mock | 139 | accounts = _find_accounts_uoa() |
529 | 175 | account_mock.list_services.return_value = [service_mock] | 140 | get_enabled.assert_called_once_with() |
530 | 176 | account_service_mock = accounts_mock.AccountService.new(account_mock, | 141 | acct.assert_called_once_with(service) |
531 | 177 | service_mock) | 142 | self.assertEqual(accounts, {acct().id: acct()}) |
532 | 178 | account_service_mock.get_service( | 143 | self.assertEqual(self.log_mock.empty(), |
533 | 179 | ).get_display_name().lower.return_value = 'protocol' | 144 | 'Flickr (fake_id) got send_enabled: True\n' |
534 | 180 | 145 | 'Accounts found: 1\n') | |
480 | 181 | service = manager._get_service(manager_mock, 10) | ||
481 | 182 | |||
482 | 183 | manager_mock.get_account.assert_called_once_with(10) | ||
483 | 184 | account_mock.list_services.assert_called_once_with() | ||
484 | 185 | accounts_mock.AccountService.new.assert_called_with(account_mock, | ||
485 | 186 | service_mock) | ||
486 | 187 | |||
487 | 188 | def test_account_manager_add_new_account(self): | ||
488 | 189 | # Explicitly adding a new account puts the account's global_id into | ||
489 | 190 | # the account manager's mapping. | ||
490 | 191 | manager = AccountManager() | ||
491 | 192 | manager._add_new_account(self.account_service) | ||
492 | 193 | self.assertIn(88, manager._accounts) | ||
493 | 194 | |||
494 | 195 | def test_account_manager_enabled_event(self): | ||
495 | 196 | manager = AccountManager() | ||
496 | 197 | manager._get_service = mock.Mock() | ||
497 | 198 | manager._get_service.return_value = mock.Mock() | ||
498 | 199 | manager._add_new_account = mock.Mock() | ||
499 | 200 | manager._add_new_account.return_value = account = mock.Mock() | ||
500 | 201 | manager._on_enabled_event(accounts_manager, 2) | ||
501 | 202 | account.protocol.assert_called_once_with('receive') | ||
502 | 203 | |||
503 | 204 | |||
504 | 205 | @mock.patch('gi.repository.Accounts.Manager', accounts_manager) | ||
505 | 206 | class TestAccountManagerRealAccount(unittest.TestCase): | ||
506 | 207 | """Test of the AccountManager API requiring the real Account class. | ||
507 | 208 | |||
508 | 209 | You'll need to guarantee other mocks are in place such that the real | ||
509 | 210 | accounts are not touched. | ||
510 | 211 | """ | ||
511 | 212 | def setUp(self): | ||
512 | 213 | self.account_service = mock.Mock() | ||
513 | 214 | |||
514 | 215 | def test_account_manager_add_new_account_unsupported(self): | ||
515 | 216 | fake_account = self.account_service.get_account() | ||
516 | 217 | fake_account.get_provider_name.return_value = 'no service' | ||
517 | 218 | manager = AccountManager() | ||
518 | 219 | with LogMock('friends.utils.account') as log_mock: | ||
519 | 220 | manager._add_new_account(self.account_service) | ||
520 | 221 | log_contents = log_mock.empty(trim=False) | ||
521 | 222 | self.assertNotIn('no service', manager._accounts) | ||
522 | 223 | self.assertEqual(log_contents, 'Unsupported protocol: no service\n') | ||
535 | 224 | 146 | ||
536 | === modified file 'friends/tests/test_authentication.py' | |||
537 | --- friends/tests/test_authentication.py 2013-02-05 01:11:35 +0000 | |||
538 | +++ friends/tests/test_authentication.py 2013-03-25 10:33:12 +0000 | |||
539 | @@ -27,7 +27,7 @@ | |||
540 | 27 | import unittest | 27 | import unittest |
541 | 28 | 28 | ||
542 | 29 | from friends.utils.authentication import Authentication | 29 | from friends.utils.authentication import Authentication |
544 | 30 | from friends.tests.mocks import FakeAccount, mock | 30 | from friends.tests.mocks import FakeAccount, LogMock, mock |
545 | 31 | from friends.errors import AuthorizationError | 31 | from friends.errors import AuthorizationError |
546 | 32 | 32 | ||
547 | 33 | 33 | ||
548 | @@ -43,7 +43,11 @@ | |||
549 | 43 | # error, and user_data arguments. We'll use the parameters | 43 | # error, and user_data arguments. We'll use the parameters |
550 | 44 | # argument as a way to specify whether an error occurred during | 44 | # argument as a way to specify whether an error occurred during |
551 | 45 | # authentication or not. | 45 | # authentication or not. |
553 | 46 | callback(None, self.results, parameters, None) | 46 | callback( |
554 | 47 | None, | ||
555 | 48 | self.results, | ||
556 | 49 | parameters if hasattr(parameters, 'message') else None, | ||
557 | 50 | None) | ||
558 | 47 | 51 | ||
559 | 48 | 52 | ||
560 | 49 | class FakeSignon: | 53 | class FakeSignon: |
561 | @@ -56,59 +60,49 @@ | |||
562 | 56 | results = dict(NoAccessToken='fail') | 60 | results = dict(NoAccessToken='fail') |
563 | 57 | 61 | ||
564 | 58 | 62 | ||
565 | 59 | class Logger: | ||
566 | 60 | def __init__(self): | ||
567 | 61 | self.debug_messages = [] | ||
568 | 62 | self.error_messages = [] | ||
569 | 63 | |||
570 | 64 | def debug(self, message, *args): | ||
571 | 65 | self.debug_messages.append(message.format(*args)) | ||
572 | 66 | |||
573 | 67 | def error(self, message, *args): | ||
574 | 68 | self.error_messages.append(message.format(*args)) | ||
575 | 69 | |||
576 | 70 | reset = __init__ | ||
577 | 71 | |||
578 | 72 | |||
579 | 73 | logger = Logger() | ||
580 | 74 | |||
581 | 75 | |||
582 | 76 | class TestAuthentication(unittest.TestCase): | 63 | class TestAuthentication(unittest.TestCase): |
583 | 77 | """Test authentication.""" | 64 | """Test authentication.""" |
584 | 78 | 65 | ||
585 | 79 | def setUp(self): | 66 | def setUp(self): |
586 | 67 | self.log_mock = LogMock('friends.utils.authentication') | ||
587 | 80 | self.account = FakeAccount() | 68 | self.account = FakeAccount() |
595 | 81 | self.account.auth.id = 'my id' | 69 | self.account.auth.get_credentials_id = lambda *ignore: 'my id' |
596 | 82 | self.account.auth.method = 'some method' | 70 | self.account.auth.get_method = lambda *ignore: 'some method' |
597 | 83 | self.account.auth.parameters = 'change me' | 71 | self.account.auth.get_parameters = lambda *ignore: 'change me' |
598 | 84 | self.account.auth.mechanism = ['whatever'] | 72 | self.account.auth.get_mechanism = lambda *ignore: 'whatever' |
599 | 85 | logger.reset() | 73 | |
600 | 86 | 74 | def tearDown(self): | |
601 | 87 | @mock.patch('friends.utils.authentication.log', logger) | 75 | self.log_mock.stop() |
602 | 76 | |||
603 | 77 | @mock.patch('friends.utils.authentication.manager') | ||
604 | 88 | @mock.patch('friends.utils.authentication.Signon', FakeSignon) | 78 | @mock.patch('friends.utils.authentication.Signon', FakeSignon) |
606 | 89 | def test_successful_login(self): | 79 | @mock.patch('friends.utils.authentication.Accounts') |
607 | 80 | def test_successful_login(self, accounts, *mocks): | ||
608 | 90 | # Prevent an error in the callback. | 81 | # Prevent an error in the callback. |
611 | 91 | self.account.auth.parameters = False | 82 | accounts.AccountService.new().get_auth_data( |
612 | 92 | authenticator = Authentication(self.account) | 83 | ).get_parameters.return_value = False |
613 | 84 | authenticator = Authentication(self.account.id) | ||
614 | 93 | reply = authenticator.login() | 85 | reply = authenticator.login() |
615 | 94 | self.assertEqual(reply, dict(AccessToken='auth reply')) | 86 | self.assertEqual(reply, dict(AccessToken='auth reply')) |
618 | 95 | self.assertEqual(logger.debug_messages, ['Login completed']) | 87 | self.assertEqual(self.log_mock.empty(), 'Login completed\n') |
617 | 96 | self.assertEqual(logger.error_messages, []) | ||
619 | 97 | 88 | ||
621 | 98 | @mock.patch('friends.utils.authentication.log', logger) | 89 | @mock.patch('friends.utils.authentication.manager') |
622 | 90 | @mock.patch('friends.utils.authentication.Accounts') | ||
623 | 99 | @mock.patch('friends.utils.authentication.Signon', FailingSignon) | 91 | @mock.patch('friends.utils.authentication.Signon', FailingSignon) |
625 | 100 | def test_missing_access_token(self): | 92 | def test_missing_access_token(self, *mocks): |
626 | 101 | # Prevent an error in the callback. | 93 | # Prevent an error in the callback. |
629 | 102 | self.account.auth.parameters = False | 94 | self.account.auth.get_parameters = lambda *ignore: False |
630 | 103 | authenticator = Authentication(self.account) | 95 | authenticator = Authentication(self.account.id) |
631 | 104 | self.assertRaises(AuthorizationError, authenticator.login) | 96 | self.assertRaises(AuthorizationError, authenticator.login) |
632 | 105 | 97 | ||
634 | 106 | @mock.patch('friends.utils.authentication.log', logger) | 98 | @mock.patch('friends.utils.authentication.manager') |
635 | 107 | @mock.patch('friends.utils.authentication.Signon', FakeSignon) | 99 | @mock.patch('friends.utils.authentication.Signon', FakeSignon) |
637 | 108 | def test_failed_login(self): | 100 | @mock.patch('friends.utils.authentication.Accounts') |
638 | 101 | def test_failed_login(self, accounts, *mocks): | ||
639 | 109 | # Trigger an error in the callback. | 102 | # Trigger an error in the callback. |
640 | 110 | class Error: | 103 | class Error: |
641 | 111 | message = 'who are you?' | 104 | message = 'who are you?' |
644 | 112 | self.account.auth.parameters = Error | 105 | accounts.AccountService.new( |
645 | 113 | authenticator = Authentication(self.account) | 106 | ).get_auth_data().get_parameters.return_value = Error |
646 | 107 | authenticator = Authentication(self.account.id) | ||
647 | 114 | self.assertRaises(AuthorizationError, authenticator.login) | 108 | self.assertRaises(AuthorizationError, authenticator.login) |
648 | 115 | 109 | ||
649 | === modified file 'friends/tests/test_dispatcher.py' | |||
650 | --- friends/tests/test_dispatcher.py 2013-03-20 01:19:39 +0000 | |||
651 | +++ friends/tests/test_dispatcher.py 2013-03-25 10:33:12 +0000 | |||
652 | @@ -38,13 +38,13 @@ | |||
653 | 38 | """Test the dispatcher's ability to dispatch.""" | 38 | """Test the dispatcher's ability to dispatch.""" |
654 | 39 | 39 | ||
655 | 40 | @mock.patch('dbus.service.BusName') | 40 | @mock.patch('dbus.service.BusName') |
658 | 41 | @mock.patch('friends.service.dispatcher.AccountManager') | 41 | @mock.patch('friends.service.dispatcher.find_accounts') |
657 | 42 | @mock.patch('friends.service.dispatcher.Dispatcher.Refresh') | ||
659 | 43 | @mock.patch('dbus.service.Object.__init__') | 42 | @mock.patch('dbus.service.Object.__init__') |
660 | 44 | def setUp(self, *mocks): | 43 | def setUp(self, *mocks): |
661 | 45 | self.log_mock = LogMock('friends.service.dispatcher', | 44 | self.log_mock = LogMock('friends.service.dispatcher', |
662 | 46 | 'friends.utils.account') | 45 | 'friends.utils.account') |
663 | 47 | self.dispatcher = Dispatcher(mock.Mock(), mock.Mock()) | 46 | self.dispatcher = Dispatcher(mock.Mock(), mock.Mock()) |
664 | 47 | self.dispatcher.accounts = {} | ||
665 | 48 | 48 | ||
666 | 49 | def tearDown(self): | 49 | def tearDown(self): |
667 | 50 | self.log_mock.stop() | 50 | self.log_mock.stop() |
668 | @@ -53,12 +53,12 @@ | |||
669 | 53 | def test_refresh(self, threading_mock): | 53 | def test_refresh(self, threading_mock): |
670 | 54 | account = mock.Mock() | 54 | account = mock.Mock() |
671 | 55 | threading_mock.activeCount.return_value = 1 | 55 | threading_mock.activeCount.return_value = 1 |
674 | 56 | self.dispatcher.account_manager = mock.Mock() | 56 | self.dispatcher.accounts = mock.Mock() |
675 | 57 | self.dispatcher.account_manager.get_all.return_value = [account] | 57 | self.dispatcher.accounts.values.return_value = [account] |
676 | 58 | 58 | ||
677 | 59 | self.assertIsNone(self.dispatcher.Refresh()) | 59 | self.assertIsNone(self.dispatcher.Refresh()) |
678 | 60 | 60 | ||
680 | 61 | self.dispatcher.account_manager.get_all.assert_called_once_with() | 61 | self.dispatcher.accounts.values.assert_called_once_with() |
681 | 62 | account.protocol.assert_called_once_with('receive') | 62 | account.protocol.assert_called_once_with('receive') |
682 | 63 | 63 | ||
683 | 64 | self.assertEqual(self.log_mock.empty(), | 64 | self.assertEqual(self.log_mock.empty(), |
684 | @@ -75,12 +75,11 @@ | |||
685 | 75 | def test_do(self): | 75 | def test_do(self): |
686 | 76 | account = mock.Mock() | 76 | account = mock.Mock() |
687 | 77 | account.id = '345' | 77 | account.id = '345' |
690 | 78 | self.dispatcher.account_manager = mock.Mock() | 78 | self.dispatcher.accounts = mock.Mock() |
691 | 79 | self.dispatcher.account_manager.get.return_value = account | 79 | self.dispatcher.accounts.get.return_value = account |
692 | 80 | 80 | ||
693 | 81 | self.dispatcher.Do('like', '345', '23346356767354626') | 81 | self.dispatcher.Do('like', '345', '23346356767354626') |
696 | 82 | self.dispatcher.account_manager.get.assert_called_once_with( | 82 | self.dispatcher.accounts.get.assert_called_once_with(345) |
695 | 83 | '345') | ||
697 | 84 | account.protocol.assert_called_once_with( | 83 | account.protocol.assert_called_once_with( |
698 | 85 | 'like', '23346356767354626', success=STUB, failure=STUB) | 84 | 'like', '23346356767354626', success=STUB, failure=STUB) |
699 | 86 | 85 | ||
700 | @@ -92,11 +91,11 @@ | |||
701 | 92 | 91 | ||
702 | 93 | def test_failing_do(self): | 92 | def test_failing_do(self): |
703 | 94 | account = mock.Mock() | 93 | account = mock.Mock() |
706 | 95 | self.dispatcher.account_manager = mock.Mock() | 94 | self.dispatcher.accounts = mock.Mock() |
707 | 96 | self.dispatcher.account_manager.get.return_value = None | 95 | self.dispatcher.accounts.get.return_value = None |
708 | 97 | 96 | ||
709 | 98 | self.dispatcher.Do('unlike', '6', '23346356767354626') | 97 | self.dispatcher.Do('unlike', '6', '23346356767354626') |
711 | 99 | self.dispatcher.account_manager.get.assert_called_once_with('6') | 98 | self.dispatcher.accounts.get.assert_called_once_with(6) |
712 | 100 | self.assertEqual(account.protocol.call_count, 0) | 99 | self.assertEqual(account.protocol.call_count, 0) |
713 | 101 | 100 | ||
714 | 102 | self.assertEqual(self.log_mock.empty(), | 101 | self.assertEqual(self.log_mock.empty(), |
715 | @@ -111,15 +110,15 @@ | |||
716 | 111 | account3 = mock.Mock() | 110 | account3 = mock.Mock() |
717 | 112 | account2.send_enabled = False | 111 | account2.send_enabled = False |
718 | 113 | 112 | ||
721 | 114 | self.dispatcher.account_manager = mock.Mock() | 113 | self.dispatcher.accounts = mock.Mock() |
722 | 115 | self.dispatcher.account_manager.get_all.return_value = [ | 114 | self.dispatcher.accounts.values.return_value = [ |
723 | 116 | account1, | 115 | account1, |
724 | 117 | account2, | 116 | account2, |
725 | 118 | account3, | 117 | account3, |
726 | 119 | ] | 118 | ] |
727 | 120 | 119 | ||
728 | 121 | self.dispatcher.SendMessage('Howdy friends!') | 120 | self.dispatcher.SendMessage('Howdy friends!') |
730 | 122 | self.dispatcher.account_manager.get_all.assert_called_once_with() | 121 | self.dispatcher.accounts.values.assert_called_once_with() |
731 | 123 | account1.protocol.assert_called_once_with( | 122 | account1.protocol.assert_called_once_with( |
732 | 124 | 'send', 'Howdy friends!', success=STUB, failure=STUB) | 123 | 'send', 'Howdy friends!', success=STUB, failure=STUB) |
733 | 125 | account3.protocol.assert_called_once_with( | 124 | account3.protocol.assert_called_once_with( |
734 | @@ -128,11 +127,11 @@ | |||
735 | 128 | 127 | ||
736 | 129 | def test_send_reply(self): | 128 | def test_send_reply(self): |
737 | 130 | account = mock.Mock() | 129 | account = mock.Mock() |
740 | 131 | self.dispatcher.account_manager = mock.Mock() | 130 | self.dispatcher.accounts = mock.Mock() |
741 | 132 | self.dispatcher.account_manager.get.return_value = account | 131 | self.dispatcher.accounts.get.return_value = account |
742 | 133 | 132 | ||
743 | 134 | self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]') | 133 | self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]') |
745 | 135 | self.dispatcher.account_manager.get.assert_called_once_with('2') | 134 | self.dispatcher.accounts.get.assert_called_once_with(2) |
746 | 136 | account.protocol.assert_called_once_with( | 135 | account.protocol.assert_called_once_with( |
747 | 137 | 'send_thread', 'objid', '[Hilarious Response]', | 136 | 'send_thread', 'objid', '[Hilarious Response]', |
748 | 138 | success=STUB, failure=STUB) | 137 | success=STUB, failure=STUB) |
749 | @@ -145,11 +144,11 @@ | |||
750 | 145 | 144 | ||
751 | 146 | def test_send_reply_failed(self): | 145 | def test_send_reply_failed(self): |
752 | 147 | account = mock.Mock() | 146 | account = mock.Mock() |
755 | 148 | self.dispatcher.account_manager = mock.Mock() | 147 | self.dispatcher.accounts = mock.Mock() |
756 | 149 | self.dispatcher.account_manager.get.return_value = None | 148 | self.dispatcher.accounts.get.return_value = None |
757 | 150 | 149 | ||
758 | 151 | self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]') | 150 | self.dispatcher.SendReply('2', 'objid', '[Hilarious Response]') |
760 | 152 | self.dispatcher.account_manager.get.assert_called_once_with('2') | 151 | self.dispatcher.accounts.get.assert_called_once_with(2) |
761 | 153 | self.assertEqual(account.protocol.call_count, 0) | 152 | self.assertEqual(account.protocol.call_count, 0) |
762 | 154 | 153 | ||
763 | 155 | self.assertEqual(self.log_mock.empty(), | 154 | self.assertEqual(self.log_mock.empty(), |
764 | @@ -161,8 +160,8 @@ | |||
765 | 161 | 160 | ||
766 | 162 | def test_upload_async(self): | 161 | def test_upload_async(self): |
767 | 163 | account = mock.Mock() | 162 | account = mock.Mock() |
770 | 164 | self.dispatcher.account_manager = mock.Mock() | 163 | self.dispatcher.accounts = mock.Mock() |
771 | 165 | self.dispatcher.account_manager.get.return_value = account | 164 | self.dispatcher.accounts.get.return_value = account |
772 | 166 | 165 | ||
773 | 167 | success = mock.Mock() | 166 | success = mock.Mock() |
774 | 168 | failure = mock.Mock() | 167 | failure = mock.Mock() |
775 | @@ -172,7 +171,7 @@ | |||
776 | 172 | 'A thousand words', | 171 | 'A thousand words', |
777 | 173 | success=success, | 172 | success=success, |
778 | 174 | failure=failure) | 173 | failure=failure) |
780 | 175 | self.dispatcher.account_manager.get.assert_called_once_with('2') | 174 | self.dispatcher.accounts.get.assert_called_once_with(2) |
781 | 176 | account.protocol.assert_called_once_with( | 175 | account.protocol.assert_called_once_with( |
782 | 177 | 'upload', | 176 | 'upload', |
783 | 178 | 'file://path/to/image.png', | 177 | 'file://path/to/image.png', |
784 | 179 | 178 | ||
785 | === modified file 'friends/tests/test_facebook.py' | |||
786 | --- friends/tests/test_facebook.py 2013-03-16 00:54:45 +0000 | |||
787 | +++ friends/tests/test_facebook.py 2013-03-25 10:33:12 +0000 | |||
788 | @@ -60,11 +60,13 @@ | |||
789 | 60 | ['contacts', 'delete', 'home', 'like', 'receive', 'search', 'send', | 60 | ['contacts', 'delete', 'home', 'like', 'receive', 'search', 'send', |
790 | 61 | 'send_thread', 'unlike', 'upload', 'wall']) | 61 | 'send_thread', 'unlike', 'upload', 'wall']) |
791 | 62 | 62 | ||
792 | 63 | @mock.patch('friends.utils.authentication.manager') | ||
793 | 64 | @mock.patch('friends.utils.authentication.Accounts') | ||
794 | 63 | @mock.patch('friends.utils.authentication.Authentication.login', | 65 | @mock.patch('friends.utils.authentication.Authentication.login', |
795 | 64 | return_value=dict(AccessToken='abc')) | 66 | return_value=dict(AccessToken='abc')) |
796 | 65 | @mock.patch('friends.utils.http.Soup.Message', | 67 | @mock.patch('friends.utils.http.Soup.Message', |
797 | 66 | FakeSoupMessage('friends.tests.data', 'facebook-login.dat')) | 68 | FakeSoupMessage('friends.tests.data', 'facebook-login.dat')) |
799 | 67 | def test_successful_login(self, mock): | 69 | def test_successful_login(self, *mocks): |
800 | 68 | # Test that a successful response from graph.facebook.com returning | 70 | # Test that a successful response from graph.facebook.com returning |
801 | 69 | # the user's data, sets up the account dict correctly. | 71 | # the user's data, sets up the account dict correctly. |
802 | 70 | self.protocol._login() | 72 | self.protocol._login() |
803 | @@ -72,14 +74,18 @@ | |||
804 | 72 | self.assertEqual(self.account.user_name, 'Bart Person') | 74 | self.assertEqual(self.account.user_name, 'Bart Person') |
805 | 73 | self.assertEqual(self.account.user_id, '801') | 75 | self.assertEqual(self.account.user_id, '801') |
806 | 74 | 76 | ||
807 | 77 | @mock.patch('friends.utils.authentication.manager') | ||
808 | 78 | @mock.patch('friends.utils.authentication.Accounts') | ||
809 | 75 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) | 79 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) |
810 | 76 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') | 80 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') |
812 | 77 | def test_login_unsuccessful_authentication(self, mock): | 81 | def test_login_unsuccessful_authentication(self, *mocks): |
813 | 78 | # The user is not already logged in, but the act of logging in fails. | 82 | # The user is not already logged in, but the act of logging in fails. |
814 | 79 | self.assertRaises(AuthorizationError, self.protocol._login) | 83 | self.assertRaises(AuthorizationError, self.protocol._login) |
815 | 80 | self.assertIsNone(self.account.access_token) | 84 | self.assertIsNone(self.account.access_token) |
816 | 81 | self.assertIsNone(self.account.user_name) | 85 | self.assertIsNone(self.account.user_name) |
817 | 82 | 86 | ||
818 | 87 | @mock.patch('friends.utils.authentication.manager') | ||
819 | 88 | @mock.patch('friends.utils.authentication.Accounts') | ||
820 | 83 | @mock.patch('friends.utils.authentication.Authentication.login', | 89 | @mock.patch('friends.utils.authentication.Authentication.login', |
821 | 84 | return_value=dict(AccessToken='abc')) | 90 | return_value=dict(AccessToken='abc')) |
822 | 85 | @mock.patch('friends.protocols.facebook.Downloader.get_json', | 91 | @mock.patch('friends.protocols.facebook.Downloader.get_json', |
823 | @@ -237,9 +243,6 @@ | |||
824 | 237 | def test_home_since_id(self, *mocks): | 243 | def test_home_since_id(self, *mocks): |
825 | 238 | self.account.access_token = 'access' | 244 | self.account.access_token = 'access' |
826 | 239 | self.account.secret_token = 'secret' | 245 | self.account.secret_token = 'secret' |
827 | 240 | self.account.auth.parameters = dict( | ||
828 | 241 | ConsumerKey='key', | ||
829 | 242 | ConsumerSecret='secret') | ||
830 | 243 | self.assertEqual(self.protocol.home(), 12) | 246 | self.assertEqual(self.protocol.home(), 12) |
831 | 244 | 247 | ||
832 | 245 | with open(self._root.format('facebook_ids'), 'r') as fd: | 248 | with open(self._root.format('facebook_ids'), 'r') as fd: |
833 | 246 | 249 | ||
834 | === modified file 'friends/tests/test_flickr.py' | |||
835 | --- friends/tests/test_flickr.py 2013-03-20 23:57:40 +0000 | |||
836 | +++ friends/tests/test_flickr.py 2013-03-25 10:33:12 +0000 | |||
837 | @@ -86,6 +86,8 @@ | |||
838 | 86 | # But also no photos. | 86 | # But also no photos. |
839 | 87 | self.assertEqual(TestModel.get_n_rows(), 0) | 87 | self.assertEqual(TestModel.get_n_rows(), 0) |
840 | 88 | 88 | ||
841 | 89 | @mock.patch('friends.utils.authentication.manager') | ||
842 | 90 | @mock.patch('friends.utils.authentication.Accounts') | ||
843 | 89 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) | 91 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) |
844 | 90 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') | 92 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') |
845 | 91 | @mock.patch('friends.utils.http.Soup.Message', | 93 | @mock.patch('friends.utils.http.Soup.Message', |
846 | @@ -95,6 +97,8 @@ | |||
847 | 95 | # AccessToken, but this fails. | 97 | # AccessToken, but this fails. |
848 | 96 | self.assertRaises(AuthorizationError, self.protocol.receive) | 98 | self.assertRaises(AuthorizationError, self.protocol.receive) |
849 | 97 | 99 | ||
850 | 100 | @mock.patch('friends.utils.authentication.manager') | ||
851 | 101 | @mock.patch('friends.utils.authentication.Accounts') | ||
852 | 98 | @mock.patch('friends.utils.http.Soup.Message', | 102 | @mock.patch('friends.utils.http.Soup.Message', |
853 | 99 | FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) | 103 | FakeSoupMessage('friends.tests.data', 'flickr-nophotos.dat')) |
854 | 100 | @mock.patch('friends.utils.authentication.Authentication.login', | 104 | @mock.patch('friends.utils.authentication.Authentication.login', |
855 | @@ -102,7 +106,7 @@ | |||
856 | 102 | user_nsid='bob', | 106 | user_nsid='bob', |
857 | 103 | AccessToken='123', | 107 | AccessToken='123', |
858 | 104 | TokenSecret='abc')) | 108 | TokenSecret='abc')) |
860 | 105 | def test_login_successful_authentication(self, mock): | 109 | def test_login_successful_authentication(self, *mocks): |
861 | 106 | # Logging in required communication with the account service to get an | 110 | # Logging in required communication with the account service to get an |
862 | 107 | # AccessToken, but this fails. | 111 | # AccessToken, but this fails. |
863 | 108 | self.protocol.receive() | 112 | self.protocol.receive() |
864 | @@ -131,7 +135,7 @@ | |||
865 | 131 | extras='date_upload,owner_name,icon_server,geo', | 135 | extras='date_upload,owner_name,icon_server,geo', |
866 | 132 | format='json', | 136 | format='json', |
867 | 133 | nojsoncallback='1', | 137 | nojsoncallback='1', |
869 | 134 | api_key='fake', | 138 | api_key='consume', |
870 | 135 | method='flickr.photos.getContactsPhotos', | 139 | method='flickr.photos.getContactsPhotos', |
871 | 136 | ), | 140 | ), |
872 | 137 | headers={}) | 141 | headers={}) |
873 | 138 | 142 | ||
874 | === modified file 'friends/tests/test_foursquare.py' | |||
875 | --- friends/tests/test_foursquare.py 2013-03-14 19:14:03 +0000 | |||
876 | +++ friends/tests/test_foursquare.py 2013-03-25 10:33:12 +0000 | |||
877 | @@ -50,6 +50,8 @@ | |||
878 | 50 | # The set of public features. | 50 | # The set of public features. |
879 | 51 | self.assertEqual(FourSquare.get_features(), ['receive']) | 51 | self.assertEqual(FourSquare.get_features(), ['receive']) |
880 | 52 | 52 | ||
881 | 53 | @mock.patch('friends.utils.authentication.manager') | ||
882 | 54 | @mock.patch('friends.utils.authentication.Accounts') | ||
883 | 53 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) | 55 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) |
884 | 54 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') | 56 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') |
885 | 55 | @mock.patch('friends.utils.http.Downloader.get_json', | 57 | @mock.patch('friends.utils.http.Downloader.get_json', |
886 | @@ -59,6 +61,8 @@ | |||
887 | 59 | self.assertIsNone(self.account.user_name) | 61 | self.assertIsNone(self.account.user_name) |
888 | 60 | self.assertIsNone(self.account.user_id) | 62 | self.assertIsNone(self.account.user_id) |
889 | 61 | 63 | ||
890 | 64 | @mock.patch('friends.utils.authentication.manager') | ||
891 | 65 | @mock.patch('friends.utils.authentication.Accounts') | ||
892 | 62 | @mock.patch('friends.utils.authentication.Authentication.login', | 66 | @mock.patch('friends.utils.authentication.Authentication.login', |
893 | 63 | return_value=dict(AccessToken='tokeny goodness')) | 67 | return_value=dict(AccessToken='tokeny goodness')) |
894 | 64 | @mock.patch('friends.protocols.foursquare.Downloader.get_json', | 68 | @mock.patch('friends.protocols.foursquare.Downloader.get_json', |
895 | 65 | 69 | ||
896 | === modified file 'friends/tests/test_identica.py' | |||
897 | --- friends/tests/test_identica.py 2013-03-14 19:14:03 +0000 | |||
898 | +++ friends/tests/test_identica.py 2013-03-25 10:33:12 +0000 | |||
899 | @@ -53,6 +53,8 @@ | |||
900 | 53 | self.log_mock.stop() | 53 | self.log_mock.stop() |
901 | 54 | shutil.rmtree(self._temp_cache) | 54 | shutil.rmtree(self._temp_cache) |
902 | 55 | 55 | ||
903 | 56 | @mock.patch('friends.utils.authentication.manager') | ||
904 | 57 | @mock.patch('friends.utils.authentication.Accounts') | ||
905 | 56 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) | 58 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) |
906 | 57 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') | 59 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') |
907 | 58 | @mock.patch('friends.utils.http.Downloader.get_json', | 60 | @mock.patch('friends.utils.http.Downloader.get_json', |
908 | @@ -62,6 +64,8 @@ | |||
909 | 62 | self.assertIsNone(self.account.user_name) | 64 | self.assertIsNone(self.account.user_name) |
910 | 63 | self.assertIsNone(self.account.user_id) | 65 | self.assertIsNone(self.account.user_id) |
911 | 64 | 66 | ||
912 | 67 | @mock.patch('friends.utils.authentication.manager') | ||
913 | 68 | @mock.patch('friends.utils.authentication.Accounts') | ||
914 | 65 | @mock.patch('friends.utils.authentication.Authentication.login', | 69 | @mock.patch('friends.utils.authentication.Authentication.login', |
915 | 66 | return_value=dict(AccessToken='some clever fake data', | 70 | return_value=dict(AccessToken='some clever fake data', |
916 | 67 | TokenSecret='sssssshhh!')) | 71 | TokenSecret='sssssshhh!')) |
917 | 68 | 72 | ||
918 | === modified file 'friends/tests/test_twitter.py' | |||
919 | --- friends/tests/test_twitter.py 2013-03-19 04:17:58 +0000 | |||
920 | +++ friends/tests/test_twitter.py 2013-03-25 10:33:12 +0000 | |||
921 | @@ -57,15 +57,19 @@ | |||
922 | 57 | self.log_mock.stop() | 57 | self.log_mock.stop() |
923 | 58 | shutil.rmtree(self._temp_cache) | 58 | shutil.rmtree(self._temp_cache) |
924 | 59 | 59 | ||
925 | 60 | @mock.patch('friends.utils.authentication.manager') | ||
926 | 61 | @mock.patch('friends.utils.authentication.Accounts') | ||
927 | 60 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) | 62 | @mock.patch.dict('friends.utils.authentication.__dict__', LOGIN_TIMEOUT=1) |
928 | 61 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') | 63 | @mock.patch('friends.utils.authentication.Signon.AuthSession.new') |
929 | 62 | @mock.patch('friends.protocols.twitter.Downloader.get_json', | 64 | @mock.patch('friends.protocols.twitter.Downloader.get_json', |
930 | 63 | return_value=None) | 65 | return_value=None) |
932 | 64 | def test_unsuccessful_authentication(self, dload, login): | 66 | def test_unsuccessful_authentication(self, dload, login, *mocks): |
933 | 65 | self.assertRaises(AuthorizationError, self.protocol._login) | 67 | self.assertRaises(AuthorizationError, self.protocol._login) |
934 | 66 | self.assertIsNone(self.account.user_name) | 68 | self.assertIsNone(self.account.user_name) |
935 | 67 | self.assertIsNone(self.account.user_id) | 69 | self.assertIsNone(self.account.user_id) |
936 | 68 | 70 | ||
937 | 71 | @mock.patch('friends.utils.authentication.manager') | ||
938 | 72 | @mock.patch('friends.utils.authentication.Accounts') | ||
939 | 69 | @mock.patch('friends.utils.authentication.Authentication.login', | 73 | @mock.patch('friends.utils.authentication.Authentication.login', |
940 | 70 | return_value=dict(AccessToken='some clever fake data', | 74 | return_value=dict(AccessToken='some clever fake data', |
941 | 71 | TokenSecret='sssssshhh!', | 75 | TokenSecret='sssssshhh!', |
942 | @@ -86,11 +90,12 @@ | |||
943 | 86 | def test_signatures(self, dload): | 90 | def test_signatures(self, dload): |
944 | 87 | self.account.secret_token = 'alpha' | 91 | self.account.secret_token = 'alpha' |
945 | 88 | self.account.access_token = 'omega' | 92 | self.account.access_token = 'omega' |
951 | 89 | self.account.auth.id = 6 | 93 | self.account.consumer_secret = 'obey' |
952 | 90 | self.account.auth.method = 'oauth2' | 94 | self.account.consumer_key = 'consume' |
953 | 91 | self.account.auth.mechanism = 'HMAC-SHA1' | 95 | self.account.auth.get_credentials_id = lambda *ignore: 6 |
954 | 92 | self.account.auth.parameters = dict(ConsumerKey='consume', | 96 | self.account.auth.get_method = lambda *ignore: 'oauth2' |
955 | 93 | ConsumerSecret='obey') | 97 | self.account.auth.get_mechanism = lambda *ignore: 'HMAC-SHA1' |
956 | 98 | |||
957 | 94 | result = '''\ | 99 | result = '''\ |
958 | 95 | OAuth oauth_nonce="once%20upon%20a%20nonce", \ | 100 | OAuth oauth_nonce="once%20upon%20a%20nonce", \ |
959 | 96 | oauth_timestamp="1348690628", \ | 101 | oauth_timestamp="1348690628", \ |
960 | @@ -122,9 +127,6 @@ | |||
961 | 122 | def test_home(self, *mocks): | 127 | def test_home(self, *mocks): |
962 | 123 | self.account.access_token = 'access' | 128 | self.account.access_token = 'access' |
963 | 124 | self.account.secret_token = 'secret' | 129 | self.account.secret_token = 'secret' |
964 | 125 | self.account.auth.parameters = dict( | ||
965 | 126 | ConsumerKey='key', | ||
966 | 127 | ConsumerSecret='secret') | ||
967 | 128 | self.assertEqual(0, TestModel.get_n_rows()) | 130 | self.assertEqual(0, TestModel.get_n_rows()) |
968 | 129 | self.assertEqual(self.protocol.home(), 3) | 131 | self.assertEqual(self.protocol.home(), 3) |
969 | 130 | self.assertEqual(3, TestModel.get_n_rows()) | 132 | self.assertEqual(3, TestModel.get_n_rows()) |
970 | @@ -171,9 +173,6 @@ | |||
971 | 171 | def test_home_since_id(self, *mocks): | 173 | def test_home_since_id(self, *mocks): |
972 | 172 | self.account.access_token = 'access' | 174 | self.account.access_token = 'access' |
973 | 173 | self.account.secret_token = 'secret' | 175 | self.account.secret_token = 'secret' |
974 | 174 | self.account.auth.parameters = dict( | ||
975 | 175 | ConsumerKey='key', | ||
976 | 176 | ConsumerSecret='secret') | ||
977 | 177 | self.assertEqual(self.protocol.home(), 3) | 176 | self.assertEqual(self.protocol.home(), 3) |
978 | 178 | 177 | ||
979 | 179 | with open(self._root.format('twitter_ids'), 'r') as fd: | 178 | with open(self._root.format('twitter_ids'), 'r') as fd: |
980 | @@ -196,9 +195,6 @@ | |||
981 | 196 | self.account.access_token = 'access' | 195 | self.account.access_token = 'access' |
982 | 197 | self.account.secret_token = 'secret' | 196 | self.account.secret_token = 'secret' |
983 | 198 | self.account.user_name = 'oauth_dancer' | 197 | self.account.user_name = 'oauth_dancer' |
984 | 199 | self.account.auth.parameters = dict( | ||
985 | 200 | ConsumerKey='key', | ||
986 | 201 | ConsumerSecret='secret') | ||
987 | 202 | self.assertEqual(0, TestModel.get_n_rows()) | 198 | self.assertEqual(0, TestModel.get_n_rows()) |
988 | 203 | self.assertEqual( | 199 | self.assertEqual( |
989 | 204 | self.protocol.send('some message'), | 200 | self.protocol.send('some message'), |
990 | @@ -399,9 +395,6 @@ | |||
991 | 399 | def test_send_thread_prepend_nick(self, *mocks): | 395 | def test_send_thread_prepend_nick(self, *mocks): |
992 | 400 | self.account.access_token = 'access' | 396 | self.account.access_token = 'access' |
993 | 401 | self.account.secret_token = 'secret' | 397 | self.account.secret_token = 'secret' |
994 | 402 | self.account.auth.parameters = dict( | ||
995 | 403 | ConsumerKey='key', | ||
996 | 404 | ConsumerSecret='secret') | ||
997 | 405 | self.assertEqual(0, TestModel.get_n_rows()) | 398 | self.assertEqual(0, TestModel.get_n_rows()) |
998 | 406 | self.assertEqual(self.protocol.home(), 3) | 399 | self.assertEqual(self.protocol.home(), 3) |
999 | 407 | self.assertEqual(3, TestModel.get_n_rows()) | 400 | self.assertEqual(3, TestModel.get_n_rows()) |
1000 | @@ -703,9 +696,6 @@ | |||
1001 | 703 | def test_protocol_rate_limiting(self, time, sleep, login): | 696 | def test_protocol_rate_limiting(self, time, sleep, login): |
1002 | 704 | self.account.access_token = 'access' | 697 | self.account.access_token = 'access' |
1003 | 705 | self.account.secret_token = 'secret' | 698 | self.account.secret_token = 'secret' |
1004 | 706 | self.account.auth.parameters = dict( | ||
1005 | 707 | ConsumerKey='key', | ||
1006 | 708 | ConsumerSecret='secret') | ||
1007 | 709 | # Test rate limiting via the Twitter plugin API. | 699 | # Test rate limiting via the Twitter plugin API. |
1008 | 710 | # | 700 | # |
1009 | 711 | # The first call doesn't get rate limited. | 701 | # The first call doesn't get rate limited. |
1010 | 712 | 702 | ||
1011 | === modified file 'friends/utils/account.py' | |||
1012 | --- friends/utils/account.py 2013-03-13 02:03:12 +0000 | |||
1013 | +++ friends/utils/account.py 2013-03-25 10:33:12 +0000 | |||
1014 | @@ -17,7 +17,7 @@ | |||
1015 | 17 | 17 | ||
1016 | 18 | __all__ = [ | 18 | __all__ = [ |
1017 | 19 | 'Account', | 19 | 'Account', |
1019 | 20 | 'AccountManager', | 20 | 'find_accounts', |
1020 | 21 | ] | 21 | ] |
1021 | 22 | 22 | ||
1022 | 23 | 23 | ||
1023 | @@ -28,65 +28,30 @@ | |||
1024 | 28 | 28 | ||
1025 | 29 | from friends.errors import UnsupportedProtocolError | 29 | from friends.errors import UnsupportedProtocolError |
1026 | 30 | from friends.utils.manager import protocol_manager | 30 | from friends.utils.manager import protocol_manager |
1027 | 31 | from friends.utils.authentication import manager | ||
1028 | 31 | 32 | ||
1029 | 32 | 33 | ||
1030 | 33 | log = logging.getLogger(__name__) | 34 | log = logging.getLogger(__name__) |
1031 | 34 | 35 | ||
1032 | 35 | 36 | ||
1064 | 36 | class AccountManager: | 37 | def _find_accounts_uoa(): |
1065 | 37 | """Manage the accounts that we know about.""" | 38 | """Consult Ubuntu Online Accounts for the accounts we have.""" |
1066 | 38 | 39 | accounts = {} | |
1067 | 39 | def __init__(self): | 40 | for service in manager.get_enabled_account_services(): |
1037 | 40 | self._accounts = {} | ||
1038 | 41 | # Ask libaccounts for a manager of the microblogging services. | ||
1039 | 42 | # Connect callbacks to the manager so that we can react when accounts | ||
1040 | 43 | # are added or deleted. | ||
1041 | 44 | manager = Accounts.Manager.new_for_service_type('microblogging') | ||
1042 | 45 | manager.connect('enabled-event', self._on_enabled_event) | ||
1043 | 46 | # Add all the currently known accounts. | ||
1044 | 47 | for account_service in manager.get_enabled_account_services(): | ||
1045 | 48 | self._add_new_account(account_service) | ||
1046 | 49 | log.info('Accounts found: {}'.format(len(self._accounts))) | ||
1047 | 50 | |||
1048 | 51 | def _get_service(self, manager, account_id): | ||
1049 | 52 | """Instantiate an AccountService and identify it.""" | ||
1050 | 53 | account = manager.get_account(account_id) | ||
1051 | 54 | for service in account.list_services(): | ||
1052 | 55 | return Accounts.AccountService.new(account, service) | ||
1053 | 56 | |||
1054 | 57 | def _on_enabled_event(self, manager, account_id): | ||
1055 | 58 | """React to new microblogging accounts being enabled or disabled.""" | ||
1056 | 59 | account_service = self._get_service(manager, account_id) | ||
1057 | 60 | if account_service is not None and account_service.get_enabled(): | ||
1058 | 61 | log.debug('Adding account {}'.format(account_id)) | ||
1059 | 62 | account = self._add_new_account(account_service) | ||
1060 | 63 | if account is not None: | ||
1061 | 64 | account.protocol('receive') | ||
1062 | 65 | |||
1063 | 66 | def _add_new_account(self, account_service): | ||
1068 | 67 | try: | 41 | try: |
1070 | 68 | new_account = Account(account_service) | 42 | account = Account(service) |
1071 | 69 | except UnsupportedProtocolError as error: | 43 | except UnsupportedProtocolError as error: |
1072 | 70 | log.info(error) | 44 | log.info(error) |
1073 | 71 | else: | 45 | else: |
1092 | 72 | self._accounts[new_account.id] = new_account | 46 | accounts[account.id] = account |
1093 | 73 | return new_account | 47 | log.info('Accounts found: {}'.format(len(accounts))) |
1094 | 74 | 48 | return accounts | |
1095 | 75 | def get_all(self): | 49 | |
1096 | 76 | return self._accounts.values() | 50 | |
1097 | 77 | 51 | def find_accounts(): | |
1098 | 78 | def get(self, account_id, default=None): | 52 | # TODO: Implement GOA support, then fill out this method with some |
1099 | 79 | return self._accounts.get(int(account_id), default) | 53 | # logic for determining whether to use UOA or GOA. |
1100 | 80 | 54 | return _find_accounts_uoa() | |
1083 | 81 | |||
1084 | 82 | class AuthData: | ||
1085 | 83 | """This class serves as a sub-namespace for Account instances.""" | ||
1086 | 84 | |||
1087 | 85 | def __init__(self, auth_data): | ||
1088 | 86 | self.id = auth_data.get_credentials_id() | ||
1089 | 87 | self.method = auth_data.get_method() | ||
1090 | 88 | self.mechanism = auth_data.get_mechanism() | ||
1091 | 89 | self.parameters = auth_data.get_parameters() | ||
1101 | 90 | 55 | ||
1102 | 91 | 56 | ||
1103 | 92 | class Account: | 57 | class Account: |
1104 | @@ -99,6 +64,8 @@ | |||
1105 | 99 | ) | 64 | ) |
1106 | 100 | 65 | ||
1107 | 101 | # Defaults for the known and useful attributes. | 66 | # Defaults for the known and useful attributes. |
1108 | 67 | consumer_secret = None | ||
1109 | 68 | consumer_key = None | ||
1110 | 102 | access_token = None | 69 | access_token = None |
1111 | 103 | secret_token = None | 70 | secret_token = None |
1112 | 104 | send_enabled = None | 71 | send_enabled = None |
1113 | @@ -108,15 +75,22 @@ | |||
1114 | 108 | id = None | 75 | id = None |
1115 | 109 | 76 | ||
1116 | 110 | def __init__(self, account_service): | 77 | def __init__(self, account_service): |
1119 | 111 | self.account_service = account_service | 78 | self.auth = account_service.get_auth_data() |
1120 | 112 | self.auth = AuthData(account_service.get_auth_data()) | 79 | if self.auth is not None: |
1121 | 80 | auth_params = self.auth.get_parameters() | ||
1122 | 81 | self.consumer_key = auth_params.get('ConsumerKey') | ||
1123 | 82 | self.consumer_secret = auth_params.get('ConsumerSecret') | ||
1124 | 83 | else: | ||
1125 | 84 | raise UnsupportedProtocolError( | ||
1126 | 85 | 'This AgAccountService is missing AgAuthData!') | ||
1127 | 86 | |||
1128 | 113 | # The provider in libaccounts should match the name of our protocol. | 87 | # The provider in libaccounts should match the name of our protocol. |
1129 | 114 | account = account_service.get_account() | 88 | account = account_service.get_account() |
1130 | 115 | self.id = account.id | 89 | self.id = account.id |
1133 | 116 | self.protocol_name = account.get_provider_name() | 90 | protocol_name = account.get_provider_name() |
1134 | 117 | protocol_class = protocol_manager.protocols.get(self.protocol_name) | 91 | protocol_class = protocol_manager.protocols.get(protocol_name) |
1135 | 118 | if protocol_class is None: | 92 | if protocol_class is None: |
1137 | 119 | raise UnsupportedProtocolError(self.protocol_name) | 93 | raise UnsupportedProtocolError(protocol_name) |
1138 | 120 | self.protocol = protocol_class(self) | 94 | self.protocol = protocol_class(self) |
1139 | 121 | # Connect responders to changes in the account information. | 95 | # Connect responders to changes in the account information. |
1140 | 122 | account_service.connect('changed', self._on_account_changed, account) | 96 | account_service.connect('changed', self._on_account_changed, account) |
1141 | @@ -152,24 +126,11 @@ | |||
1142 | 152 | while True: | 126 | while True: |
1143 | 153 | success, key, value = settings.next() | 127 | success, key, value = settings.next() |
1144 | 154 | if success: | 128 | if success: |
1146 | 155 | log.debug('{} got {}: {}'.format(self.id, key, value)) | 129 | log.debug('{} ({}) got {}: {}'.format( |
1147 | 130 | self.protocol._Name, self.id, key, value)) | ||
1148 | 156 | # Testing for tuple membership makes this easy to expand | 131 | # Testing for tuple membership makes this easy to expand |
1149 | 157 | # later, if necessary. | 132 | # later, if necessary. |
1150 | 158 | if key in Account._LIBACCOUNTS_PROPERTIES: | 133 | if key in Account._LIBACCOUNTS_PROPERTIES: |
1151 | 159 | setattr(self, key, value) | 134 | setattr(self, key, value) |
1152 | 160 | else: | 135 | else: |
1153 | 161 | break | 136 | break |
1154 | 162 | |||
1155 | 163 | @property | ||
1156 | 164 | def enabled(self): | ||
1157 | 165 | return self.account_service.get_enabled() | ||
1158 | 166 | |||
1159 | 167 | def __eq__(self, other): | ||
1160 | 168 | if other is None: | ||
1161 | 169 | return False | ||
1162 | 170 | return self.account_service == other.account_service | ||
1163 | 171 | |||
1164 | 172 | def __ne__(self, other): | ||
1165 | 173 | if other is None: | ||
1166 | 174 | return True | ||
1167 | 175 | return self.account_service != other.account_service | ||
1168 | 176 | 137 | ||
1169 | === modified file 'friends/utils/authentication.py' | |||
1170 | --- friends/utils/authentication.py 2013-03-13 17:34:26 +0000 | |||
1171 | +++ friends/utils/authentication.py 2013-03-25 10:33:12 +0000 | |||
1172 | @@ -23,7 +23,7 @@ | |||
1173 | 23 | import logging | 23 | import logging |
1174 | 24 | import time | 24 | import time |
1175 | 25 | 25 | ||
1177 | 26 | from gi.repository import GObject, Signon | 26 | from gi.repository import GObject, Accounts, Signon |
1178 | 27 | 27 | ||
1179 | 28 | from friends.errors import AuthorizationError | 28 | from friends.errors import AuthorizationError |
1180 | 29 | 29 | ||
1181 | @@ -37,17 +37,30 @@ | |||
1182 | 37 | LOGIN_TIMEOUT = 30 # Currently this is measured in half-seconds. | 37 | LOGIN_TIMEOUT = 30 # Currently this is measured in half-seconds. |
1183 | 38 | 38 | ||
1184 | 39 | 39 | ||
1185 | 40 | # Yes, this is not the most logical place to instantiate this, but I | ||
1186 | 41 | # couldn't do it in account.py due to cyclical import dependencies. | ||
1187 | 42 | manager = Accounts.Manager.new_for_service_type('microblogging') | ||
1188 | 43 | |||
1189 | 44 | |||
1190 | 40 | class Authentication: | 45 | class Authentication: |
1193 | 41 | def __init__(self, account): | 46 | def __init__(self, account_id): |
1194 | 42 | self.account = account | 47 | self.account_id = account_id |
1195 | 48 | account = manager.get_account(account_id) | ||
1196 | 49 | service = account.list_services()[0] | ||
1197 | 50 | self.auth = Accounts.AccountService.new( | ||
1198 | 51 | account, service).get_auth_data() | ||
1199 | 43 | self._reply = None | 52 | self._reply = None |
1200 | 44 | 53 | ||
1201 | 45 | def login(self): | 54 | def login(self): |
1204 | 46 | auth = self.account.auth | 55 | auth = self.auth |
1205 | 47 | self.auth_session = Signon.AuthSession.new(auth.id, auth.method) | 56 | self.auth_session = Signon.AuthSession.new( |
1206 | 57 | auth.get_credentials_id(), | ||
1207 | 58 | auth.get_method()) | ||
1208 | 48 | self.auth_session.process( | 59 | self.auth_session.process( |
1211 | 49 | auth.parameters, auth.mechanism, | 60 | auth.get_parameters(), |
1212 | 50 | self._login_cb, None) | 61 | auth.get_mechanism(), |
1213 | 62 | self._login_cb, | ||
1214 | 63 | None) | ||
1215 | 51 | timeout = LOGIN_TIMEOUT | 64 | timeout = LOGIN_TIMEOUT |
1216 | 52 | while self._reply is None and timeout > 0: | 65 | while self._reply is None and timeout > 0: |
1217 | 53 | # We're building a synchronous API on top of an inherently | 66 | # We're building a synchronous API on top of an inherently |
1218 | @@ -56,17 +69,17 @@ | |||
1219 | 56 | time.sleep(0.5) | 69 | time.sleep(0.5) |
1220 | 57 | timeout -= 1 | 70 | timeout -= 1 |
1221 | 58 | if self._reply is None: | 71 | if self._reply is None: |
1223 | 59 | raise AuthorizationError(self.account.id, 'Login timed out.') | 72 | raise AuthorizationError(self.account_id, 'Login timed out.') |
1224 | 60 | if 'AccessToken' not in self._reply: | 73 | if 'AccessToken' not in self._reply: |
1225 | 61 | raise AuthorizationError( | 74 | raise AuthorizationError( |
1227 | 62 | self.account.id, | 75 | self.account_id, |
1228 | 63 | 'No AccessToken found: {!r}'.format(self._reply)) | 76 | 'No AccessToken found: {!r}'.format(self._reply)) |
1229 | 64 | return self._reply | 77 | return self._reply |
1230 | 65 | 78 | ||
1231 | 66 | def _login_cb(self, session, reply, error, user_data): | 79 | def _login_cb(self, session, reply, error, user_data): |
1232 | 67 | self._reply = reply | 80 | self._reply = reply |
1233 | 68 | if error: | 81 | if error: |
1235 | 69 | exception = AuthorizationError(self.account.id, error.message) | 82 | exception = AuthorizationError(self.account_id, error.message) |
1236 | 70 | # Mardy says this error can happen during normal operation. | 83 | # Mardy says this error can happen during normal operation. |
1237 | 71 | if error.message.endswith('userActionFinished error: 10'): | 84 | if error.message.endswith('userActionFinished error: 10'): |
1238 | 72 | log.error(str(exception)) | 85 | log.error(str(exception)) |
1239 | 73 | 86 | ||
1240 | === modified file 'friends/utils/base.py' | |||
1241 | --- friends/utils/base.py 2013-03-20 01:19:39 +0000 | |||
1242 | +++ friends/utils/base.py 2013-03-25 10:33:12 +0000 | |||
1243 | @@ -460,7 +460,7 @@ | |||
1244 | 460 | log.debug('{} to {}'.format( | 460 | log.debug('{} to {}'.format( |
1245 | 461 | 'Re-authenticating' if old_token else 'Logging in', self._Name)) | 461 | 'Re-authenticating' if old_token else 'Logging in', self._Name)) |
1246 | 462 | 462 | ||
1248 | 463 | result = Authentication(self._account).login() | 463 | result = Authentication(self._account.id).login() |
1249 | 464 | 464 | ||
1250 | 465 | self._account.access_token = result.get('AccessToken') | 465 | self._account.access_token = result.get('AccessToken') |
1251 | 466 | self._whoami(result) | 466 | self._whoami(result) |
1252 | @@ -469,8 +469,8 @@ | |||
1253 | 469 | def _get_oauth_headers(self, method, url, data=None, headers=None): | 469 | def _get_oauth_headers(self, method, url, data=None, headers=None): |
1254 | 470 | """Basic wrapper around oauthlib that we use for Twitter and Flickr.""" | 470 | """Basic wrapper around oauthlib that we use for Twitter and Flickr.""" |
1255 | 471 | # "Client" == "Consumer" in oauthlib parlance. | 471 | # "Client" == "Consumer" in oauthlib parlance. |
1258 | 472 | client_key = self._account.auth.parameters['ConsumerKey'] | 472 | client_key = self._account.consumer_key |
1259 | 473 | client_secret = self._account.auth.parameters['ConsumerSecret'] | 473 | client_secret = self._account.consumer_secret |
1260 | 474 | 474 | ||
1261 | 475 | # "resource_owner" == secret and token. | 475 | # "resource_owner" == secret and token. |
1262 | 476 | resource_owner_key = self._get_access_token() | 476 | resource_owner_key = self._get_access_token() |
1263 | 477 | 477 | ||
1264 | === modified file 'tools/debug_live.py' | |||
1265 | --- tools/debug_live.py 2013-03-13 18:05:13 +0000 | |||
1266 | +++ tools/debug_live.py 2013-03-25 10:33:12 +0000 | |||
1267 | @@ -30,7 +30,7 @@ | |||
1268 | 30 | # Print all logs for debugging purposes | 30 | # Print all logs for debugging purposes |
1269 | 31 | initialize(debug=True, console=True) | 31 | initialize(debug=True, console=True) |
1270 | 32 | 32 | ||
1272 | 33 | from friends.utils.account import AccountManager | 33 | from friends.utils.account import find_accounts |
1273 | 34 | from friends.utils.base import initialize_caches, _OperationThread | 34 | from friends.utils.base import initialize_caches, _OperationThread |
1274 | 35 | from friends.utils.model import Model | 35 | from friends.utils.model import Model |
1275 | 36 | 36 | ||
1276 | @@ -53,12 +53,10 @@ | |||
1277 | 53 | 53 | ||
1278 | 54 | initialize_caches() | 54 | initialize_caches() |
1279 | 55 | 55 | ||
1280 | 56 | Model.connect('row-added', row_added) | ||
1281 | 57 | |||
1282 | 56 | found = False | 58 | found = False |
1288 | 57 | a = AccountManager() | 59 | for account in find_accounts().values(): |
1284 | 58 | |||
1285 | 59 | Model.connect('row-added', row_added) | ||
1286 | 60 | |||
1287 | 61 | for account in a._accounts.values(): | ||
1289 | 62 | if account.protocol._name == protocol.lower(): | 60 | if account.protocol._name == protocol.lower(): |
1290 | 63 | found = True | 61 | found = True |
1291 | 64 | account.protocol(*args) | 62 | account.protocol(*args) |
MP into wrong branch ;-)