Merge lp:~sergiusens/account-polld/refresh_accounts into lp:~phablet-team/account-polld/trunk
- refresh_accounts
- Merge into trunk
Proposed by
Sergio Schvezov
Status: | Merged |
---|---|
Merged at revision: | 21 |
Proposed branch: | lp:~sergiusens/account-polld/refresh_accounts |
Merge into: | lp:~phablet-team/account-polld/trunk |
Diff against target: |
634 lines (+231/-68) 12 files modified
accounts/account-watcher.c (+51/-27) accounts/account-watcher.h (+3/-0) accounts/accounts.c (+17/-2) accounts/accounts.go (+43/-12) cmd/account-polld/account_manager.go (+30/-18) cmd/account-polld/main.go (+4/-2) cmd/account-watcher-test/main.go (+1/-1) plugins/facebook/facebook.go (+3/-0) plugins/facebook/facebook_test.go (+25/-5) plugins/plugins.go (+9/-1) plugins/twitter/twitter.go (+12/-0) plugins/twitter/twitter_test.go (+33/-0) |
To merge this branch: | bzr merge lp:~sergiusens/account-polld/refresh_accounts |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Roberto Alsina | Pending | ||
Review via email: mp+228201@code.launchpad.net |
Commit message
Add support for handling token expiry
Description of the change
This is part of a series of commits from https:/
I've taken a subset from it which would be good to have now and leave the other part for review.
To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : | # |
review:
Approve
(continuous-integration)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'accounts/account-watcher.c' |
2 | --- accounts/account-watcher.c 2014-07-11 12:01:42 +0000 |
3 | +++ accounts/account-watcher.c 2014-07-24 20:44:55 +0000 |
4 | @@ -82,7 +82,7 @@ |
5 | g_free(info); |
6 | } |
7 | |
8 | -static void account_info_notify(AccountInfo *info) { |
9 | +static void account_info_notify(AccountInfo *info, GError *error) { |
10 | AgService *service = ag_account_service_get_service(info->account_service); |
11 | const char *service_name = ag_service_get_name(service); |
12 | char *client_id = NULL; |
13 | @@ -108,6 +108,7 @@ |
14 | info->watcher->callback(info->watcher, |
15 | info->account_id, |
16 | service_name, |
17 | + error, |
18 | info->enabled, |
19 | client_id, |
20 | client_secret, |
21 | @@ -124,32 +125,18 @@ |
22 | |
23 | GError *error = NULL; |
24 | info->session_data = signon_auth_session_process_finish(session, result, &error); |
25 | + account_info_notify(info, error); |
26 | |
27 | if (error != NULL) { |
28 | fprintf(stderr, "Authentication failed: %s\n", error->message); |
29 | g_error_free(error); |
30 | - return; |
31 | } |
32 | - account_info_notify(info); |
33 | } |
34 | |
35 | -static void account_info_enabled_cb( |
36 | - AgAccountService *account_service, gboolean enabled, AccountInfo *info) { |
37 | - fprintf(stderr, "account_info_enabled_cb for %u, enabled=%d\n", info->account_id, enabled); |
38 | - if (info->enabled == enabled) { |
39 | - /* no change */ |
40 | - return; |
41 | - } |
42 | - info->enabled = enabled; |
43 | - |
44 | +static void account_info_login(AccountInfo *info) { |
45 | account_info_clear_login(info); |
46 | - if (!enabled) { |
47 | - // Send notification that account has been disabled */ |
48 | - account_info_notify(info); |
49 | - return; |
50 | - } |
51 | |
52 | - AgAuthData *auth_data = ag_account_service_get_auth_data(account_service); |
53 | + AgAuthData *auth_data = ag_account_service_get_auth_data(info->account_service); |
54 | GError *error = NULL; |
55 | fprintf(stderr, "Starting authentication session for account %u\n", info->account_id); |
56 | info->session = signon_auth_session_new( |
57 | @@ -157,21 +144,15 @@ |
58 | ag_auth_data_get_method(auth_data), &error); |
59 | if (error != NULL) { |
60 | fprintf(stderr, "Could not set up auth session: %s\n", error->message); |
61 | + account_info_notify(info, error); |
62 | g_error_free(error); |
63 | g_object_unref(auth_data); |
64 | return; |
65 | } |
66 | |
67 | - GVariantBuilder builder; |
68 | - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); |
69 | - g_variant_builder_add( |
70 | - &builder, "{sv}", |
71 | - SIGNON_SESSION_DATA_UI_POLICY, |
72 | - g_variant_new_int32(SIGNON_POLICY_NO_USER_INTERACTION)); |
73 | - |
74 | info->auth_params = g_variant_ref_sink( |
75 | ag_auth_data_get_login_parameters( |
76 | - auth_data, g_variant_builder_end(&builder))); |
77 | + auth_data, NULL)); |
78 | |
79 | signon_auth_session_process_async( |
80 | info->session, |
81 | @@ -182,6 +163,24 @@ |
82 | ag_auth_data_unref(auth_data); |
83 | } |
84 | |
85 | +static void account_info_enabled_cb( |
86 | + AgAccountService *account_service, gboolean enabled, AccountInfo *info) { |
87 | + fprintf(stderr, "account_info_enabled_cb for %u, enabled=%d\n", info->account_id, enabled); |
88 | + if (info->enabled == enabled) { |
89 | + /* no change */ |
90 | + return; |
91 | + } |
92 | + info->enabled = enabled; |
93 | + |
94 | + if (enabled) { |
95 | + account_info_login(info); |
96 | + } else { |
97 | + account_info_clear_login(info); |
98 | + // Send notification that account has been disabled */ |
99 | + account_info_notify(info, NULL); |
100 | + } |
101 | +} |
102 | + |
103 | static AccountInfo *account_info_new(AccountWatcher *watcher, AgAccountService *account_service) { |
104 | AccountInfo *info = g_new0(AccountInfo, 1); |
105 | info->watcher = watcher; |
106 | @@ -258,7 +257,7 @@ |
107 | } |
108 | ag_manager_list_free(enabled_accounts); |
109 | |
110 | - return FALSE; |
111 | + return G_SOURCE_REMOVE; |
112 | } |
113 | |
114 | AccountWatcher *account_watcher_new(GHashTable *services_to_watch, |
115 | @@ -277,3 +276,28 @@ |
116 | g_idle_add(account_watcher_setup, watcher); |
117 | return watcher; |
118 | } |
119 | + |
120 | +struct refresh_info { |
121 | + AccountWatcher *watcher; |
122 | + AgAccountId account_id; |
123 | +}; |
124 | + |
125 | +static gboolean account_watcher_refresh_cb(void *user_data) { |
126 | + struct refresh_info *data = (struct refresh_info *)user_data; |
127 | + |
128 | + AccountInfo *info = g_hash_table_lookup( |
129 | + data->watcher->services, GUINT_TO_POINTER(data->account_id)); |
130 | + if (info != NULL) { |
131 | + account_info_login(info); |
132 | + } |
133 | + |
134 | + return G_SOURCE_REMOVE; |
135 | +} |
136 | + |
137 | +void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id) { |
138 | + struct refresh_info *data = g_new(struct refresh_info, 1); |
139 | + data->watcher = watcher; |
140 | + data->account_id = account_id; |
141 | + g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, account_watcher_refresh_cb, |
142 | + data, g_free); |
143 | +} |
144 | |
145 | === modified file 'accounts/account-watcher.h' |
146 | --- accounts/account-watcher.h 2014-07-11 12:01:42 +0000 |
147 | +++ accounts/account-watcher.h 2014-07-24 20:44:55 +0000 |
148 | @@ -24,6 +24,7 @@ |
149 | typedef void (*AccountEnabledCallback)(AccountWatcher *watcher, |
150 | unsigned int account_id, |
151 | const char *service_name, |
152 | + GError *error, |
153 | int enabled, |
154 | const char *client_id, |
155 | const char *client_secret, |
156 | @@ -35,4 +36,6 @@ |
157 | AccountEnabledCallback callback, |
158 | void *user_data); |
159 | |
160 | +void account_watcher_refresh(AccountWatcher *watcher, unsigned int account_id); |
161 | + |
162 | #endif |
163 | |
164 | === modified file 'accounts/accounts.c' |
165 | --- accounts/accounts.c 2014-07-11 13:44:14 +0000 |
166 | +++ accounts/accounts.c 2014-07-24 20:44:55 +0000 |
167 | @@ -1,5 +1,4 @@ |
168 | #include "_cgo_export.h" |
169 | -#include "account-watcher.h" |
170 | |
171 | AccountWatcher *watch_for_services(void *array_of_service_names, int length) { |
172 | /* Transfer service names to hash table */ |
173 | @@ -11,8 +10,24 @@ |
174 | g_hash_table_insert(services_to_watch, g_strdup(service_names[i].p), NULL); |
175 | } |
176 | |
177 | + if (FALSE) { |
178 | + /* The Go callback doesn't quite match the |
179 | + * AccountEnabledCallback function prototype, so we cast the |
180 | + * argument in the account_watcher_new() call below. |
181 | + * |
182 | + * This is just a check to see that the function still has the |
183 | + * prototype we expect. |
184 | + */ |
185 | + void (*unused)(void *watcher, |
186 | + unsigned int account_id, char *service_name, |
187 | + GError *error, int enabled, |
188 | + char *client_id, char *client_secret, |
189 | + char *access_token, char *token_secret, |
190 | + void *user_data) = authCallback; |
191 | + } |
192 | + |
193 | AccountWatcher *watcher = account_watcher_new( |
194 | - services_to_watch, authCallback, NULL); |
195 | + services_to_watch, (AccountEnabledCallback)authCallback, NULL); |
196 | g_hash_table_unref(services_to_watch); |
197 | return watcher; |
198 | } |
199 | |
200 | === modified file 'accounts/accounts.go' |
201 | --- accounts/accounts.go 2014-07-11 12:14:35 +0000 |
202 | +++ accounts/accounts.go 2014-07-24 20:44:55 +0000 |
203 | @@ -21,17 +21,26 @@ |
204 | #cgo pkg-config: glib-2.0 libaccounts-glib libsignon-glib |
205 | #include <stdlib.h> |
206 | #include <glib.h> |
207 | - |
208 | -typedef struct _AccountWatcher AccountWatcher; |
209 | +#include "account-watcher.h" |
210 | |
211 | AccountWatcher *watch_for_services(void *array_of_service_names, int length); |
212 | */ |
213 | import "C" |
214 | -import "unsafe" |
215 | +import ( |
216 | + "errors" |
217 | + "sync" |
218 | + "unsafe" |
219 | +) |
220 | + |
221 | +type Watcher struct { |
222 | + C <-chan AuthData |
223 | + watcher *C.AccountWatcher |
224 | +} |
225 | |
226 | type AuthData struct { |
227 | AccountId uint |
228 | ServiceName string |
229 | + Error error |
230 | Enabled bool |
231 | |
232 | ClientId string |
233 | @@ -41,28 +50,47 @@ |
234 | } |
235 | |
236 | var ( |
237 | - mainLoop *C.GMainLoop |
238 | + mainLoopOnce sync.Once |
239 | authChannels = make(map[*C.AccountWatcher]chan<- AuthData) |
240 | + authChannelsLock sync.Mutex |
241 | ) |
242 | |
243 | -func WatchForService(serviceNames... string) <-chan AuthData { |
244 | - if mainLoop == nil { |
245 | - mainLoop = C.g_main_loop_new(nil, C.gboolean(1)) |
246 | +func startMainLoop() { |
247 | + mainLoopOnce.Do(func() { |
248 | + mainLoop := C.g_main_loop_new(nil, C.gboolean(1)) |
249 | go C.g_main_loop_run(mainLoop) |
250 | - } |
251 | + }) |
252 | +} |
253 | |
254 | - watcher := C.watch_for_services(unsafe.Pointer(&serviceNames[0]), C.int(len(serviceNames))) |
255 | +// NewWatcher creates a new account watcher for the given service names |
256 | +func NewWatcher(serviceNames... string) *Watcher { |
257 | + w := new(Watcher) |
258 | + w.watcher = C.watch_for_services(unsafe.Pointer(&serviceNames[0]), C.int(len(serviceNames))) |
259 | |
260 | ch := make(chan AuthData) |
261 | - authChannels[watcher] = ch |
262 | - return ch |
263 | + w.C = ch |
264 | + authChannelsLock.Lock() |
265 | + authChannels[w.watcher] = ch |
266 | + authChannelsLock.Unlock() |
267 | + |
268 | + startMainLoop() |
269 | + |
270 | + return w |
271 | +} |
272 | + |
273 | +// Refresh requests that the token for the given account be refreshed. |
274 | +// The new access token will be delivered over the watcher's channel. |
275 | +func (w *Watcher) Refresh(accountId uint) { |
276 | + C.account_watcher_refresh(w.watcher, C.uint(accountId)) |
277 | } |
278 | |
279 | //export authCallback |
280 | -func authCallback(watcher unsafe.Pointer, accountId C.uint, serviceName *C.char, enabled C.int, clientId, clientSecret, accessToken, tokenSecret *C.char, userData unsafe.Pointer) { |
281 | +func authCallback(watcher unsafe.Pointer, accountId C.uint, serviceName *C.char, error *C.GError, enabled C.int, clientId, clientSecret, accessToken, tokenSecret *C.char, userData unsafe.Pointer) { |
282 | // Ideally the first argument would be of type |
283 | // *C.AccountWatcher, but that fails with Go 1.2. |
284 | + authChannelsLock.Lock() |
285 | ch := authChannels[(*C.AccountWatcher)(watcher)] |
286 | + authChannelsLock.Unlock() |
287 | if ch == nil { |
288 | // Log the error |
289 | return |
290 | @@ -71,6 +99,9 @@ |
291 | var data AuthData |
292 | data.AccountId = uint(accountId) |
293 | data.ServiceName = C.GoString(serviceName) |
294 | + if error != nil { |
295 | + data.Error = errors.New(C.GoString((*C.char)(error.message))) |
296 | + } |
297 | if enabled != 0 { |
298 | data.Enabled = true |
299 | } |
300 | |
301 | === modified file 'cmd/account-polld/account_manager.go' |
302 | --- cmd/account-polld/account_manager.go 2014-07-17 18:35:36 +0000 |
303 | +++ cmd/account-polld/account_manager.go 2014-07-24 20:44:55 +0000 |
304 | @@ -19,7 +19,6 @@ |
305 | |
306 | import ( |
307 | "log" |
308 | - "sync" |
309 | "time" |
310 | |
311 | "launchpad.net/account-polld/accounts" |
312 | @@ -27,59 +26,74 @@ |
313 | ) |
314 | |
315 | type AccountManager struct { |
316 | + watcher *accounts.Watcher |
317 | authData accounts.AuthData |
318 | - authMutex *sync.Mutex |
319 | plugin plugins.Plugin |
320 | interval time.Duration |
321 | postWatch chan *PostWatch |
322 | - terminate chan bool |
323 | + authChan chan accounts.AuthData |
324 | } |
325 | |
326 | const DEFAULT_INTERVAL = time.Duration(60 * time.Second) |
327 | |
328 | -func NewAccountManager(authData accounts.AuthData, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountManager { |
329 | +func NewAccountManager(watcher *accounts.Watcher, postWatch chan *PostWatch, plugin plugins.Plugin) *AccountManager { |
330 | return &AccountManager{ |
331 | + watcher: watcher, |
332 | plugin: plugin, |
333 | - authData: authData, |
334 | - authMutex: &sync.Mutex{}, |
335 | + interval: DEFAULT_INTERVAL, |
336 | postWatch: postWatch, |
337 | - interval: DEFAULT_INTERVAL, |
338 | - terminate: make(chan bool), |
339 | + authChan: make(chan accounts.AuthData, 1), |
340 | } |
341 | } |
342 | |
343 | func (a *AccountManager) Delete() { |
344 | - a.terminate <- true |
345 | + close(a.authChan) |
346 | } |
347 | |
348 | func (a *AccountManager) Loop() { |
349 | - defer close(a.terminate) |
350 | + var ok bool |
351 | + if a.authData, ok = <- a.authChan; !ok { |
352 | + return |
353 | + } |
354 | L: |
355 | for { |
356 | log.Println("Polling set to", a.interval, "for", a.authData.AccountId) |
357 | select { |
358 | case <-time.After(a.interval): |
359 | a.poll() |
360 | - case <-a.terminate: |
361 | - break L |
362 | + case a.authData, ok = <-a.authChan: |
363 | + if !ok { |
364 | + break L |
365 | + } |
366 | + a.poll() |
367 | } |
368 | } |
369 | log.Printf("Ending poll loop for account %d", a.authData.AccountId) |
370 | } |
371 | |
372 | func (a *AccountManager) poll() { |
373 | - a.authMutex.Lock() |
374 | - defer a.authMutex.Unlock() |
375 | - |
376 | if !a.authData.Enabled { |
377 | log.Println("Account", a.authData.AccountId, "no longer enabled") |
378 | return |
379 | } |
380 | |
381 | + if a.authData.Error != nil { |
382 | + log.Println("Account", a.authData.AccountId, "failed to authenticate:", a.authData.Error) |
383 | + return |
384 | + } |
385 | + |
386 | if n, err := a.plugin.Poll(&a.authData); err != nil { |
387 | log.Print("Error while polling ", a.authData.AccountId, ": ", err) |
388 | // penalizing the next poll |
389 | a.interval += DEFAULT_INTERVAL |
390 | + |
391 | + // If the error indicates that the authentication |
392 | + // token has expired, request reauthentication and |
393 | + // mark data as disabled. |
394 | + if err == plugins.ErrTokenExpired { |
395 | + a.watcher.Refresh(a.authData.AccountId) |
396 | + a.authData.Enabled = false |
397 | + } |
398 | } else if len(n) > 0 { |
399 | // on success we reset the timeout to the default interval |
400 | a.interval = DEFAULT_INTERVAL |
401 | @@ -88,7 +102,5 @@ |
402 | } |
403 | |
404 | func (a *AccountManager) updateAuthData(authData accounts.AuthData) { |
405 | - a.authMutex.Lock() |
406 | - defer a.authMutex.Unlock() |
407 | - a.authData = authData |
408 | + a.authChan <- authData |
409 | } |
410 | |
411 | === modified file 'cmd/account-polld/main.go' |
412 | --- cmd/account-polld/main.go 2014-07-18 11:42:52 +0000 |
413 | +++ cmd/account-polld/main.go 2014-07-24 20:44:55 +0000 |
414 | @@ -69,9 +69,10 @@ |
415 | } |
416 | |
417 | func monitorAccounts(postWatch chan *PostWatch) { |
418 | + watcher := accounts.NewWatcher(SERVICENAME_GMAIL, SERVICENAME_FACEBOOK, SERVICENAME_TWITTER) |
419 | mgr := make(map[uint]*AccountManager) |
420 | L: |
421 | - for data := range accounts.WatchForService(SERVICENAME_GMAIL, SERVICENAME_FACEBOOK, SERVICENAME_TWITTER) { |
422 | + for data := range watcher.C { |
423 | if account, ok := mgr[data.AccountId]; ok { |
424 | if data.Enabled { |
425 | log.Printf("New account data for %d - was %#v, now is %#v", data.AccountId, account.authData, data) |
426 | @@ -98,7 +99,8 @@ |
427 | log.Println("Unhandled account with id", data.AccountId, "for", data.ServiceName) |
428 | continue L |
429 | } |
430 | - mgr[data.AccountId] = NewAccountManager(data, postWatch, plugin) |
431 | + mgr[data.AccountId] = NewAccountManager(watcher, postWatch, plugin) |
432 | + mgr[data.AccountId].updateAuthData(data) |
433 | go mgr[data.AccountId].Loop() |
434 | } |
435 | } |
436 | |
437 | === modified file 'cmd/account-watcher-test/main.go' |
438 | --- cmd/account-watcher-test/main.go 2014-07-11 12:07:47 +0000 |
439 | +++ cmd/account-watcher-test/main.go 2014-07-24 20:44:55 +0000 |
440 | @@ -9,7 +9,7 @@ |
441 | |
442 | func main() { |
443 | // Expects a list of service names as command line arguments |
444 | - for data := range accounts.WatchForService(os.Args[1:]...) { |
445 | + for data := range accounts.NewWatcher(os.Args[1:]...).C { |
446 | fmt.Printf("%#v\n", data) |
447 | } |
448 | } |
449 | |
450 | === modified file 'plugins/facebook/facebook.go' |
451 | --- plugins/facebook/facebook.go 2014-07-17 18:35:36 +0000 |
452 | +++ plugins/facebook/facebook.go 2014-07-24 20:44:55 +0000 |
453 | @@ -62,6 +62,9 @@ |
454 | if err := decoder.Decode(&result); err != nil { |
455 | return nil, err |
456 | } |
457 | + if result.Error.Code == 190 { |
458 | + return nil, plugins.ErrTokenExpired |
459 | + } |
460 | return nil, &result.Error |
461 | } |
462 | |
463 | |
464 | === modified file 'plugins/facebook/facebook_test.go' |
465 | --- plugins/facebook/facebook_test.go 2014-07-18 11:38:36 +0000 |
466 | +++ plugins/facebook/facebook_test.go 2014-07-24 20:44:55 +0000 |
467 | @@ -23,6 +23,8 @@ |
468 | "testing" |
469 | |
470 | . "launchpad.net/gocheck" |
471 | + |
472 | + "launchpad.net/account-polld/plugins" |
473 | ) |
474 | |
475 | type S struct{} |
476 | @@ -48,10 +50,18 @@ |
477 | errorBody = ` |
478 | { |
479 | "error": { |
480 | - "message": "Message describing the error", |
481 | + "message": "Unknown path components: /xyz", |
482 | + "type": "OAuthException", |
483 | + "code": 2500 |
484 | + } |
485 | +}` |
486 | + tokenExpiredErrorBody = ` |
487 | +{ |
488 | + "error": { |
489 | + "message": "Error validating access token: Session has expired", |
490 | "type": "OAuthException", |
491 | "code": 190 , |
492 | - "error_subcode": 460 |
493 | + "error_subcode": 463 |
494 | } |
495 | }` |
496 | notificationsBody = ` |
497 | @@ -151,7 +161,17 @@ |
498 | c.Check(notifications, IsNil) |
499 | c.Assert(err, Not(IsNil)) |
500 | graphErr := err.(*GraphError) |
501 | - c.Check(graphErr.Message, Equals, "Message describing the error") |
502 | - c.Check(graphErr.Code, Equals, 190) |
503 | - c.Check(graphErr.Subcode, Equals, 460) |
504 | + c.Check(graphErr.Message, Equals, "Unknown path components: /xyz") |
505 | + c.Check(graphErr.Code, Equals, 2500) |
506 | +} |
507 | + |
508 | +func (s S) TestTokenExpiredErrorResponse(c *C) { |
509 | + resp := &http.Response{ |
510 | + StatusCode: http.StatusBadRequest, |
511 | + Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))}, |
512 | + } |
513 | + p := &fbPlugin{} |
514 | + notifications, err := p.parseResponse(resp) |
515 | + c.Check(notifications, IsNil) |
516 | + c.Assert(err, Equals, plugins.ErrTokenExpired) |
517 | } |
518 | |
519 | === modified file 'plugins/plugins.go' |
520 | --- plugins/plugins.go 2014-07-17 18:35:36 +0000 |
521 | +++ plugins/plugins.go 2014-07-24 20:44:55 +0000 |
522 | @@ -17,7 +17,11 @@ |
523 | |
524 | package plugins |
525 | |
526 | -import "launchpad.net/account-polld/accounts" |
527 | +import ( |
528 | + "errors" |
529 | + |
530 | + "launchpad.net/account-polld/accounts" |
531 | +) |
532 | |
533 | // Plugin is an interface which the plugins will adhere to for the poll |
534 | // daemon to interact with. |
535 | @@ -122,3 +126,7 @@ |
536 | PLUGIN_EMAIL = 0 |
537 | PLUGIN_SOCIAL |
538 | ) |
539 | + |
540 | +// ErrTokenExpired is the error returned by a plugin to indicate that |
541 | +// the web service reported that the authentication token has expired. |
542 | +var ErrTokenExpired = errors.New("Token expired") |
543 | |
544 | === modified file 'plugins/twitter/twitter.go' |
545 | --- plugins/twitter/twitter.go 2014-07-23 10:35:13 +0000 |
546 | +++ plugins/twitter/twitter.go 2014-07-24 20:44:55 +0000 |
547 | @@ -77,6 +77,12 @@ |
548 | if err := decoder.Decode(&result); err != nil { |
549 | return nil, err |
550 | } |
551 | + // Error code 89 is used for invalid or expired tokens. |
552 | + for _, e := range result.Errors { |
553 | + if e.Code == 89 { |
554 | + return nil, plugins.ErrTokenExpired |
555 | + } |
556 | + } |
557 | return nil, &result |
558 | } |
559 | |
560 | @@ -113,6 +119,12 @@ |
561 | if err := decoder.Decode(&result); err != nil { |
562 | return nil, err |
563 | } |
564 | + // Error code 89 is used for invalid or expired tokens. |
565 | + for _, e := range result.Errors { |
566 | + if e.Code == 89 { |
567 | + return nil, plugins.ErrTokenExpired |
568 | + } |
569 | + } |
570 | return nil, &result |
571 | } |
572 | |
573 | |
574 | === modified file 'plugins/twitter/twitter_test.go' |
575 | --- plugins/twitter/twitter_test.go 2014-07-18 11:37:04 +0000 |
576 | +++ plugins/twitter/twitter_test.go 2014-07-24 20:44:55 +0000 |
577 | @@ -23,6 +23,8 @@ |
578 | "testing" |
579 | |
580 | . "launchpad.net/gocheck" |
581 | + |
582 | + "launchpad.net/account-polld/plugins" |
583 | ) |
584 | |
585 | type S struct{} |
586 | @@ -54,6 +56,15 @@ |
587 | } |
588 | ] |
589 | }` |
590 | + tokenExpiredErrorBody = ` |
591 | +{ |
592 | + "errors": [ |
593 | + { |
594 | + "message":"Invalid or expired token", |
595 | + "code":89 |
596 | + } |
597 | + ] |
598 | +}` |
599 | statusesBody = ` |
600 | [ |
601 | { |
602 | @@ -425,6 +436,17 @@ |
603 | c.Check(twErr.Errors[0].Code, Equals, 34) |
604 | } |
605 | |
606 | +func (s S) TestParseStatusesTokenExpiredError(c *C) { |
607 | + resp := &http.Response{ |
608 | + StatusCode: http.StatusBadRequest, |
609 | + Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))}, |
610 | + } |
611 | + p := &twitterPlugin{} |
612 | + messages, err := p.parseStatuses(resp) |
613 | + c.Check(messages, IsNil) |
614 | + c.Assert(err, Equals, plugins.ErrTokenExpired) |
615 | +} |
616 | + |
617 | func (s S) TestParseDirectMessages(c *C) { |
618 | resp := &http.Response{ |
619 | StatusCode: http.StatusOK, |
620 | @@ -453,3 +475,14 @@ |
621 | c.Check(twErr.Errors[0].Message, Equals, "Sorry, that page does not exist") |
622 | c.Check(twErr.Errors[0].Code, Equals, 34) |
623 | } |
624 | + |
625 | +func (s S) TestParseDirectMessagesTokenExpiredError(c *C) { |
626 | + resp := &http.Response{ |
627 | + StatusCode: http.StatusBadRequest, |
628 | + Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))}, |
629 | + } |
630 | + p := &twitterPlugin{} |
631 | + messages, err := p.parseDirectMessages(resp) |
632 | + c.Check(messages, IsNil) |
633 | + c.Assert(err, Equals, plugins.ErrTokenExpired) |
634 | +} |
PASSED: Continuous integration, rev:22 jenkins. qa.ubuntu. com/job/ account- polld-ci/ 11/ jenkins. qa.ubuntu. com/job/ account- polld-utopic- amd64-ci/ 11 jenkins. qa.ubuntu. com/job/ account- polld-utopic- armhf-ci/ 11 jenkins. qa.ubuntu. com/job/ account- polld-utopic- armhf-ci/ 11/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ account- polld-utopic- i386-ci/ 11
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/account- polld-ci/ 11/rebuild
http://