Merge lp:~mardy/account-polld/lp1493733 into lp:~ubuntu-push-hackers/account-polld/trunk

Proposed by Alberto Mardegan
Status: Merged
Approved by: Jonas G. Drange
Approved revision: 140
Merged at revision: 141
Proposed branch: lp:~mardy/account-polld/lp1493733
Merge into: lp:~ubuntu-push-hackers/account-polld/trunk
Diff against target: 1374 lines (+16/-1296)
6 files modified
cmd/account-polld/main.go (+0/-6)
debian/account-polld.conf (+1/-1)
debian/changelog (+14/-0)
debian/control (+1/-1)
plugins/facebook/facebook.go (+0/-453)
plugins/facebook/facebook_test.go (+0/-835)
To merge this branch: bzr merge lp:~mardy/account-polld/lp1493733
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Jonas G. Drange (community) Needs Information
Review via email: mp+270503@code.launchpad.net

Commit message

Remove non working Facebook plugin

Facebook removed the possibility to retrieve notifications, effective from October 6, 2015. From https://developers.facebook.com/docs/apps/changelog/#v2_4:

"The GET /v2.4/{user_id}/home, GET /v2.4/{user_id}/inbox, and GET /v2.4/{user_id}/notifications operations as well as read_stream, read_mailbox, and manage_notifications permissions are deprecated in v2.4."

and

"From October 6, 2015 onwards, in all previous API versions, these endpoints will return empty arrays"

This change contains a couple of packaging changes:
* debian/control, debian/account-polld.conf:
  Remove mentions of Facebook from the descriptions.

Description of the change

Remove non working Facebook plugin

Facebook removed the possibility to retrieve notifications, effective from October 6, 2015. From https://developers.facebook.com/docs/apps/changelog/#v2_4:

"The GET /v2.4/{user_id}/home, GET /v2.4/{user_id}/inbox, and GET /v2.4/{user_id}/notifications operations as well as read_stream, read_mailbox, and manage_notifications permissions are deprecated in v2.4."

and

"From October 6, 2015 onwards, in all previous API versions, these endpoints will return empty arrays"

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:~mardy/account-polld/lp1493733 updated
139. By Alberto Mardegan

Sync changelog from archive

No-change test rebuild for g++5 ABI transition

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Jonas G. Drange (jonas-drange) wrote :

Could you make a note of why the files under debian/ changed, as I think it's required for landing. Thanks!

review: Needs Information
lp:~mardy/account-polld/lp1493733 updated
140. By Alberto Mardegan

Update changelog

* Remove non-working facebook integration. (LP: #1493733)
* debian/control, debian/account-polld.conf:
  Remove mentions of Facebook from the descriptions.

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
=== modified file 'cmd/account-polld/main.go'
--- cmd/account-polld/main.go 2015-03-20 17:51:02 +0000
+++ cmd/account-polld/main.go 2015-10-12 13:54:21 +0000
@@ -27,7 +27,6 @@
27 "launchpad.net/account-polld/accounts"27 "launchpad.net/account-polld/accounts"
28 "launchpad.net/account-polld/gettext"28 "launchpad.net/account-polld/gettext"
29 "launchpad.net/account-polld/plugins"29 "launchpad.net/account-polld/plugins"
30 "launchpad.net/account-polld/plugins/facebook"
31 "launchpad.net/account-polld/plugins/gmail"30 "launchpad.net/account-polld/plugins/gmail"
32 "launchpad.net/account-polld/plugins/twitter"31 "launchpad.net/account-polld/plugins/twitter"
33 "launchpad.net/account-polld/pollbus"32 "launchpad.net/account-polld/pollbus"
@@ -47,7 +46,6 @@
4746
48 SERVICENAME_GMAIL = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail"47 SERVICENAME_GMAIL = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail"
49 SERVICENAME_TWITTER = "com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter"48 SERVICENAME_TWITTER = "com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter"
50 SERVICENAME_FACEBOOK = "com.ubuntu.developer.webapps.webapp-facebook_webapp-facebook"
51)49)
5250
53const (51const (
@@ -119,10 +117,6 @@
119 case SERVICENAME_GMAIL:117 case SERVICENAME_GMAIL:
120 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)118 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
121 plugin = gmail.New(data.AccountId)119 plugin = gmail.New(data.AccountId)
122 case SERVICENAME_FACEBOOK:
123 // This is just stubbed until the plugin exists.
124 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
125 plugin = facebook.New(data.AccountId)
126 case SERVICENAME_TWITTER:120 case SERVICENAME_TWITTER:
127 // This is just stubbed until the plugin exists.121 // This is just stubbed until the plugin exists.
128 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)122 log.Println("Creating account with id", data.AccountId, "for", data.ServiceName)
129123
=== modified file 'debian/account-polld.conf'
--- debian/account-polld.conf 2014-08-06 14:29:13 +0000
+++ debian/account-polld.conf 2015-10-12 13:54:21 +0000
@@ -1,4 +1,4 @@
1description "account-polld checks for notifications for twitter, facebook and gmail and passes them on to the Ubuntu Push Postal service"1description "account-polld checks for notifications for twitter and gmail and passes them on to the Ubuntu Push Postal service"
22
3start on started ubuntu-push-client3start on started ubuntu-push-client
4stop on stopping ubuntu-push-client4stop on stopping ubuntu-push-client
55
=== modified file 'debian/changelog'
--- debian/changelog 2015-04-10 17:19:48 +0000
+++ debian/changelog 2015-10-12 13:54:21 +0000
@@ -1,3 +1,17 @@
1account-polld (0.1+15.04.20151012-0ubuntu1) UNRELEASED; urgency=medium
2
3 * Remove non-working facebook integration. (LP: #1493733)
4 * debian/control, debian/account-polld.conf:
5 Remove mentions of Facebook from the descriptions.
6
7 -- Alberto Mardegan <alberto.mardegan@canonical.com> Mon, 12 Oct 2015 16:49:52 +0300
8
9account-polld (0.1+15.04.20150410-0ubuntu2~gcc5.1) wily; urgency=medium
10
11 * No-change test rebuild for g++5 ABI transition
12
13 -- Steve Langasek <steve.langasek@ubuntu.com> Wed, 15 Jul 2015 07:18:29 +0000
14
1account-polld (0.1+15.04.20150410-0ubuntu1) vivid; urgency=medium15account-polld (0.1+15.04.20150410-0ubuntu1) vivid; urgency=medium
216
3 [ John R. Lenton ]17 [ John R. Lenton ]
418
=== modified file 'debian/control'
--- debian/control 2014-08-24 00:04:44 +0000
+++ debian/control 2015-10-12 13:54:21 +0000
@@ -29,7 +29,7 @@
29Built-Using: ${misc:Built-Using}29Built-Using: ${misc:Built-Using}
30Recommends: accountsservice,30Recommends: accountsservice,
31Description: Poll daemon for notifications though the Ubuntu Push Client31Description: Poll daemon for notifications though the Ubuntu Push Client
32 This component polls facebook, twitter and gmail for updates and32 This component polls twitter and gmail for updates and
33 communicates with the postal service provided by the ubuntu push client33 communicates with the postal service provided by the ubuntu push client
34 to expose notifications for the click webapps for the aforementioned34 to expose notifications for the click webapps for the aforementioned
35 services.35 services.
3636
=== removed directory 'plugins/facebook'
=== removed file 'plugins/facebook/facebook.go'
--- plugins/facebook/facebook.go 2015-03-20 14:34:48 +0000
+++ plugins/facebook/facebook.go 1970-01-01 00:00:00 +0000
@@ -1,453 +0,0 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package facebook
18
19import (
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "net/url"
24 "os"
25 "strings"
26 "time"
27
28 "log"
29
30 "launchpad.net/account-polld/accounts"
31 "launchpad.net/account-polld/gettext"
32 "launchpad.net/account-polld/plugins"
33)
34
35const (
36 facebookTime = "2006-01-02T15:04:05-0700"
37 maxIndividualNotifications = 2
38 consolidatedNotificationsIndexStart = maxIndividualNotifications
39 maxIndividualThreads = 2
40 consolidatedThreadsIndexStart = maxIndividualThreads
41 timeOffset = -5
42 pluginName = "facebook"
43)
44
45var baseUrl, _ = url.Parse("https://graph.facebook.com/v2.0/")
46
47type timeStamp string
48
49func (state fbState) persist(accountId uint) (err error) {
50 err = plugins.Persist(pluginName, accountId, state)
51 if err != nil {
52 log.Print("facebook plugin", accountId, ": failed to save state: ", err)
53 return err
54 }
55 return nil
56}
57
58func stateFromStorage(accountId uint) (state fbState, err error) {
59 err = plugins.FromPersist(pluginName, accountId, &state)
60 if err != nil {
61 return state, err
62 }
63 if _, err := time.Parse(facebookTime, string(state.LastUpdate)); err != nil {
64 return state, err
65 }
66 if _, err := time.Parse(facebookTime, string(state.LastInboxUpdate)); err != nil {
67 return state, err
68 }
69 return state, nil
70}
71
72type fbState struct {
73 LastUpdate timeStamp `json:"last_notification_update"`
74 LastInboxUpdate timeStamp `json:"last_inbox_update"`
75}
76
77type fbPlugin struct {
78 state fbState
79 accountId uint
80}
81
82var doRequest = request
83
84func request(authData *accounts.AuthData, path string) (*http.Response, error) {
85 // Resolve path relative to Graph API base URL, and add access token
86 u, err := baseUrl.Parse(path)
87 if err != nil {
88 return nil, err
89 }
90 query := u.Query()
91 query.Add("access_token", authData.AccessToken)
92 u.RawQuery = query.Encode()
93
94 return http.Get(u.String())
95}
96
97func New(accountId uint) plugins.Plugin {
98 state, err := stateFromStorage(accountId)
99 if err != nil {
100 log.Print("facebook plugin ", accountId, ": cannot load previous state from storage: ", err)
101 } else {
102 log.Print("facebook plugin ", accountId, ": last state loaded from storage")
103 }
104 return &fbPlugin{state: state, accountId: accountId}
105}
106
107func (p *fbPlugin) ApplicationId() plugins.ApplicationId {
108 return "com.ubuntu.developer.webapps.webapp-facebook_webapp-facebook"
109}
110
111func (p *fbPlugin) decodeResponse(resp *http.Response, result interface{}) error {
112 defer resp.Body.Close()
113 decoder := json.NewDecoder(resp.Body)
114 if resp.StatusCode != http.StatusOK {
115 var result errorDoc
116 if err := decoder.Decode(&result); err != nil {
117 return err
118 }
119 if result.Error.Code == 190 {
120 return plugins.ErrTokenExpired
121 }
122 return &result.Error
123 }
124 // TODO: Follow the "paging.next" link if we get more than one
125 // page full of notifications.
126 return decoder.Decode(result)
127}
128
129func (p *fbPlugin) filterNotifications(doc Document, lastUpdate *timeStamp) []Notification {
130 var validNotifications []Notification
131 latestUpdate := *lastUpdate
132 for i := 0; i < doc.size(); i++ {
133 n := doc.notification(i)
134 if !n.isValid(*lastUpdate) {
135 log.Println("facebook plugin: skipping:", n)
136 } else {
137 log.Println("facebook plugin: valid:", n)
138 validNotifications = append(validNotifications, n) // get the actual reference, not the copy
139 if n.updatedTime() > latestUpdate {
140 latestUpdate = n.updatedTime()
141 }
142 }
143 }
144 *lastUpdate = latestUpdate
145 p.state.persist(p.accountId)
146 return validNotifications
147}
148
149func (p *fbPlugin) buildPushMessages(notifications []Notification, doc Document, max int, consolidatedIndexStart int) *plugins.PushMessageBatch {
150 pushMsg := make([]*plugins.PushMessage, len(notifications))
151 for i, n := range notifications {
152 pushMsg[i] = n.buildPushMessage()
153 }
154 return &plugins.PushMessageBatch{
155 Messages: pushMsg,
156 Limit: max,
157 OverflowHandler: doc.handleOverflow,
158 Tag: doc.getTag(),
159 }
160}
161
162func (p *fbPlugin) parseResponse(resp *http.Response) (*plugins.PushMessageBatch, error) {
163 var result notificationDoc
164 if err := p.decodeResponse(resp, &result); err != nil {
165 return nil, err
166 }
167 // TODO filter out of date messages before operating?
168 validNotifications := p.filterNotifications(&result, &p.state.LastUpdate)
169 pushMsgs := p.buildPushMessages(validNotifications, &result, maxIndividualNotifications, consolidatedNotificationsIndexStart)
170 return pushMsgs, nil
171}
172
173func (p *fbPlugin) parseInboxResponse(resp *http.Response) (*plugins.PushMessageBatch, error) {
174 var result inboxDoc
175 if err := p.decodeResponse(resp, &result); err != nil {
176 return nil, err
177 }
178 validThreads := p.filterNotifications(&result, &p.state.LastInboxUpdate)
179 pushMsgs := p.buildPushMessages(validThreads, &result, maxIndividualThreads, consolidatedThreadsIndexStart)
180 return pushMsgs, nil
181}
182
183func (p *fbPlugin) getNotifications(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) {
184 resp, err := doRequest(authData, "me/notifications")
185 if err != nil {
186 log.Println("facebook plugin: notifications poll failed: ", err)
187 return nil, err
188 }
189 notifications, err := p.parseResponse(resp)
190 if err != nil {
191 log.Println("facebook plugin: failed to parse notification response: ", err)
192 return nil, err
193 }
194 return notifications, nil
195}
196
197func (p *fbPlugin) getInbox(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) {
198 resp, err := doRequest(authData, "me/inbox?fields=unread,unseen,comments.limit(1)")
199 if err != nil {
200 log.Println("facebook plugin: inbox poll failed: ", err)
201 return nil, err
202 }
203 inbox, err := p.parseInboxResponse(resp)
204 if err != nil {
205 log.Println("facebook plugin: failed to parse inbox response: ", err)
206 return nil, err
207 }
208 return inbox, nil
209}
210
211func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) {
212 // This envvar check is to ease testing.
213 if token := os.Getenv("ACCOUNT_POLLD_TOKEN_FACEBOOK"); token != "" {
214 authData.AccessToken = token
215 }
216 notifications, notifErr := p.getNotifications(authData)
217 inbox, inboxErr := p.getInbox(authData)
218 // only return error if both requests failed
219 if notifErr != nil && inboxErr != nil {
220 return nil, fmt.Errorf("poll failed with '%s' and '%s'", notifErr, inboxErr)
221 }
222 if notifErr != nil {
223 return []*plugins.PushMessageBatch{inbox}, nil
224 }
225 if inboxErr != nil {
226 return []*plugins.PushMessageBatch{notifications}, nil
227 }
228 return []*plugins.PushMessageBatch{notifications, inbox}, nil
229}
230
231func toEpoch(stamp timeStamp) int64 {
232 if t, err := time.Parse(facebookTime, string(stamp)); err == nil {
233 return t.Unix()
234 }
235 return time.Now().Unix()
236}
237
238// The notifications response format is described here:
239// https://developers.facebook.com/docs/graph-api/reference/v2.0/user/notifications/
240type notificationDoc struct {
241 Data []notification `json:"data"`
242 Paging struct {
243 Previous string `json:"previous"`
244 Next string `json:"next"`
245 } `json:"paging"`
246}
247
248type notification struct {
249 Id string `json:"id"`
250 From object `json:"from"`
251 To object `json:"to"`
252 CreatedTime timeStamp `json:"created_time"`
253 UpdatedTime timeStamp `json:"updated_time"`
254 Title string `json:"title"`
255 Link string `json:"link"`
256 Application object `json:"application"`
257 Unread int `json:"unread"`
258 Object object `json:"object"`
259}
260
261func picture(msgId string, id string) string {
262 u, err := baseUrl.Parse(fmt.Sprintf("%s/picture", id))
263 if err != nil {
264 log.Println("facebook plugin: cannot get picture for", msgId)
265 return ""
266 }
267 query := u.Query()
268 query.Add("redirect", "true")
269 u.RawQuery = query.Encode()
270 return u.String()
271}
272
273type object struct {
274 Id string `json:"id"`
275 Name string `json:"name"`
276}
277
278// The error response format is described here:
279// https://developers.facebook.com/docs/graph-api/using-graph-api/v2.0#errors
280type errorDoc struct {
281 Error GraphError `json:"error"`
282}
283
284type GraphError struct {
285 Message string `json:"message"`
286 Type string `json:"type"`
287 Code int `json:"code"`
288 Subcode int `json:"error_subcode"`
289}
290
291func (err *GraphError) Error() string {
292 return err.Message
293}
294
295// The inbox response format is described here:
296// https://developers.facebook.com/docs/graph-api/reference/v2.0/user/inbox
297type inboxDoc struct {
298 Data []thread `json:"data"`
299 Paging struct {
300 Previous string `json:"previous"`
301 Next string `json:"next"`
302 } `json:"paging"`
303 Summary summary `json:"summary"`
304}
305
306type summary struct {
307 UnseenCount int `json:"unseen_count"`
308 UnreadCount int `json:"unread_count"`
309 UpdatedTime timeStamp `json:"updated_time"`
310}
311
312type thread struct {
313 Id string `json:"id"`
314 Comments comments `json:"comments"`
315 To []object `json:"to"`
316 Unread int `json:"unread"`
317 Unseen int `json:"unseen"`
318 UpdatedTime timeStamp `json:"updated_time"`
319 Paging struct {
320 Previous string `json:"previous"`
321 Next string `json:"next"`
322 } `json:"paging"`
323}
324
325type comments struct {
326 Data []message `json:"data"`
327 Paging struct {
328 Previous string `json:"previous"`
329 Next string `json:"next"`
330 } `json:"paging"`
331}
332
333type message struct {
334 CreatedTime timeStamp `json:"created_time"`
335 From object `json:"from"`
336 Id string `json:"id"`
337 Message string `json:message`
338}
339
340func (doc *inboxDoc) getTag() string {
341 return "inbox"
342}
343
344func (doc *inboxDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
345 usernames := []string{}
346 for _, m := range pushMsg {
347 usernames = append(usernames, m.Notification.Card.Summary)
348 if len(usernames) > 10 {
349 usernames[10] = "…"
350 break
351 }
352 }
353 // TRANSLATORS: This represents a message summary about more facebook messages
354 summary := gettext.Gettext("Multiple more messages")
355 // TRANSLATORS: This represents a message body with the comma separated facebook usernames
356 body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(usernames, ", "))
357 action := "https://m.facebook.com/messages"
358 epoch := time.Now().Unix()
359 return plugins.NewStandardPushMessage(summary, body, action, "", epoch)
360}
361
362func (doc *inboxDoc) size() int {
363 return len(doc.Data)
364}
365
366func (doc *inboxDoc) notification(idx int) Notification {
367 return &doc.Data[idx]
368}
369
370func (t *thread) buildPushMessage() *plugins.PushMessage {
371 link := "https://www.facebook.com/messages?action=recent-messages"
372 epoch := toEpoch(t.UpdatedTime)
373 // get the single message we fetch
374 message := t.Comments.Data[0]
375 return plugins.NewStandardPushMessage(message.From.Name, message.Message, link, picture(t.Id, message.From.Id), epoch)
376}
377
378func (t *thread) isValid(tStamp timeStamp) bool {
379 limit := timeStamp(time.Now().Add(timeOffset * time.Minute).Format(facebookTime))
380 return tStamp < t.UpdatedTime && t.UpdatedTime < limit && t.Unread != 0 && t.Unseen != 0
381}
382
383func (t *thread) updatedTime() timeStamp {
384 return t.UpdatedTime
385}
386
387func (t *thread) String() string {
388 return fmt.Sprintf("id: %s, dated: %s, unread: %d, unseen: %d", t.Id, t.UpdatedTime, t.Unread, t.Unseen)
389}
390
391func (doc *notificationDoc) getTag() string {
392 return "notification"
393}
394
395func (doc *notificationDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage {
396 usernames := []string{}
397 for _, m := range pushMsg {
398 usernames = append(usernames, m.Notification.Card.Summary)
399 if len(usernames) > 10 {
400 usernames[10] = "…"
401 break
402 }
403 }
404 // TRANSLATORS: This represents a notification summary about more facebook notifications
405 summary := gettext.Gettext("Multiple more notifications")
406 // TRANSLATORS: This represents a notification body with the comma separated facebook usernames
407 body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(usernames, ", "))
408 action := "https://m.facebook.com"
409 epoch := time.Now().Unix()
410 return plugins.NewStandardPushMessage(summary, body, action, "", epoch)
411}
412
413func (doc *notificationDoc) size() int {
414 return len(doc.Data)
415}
416
417func (doc *notificationDoc) notification(idx int) Notification {
418 return &doc.Data[idx]
419}
420
421func (n *notification) buildPushMessage() *plugins.PushMessage {
422 epoch := toEpoch(n.UpdatedTime)
423 return plugins.NewStandardPushMessage(n.From.Name, n.Title, n.Link, picture(n.Id, n.From.Id), epoch)
424}
425
426func (n *notification) isValid(tStamp timeStamp) bool {
427 return n.UpdatedTime > tStamp && n.Unread >= 1
428}
429
430func (n *notification) updatedTime() timeStamp {
431 return n.UpdatedTime
432}
433
434func (n *notification) String() string {
435 return fmt.Sprintf("id: %s, dated: %s, unread: %d", n.Id, n.UpdatedTime, n.Unread)
436}
437
438type Document interface {
439 getTag() string
440 handleOverflow([]*plugins.PushMessage) *plugins.PushMessage
441 size() int
442 notification(int) Notification
443}
444
445type Notification interface {
446 buildPushMessage() *plugins.PushMessage
447 isValid(timeStamp) bool
448 updatedTime() timeStamp
449 String() string
450}
451
452var _ Notification = (*thread)(nil)
453var _ Notification = (*notification)(nil)
4540
=== removed file 'plugins/facebook/facebook_test.go'
--- plugins/facebook/facebook_test.go 2015-03-20 14:34:48 +0000
+++ plugins/facebook/facebook_test.go 1970-01-01 00:00:00 +0000
@@ -1,835 +0,0 @@
1/*
2 Copyright 2014 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16package facebook
17
18import (
19 "bytes"
20 "encoding/json"
21 "fmt"
22 "io"
23 "io/ioutil"
24 "net/http"
25 "net/url"
26 "os"
27 "path"
28 "path/filepath"
29 "time"
30
31 "net/http/httptest"
32 "testing"
33
34 . "launchpad.net/gocheck"
35
36 "launchpad.net/account-polld/accounts"
37 "launchpad.net/account-polld/plugins"
38 "launchpad.net/go-xdg/v0"
39)
40
41type S struct {
42 tempDir string
43 ts *httptest.Server
44}
45
46var _ = Suite(&S{})
47
48func Test(t *testing.T) { TestingT(t) }
49
50// closeWraper adds a dummy Close() method to a reader
51type closeWrapper struct {
52 io.Reader
53}
54
55func (r closeWrapper) Close() error {
56 return nil
57}
58
59const (
60 errorBody = `
61{
62 "error": {
63 "message": "Unknown path components: /xyz",
64 "type": "OAuthException",
65 "code": 2500
66 }
67}`
68 tokenExpiredErrorBody = `
69{
70 "error": {
71 "message": "Error validating access token: Session has expired",
72 "type": "OAuthException",
73 "code": 190 ,
74 "error_subcode": 463
75 }
76}`
77 notificationsBody = `
78{
79 "data": [
80 {
81 "id": "notif_id",
82 "from": {
83 "id": "sender_id",
84 "name": "Sender"
85 },
86 "to": {
87 "id": "recipient_id",
88 "name": "Recipient"
89 },
90 "created_time": "2014-07-12T09:51:57+0000",
91 "updated_time": "2014-07-12T09:51:57+0000",
92 "title": "Sender posted on your timeline: \"The message...\"",
93 "link": "http://www.facebook.com/recipient/posts/id",
94 "application": {
95 "name": "Wall",
96 "namespace": "wall",
97 "id": "2719290516"
98 },
99 "unread": 1
100 },
101 {
102 "id": "notif_1105650586_80600069",
103 "from": {
104 "id": "sender2_id",
105 "name": "Sender2"
106 },
107 "to": {
108 "id": "recipient_id",
109 "name": "Recipient"
110 },
111 "created_time": "2014-07-08T06:17:52+0000",
112 "updated_time": "2014-07-08T06:17:52+0000",
113 "title": "Sender2's birthday was on July 7.",
114 "link": "http://www.facebook.com/profile.php?id=xxx&ref=brem",
115 "application": {
116 "name": "Gifts",
117 "namespace": "superkarma",
118 "id": "329122197162272"
119 },
120 "unread": 1,
121 "object": {
122 "id": "sender2_id",
123 "name": "Sender2"
124 }
125 }
126 ],
127 "paging": {
128 "previous": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&since=1405158717&__paging_token=enc_AewDzwIQmWOwPNO-36GaZsaJAog8l93HQ7uLEO-gp1Tb6KCiolXfzMCcGY2KjrJJsDJXdDmNJObICr5dewfMZgGs",
129 "next": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&until=1404705077&__paging_token=enc_Aewlhut5DQyhqtLNr7pLCMlYU012t4XY7FOt7cooz4wsWIWi-Jqz0a0IDnciJoeLu2vNNQkbtOpCmEmsVsN4hkM4"
130 },
131 "summary": [
132 ]
133}
134`
135 largeNotificationsBody = `
136{
137 "data": [
138 {
139 "id": "notif_id",
140 "from": {
141 "id": "sender_id",
142 "name": "Sender"
143 },
144 "to": {
145 "id": "recipient_id",
146 "name": "Recipient"
147 },
148 "created_time": "2014-07-12T09:51:57+0000",
149 "updated_time": "2014-07-12T09:51:57+0000",
150 "title": "Sender posted on your timeline: \"The message...\"",
151 "link": "http://www.facebook.com/recipient/posts/id",
152 "application": {
153 "name": "Wall",
154 "namespace": "wall",
155 "id": "2719290516"
156 },
157 "unread": 1
158 },
159 {
160 "id": "notif_1105650586_80600069",
161 "from": {
162 "id": "sender2_id",
163 "name": "Sender2"
164 },
165 "to": {
166 "id": "recipient_id",
167 "name": "Recipient"
168 },
169 "created_time": "2014-07-08T06:17:52+0000",
170 "updated_time": "2014-07-08T06:17:52+0000",
171 "title": "Sender2's birthday was on July 7.",
172 "link": "http://www.facebook.com/profile.php?id=xxx&ref=brem",
173 "application": {
174 "name": "Gifts",
175 "namespace": "superkarma",
176 "id": "329122197162272"
177 },
178 "unread": 1,
179 "object": {
180 "id": "sender2_id",
181 "name": "Sender2"
182 }
183 },
184 {
185 "id": "notif_id_3",
186 "from": {
187 "id": "sender3_id",
188 "name": "Sender3"
189 },
190 "to": {
191 "id": "recipient_id",
192 "name": "Recipient"
193 },
194 "created_time": "2014-07-12T09:51:57+0000",
195 "updated_time": "2014-07-12T09:51:57+0000",
196 "title": "Sender posted on your timeline: \"The message...\"",
197 "link": "http://www.facebook.com/recipient/posts/id",
198 "application": {
199 "name": "Wall",
200 "namespace": "wall",
201 "id": "2719290516"
202 },
203 "unread": 1
204 },
205 {
206 "id": "notif_id_4",
207 "from": {
208 "id": "sender4_id",
209 "name": "Sender2"
210 },
211 "to": {
212 "id": "recipient_id",
213 "name": "Recipient"
214 },
215 "created_time": "2014-07-08T06:17:52+0000",
216 "updated_time": "2014-07-08T06:17:52+0000",
217 "title": "Sender2's birthday was on July 7.",
218 "link": "http://www.facebook.com/profile.php?id=xxx&ref=brem",
219 "application": {
220 "name": "Gifts",
221 "namespace": "superkarma",
222 "id": "329122197162272"
223 },
224 "unread": 1
225 }
226 ],
227 "paging": {
228 "previous": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&since=1405158717&__paging_token=enc_AewDzwIQmWOwPNO-36GaZsaJAog8l93HQ7uLEO-gp1Tb6KCiolXfzMCcGY2KjrJJsDJXdDmNJObICr5dewfMZgGs",
229 "next": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&until=1404705077&__paging_token=enc_Aewlhut5DQyhqtLNr7pLCMlYU012t4XY7FOt7cooz4wsWIWi-Jqz0a0IDnciJoeLu2vNNQkbtOpCmEmsVsN4hkM4"
230 },
231 "summary": [
232 ]
233}
234`
235
236 inboxBody = `
237{
238 "data": [
239 {
240 "unread": 1,
241 "unseen": 1,
242 "id": "445809168892281",
243 "updated_time": "2014-08-25T18:39:32+0000",
244 "comments": {
245 "data": [
246 {
247 "id": "445809168892281_1408991972",
248 "from": {
249 "id": "346217352202239",
250 "name": "Pollod Magnifico"
251 },
252 "message": "Hola mundo!",
253 "created_time": "2014-08-25T18:39:32+0000"
254 }
255 ],
256 "paging": {
257 "previous": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&since=1408991972&__paging_token=enc_Aew2kKJXEXzdm9k89DvLYz_y8nYxUbvElWcn6h_pKMRsoAPTPpkU7-AsGhkcYF6M1qbomOnFJf9ckL5J3hTltLFq",
258 "next": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&until=1408991972&__paging_token=enc_Aewlixpk4h4Vq79-W1ixrTM6ONbsMUDrcj0vLABs34tbhWarfpQLf818uoASWNDEpQO4XEXh5HbgHpcCqnuNVEOR"
259 }
260 }
261 },
262 {
263 "unread": 2,
264 "unseen": 1,
265 "id": "445809168892282",
266 "updated_time": "2014-08-25T18:39:32+0000",
267 "comments": {
268 "data": [
269 {
270 "id": "445809168892282_1408991973",
271 "from": {
272 "id": "346217352202239",
273 "name": "Pollitod Magnifico"
274 },
275 "message": "Hola!",
276 "created_time": "2014-08-25T18:39:32+0000"
277 }
278 ],
279 "paging": {
280 "previous": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&since=1408991972&__paging_token=enc_Aew2kKJXEXzdm9k89DvLYz_y8nYxUbvElWcn6h_pKMRsoAPTPpkU7-AsGhkcYF6M1qbomOnFJf9ckL5J3hTltLFq",
281 "next": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&until=1408991972&__paging_token=enc_Aewlixpk4h4Vq79-W1ixrTM6ONbsMUDrcj0vLABs34tbhWarfpQLf818uoASWNDEpQO4XEXh5HbgHpcCqnuNVEOR"
282 }
283 }
284 },
285 {
286 "unread": 2,
287 "unseen": 1,
288 "id": "445809168892283",
289 "updated_time": "2014-08-25T18:39:32+0000",
290 "comments": {
291 "data": [
292 {
293 "id": "445809168892282_1408991973",
294 "from": {
295 "id": "346217352202240",
296 "name": "A Friend"
297 },
298 "message": "mellon",
299 "created_time": "2014-08-25T18:39:32+0000"
300 }
301 ],
302 "paging": {
303 "previous": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&since=1408991972&__paging_token=enc_Aew2kKJXEXzdm9k89DvLYz_y8nYxUbvElWcn6h_pKMRsoAPTPpkU7-AsGhkcYF6M1qbomOnFJf9ckL5J3hTltLFq",
304 "next": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&until=1408991972&__paging_token=enc_Aewlixpk4h4Vq79-W1ixrTM6ONbsMUDrcj0vLABs34tbhWarfpQLf818uoASWNDEpQO4XEXh5HbgHpcCqnuNVEOR"
305 }
306 }
307 }
308
309
310 ],
311 "paging": {
312 "previous": "https://graph.facebook.com/v2.0/270128826512416/inbox?fields=unread,unseen,comments.limit(1)&limit=25&since=1408991972&__paging_token=enc_Aey99ACSOyZqN_7I-yWLnY8K3dqu4wVsx-Th3kMHMTMQ5VPbQRPgCQiJps0II1QAXDAVzHplqPS8yNgq8Zs_G2aK",
313 "next": "https://graph.facebook.com/v2.0/270128826512416/inbox?fields=unread,unseen,comments.limit(1)&limit=25&until=1408991972&__paging_token=enc_AewjHkk10NNjRCXJCoaP5hyf22kw-htwxsDaVOiLY-IiXxB99sKNGlfFFmkcG-VeMGUETI2agZGR_1IWP5W4vyPL"
314 },
315 "summary": {
316 "unseen_count": 0,
317 "unread_count": 1,
318 "updated_time": "2014-08-25T19:05:49+0000"
319 }
320}
321`
322)
323
324func (s *S) SetUpTest(c *C) {
325 s.tempDir = c.MkDir()
326 plugins.XdgDataFind = func(a string) (string, error) {
327 return filepath.Join(s.tempDir, a), nil
328 }
329 plugins.XdgDataEnsure = func(a string) (string, error) {
330 p := filepath.Join(s.tempDir, a)
331 base := path.Dir(p)
332 if _, err := os.Stat(base); err != nil {
333 os.MkdirAll(base, 0700)
334 }
335 return p, nil
336 }
337 s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
338 fmt.Fprintln(w, "Hello, client")
339 }))
340 baseUrl, _ = url.Parse(s.ts.URL)
341}
342
343func (s *S) TearDownTest(c *C) {
344 plugins.XdgDataFind = xdg.Data.Find
345 plugins.XdgDataEnsure = xdg.Data.Find
346 doRequest = request
347 s.ts.Close()
348}
349
350func (s *S) TestParseNotifications(c *C) {
351 resp := &http.Response{
352 StatusCode: http.StatusOK,
353 Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))},
354 }
355 p := &fbPlugin{}
356 batch, err := p.parseResponse(resp)
357 c.Assert(err, IsNil)
358 c.Assert(batch, NotNil)
359 messages := batch.Messages
360 c.Assert(len(messages), Equals, 2)
361 c.Check(messages[0].Notification.Card.Summary, Equals, "Sender")
362 c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"")
363 c.Check(messages[1].Notification.Card.Summary, Equals, "Sender2")
364 c.Check(messages[1].Notification.Card.Body, Equals, "Sender2's birthday was on July 7.")
365 c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000"))
366}
367
368func (s *S) TestParseLotsOfNotifications(c *C) {
369 resp := &http.Response{
370 StatusCode: http.StatusOK,
371 Body: closeWrapper{bytes.NewReader([]byte(largeNotificationsBody))},
372 }
373 p := &fbPlugin{}
374 batch, err := p.parseResponse(resp)
375 c.Assert(err, IsNil)
376 c.Assert(batch, NotNil)
377 messages := batch.Messages
378 c.Assert(len(messages), Equals, 4)
379 c.Check(messages[0].Notification.Card.Summary, Equals, "Sender")
380 c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"")
381 c.Check(messages[1].Notification.Card.Summary, Equals, "Sender2")
382 c.Check(messages[1].Notification.Card.Body, Equals, "Sender2's birthday was on July 7.")
383 ofMsg := batch.OverflowHandler(messages[2:])
384 c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more notifications")
385 c.Check(ofMsg.Notification.Card.Body, Equals, "From Sender3, Sender2")
386 c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000"))
387}
388
389func (s *S) TestIgnoreOldNotifications(c *C) {
390 resp := &http.Response{
391 StatusCode: http.StatusOK,
392 Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))},
393 }
394 p := &fbPlugin{state: fbState{LastUpdate: "2014-07-08T06:17:52+0000"}}
395 batch, err := p.parseResponse(resp)
396 c.Assert(err, IsNil)
397 c.Assert(batch, NotNil)
398 messages := batch.Messages
399 c.Assert(len(messages), Equals, 1)
400 c.Check(messages[0].Notification.Card.Summary, Equals, "Sender")
401 c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"")
402 c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000"))
403}
404
405func (s *S) TestParseResponseErrorResponse(c *C) {
406 resp := &http.Response{
407 StatusCode: http.StatusBadRequest,
408 Body: closeWrapper{bytes.NewReader([]byte(errorBody))},
409 }
410 p := &fbPlugin{}
411 notifications, err := p.parseResponse(resp)
412 c.Check(notifications, IsNil)
413 c.Assert(err, Not(IsNil))
414 graphErr := err.(*GraphError)
415 c.Check(graphErr.Message, Equals, "Unknown path components: /xyz")
416 c.Check(graphErr.Code, Equals, 2500)
417}
418
419func (s *S) TestDecodeResponseErrorResponse(c *C) {
420 resp := &http.Response{
421 StatusCode: http.StatusBadRequest,
422 Body: closeWrapper{bytes.NewReader([]byte(errorBody))},
423 }
424 p := &fbPlugin{}
425 var result notificationDoc
426 err := p.decodeResponse(resp, &result)
427 c.Check(result, DeepEquals, notificationDoc{})
428 c.Assert(err, Not(IsNil))
429 graphErr := err.(*GraphError)
430 c.Check(graphErr.Message, Equals, "Unknown path components: /xyz")
431 c.Check(graphErr.Code, Equals, 2500)
432}
433
434func (s *S) TestDecodeResponseErrorResponseFails(c *C) {
435 resp := &http.Response{
436 StatusCode: http.StatusBadRequest,
437 Body: closeWrapper{bytes.NewReader([]byte("hola" + errorBody))},
438 }
439 p := &fbPlugin{}
440 var result notificationDoc
441 err := p.decodeResponse(resp, &result)
442 c.Check(result, DeepEquals, notificationDoc{})
443 c.Assert(err, NotNil)
444 jsonErr := err.(*json.SyntaxError)
445 c.Check(jsonErr.Offset, Equals, int64(1))
446}
447
448func (s *S) TestTokenExpiredErrorResponse(c *C) {
449 resp := &http.Response{
450 StatusCode: http.StatusBadRequest,
451 Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))},
452 }
453 p := &fbPlugin{}
454 notifications, err := p.parseResponse(resp)
455 c.Check(notifications, IsNil)
456 c.Assert(err, Equals, plugins.ErrTokenExpired)
457}
458
459func (s *S) TestParseInbox(c *C) {
460 resp := &http.Response{
461 StatusCode: http.StatusOK,
462 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
463 }
464 p := &fbPlugin{}
465 batch, err := p.parseInboxResponse(resp)
466 c.Assert(err, IsNil)
467 c.Assert(batch, NotNil)
468 messages := batch.Messages
469 c.Assert(len(messages), Equals, 3)
470 c.Check(messages[0].Notification.Card.Summary, Equals, "Pollod Magnifico")
471 c.Check(messages[0].Notification.Card.Body, Equals, "Hola mundo!")
472 c.Check(messages[1].Notification.Card.Summary, Equals, "Pollitod Magnifico")
473 c.Check(messages[1].Notification.Card.Body, Equals, "Hola!")
474
475 ofMsg := batch.OverflowHandler(messages[batch.Limit:])
476
477 c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more messages")
478 c.Check(ofMsg.Notification.Card.Body, Equals, "From A Friend")
479 c.Check(p.state.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000"))
480}
481
482func (s *S) TestDecodeResponse(c *C) {
483 resp := &http.Response{
484 StatusCode: http.StatusOK,
485 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
486 }
487 p := &fbPlugin{}
488 var doc inboxDoc
489 err := p.decodeResponse(resp, &doc)
490 c.Check(err, IsNil)
491 c.Check(len(doc.Data), Equals, 3)
492}
493
494func (s *S) TestDecodeResponseFails(c *C) {
495 resp := &http.Response{
496 StatusCode: http.StatusOK,
497 Body: closeWrapper{bytes.NewReader([]byte("hola" + inboxBody))},
498 }
499 p := &fbPlugin{}
500 var doc inboxDoc
501 err := p.decodeResponse(resp, &doc)
502 c.Assert(err, NotNil)
503 jsonErr := err.(*json.SyntaxError)
504 c.Check(jsonErr.Offset, Equals, int64(1))
505}
506
507func (s *S) TestFilterNotifications(c *C) {
508 resp := &http.Response{
509 StatusCode: http.StatusOK,
510 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
511 }
512 p := &fbPlugin{}
513 var doc inboxDoc
514 p.decodeResponse(resp, &doc)
515 var state fbState
516 notifications := p.filterNotifications(&doc, &state.LastInboxUpdate)
517 c.Check(notifications, HasLen, 3)
518 // check if the lastInboxUpdate is updated
519 c.Check(state.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000"))
520}
521
522func (s *S) TestBuildPushMessages(c *C) {
523 resp := &http.Response{
524 StatusCode: http.StatusOK,
525 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
526 }
527 var state fbState
528 p := &fbPlugin{state: state, accountId: 32}
529 var doc inboxDoc
530 p.decodeResponse(resp, &doc)
531 notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate)
532 batch := p.buildPushMessages(notifications, &doc, doc.size(), doc.size())
533 c.Assert(batch, NotNil)
534 c.Check(batch.Messages, HasLen, doc.size())
535}
536
537func (s *S) TestBuildPushMessagesConsolidate(c *C) {
538 resp := &http.Response{
539 StatusCode: http.StatusOK,
540 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
541 }
542 var state fbState
543 p := &fbPlugin{state: state, accountId: 32}
544 var doc inboxDoc
545 p.decodeResponse(resp, &doc)
546 notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate)
547 max := doc.size() - 2
548 batch := p.buildPushMessages(notifications, &doc, max, max)
549 c.Assert(batch, NotNil)
550 // we should get all the messages in a batch with Limit=max
551 c.Check(batch.Messages, HasLen, len(notifications))
552 c.Check(batch.Limit, Equals, max)
553}
554
555func (s *S) TestStateFromStorageInitialState(c *C) {
556 state, err := stateFromStorage(32)
557 c.Check(err, NotNil)
558 c.Check(state.LastUpdate, Equals, timeStamp(""))
559 c.Check(state.LastInboxUpdate, Equals, timeStamp(""))
560}
561
562func (s *S) TestStateFromStoragePersist(c *C) {
563 filePath := filepath.Join(s.tempDir, "facebook.test/facebook-32.json")
564 state, err := stateFromStorage(32)
565 c.Check(err, NotNil)
566 state.LastInboxUpdate = timeStamp("2014-08-25T18:39:32+0000")
567 state.LastUpdate = timeStamp("2014-08-25T18:39:33+0000")
568 err = state.persist(32)
569 c.Check(err, IsNil)
570 jsonData, err := ioutil.ReadFile(filePath)
571 c.Check(err, IsNil)
572 var data map[string]string
573 json.Unmarshal(jsonData, &data)
574 c.Check(data["last_inbox_update"], Equals, "2014-08-25T18:39:32+0000")
575 c.Check(data["last_notification_update"], Equals, "2014-08-25T18:39:33+0000")
576}
577
578func (s *S) TestStateFromStoragePersistFails(c *C) {
579 state := fbState{LastInboxUpdate: "2014-08-25T18:39:32+0000", LastUpdate: "2014-08-25T18:39:33+0000"}
580 plugins.XdgDataEnsure = plugins.XdgDataFind
581 err := state.persist(32)
582 c.Check(err, NotNil)
583}
584
585func (s *S) TestStateFromStorage(c *C) {
586 state := fbState{LastInboxUpdate: "2014-08-25T18:39:32+0000", LastUpdate: "2014-08-25T18:39:33+0000"}
587 err := state.persist(32)
588 c.Check(err, IsNil)
589 newState, err := stateFromStorage(32)
590 c.Check(err, IsNil)
591 c.Check(newState.LastUpdate, Equals, timeStamp("2014-08-25T18:39:33+0000"))
592 c.Check(newState.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000"))
593 // bad format
594 state = fbState{LastInboxUpdate: "yesterday", LastUpdate: "2014-08-25T18:39:33+0000"}
595 state.persist(32)
596 _, err = stateFromStorage(32)
597 c.Check(err, NotNil)
598 state = fbState{LastInboxUpdate: "2014-08-25T18:39:33+0000", LastUpdate: "today"}
599 state.persist(32)
600 _, err = stateFromStorage(32)
601 c.Check(err, NotNil)
602}
603
604func (s *S) TestNew(c *C) {
605 state := fbState{LastInboxUpdate: "2014-08-25T18:39:32+0000", LastUpdate: "2014-08-25T18:39:33+0000"}
606 state.persist(32)
607 p := New(32)
608 fb := p.(*fbPlugin)
609 c.Check(fb.state, DeepEquals, state)
610 // with bad format
611 state = fbState{LastInboxUpdate: "hola", LastUpdate: "mundo"}
612 state.persist(32)
613 p = New(32)
614 fb = p.(*fbPlugin)
615 c.Check(fb.state, DeepEquals, state)
616}
617
618func (s *S) TestApplicationId(c *C) {
619 expected := plugins.ApplicationId("com.ubuntu.developer.webapps.webapp-facebook_webapp-facebook")
620 p := New(32)
621 c.Check(p.ApplicationId(), Equals, expected)
622}
623
624func (s *S) TestThreadIsValid(c *C) {
625 t := thread{}
626 t.Unseen = 1
627 t.Unread = 2
628 // 10m before Now, the thread should be valid
629 t.UpdatedTime = timeStamp(time.Now().Add(-10 * time.Minute).Format(facebookTime))
630 tStamp := timeStamp(time.Now().Add(-20 * time.Minute).Format(facebookTime))
631 c.Check(t.isValid(tStamp), Equals, true)
632 // 2m before Now, the thread should be invalid
633 t.UpdatedTime = timeStamp(time.Now().Add(-2 * time.Minute).Format(facebookTime))
634 c.Check(t.isValid(tStamp), Equals, false)
635 // unseen = 0
636 t.Unseen = 0
637 t.UpdatedTime = timeStamp(time.Now().Add(-10 * time.Minute).Format(facebookTime))
638 c.Check(t.isValid(tStamp), Equals, false)
639 // unread = 0, unseen = 1
640 t.Unread = 0
641 t.Unseen = 1
642 c.Check(t.isValid(tStamp), Equals, false)
643}
644
645func (s *S) TestGetInbox(c *C) {
646 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
647 return &http.Response{
648 StatusCode: http.StatusOK,
649 Body: closeWrapper{bytes.NewReader([]byte(inboxBody))},
650 }, nil
651 }
652 var state fbState
653 authData := accounts.AuthData{}
654 authData.AccessToken = "foo"
655 p := &fbPlugin{state: state, accountId: 32}
656 batch, err := p.getInbox(&authData)
657 c.Assert(err, IsNil)
658 c.Assert(batch, NotNil)
659 msgs := batch.Messages
660 c.Check(msgs, HasLen, 3)
661}
662
663func (s *S) TestGetInboxRequestFails(c *C) {
664 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
665 return &http.Response{
666 StatusCode: http.StatusBadRequest,
667 Body: closeWrapper{bytes.NewReader([]byte(""))},
668 }, fmt.Errorf("please, fail")
669 }
670 var state fbState
671 authData := accounts.AuthData{}
672 authData.AccessToken = "foo"
673 p := &fbPlugin{state: state, accountId: 32}
674 _, err := p.getInbox(&authData)
675 c.Check(err, NotNil)
676}
677
678func (s *S) TestGetInboxParseResponseFails(c *C) {
679 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
680 return &http.Response{
681 StatusCode: http.StatusOK,
682 Body: closeWrapper{bytes.NewReader([]byte("hola" + inboxBody))},
683 }, nil
684 }
685 var state fbState
686 authData := accounts.AuthData{}
687 authData.AccessToken = "foo"
688 p := &fbPlugin{state: state, accountId: 32}
689 _, err := p.getInbox(&authData)
690 c.Check(err, NotNil)
691}
692
693func (s *S) TestGetNotifications(c *C) {
694 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
695 return &http.Response{
696 StatusCode: http.StatusOK,
697 Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))},
698 }, nil
699 }
700 var state fbState
701 authData := accounts.AuthData{}
702 authData.AccessToken = "foo"
703 p := &fbPlugin{state: state, accountId: 32}
704 batch, err := p.getNotifications(&authData)
705 c.Assert(err, IsNil)
706 c.Assert(batch, NotNil)
707 msgs := batch.Messages
708 c.Check(msgs, HasLen, 2)
709}
710
711func (s *S) TestGetNotificationsRequestFails(c *C) {
712 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
713 return &http.Response{
714 StatusCode: http.StatusBadRequest,
715 Body: closeWrapper{bytes.NewReader([]byte(""))},
716 }, fmt.Errorf("please, fail")
717 }
718 var state fbState
719 authData := accounts.AuthData{}
720 authData.AccessToken = "foo"
721 p := &fbPlugin{state: state, accountId: 32}
722 _, err := p.getNotifications(&authData)
723 c.Check(err, NotNil)
724}
725
726func (s *S) TestGetNotificationsParseResponseFails(c *C) {
727 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
728 return &http.Response{
729 StatusCode: http.StatusOK,
730 Body: closeWrapper{bytes.NewReader([]byte("hola" + notificationsBody))},
731 }, nil
732 }
733 var state fbState
734 authData := accounts.AuthData{}
735 authData.AccessToken = "foo"
736 p := &fbPlugin{state: state, accountId: 32}
737 _, err := p.getNotifications(&authData)
738 c.Check(err, NotNil)
739}
740
741func (s *S) TestPoll(c *C) {
742 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
743 body := inboxBody
744 if url == "me/notifications" {
745 body = notificationsBody
746 }
747 return &http.Response{
748 StatusCode: http.StatusOK,
749 Body: closeWrapper{bytes.NewReader([]byte(body))},
750 }, nil
751 }
752 var state fbState
753 authData := accounts.AuthData{}
754 authData.AccessToken = "foo"
755 p := &fbPlugin{state: state, accountId: 32}
756 batches, err := p.Poll(&authData)
757 c.Assert(err, IsNil)
758 c.Assert(batches, NotNil)
759 c.Assert(batches, HasLen, 2)
760 c.Assert(batches[0], NotNil)
761 c.Assert(batches[1], NotNil)
762 c.Check(batches[0].Tag, Equals, "notification")
763 c.Check(batches[0].Messages, HasLen, 2)
764 c.Check(batches[1].Tag, Equals, "inbox")
765 c.Check(batches[1].Messages, HasLen, 3)
766}
767
768func (s *S) TestPollSingleError(c *C) {
769 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
770 body := inboxBody
771 if url == "me/notifications" {
772 body = "hola" + notificationsBody
773 }
774 return &http.Response{
775 StatusCode: http.StatusOK,
776 Body: closeWrapper{bytes.NewReader([]byte(body))},
777 }, nil
778 }
779 var state fbState
780 authData := accounts.AuthData{}
781 authData.AccessToken = "foo"
782 p := &fbPlugin{state: state, accountId: 32}
783 batches, err := p.Poll(&authData)
784 c.Assert(err, IsNil)
785 c.Assert(batches, HasLen, 1)
786 c.Assert(batches[0], NotNil)
787 c.Check(batches[0].Messages, HasLen, 3)
788}
789
790func (s *S) TestPollError(c *C) {
791 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
792 body := "hola" + inboxBody
793 if url == "me/notifications" {
794 body = "hola" + notificationsBody
795 }
796 return &http.Response{
797 StatusCode: http.StatusOK,
798 Body: closeWrapper{bytes.NewReader([]byte(body))},
799 }, nil
800 }
801 var state fbState
802 authData := accounts.AuthData{}
803 authData.AccessToken = "foo"
804 p := &fbPlugin{state: state, accountId: 32}
805 _, err := p.Poll(&authData)
806 c.Check(err, NotNil)
807}
808
809func (s *S) TestPollUseEnvToken(c *C) {
810 doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) {
811 c.Check(a.AccessToken, Equals, "bar")
812 return nil, fmt.Errorf("please, fail")
813 }
814 var state fbState
815 authData := accounts.AuthData{}
816 authData.AccessToken = "foo"
817 os.Setenv("ACCOUNT_POLLD_TOKEN_FACEBOOK", "bar")
818 // defer the unset
819 defer os.Setenv("ACCOUNT_POLLD_TOKEN_FACEBOOK", "")
820 p := &fbPlugin{state: state, accountId: 32}
821 _, err := p.Poll(&authData)
822 c.Check(err, NotNil)
823}
824
825func (s *S) TestRequest(c *C) {
826 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
827 fmt.Fprintln(w, "Hello, client")
828 }))
829 defer ts.Close()
830 baseUrl, _ = url.Parse(ts.URL)
831 authData := accounts.AuthData{}
832 authData.AccessToken = "foo"
833 _, err := request(&authData, "me/notifications")
834 c.Check(err, IsNil)
835}

Subscribers

People subscribed via source and target branches