Merge lp:~mardy/account-polld/lp1493733 into lp:~ubuntu-push-hackers/account-polld/trunk
- lp1493733
- Merge into trunk
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 | ||||
Related bugs: |
|
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:/
"The GET /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/
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:/
"The GET /v2.4/{
and
"From October 6, 2015 onwards, in all previous API versions, these endpoints will return empty arrays"
PS Jenkins bot (ps-jenkins) wrote : | # |
- 139. By Alberto Mardegan
-
Sync changelog from archive
No-change test rebuild for g++5 ABI transition
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:139
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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!
- 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:140
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'cmd/account-polld/main.go' | |||
2 | --- cmd/account-polld/main.go 2015-03-20 17:51:02 +0000 | |||
3 | +++ cmd/account-polld/main.go 2015-10-12 13:54:21 +0000 | |||
4 | @@ -27,7 +27,6 @@ | |||
5 | 27 | "launchpad.net/account-polld/accounts" | 27 | "launchpad.net/account-polld/accounts" |
6 | 28 | "launchpad.net/account-polld/gettext" | 28 | "launchpad.net/account-polld/gettext" |
7 | 29 | "launchpad.net/account-polld/plugins" | 29 | "launchpad.net/account-polld/plugins" |
8 | 30 | "launchpad.net/account-polld/plugins/facebook" | ||
9 | 31 | "launchpad.net/account-polld/plugins/gmail" | 30 | "launchpad.net/account-polld/plugins/gmail" |
10 | 32 | "launchpad.net/account-polld/plugins/twitter" | 31 | "launchpad.net/account-polld/plugins/twitter" |
11 | 33 | "launchpad.net/account-polld/pollbus" | 32 | "launchpad.net/account-polld/pollbus" |
12 | @@ -47,7 +46,6 @@ | |||
13 | 47 | 46 | ||
14 | 48 | SERVICENAME_GMAIL = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail" | 47 | SERVICENAME_GMAIL = "com.ubuntu.developer.webapps.webapp-gmail_webapp-gmail" |
15 | 49 | SERVICENAME_TWITTER = "com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter" | 48 | SERVICENAME_TWITTER = "com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter" |
16 | 50 | SERVICENAME_FACEBOOK = "com.ubuntu.developer.webapps.webapp-facebook_webapp-facebook" | ||
17 | 51 | ) | 49 | ) |
18 | 52 | 50 | ||
19 | 53 | const ( | 51 | const ( |
20 | @@ -119,10 +117,6 @@ | |||
21 | 119 | case SERVICENAME_GMAIL: | 117 | case SERVICENAME_GMAIL: |
22 | 120 | log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) | 118 | log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
23 | 121 | plugin = gmail.New(data.AccountId) | 119 | plugin = gmail.New(data.AccountId) |
24 | 122 | case SERVICENAME_FACEBOOK: | ||
25 | 123 | // This is just stubbed until the plugin exists. | ||
26 | 124 | log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) | ||
27 | 125 | plugin = facebook.New(data.AccountId) | ||
28 | 126 | case SERVICENAME_TWITTER: | 120 | case SERVICENAME_TWITTER: |
29 | 127 | // This is just stubbed until the plugin exists. | 121 | // This is just stubbed until the plugin exists. |
30 | 128 | log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) | 122 | log.Println("Creating account with id", data.AccountId, "for", data.ServiceName) |
31 | 129 | 123 | ||
32 | === modified file 'debian/account-polld.conf' | |||
33 | --- debian/account-polld.conf 2014-08-06 14:29:13 +0000 | |||
34 | +++ debian/account-polld.conf 2015-10-12 13:54:21 +0000 | |||
35 | @@ -1,4 +1,4 @@ | |||
37 | 1 | description "account-polld checks for notifications for twitter, facebook and gmail and passes them on to the Ubuntu Push Postal service" | 1 | description "account-polld checks for notifications for twitter and gmail and passes them on to the Ubuntu Push Postal service" |
38 | 2 | 2 | ||
39 | 3 | start on started ubuntu-push-client | 3 | start on started ubuntu-push-client |
40 | 4 | stop on stopping ubuntu-push-client | 4 | stop on stopping ubuntu-push-client |
41 | 5 | 5 | ||
42 | === modified file 'debian/changelog' | |||
43 | --- debian/changelog 2015-04-10 17:19:48 +0000 | |||
44 | +++ debian/changelog 2015-10-12 13:54:21 +0000 | |||
45 | @@ -1,3 +1,17 @@ | |||
46 | 1 | account-polld (0.1+15.04.20151012-0ubuntu1) UNRELEASED; urgency=medium | ||
47 | 2 | |||
48 | 3 | * Remove non-working facebook integration. (LP: #1493733) | ||
49 | 4 | * debian/control, debian/account-polld.conf: | ||
50 | 5 | Remove mentions of Facebook from the descriptions. | ||
51 | 6 | |||
52 | 7 | -- Alberto Mardegan <alberto.mardegan@canonical.com> Mon, 12 Oct 2015 16:49:52 +0300 | ||
53 | 8 | |||
54 | 9 | account-polld (0.1+15.04.20150410-0ubuntu2~gcc5.1) wily; urgency=medium | ||
55 | 10 | |||
56 | 11 | * No-change test rebuild for g++5 ABI transition | ||
57 | 12 | |||
58 | 13 | -- Steve Langasek <steve.langasek@ubuntu.com> Wed, 15 Jul 2015 07:18:29 +0000 | ||
59 | 14 | |||
60 | 1 | account-polld (0.1+15.04.20150410-0ubuntu1) vivid; urgency=medium | 15 | account-polld (0.1+15.04.20150410-0ubuntu1) vivid; urgency=medium |
61 | 2 | 16 | ||
62 | 3 | [ John R. Lenton ] | 17 | [ John R. Lenton ] |
63 | 4 | 18 | ||
64 | === modified file 'debian/control' | |||
65 | --- debian/control 2014-08-24 00:04:44 +0000 | |||
66 | +++ debian/control 2015-10-12 13:54:21 +0000 | |||
67 | @@ -29,7 +29,7 @@ | |||
68 | 29 | Built-Using: ${misc:Built-Using} | 29 | Built-Using: ${misc:Built-Using} |
69 | 30 | Recommends: accountsservice, | 30 | Recommends: accountsservice, |
70 | 31 | Description: Poll daemon for notifications though the Ubuntu Push Client | 31 | Description: Poll daemon for notifications though the Ubuntu Push Client |
72 | 32 | This component polls facebook, twitter and gmail for updates and | 32 | This component polls twitter and gmail for updates and |
73 | 33 | communicates with the postal service provided by the ubuntu push client | 33 | communicates with the postal service provided by the ubuntu push client |
74 | 34 | to expose notifications for the click webapps for the aforementioned | 34 | to expose notifications for the click webapps for the aforementioned |
75 | 35 | services. | 35 | services. |
76 | 36 | 36 | ||
77 | === removed directory 'plugins/facebook' | |||
78 | === removed file 'plugins/facebook/facebook.go' | |||
79 | --- plugins/facebook/facebook.go 2015-03-20 14:34:48 +0000 | |||
80 | +++ plugins/facebook/facebook.go 1970-01-01 00:00:00 +0000 | |||
81 | @@ -1,453 +0,0 @@ | |||
82 | 1 | /* | ||
83 | 2 | Copyright 2014 Canonical Ltd. | ||
84 | 3 | |||
85 | 4 | This program is free software: you can redistribute it and/or modify it | ||
86 | 5 | under the terms of the GNU General Public License version 3, as published | ||
87 | 6 | by the Free Software Foundation. | ||
88 | 7 | |||
89 | 8 | This program is distributed in the hope that it will be useful, but | ||
90 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
91 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
92 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
93 | 12 | |||
94 | 13 | You should have received a copy of the GNU General Public License along | ||
95 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
96 | 15 | */ | ||
97 | 16 | |||
98 | 17 | package facebook | ||
99 | 18 | |||
100 | 19 | import ( | ||
101 | 20 | "encoding/json" | ||
102 | 21 | "fmt" | ||
103 | 22 | "net/http" | ||
104 | 23 | "net/url" | ||
105 | 24 | "os" | ||
106 | 25 | "strings" | ||
107 | 26 | "time" | ||
108 | 27 | |||
109 | 28 | "log" | ||
110 | 29 | |||
111 | 30 | "launchpad.net/account-polld/accounts" | ||
112 | 31 | "launchpad.net/account-polld/gettext" | ||
113 | 32 | "launchpad.net/account-polld/plugins" | ||
114 | 33 | ) | ||
115 | 34 | |||
116 | 35 | const ( | ||
117 | 36 | facebookTime = "2006-01-02T15:04:05-0700" | ||
118 | 37 | maxIndividualNotifications = 2 | ||
119 | 38 | consolidatedNotificationsIndexStart = maxIndividualNotifications | ||
120 | 39 | maxIndividualThreads = 2 | ||
121 | 40 | consolidatedThreadsIndexStart = maxIndividualThreads | ||
122 | 41 | timeOffset = -5 | ||
123 | 42 | pluginName = "facebook" | ||
124 | 43 | ) | ||
125 | 44 | |||
126 | 45 | var baseUrl, _ = url.Parse("https://graph.facebook.com/v2.0/") | ||
127 | 46 | |||
128 | 47 | type timeStamp string | ||
129 | 48 | |||
130 | 49 | func (state fbState) persist(accountId uint) (err error) { | ||
131 | 50 | err = plugins.Persist(pluginName, accountId, state) | ||
132 | 51 | if err != nil { | ||
133 | 52 | log.Print("facebook plugin", accountId, ": failed to save state: ", err) | ||
134 | 53 | return err | ||
135 | 54 | } | ||
136 | 55 | return nil | ||
137 | 56 | } | ||
138 | 57 | |||
139 | 58 | func stateFromStorage(accountId uint) (state fbState, err error) { | ||
140 | 59 | err = plugins.FromPersist(pluginName, accountId, &state) | ||
141 | 60 | if err != nil { | ||
142 | 61 | return state, err | ||
143 | 62 | } | ||
144 | 63 | if _, err := time.Parse(facebookTime, string(state.LastUpdate)); err != nil { | ||
145 | 64 | return state, err | ||
146 | 65 | } | ||
147 | 66 | if _, err := time.Parse(facebookTime, string(state.LastInboxUpdate)); err != nil { | ||
148 | 67 | return state, err | ||
149 | 68 | } | ||
150 | 69 | return state, nil | ||
151 | 70 | } | ||
152 | 71 | |||
153 | 72 | type fbState struct { | ||
154 | 73 | LastUpdate timeStamp `json:"last_notification_update"` | ||
155 | 74 | LastInboxUpdate timeStamp `json:"last_inbox_update"` | ||
156 | 75 | } | ||
157 | 76 | |||
158 | 77 | type fbPlugin struct { | ||
159 | 78 | state fbState | ||
160 | 79 | accountId uint | ||
161 | 80 | } | ||
162 | 81 | |||
163 | 82 | var doRequest = request | ||
164 | 83 | |||
165 | 84 | func request(authData *accounts.AuthData, path string) (*http.Response, error) { | ||
166 | 85 | // Resolve path relative to Graph API base URL, and add access token | ||
167 | 86 | u, err := baseUrl.Parse(path) | ||
168 | 87 | if err != nil { | ||
169 | 88 | return nil, err | ||
170 | 89 | } | ||
171 | 90 | query := u.Query() | ||
172 | 91 | query.Add("access_token", authData.AccessToken) | ||
173 | 92 | u.RawQuery = query.Encode() | ||
174 | 93 | |||
175 | 94 | return http.Get(u.String()) | ||
176 | 95 | } | ||
177 | 96 | |||
178 | 97 | func New(accountId uint) plugins.Plugin { | ||
179 | 98 | state, err := stateFromStorage(accountId) | ||
180 | 99 | if err != nil { | ||
181 | 100 | log.Print("facebook plugin ", accountId, ": cannot load previous state from storage: ", err) | ||
182 | 101 | } else { | ||
183 | 102 | log.Print("facebook plugin ", accountId, ": last state loaded from storage") | ||
184 | 103 | } | ||
185 | 104 | return &fbPlugin{state: state, accountId: accountId} | ||
186 | 105 | } | ||
187 | 106 | |||
188 | 107 | func (p *fbPlugin) ApplicationId() plugins.ApplicationId { | ||
189 | 108 | return "com.ubuntu.developer.webapps.webapp-facebook_webapp-facebook" | ||
190 | 109 | } | ||
191 | 110 | |||
192 | 111 | func (p *fbPlugin) decodeResponse(resp *http.Response, result interface{}) error { | ||
193 | 112 | defer resp.Body.Close() | ||
194 | 113 | decoder := json.NewDecoder(resp.Body) | ||
195 | 114 | if resp.StatusCode != http.StatusOK { | ||
196 | 115 | var result errorDoc | ||
197 | 116 | if err := decoder.Decode(&result); err != nil { | ||
198 | 117 | return err | ||
199 | 118 | } | ||
200 | 119 | if result.Error.Code == 190 { | ||
201 | 120 | return plugins.ErrTokenExpired | ||
202 | 121 | } | ||
203 | 122 | return &result.Error | ||
204 | 123 | } | ||
205 | 124 | // TODO: Follow the "paging.next" link if we get more than one | ||
206 | 125 | // page full of notifications. | ||
207 | 126 | return decoder.Decode(result) | ||
208 | 127 | } | ||
209 | 128 | |||
210 | 129 | func (p *fbPlugin) filterNotifications(doc Document, lastUpdate *timeStamp) []Notification { | ||
211 | 130 | var validNotifications []Notification | ||
212 | 131 | latestUpdate := *lastUpdate | ||
213 | 132 | for i := 0; i < doc.size(); i++ { | ||
214 | 133 | n := doc.notification(i) | ||
215 | 134 | if !n.isValid(*lastUpdate) { | ||
216 | 135 | log.Println("facebook plugin: skipping:", n) | ||
217 | 136 | } else { | ||
218 | 137 | log.Println("facebook plugin: valid:", n) | ||
219 | 138 | validNotifications = append(validNotifications, n) // get the actual reference, not the copy | ||
220 | 139 | if n.updatedTime() > latestUpdate { | ||
221 | 140 | latestUpdate = n.updatedTime() | ||
222 | 141 | } | ||
223 | 142 | } | ||
224 | 143 | } | ||
225 | 144 | *lastUpdate = latestUpdate | ||
226 | 145 | p.state.persist(p.accountId) | ||
227 | 146 | return validNotifications | ||
228 | 147 | } | ||
229 | 148 | |||
230 | 149 | func (p *fbPlugin) buildPushMessages(notifications []Notification, doc Document, max int, consolidatedIndexStart int) *plugins.PushMessageBatch { | ||
231 | 150 | pushMsg := make([]*plugins.PushMessage, len(notifications)) | ||
232 | 151 | for i, n := range notifications { | ||
233 | 152 | pushMsg[i] = n.buildPushMessage() | ||
234 | 153 | } | ||
235 | 154 | return &plugins.PushMessageBatch{ | ||
236 | 155 | Messages: pushMsg, | ||
237 | 156 | Limit: max, | ||
238 | 157 | OverflowHandler: doc.handleOverflow, | ||
239 | 158 | Tag: doc.getTag(), | ||
240 | 159 | } | ||
241 | 160 | } | ||
242 | 161 | |||
243 | 162 | func (p *fbPlugin) parseResponse(resp *http.Response) (*plugins.PushMessageBatch, error) { | ||
244 | 163 | var result notificationDoc | ||
245 | 164 | if err := p.decodeResponse(resp, &result); err != nil { | ||
246 | 165 | return nil, err | ||
247 | 166 | } | ||
248 | 167 | // TODO filter out of date messages before operating? | ||
249 | 168 | validNotifications := p.filterNotifications(&result, &p.state.LastUpdate) | ||
250 | 169 | pushMsgs := p.buildPushMessages(validNotifications, &result, maxIndividualNotifications, consolidatedNotificationsIndexStart) | ||
251 | 170 | return pushMsgs, nil | ||
252 | 171 | } | ||
253 | 172 | |||
254 | 173 | func (p *fbPlugin) parseInboxResponse(resp *http.Response) (*plugins.PushMessageBatch, error) { | ||
255 | 174 | var result inboxDoc | ||
256 | 175 | if err := p.decodeResponse(resp, &result); err != nil { | ||
257 | 176 | return nil, err | ||
258 | 177 | } | ||
259 | 178 | validThreads := p.filterNotifications(&result, &p.state.LastInboxUpdate) | ||
260 | 179 | pushMsgs := p.buildPushMessages(validThreads, &result, maxIndividualThreads, consolidatedThreadsIndexStart) | ||
261 | 180 | return pushMsgs, nil | ||
262 | 181 | } | ||
263 | 182 | |||
264 | 183 | func (p *fbPlugin) getNotifications(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) { | ||
265 | 184 | resp, err := doRequest(authData, "me/notifications") | ||
266 | 185 | if err != nil { | ||
267 | 186 | log.Println("facebook plugin: notifications poll failed: ", err) | ||
268 | 187 | return nil, err | ||
269 | 188 | } | ||
270 | 189 | notifications, err := p.parseResponse(resp) | ||
271 | 190 | if err != nil { | ||
272 | 191 | log.Println("facebook plugin: failed to parse notification response: ", err) | ||
273 | 192 | return nil, err | ||
274 | 193 | } | ||
275 | 194 | return notifications, nil | ||
276 | 195 | } | ||
277 | 196 | |||
278 | 197 | func (p *fbPlugin) getInbox(authData *accounts.AuthData) (*plugins.PushMessageBatch, error) { | ||
279 | 198 | resp, err := doRequest(authData, "me/inbox?fields=unread,unseen,comments.limit(1)") | ||
280 | 199 | if err != nil { | ||
281 | 200 | log.Println("facebook plugin: inbox poll failed: ", err) | ||
282 | 201 | return nil, err | ||
283 | 202 | } | ||
284 | 203 | inbox, err := p.parseInboxResponse(resp) | ||
285 | 204 | if err != nil { | ||
286 | 205 | log.Println("facebook plugin: failed to parse inbox response: ", err) | ||
287 | 206 | return nil, err | ||
288 | 207 | } | ||
289 | 208 | return inbox, nil | ||
290 | 209 | } | ||
291 | 210 | |||
292 | 211 | func (p *fbPlugin) Poll(authData *accounts.AuthData) ([]*plugins.PushMessageBatch, error) { | ||
293 | 212 | // This envvar check is to ease testing. | ||
294 | 213 | if token := os.Getenv("ACCOUNT_POLLD_TOKEN_FACEBOOK"); token != "" { | ||
295 | 214 | authData.AccessToken = token | ||
296 | 215 | } | ||
297 | 216 | notifications, notifErr := p.getNotifications(authData) | ||
298 | 217 | inbox, inboxErr := p.getInbox(authData) | ||
299 | 218 | // only return error if both requests failed | ||
300 | 219 | if notifErr != nil && inboxErr != nil { | ||
301 | 220 | return nil, fmt.Errorf("poll failed with '%s' and '%s'", notifErr, inboxErr) | ||
302 | 221 | } | ||
303 | 222 | if notifErr != nil { | ||
304 | 223 | return []*plugins.PushMessageBatch{inbox}, nil | ||
305 | 224 | } | ||
306 | 225 | if inboxErr != nil { | ||
307 | 226 | return []*plugins.PushMessageBatch{notifications}, nil | ||
308 | 227 | } | ||
309 | 228 | return []*plugins.PushMessageBatch{notifications, inbox}, nil | ||
310 | 229 | } | ||
311 | 230 | |||
312 | 231 | func toEpoch(stamp timeStamp) int64 { | ||
313 | 232 | if t, err := time.Parse(facebookTime, string(stamp)); err == nil { | ||
314 | 233 | return t.Unix() | ||
315 | 234 | } | ||
316 | 235 | return time.Now().Unix() | ||
317 | 236 | } | ||
318 | 237 | |||
319 | 238 | // The notifications response format is described here: | ||
320 | 239 | // https://developers.facebook.com/docs/graph-api/reference/v2.0/user/notifications/ | ||
321 | 240 | type notificationDoc struct { | ||
322 | 241 | Data []notification `json:"data"` | ||
323 | 242 | Paging struct { | ||
324 | 243 | Previous string `json:"previous"` | ||
325 | 244 | Next string `json:"next"` | ||
326 | 245 | } `json:"paging"` | ||
327 | 246 | } | ||
328 | 247 | |||
329 | 248 | type notification struct { | ||
330 | 249 | Id string `json:"id"` | ||
331 | 250 | From object `json:"from"` | ||
332 | 251 | To object `json:"to"` | ||
333 | 252 | CreatedTime timeStamp `json:"created_time"` | ||
334 | 253 | UpdatedTime timeStamp `json:"updated_time"` | ||
335 | 254 | Title string `json:"title"` | ||
336 | 255 | Link string `json:"link"` | ||
337 | 256 | Application object `json:"application"` | ||
338 | 257 | Unread int `json:"unread"` | ||
339 | 258 | Object object `json:"object"` | ||
340 | 259 | } | ||
341 | 260 | |||
342 | 261 | func picture(msgId string, id string) string { | ||
343 | 262 | u, err := baseUrl.Parse(fmt.Sprintf("%s/picture", id)) | ||
344 | 263 | if err != nil { | ||
345 | 264 | log.Println("facebook plugin: cannot get picture for", msgId) | ||
346 | 265 | return "" | ||
347 | 266 | } | ||
348 | 267 | query := u.Query() | ||
349 | 268 | query.Add("redirect", "true") | ||
350 | 269 | u.RawQuery = query.Encode() | ||
351 | 270 | return u.String() | ||
352 | 271 | } | ||
353 | 272 | |||
354 | 273 | type object struct { | ||
355 | 274 | Id string `json:"id"` | ||
356 | 275 | Name string `json:"name"` | ||
357 | 276 | } | ||
358 | 277 | |||
359 | 278 | // The error response format is described here: | ||
360 | 279 | // https://developers.facebook.com/docs/graph-api/using-graph-api/v2.0#errors | ||
361 | 280 | type errorDoc struct { | ||
362 | 281 | Error GraphError `json:"error"` | ||
363 | 282 | } | ||
364 | 283 | |||
365 | 284 | type GraphError struct { | ||
366 | 285 | Message string `json:"message"` | ||
367 | 286 | Type string `json:"type"` | ||
368 | 287 | Code int `json:"code"` | ||
369 | 288 | Subcode int `json:"error_subcode"` | ||
370 | 289 | } | ||
371 | 290 | |||
372 | 291 | func (err *GraphError) Error() string { | ||
373 | 292 | return err.Message | ||
374 | 293 | } | ||
375 | 294 | |||
376 | 295 | // The inbox response format is described here: | ||
377 | 296 | // https://developers.facebook.com/docs/graph-api/reference/v2.0/user/inbox | ||
378 | 297 | type inboxDoc struct { | ||
379 | 298 | Data []thread `json:"data"` | ||
380 | 299 | Paging struct { | ||
381 | 300 | Previous string `json:"previous"` | ||
382 | 301 | Next string `json:"next"` | ||
383 | 302 | } `json:"paging"` | ||
384 | 303 | Summary summary `json:"summary"` | ||
385 | 304 | } | ||
386 | 305 | |||
387 | 306 | type summary struct { | ||
388 | 307 | UnseenCount int `json:"unseen_count"` | ||
389 | 308 | UnreadCount int `json:"unread_count"` | ||
390 | 309 | UpdatedTime timeStamp `json:"updated_time"` | ||
391 | 310 | } | ||
392 | 311 | |||
393 | 312 | type thread struct { | ||
394 | 313 | Id string `json:"id"` | ||
395 | 314 | Comments comments `json:"comments"` | ||
396 | 315 | To []object `json:"to"` | ||
397 | 316 | Unread int `json:"unread"` | ||
398 | 317 | Unseen int `json:"unseen"` | ||
399 | 318 | UpdatedTime timeStamp `json:"updated_time"` | ||
400 | 319 | Paging struct { | ||
401 | 320 | Previous string `json:"previous"` | ||
402 | 321 | Next string `json:"next"` | ||
403 | 322 | } `json:"paging"` | ||
404 | 323 | } | ||
405 | 324 | |||
406 | 325 | type comments struct { | ||
407 | 326 | Data []message `json:"data"` | ||
408 | 327 | Paging struct { | ||
409 | 328 | Previous string `json:"previous"` | ||
410 | 329 | Next string `json:"next"` | ||
411 | 330 | } `json:"paging"` | ||
412 | 331 | } | ||
413 | 332 | |||
414 | 333 | type message struct { | ||
415 | 334 | CreatedTime timeStamp `json:"created_time"` | ||
416 | 335 | From object `json:"from"` | ||
417 | 336 | Id string `json:"id"` | ||
418 | 337 | Message string `json:message` | ||
419 | 338 | } | ||
420 | 339 | |||
421 | 340 | func (doc *inboxDoc) getTag() string { | ||
422 | 341 | return "inbox" | ||
423 | 342 | } | ||
424 | 343 | |||
425 | 344 | func (doc *inboxDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { | ||
426 | 345 | usernames := []string{} | ||
427 | 346 | for _, m := range pushMsg { | ||
428 | 347 | usernames = append(usernames, m.Notification.Card.Summary) | ||
429 | 348 | if len(usernames) > 10 { | ||
430 | 349 | usernames[10] = "…" | ||
431 | 350 | break | ||
432 | 351 | } | ||
433 | 352 | } | ||
434 | 353 | // TRANSLATORS: This represents a message summary about more facebook messages | ||
435 | 354 | summary := gettext.Gettext("Multiple more messages") | ||
436 | 355 | // TRANSLATORS: This represents a message body with the comma separated facebook usernames | ||
437 | 356 | body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(usernames, ", ")) | ||
438 | 357 | action := "https://m.facebook.com/messages" | ||
439 | 358 | epoch := time.Now().Unix() | ||
440 | 359 | return plugins.NewStandardPushMessage(summary, body, action, "", epoch) | ||
441 | 360 | } | ||
442 | 361 | |||
443 | 362 | func (doc *inboxDoc) size() int { | ||
444 | 363 | return len(doc.Data) | ||
445 | 364 | } | ||
446 | 365 | |||
447 | 366 | func (doc *inboxDoc) notification(idx int) Notification { | ||
448 | 367 | return &doc.Data[idx] | ||
449 | 368 | } | ||
450 | 369 | |||
451 | 370 | func (t *thread) buildPushMessage() *plugins.PushMessage { | ||
452 | 371 | link := "https://www.facebook.com/messages?action=recent-messages" | ||
453 | 372 | epoch := toEpoch(t.UpdatedTime) | ||
454 | 373 | // get the single message we fetch | ||
455 | 374 | message := t.Comments.Data[0] | ||
456 | 375 | return plugins.NewStandardPushMessage(message.From.Name, message.Message, link, picture(t.Id, message.From.Id), epoch) | ||
457 | 376 | } | ||
458 | 377 | |||
459 | 378 | func (t *thread) isValid(tStamp timeStamp) bool { | ||
460 | 379 | limit := timeStamp(time.Now().Add(timeOffset * time.Minute).Format(facebookTime)) | ||
461 | 380 | return tStamp < t.UpdatedTime && t.UpdatedTime < limit && t.Unread != 0 && t.Unseen != 0 | ||
462 | 381 | } | ||
463 | 382 | |||
464 | 383 | func (t *thread) updatedTime() timeStamp { | ||
465 | 384 | return t.UpdatedTime | ||
466 | 385 | } | ||
467 | 386 | |||
468 | 387 | func (t *thread) String() string { | ||
469 | 388 | return fmt.Sprintf("id: %s, dated: %s, unread: %d, unseen: %d", t.Id, t.UpdatedTime, t.Unread, t.Unseen) | ||
470 | 389 | } | ||
471 | 390 | |||
472 | 391 | func (doc *notificationDoc) getTag() string { | ||
473 | 392 | return "notification" | ||
474 | 393 | } | ||
475 | 394 | |||
476 | 395 | func (doc *notificationDoc) handleOverflow(pushMsg []*plugins.PushMessage) *plugins.PushMessage { | ||
477 | 396 | usernames := []string{} | ||
478 | 397 | for _, m := range pushMsg { | ||
479 | 398 | usernames = append(usernames, m.Notification.Card.Summary) | ||
480 | 399 | if len(usernames) > 10 { | ||
481 | 400 | usernames[10] = "…" | ||
482 | 401 | break | ||
483 | 402 | } | ||
484 | 403 | } | ||
485 | 404 | // TRANSLATORS: This represents a notification summary about more facebook notifications | ||
486 | 405 | summary := gettext.Gettext("Multiple more notifications") | ||
487 | 406 | // TRANSLATORS: This represents a notification body with the comma separated facebook usernames | ||
488 | 407 | body := fmt.Sprintf(gettext.Gettext("From %s"), strings.Join(usernames, ", ")) | ||
489 | 408 | action := "https://m.facebook.com" | ||
490 | 409 | epoch := time.Now().Unix() | ||
491 | 410 | return plugins.NewStandardPushMessage(summary, body, action, "", epoch) | ||
492 | 411 | } | ||
493 | 412 | |||
494 | 413 | func (doc *notificationDoc) size() int { | ||
495 | 414 | return len(doc.Data) | ||
496 | 415 | } | ||
497 | 416 | |||
498 | 417 | func (doc *notificationDoc) notification(idx int) Notification { | ||
499 | 418 | return &doc.Data[idx] | ||
500 | 419 | } | ||
501 | 420 | |||
502 | 421 | func (n *notification) buildPushMessage() *plugins.PushMessage { | ||
503 | 422 | epoch := toEpoch(n.UpdatedTime) | ||
504 | 423 | return plugins.NewStandardPushMessage(n.From.Name, n.Title, n.Link, picture(n.Id, n.From.Id), epoch) | ||
505 | 424 | } | ||
506 | 425 | |||
507 | 426 | func (n *notification) isValid(tStamp timeStamp) bool { | ||
508 | 427 | return n.UpdatedTime > tStamp && n.Unread >= 1 | ||
509 | 428 | } | ||
510 | 429 | |||
511 | 430 | func (n *notification) updatedTime() timeStamp { | ||
512 | 431 | return n.UpdatedTime | ||
513 | 432 | } | ||
514 | 433 | |||
515 | 434 | func (n *notification) String() string { | ||
516 | 435 | return fmt.Sprintf("id: %s, dated: %s, unread: %d", n.Id, n.UpdatedTime, n.Unread) | ||
517 | 436 | } | ||
518 | 437 | |||
519 | 438 | type Document interface { | ||
520 | 439 | getTag() string | ||
521 | 440 | handleOverflow([]*plugins.PushMessage) *plugins.PushMessage | ||
522 | 441 | size() int | ||
523 | 442 | notification(int) Notification | ||
524 | 443 | } | ||
525 | 444 | |||
526 | 445 | type Notification interface { | ||
527 | 446 | buildPushMessage() *plugins.PushMessage | ||
528 | 447 | isValid(timeStamp) bool | ||
529 | 448 | updatedTime() timeStamp | ||
530 | 449 | String() string | ||
531 | 450 | } | ||
532 | 451 | |||
533 | 452 | var _ Notification = (*thread)(nil) | ||
534 | 453 | var _ Notification = (*notification)(nil) | ||
535 | 454 | 0 | ||
536 | === removed file 'plugins/facebook/facebook_test.go' | |||
537 | --- plugins/facebook/facebook_test.go 2015-03-20 14:34:48 +0000 | |||
538 | +++ plugins/facebook/facebook_test.go 1970-01-01 00:00:00 +0000 | |||
539 | @@ -1,835 +0,0 @@ | |||
540 | 1 | /* | ||
541 | 2 | Copyright 2014 Canonical Ltd. | ||
542 | 3 | |||
543 | 4 | This program is free software: you can redistribute it and/or modify it | ||
544 | 5 | under the terms of the GNU General Public License version 3, as published | ||
545 | 6 | by the Free Software Foundation. | ||
546 | 7 | |||
547 | 8 | This program is distributed in the hope that it will be useful, but | ||
548 | 9 | WITHOUT ANY WARRANTY; without even the implied warranties of | ||
549 | 10 | MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | ||
550 | 11 | PURPOSE. See the GNU General Public License for more details. | ||
551 | 12 | |||
552 | 13 | You should have received a copy of the GNU General Public License along | ||
553 | 14 | with this program. If not, see <http://www.gnu.org/licenses/>. | ||
554 | 15 | */ | ||
555 | 16 | package facebook | ||
556 | 17 | |||
557 | 18 | import ( | ||
558 | 19 | "bytes" | ||
559 | 20 | "encoding/json" | ||
560 | 21 | "fmt" | ||
561 | 22 | "io" | ||
562 | 23 | "io/ioutil" | ||
563 | 24 | "net/http" | ||
564 | 25 | "net/url" | ||
565 | 26 | "os" | ||
566 | 27 | "path" | ||
567 | 28 | "path/filepath" | ||
568 | 29 | "time" | ||
569 | 30 | |||
570 | 31 | "net/http/httptest" | ||
571 | 32 | "testing" | ||
572 | 33 | |||
573 | 34 | . "launchpad.net/gocheck" | ||
574 | 35 | |||
575 | 36 | "launchpad.net/account-polld/accounts" | ||
576 | 37 | "launchpad.net/account-polld/plugins" | ||
577 | 38 | "launchpad.net/go-xdg/v0" | ||
578 | 39 | ) | ||
579 | 40 | |||
580 | 41 | type S struct { | ||
581 | 42 | tempDir string | ||
582 | 43 | ts *httptest.Server | ||
583 | 44 | } | ||
584 | 45 | |||
585 | 46 | var _ = Suite(&S{}) | ||
586 | 47 | |||
587 | 48 | func Test(t *testing.T) { TestingT(t) } | ||
588 | 49 | |||
589 | 50 | // closeWraper adds a dummy Close() method to a reader | ||
590 | 51 | type closeWrapper struct { | ||
591 | 52 | io.Reader | ||
592 | 53 | } | ||
593 | 54 | |||
594 | 55 | func (r closeWrapper) Close() error { | ||
595 | 56 | return nil | ||
596 | 57 | } | ||
597 | 58 | |||
598 | 59 | const ( | ||
599 | 60 | errorBody = ` | ||
600 | 61 | { | ||
601 | 62 | "error": { | ||
602 | 63 | "message": "Unknown path components: /xyz", | ||
603 | 64 | "type": "OAuthException", | ||
604 | 65 | "code": 2500 | ||
605 | 66 | } | ||
606 | 67 | }` | ||
607 | 68 | tokenExpiredErrorBody = ` | ||
608 | 69 | { | ||
609 | 70 | "error": { | ||
610 | 71 | "message": "Error validating access token: Session has expired", | ||
611 | 72 | "type": "OAuthException", | ||
612 | 73 | "code": 190 , | ||
613 | 74 | "error_subcode": 463 | ||
614 | 75 | } | ||
615 | 76 | }` | ||
616 | 77 | notificationsBody = ` | ||
617 | 78 | { | ||
618 | 79 | "data": [ | ||
619 | 80 | { | ||
620 | 81 | "id": "notif_id", | ||
621 | 82 | "from": { | ||
622 | 83 | "id": "sender_id", | ||
623 | 84 | "name": "Sender" | ||
624 | 85 | }, | ||
625 | 86 | "to": { | ||
626 | 87 | "id": "recipient_id", | ||
627 | 88 | "name": "Recipient" | ||
628 | 89 | }, | ||
629 | 90 | "created_time": "2014-07-12T09:51:57+0000", | ||
630 | 91 | "updated_time": "2014-07-12T09:51:57+0000", | ||
631 | 92 | "title": "Sender posted on your timeline: \"The message...\"", | ||
632 | 93 | "link": "http://www.facebook.com/recipient/posts/id", | ||
633 | 94 | "application": { | ||
634 | 95 | "name": "Wall", | ||
635 | 96 | "namespace": "wall", | ||
636 | 97 | "id": "2719290516" | ||
637 | 98 | }, | ||
638 | 99 | "unread": 1 | ||
639 | 100 | }, | ||
640 | 101 | { | ||
641 | 102 | "id": "notif_1105650586_80600069", | ||
642 | 103 | "from": { | ||
643 | 104 | "id": "sender2_id", | ||
644 | 105 | "name": "Sender2" | ||
645 | 106 | }, | ||
646 | 107 | "to": { | ||
647 | 108 | "id": "recipient_id", | ||
648 | 109 | "name": "Recipient" | ||
649 | 110 | }, | ||
650 | 111 | "created_time": "2014-07-08T06:17:52+0000", | ||
651 | 112 | "updated_time": "2014-07-08T06:17:52+0000", | ||
652 | 113 | "title": "Sender2's birthday was on July 7.", | ||
653 | 114 | "link": "http://www.facebook.com/profile.php?id=xxx&ref=brem", | ||
654 | 115 | "application": { | ||
655 | 116 | "name": "Gifts", | ||
656 | 117 | "namespace": "superkarma", | ||
657 | 118 | "id": "329122197162272" | ||
658 | 119 | }, | ||
659 | 120 | "unread": 1, | ||
660 | 121 | "object": { | ||
661 | 122 | "id": "sender2_id", | ||
662 | 123 | "name": "Sender2" | ||
663 | 124 | } | ||
664 | 125 | } | ||
665 | 126 | ], | ||
666 | 127 | "paging": { | ||
667 | 128 | "previous": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&since=1405158717&__paging_token=enc_AewDzwIQmWOwPNO-36GaZsaJAog8l93HQ7uLEO-gp1Tb6KCiolXfzMCcGY2KjrJJsDJXdDmNJObICr5dewfMZgGs", | ||
668 | 129 | "next": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&until=1404705077&__paging_token=enc_Aewlhut5DQyhqtLNr7pLCMlYU012t4XY7FOt7cooz4wsWIWi-Jqz0a0IDnciJoeLu2vNNQkbtOpCmEmsVsN4hkM4" | ||
669 | 130 | }, | ||
670 | 131 | "summary": [ | ||
671 | 132 | ] | ||
672 | 133 | } | ||
673 | 134 | ` | ||
674 | 135 | largeNotificationsBody = ` | ||
675 | 136 | { | ||
676 | 137 | "data": [ | ||
677 | 138 | { | ||
678 | 139 | "id": "notif_id", | ||
679 | 140 | "from": { | ||
680 | 141 | "id": "sender_id", | ||
681 | 142 | "name": "Sender" | ||
682 | 143 | }, | ||
683 | 144 | "to": { | ||
684 | 145 | "id": "recipient_id", | ||
685 | 146 | "name": "Recipient" | ||
686 | 147 | }, | ||
687 | 148 | "created_time": "2014-07-12T09:51:57+0000", | ||
688 | 149 | "updated_time": "2014-07-12T09:51:57+0000", | ||
689 | 150 | "title": "Sender posted on your timeline: \"The message...\"", | ||
690 | 151 | "link": "http://www.facebook.com/recipient/posts/id", | ||
691 | 152 | "application": { | ||
692 | 153 | "name": "Wall", | ||
693 | 154 | "namespace": "wall", | ||
694 | 155 | "id": "2719290516" | ||
695 | 156 | }, | ||
696 | 157 | "unread": 1 | ||
697 | 158 | }, | ||
698 | 159 | { | ||
699 | 160 | "id": "notif_1105650586_80600069", | ||
700 | 161 | "from": { | ||
701 | 162 | "id": "sender2_id", | ||
702 | 163 | "name": "Sender2" | ||
703 | 164 | }, | ||
704 | 165 | "to": { | ||
705 | 166 | "id": "recipient_id", | ||
706 | 167 | "name": "Recipient" | ||
707 | 168 | }, | ||
708 | 169 | "created_time": "2014-07-08T06:17:52+0000", | ||
709 | 170 | "updated_time": "2014-07-08T06:17:52+0000", | ||
710 | 171 | "title": "Sender2's birthday was on July 7.", | ||
711 | 172 | "link": "http://www.facebook.com/profile.php?id=xxx&ref=brem", | ||
712 | 173 | "application": { | ||
713 | 174 | "name": "Gifts", | ||
714 | 175 | "namespace": "superkarma", | ||
715 | 176 | "id": "329122197162272" | ||
716 | 177 | }, | ||
717 | 178 | "unread": 1, | ||
718 | 179 | "object": { | ||
719 | 180 | "id": "sender2_id", | ||
720 | 181 | "name": "Sender2" | ||
721 | 182 | } | ||
722 | 183 | }, | ||
723 | 184 | { | ||
724 | 185 | "id": "notif_id_3", | ||
725 | 186 | "from": { | ||
726 | 187 | "id": "sender3_id", | ||
727 | 188 | "name": "Sender3" | ||
728 | 189 | }, | ||
729 | 190 | "to": { | ||
730 | 191 | "id": "recipient_id", | ||
731 | 192 | "name": "Recipient" | ||
732 | 193 | }, | ||
733 | 194 | "created_time": "2014-07-12T09:51:57+0000", | ||
734 | 195 | "updated_time": "2014-07-12T09:51:57+0000", | ||
735 | 196 | "title": "Sender posted on your timeline: \"The message...\"", | ||
736 | 197 | "link": "http://www.facebook.com/recipient/posts/id", | ||
737 | 198 | "application": { | ||
738 | 199 | "name": "Wall", | ||
739 | 200 | "namespace": "wall", | ||
740 | 201 | "id": "2719290516" | ||
741 | 202 | }, | ||
742 | 203 | "unread": 1 | ||
743 | 204 | }, | ||
744 | 205 | { | ||
745 | 206 | "id": "notif_id_4", | ||
746 | 207 | "from": { | ||
747 | 208 | "id": "sender4_id", | ||
748 | 209 | "name": "Sender2" | ||
749 | 210 | }, | ||
750 | 211 | "to": { | ||
751 | 212 | "id": "recipient_id", | ||
752 | 213 | "name": "Recipient" | ||
753 | 214 | }, | ||
754 | 215 | "created_time": "2014-07-08T06:17:52+0000", | ||
755 | 216 | "updated_time": "2014-07-08T06:17:52+0000", | ||
756 | 217 | "title": "Sender2's birthday was on July 7.", | ||
757 | 218 | "link": "http://www.facebook.com/profile.php?id=xxx&ref=brem", | ||
758 | 219 | "application": { | ||
759 | 220 | "name": "Gifts", | ||
760 | 221 | "namespace": "superkarma", | ||
761 | 222 | "id": "329122197162272" | ||
762 | 223 | }, | ||
763 | 224 | "unread": 1 | ||
764 | 225 | } | ||
765 | 226 | ], | ||
766 | 227 | "paging": { | ||
767 | 228 | "previous": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&since=1405158717&__paging_token=enc_AewDzwIQmWOwPNO-36GaZsaJAog8l93HQ7uLEO-gp1Tb6KCiolXfzMCcGY2KjrJJsDJXdDmNJObICr5dewfMZgGs", | ||
768 | 229 | "next": "https://graph.facebook.com/v2.0/recipient/notifications?limit=5000&until=1404705077&__paging_token=enc_Aewlhut5DQyhqtLNr7pLCMlYU012t4XY7FOt7cooz4wsWIWi-Jqz0a0IDnciJoeLu2vNNQkbtOpCmEmsVsN4hkM4" | ||
769 | 230 | }, | ||
770 | 231 | "summary": [ | ||
771 | 232 | ] | ||
772 | 233 | } | ||
773 | 234 | ` | ||
774 | 235 | |||
775 | 236 | inboxBody = ` | ||
776 | 237 | { | ||
777 | 238 | "data": [ | ||
778 | 239 | { | ||
779 | 240 | "unread": 1, | ||
780 | 241 | "unseen": 1, | ||
781 | 242 | "id": "445809168892281", | ||
782 | 243 | "updated_time": "2014-08-25T18:39:32+0000", | ||
783 | 244 | "comments": { | ||
784 | 245 | "data": [ | ||
785 | 246 | { | ||
786 | 247 | "id": "445809168892281_1408991972", | ||
787 | 248 | "from": { | ||
788 | 249 | "id": "346217352202239", | ||
789 | 250 | "name": "Pollod Magnifico" | ||
790 | 251 | }, | ||
791 | 252 | "message": "Hola mundo!", | ||
792 | 253 | "created_time": "2014-08-25T18:39:32+0000" | ||
793 | 254 | } | ||
794 | 255 | ], | ||
795 | 256 | "paging": { | ||
796 | 257 | "previous": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&since=1408991972&__paging_token=enc_Aew2kKJXEXzdm9k89DvLYz_y8nYxUbvElWcn6h_pKMRsoAPTPpkU7-AsGhkcYF6M1qbomOnFJf9ckL5J3hTltLFq", | ||
797 | 258 | "next": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&until=1408991972&__paging_token=enc_Aewlixpk4h4Vq79-W1ixrTM6ONbsMUDrcj0vLABs34tbhWarfpQLf818uoASWNDEpQO4XEXh5HbgHpcCqnuNVEOR" | ||
798 | 259 | } | ||
799 | 260 | } | ||
800 | 261 | }, | ||
801 | 262 | { | ||
802 | 263 | "unread": 2, | ||
803 | 264 | "unseen": 1, | ||
804 | 265 | "id": "445809168892282", | ||
805 | 266 | "updated_time": "2014-08-25T18:39:32+0000", | ||
806 | 267 | "comments": { | ||
807 | 268 | "data": [ | ||
808 | 269 | { | ||
809 | 270 | "id": "445809168892282_1408991973", | ||
810 | 271 | "from": { | ||
811 | 272 | "id": "346217352202239", | ||
812 | 273 | "name": "Pollitod Magnifico" | ||
813 | 274 | }, | ||
814 | 275 | "message": "Hola!", | ||
815 | 276 | "created_time": "2014-08-25T18:39:32+0000" | ||
816 | 277 | } | ||
817 | 278 | ], | ||
818 | 279 | "paging": { | ||
819 | 280 | "previous": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&since=1408991972&__paging_token=enc_Aew2kKJXEXzdm9k89DvLYz_y8nYxUbvElWcn6h_pKMRsoAPTPpkU7-AsGhkcYF6M1qbomOnFJf9ckL5J3hTltLFq", | ||
820 | 281 | "next": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&until=1408991972&__paging_token=enc_Aewlixpk4h4Vq79-W1ixrTM6ONbsMUDrcj0vLABs34tbhWarfpQLf818uoASWNDEpQO4XEXh5HbgHpcCqnuNVEOR" | ||
821 | 282 | } | ||
822 | 283 | } | ||
823 | 284 | }, | ||
824 | 285 | { | ||
825 | 286 | "unread": 2, | ||
826 | 287 | "unseen": 1, | ||
827 | 288 | "id": "445809168892283", | ||
828 | 289 | "updated_time": "2014-08-25T18:39:32+0000", | ||
829 | 290 | "comments": { | ||
830 | 291 | "data": [ | ||
831 | 292 | { | ||
832 | 293 | "id": "445809168892282_1408991973", | ||
833 | 294 | "from": { | ||
834 | 295 | "id": "346217352202240", | ||
835 | 296 | "name": "A Friend" | ||
836 | 297 | }, | ||
837 | 298 | "message": "mellon", | ||
838 | 299 | "created_time": "2014-08-25T18:39:32+0000" | ||
839 | 300 | } | ||
840 | 301 | ], | ||
841 | 302 | "paging": { | ||
842 | 303 | "previous": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&since=1408991972&__paging_token=enc_Aew2kKJXEXzdm9k89DvLYz_y8nYxUbvElWcn6h_pKMRsoAPTPpkU7-AsGhkcYF6M1qbomOnFJf9ckL5J3hTltLFq", | ||
843 | 304 | "next": "https://graph.facebook.com/v2.0/445809168892281/comments?limit=1&until=1408991972&__paging_token=enc_Aewlixpk4h4Vq79-W1ixrTM6ONbsMUDrcj0vLABs34tbhWarfpQLf818uoASWNDEpQO4XEXh5HbgHpcCqnuNVEOR" | ||
844 | 305 | } | ||
845 | 306 | } | ||
846 | 307 | } | ||
847 | 308 | |||
848 | 309 | |||
849 | 310 | ], | ||
850 | 311 | "paging": { | ||
851 | 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", | ||
852 | 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" | ||
853 | 314 | }, | ||
854 | 315 | "summary": { | ||
855 | 316 | "unseen_count": 0, | ||
856 | 317 | "unread_count": 1, | ||
857 | 318 | "updated_time": "2014-08-25T19:05:49+0000" | ||
858 | 319 | } | ||
859 | 320 | } | ||
860 | 321 | ` | ||
861 | 322 | ) | ||
862 | 323 | |||
863 | 324 | func (s *S) SetUpTest(c *C) { | ||
864 | 325 | s.tempDir = c.MkDir() | ||
865 | 326 | plugins.XdgDataFind = func(a string) (string, error) { | ||
866 | 327 | return filepath.Join(s.tempDir, a), nil | ||
867 | 328 | } | ||
868 | 329 | plugins.XdgDataEnsure = func(a string) (string, error) { | ||
869 | 330 | p := filepath.Join(s.tempDir, a) | ||
870 | 331 | base := path.Dir(p) | ||
871 | 332 | if _, err := os.Stat(base); err != nil { | ||
872 | 333 | os.MkdirAll(base, 0700) | ||
873 | 334 | } | ||
874 | 335 | return p, nil | ||
875 | 336 | } | ||
876 | 337 | s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
877 | 338 | fmt.Fprintln(w, "Hello, client") | ||
878 | 339 | })) | ||
879 | 340 | baseUrl, _ = url.Parse(s.ts.URL) | ||
880 | 341 | } | ||
881 | 342 | |||
882 | 343 | func (s *S) TearDownTest(c *C) { | ||
883 | 344 | plugins.XdgDataFind = xdg.Data.Find | ||
884 | 345 | plugins.XdgDataEnsure = xdg.Data.Find | ||
885 | 346 | doRequest = request | ||
886 | 347 | s.ts.Close() | ||
887 | 348 | } | ||
888 | 349 | |||
889 | 350 | func (s *S) TestParseNotifications(c *C) { | ||
890 | 351 | resp := &http.Response{ | ||
891 | 352 | StatusCode: http.StatusOK, | ||
892 | 353 | Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))}, | ||
893 | 354 | } | ||
894 | 355 | p := &fbPlugin{} | ||
895 | 356 | batch, err := p.parseResponse(resp) | ||
896 | 357 | c.Assert(err, IsNil) | ||
897 | 358 | c.Assert(batch, NotNil) | ||
898 | 359 | messages := batch.Messages | ||
899 | 360 | c.Assert(len(messages), Equals, 2) | ||
900 | 361 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sender") | ||
901 | 362 | c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"") | ||
902 | 363 | c.Check(messages[1].Notification.Card.Summary, Equals, "Sender2") | ||
903 | 364 | c.Check(messages[1].Notification.Card.Body, Equals, "Sender2's birthday was on July 7.") | ||
904 | 365 | c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000")) | ||
905 | 366 | } | ||
906 | 367 | |||
907 | 368 | func (s *S) TestParseLotsOfNotifications(c *C) { | ||
908 | 369 | resp := &http.Response{ | ||
909 | 370 | StatusCode: http.StatusOK, | ||
910 | 371 | Body: closeWrapper{bytes.NewReader([]byte(largeNotificationsBody))}, | ||
911 | 372 | } | ||
912 | 373 | p := &fbPlugin{} | ||
913 | 374 | batch, err := p.parseResponse(resp) | ||
914 | 375 | c.Assert(err, IsNil) | ||
915 | 376 | c.Assert(batch, NotNil) | ||
916 | 377 | messages := batch.Messages | ||
917 | 378 | c.Assert(len(messages), Equals, 4) | ||
918 | 379 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sender") | ||
919 | 380 | c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"") | ||
920 | 381 | c.Check(messages[1].Notification.Card.Summary, Equals, "Sender2") | ||
921 | 382 | c.Check(messages[1].Notification.Card.Body, Equals, "Sender2's birthday was on July 7.") | ||
922 | 383 | ofMsg := batch.OverflowHandler(messages[2:]) | ||
923 | 384 | c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more notifications") | ||
924 | 385 | c.Check(ofMsg.Notification.Card.Body, Equals, "From Sender3, Sender2") | ||
925 | 386 | c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000")) | ||
926 | 387 | } | ||
927 | 388 | |||
928 | 389 | func (s *S) TestIgnoreOldNotifications(c *C) { | ||
929 | 390 | resp := &http.Response{ | ||
930 | 391 | StatusCode: http.StatusOK, | ||
931 | 392 | Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))}, | ||
932 | 393 | } | ||
933 | 394 | p := &fbPlugin{state: fbState{LastUpdate: "2014-07-08T06:17:52+0000"}} | ||
934 | 395 | batch, err := p.parseResponse(resp) | ||
935 | 396 | c.Assert(err, IsNil) | ||
936 | 397 | c.Assert(batch, NotNil) | ||
937 | 398 | messages := batch.Messages | ||
938 | 399 | c.Assert(len(messages), Equals, 1) | ||
939 | 400 | c.Check(messages[0].Notification.Card.Summary, Equals, "Sender") | ||
940 | 401 | c.Check(messages[0].Notification.Card.Body, Equals, "Sender posted on your timeline: \"The message...\"") | ||
941 | 402 | c.Check(p.state.LastUpdate, Equals, timeStamp("2014-07-12T09:51:57+0000")) | ||
942 | 403 | } | ||
943 | 404 | |||
944 | 405 | func (s *S) TestParseResponseErrorResponse(c *C) { | ||
945 | 406 | resp := &http.Response{ | ||
946 | 407 | StatusCode: http.StatusBadRequest, | ||
947 | 408 | Body: closeWrapper{bytes.NewReader([]byte(errorBody))}, | ||
948 | 409 | } | ||
949 | 410 | p := &fbPlugin{} | ||
950 | 411 | notifications, err := p.parseResponse(resp) | ||
951 | 412 | c.Check(notifications, IsNil) | ||
952 | 413 | c.Assert(err, Not(IsNil)) | ||
953 | 414 | graphErr := err.(*GraphError) | ||
954 | 415 | c.Check(graphErr.Message, Equals, "Unknown path components: /xyz") | ||
955 | 416 | c.Check(graphErr.Code, Equals, 2500) | ||
956 | 417 | } | ||
957 | 418 | |||
958 | 419 | func (s *S) TestDecodeResponseErrorResponse(c *C) { | ||
959 | 420 | resp := &http.Response{ | ||
960 | 421 | StatusCode: http.StatusBadRequest, | ||
961 | 422 | Body: closeWrapper{bytes.NewReader([]byte(errorBody))}, | ||
962 | 423 | } | ||
963 | 424 | p := &fbPlugin{} | ||
964 | 425 | var result notificationDoc | ||
965 | 426 | err := p.decodeResponse(resp, &result) | ||
966 | 427 | c.Check(result, DeepEquals, notificationDoc{}) | ||
967 | 428 | c.Assert(err, Not(IsNil)) | ||
968 | 429 | graphErr := err.(*GraphError) | ||
969 | 430 | c.Check(graphErr.Message, Equals, "Unknown path components: /xyz") | ||
970 | 431 | c.Check(graphErr.Code, Equals, 2500) | ||
971 | 432 | } | ||
972 | 433 | |||
973 | 434 | func (s *S) TestDecodeResponseErrorResponseFails(c *C) { | ||
974 | 435 | resp := &http.Response{ | ||
975 | 436 | StatusCode: http.StatusBadRequest, | ||
976 | 437 | Body: closeWrapper{bytes.NewReader([]byte("hola" + errorBody))}, | ||
977 | 438 | } | ||
978 | 439 | p := &fbPlugin{} | ||
979 | 440 | var result notificationDoc | ||
980 | 441 | err := p.decodeResponse(resp, &result) | ||
981 | 442 | c.Check(result, DeepEquals, notificationDoc{}) | ||
982 | 443 | c.Assert(err, NotNil) | ||
983 | 444 | jsonErr := err.(*json.SyntaxError) | ||
984 | 445 | c.Check(jsonErr.Offset, Equals, int64(1)) | ||
985 | 446 | } | ||
986 | 447 | |||
987 | 448 | func (s *S) TestTokenExpiredErrorResponse(c *C) { | ||
988 | 449 | resp := &http.Response{ | ||
989 | 450 | StatusCode: http.StatusBadRequest, | ||
990 | 451 | Body: closeWrapper{bytes.NewReader([]byte(tokenExpiredErrorBody))}, | ||
991 | 452 | } | ||
992 | 453 | p := &fbPlugin{} | ||
993 | 454 | notifications, err := p.parseResponse(resp) | ||
994 | 455 | c.Check(notifications, IsNil) | ||
995 | 456 | c.Assert(err, Equals, plugins.ErrTokenExpired) | ||
996 | 457 | } | ||
997 | 458 | |||
998 | 459 | func (s *S) TestParseInbox(c *C) { | ||
999 | 460 | resp := &http.Response{ | ||
1000 | 461 | StatusCode: http.StatusOK, | ||
1001 | 462 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, | ||
1002 | 463 | } | ||
1003 | 464 | p := &fbPlugin{} | ||
1004 | 465 | batch, err := p.parseInboxResponse(resp) | ||
1005 | 466 | c.Assert(err, IsNil) | ||
1006 | 467 | c.Assert(batch, NotNil) | ||
1007 | 468 | messages := batch.Messages | ||
1008 | 469 | c.Assert(len(messages), Equals, 3) | ||
1009 | 470 | c.Check(messages[0].Notification.Card.Summary, Equals, "Pollod Magnifico") | ||
1010 | 471 | c.Check(messages[0].Notification.Card.Body, Equals, "Hola mundo!") | ||
1011 | 472 | c.Check(messages[1].Notification.Card.Summary, Equals, "Pollitod Magnifico") | ||
1012 | 473 | c.Check(messages[1].Notification.Card.Body, Equals, "Hola!") | ||
1013 | 474 | |||
1014 | 475 | ofMsg := batch.OverflowHandler(messages[batch.Limit:]) | ||
1015 | 476 | |||
1016 | 477 | c.Check(ofMsg.Notification.Card.Summary, Equals, "Multiple more messages") | ||
1017 | 478 | c.Check(ofMsg.Notification.Card.Body, Equals, "From A Friend") | ||
1018 | 479 | c.Check(p.state.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000")) | ||
1019 | 480 | } | ||
1020 | 481 | |||
1021 | 482 | func (s *S) TestDecodeResponse(c *C) { | ||
1022 | 483 | resp := &http.Response{ | ||
1023 | 484 | StatusCode: http.StatusOK, | ||
1024 | 485 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, | ||
1025 | 486 | } | ||
1026 | 487 | p := &fbPlugin{} | ||
1027 | 488 | var doc inboxDoc | ||
1028 | 489 | err := p.decodeResponse(resp, &doc) | ||
1029 | 490 | c.Check(err, IsNil) | ||
1030 | 491 | c.Check(len(doc.Data), Equals, 3) | ||
1031 | 492 | } | ||
1032 | 493 | |||
1033 | 494 | func (s *S) TestDecodeResponseFails(c *C) { | ||
1034 | 495 | resp := &http.Response{ | ||
1035 | 496 | StatusCode: http.StatusOK, | ||
1036 | 497 | Body: closeWrapper{bytes.NewReader([]byte("hola" + inboxBody))}, | ||
1037 | 498 | } | ||
1038 | 499 | p := &fbPlugin{} | ||
1039 | 500 | var doc inboxDoc | ||
1040 | 501 | err := p.decodeResponse(resp, &doc) | ||
1041 | 502 | c.Assert(err, NotNil) | ||
1042 | 503 | jsonErr := err.(*json.SyntaxError) | ||
1043 | 504 | c.Check(jsonErr.Offset, Equals, int64(1)) | ||
1044 | 505 | } | ||
1045 | 506 | |||
1046 | 507 | func (s *S) TestFilterNotifications(c *C) { | ||
1047 | 508 | resp := &http.Response{ | ||
1048 | 509 | StatusCode: http.StatusOK, | ||
1049 | 510 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, | ||
1050 | 511 | } | ||
1051 | 512 | p := &fbPlugin{} | ||
1052 | 513 | var doc inboxDoc | ||
1053 | 514 | p.decodeResponse(resp, &doc) | ||
1054 | 515 | var state fbState | ||
1055 | 516 | notifications := p.filterNotifications(&doc, &state.LastInboxUpdate) | ||
1056 | 517 | c.Check(notifications, HasLen, 3) | ||
1057 | 518 | // check if the lastInboxUpdate is updated | ||
1058 | 519 | c.Check(state.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000")) | ||
1059 | 520 | } | ||
1060 | 521 | |||
1061 | 522 | func (s *S) TestBuildPushMessages(c *C) { | ||
1062 | 523 | resp := &http.Response{ | ||
1063 | 524 | StatusCode: http.StatusOK, | ||
1064 | 525 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, | ||
1065 | 526 | } | ||
1066 | 527 | var state fbState | ||
1067 | 528 | p := &fbPlugin{state: state, accountId: 32} | ||
1068 | 529 | var doc inboxDoc | ||
1069 | 530 | p.decodeResponse(resp, &doc) | ||
1070 | 531 | notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate) | ||
1071 | 532 | batch := p.buildPushMessages(notifications, &doc, doc.size(), doc.size()) | ||
1072 | 533 | c.Assert(batch, NotNil) | ||
1073 | 534 | c.Check(batch.Messages, HasLen, doc.size()) | ||
1074 | 535 | } | ||
1075 | 536 | |||
1076 | 537 | func (s *S) TestBuildPushMessagesConsolidate(c *C) { | ||
1077 | 538 | resp := &http.Response{ | ||
1078 | 539 | StatusCode: http.StatusOK, | ||
1079 | 540 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, | ||
1080 | 541 | } | ||
1081 | 542 | var state fbState | ||
1082 | 543 | p := &fbPlugin{state: state, accountId: 32} | ||
1083 | 544 | var doc inboxDoc | ||
1084 | 545 | p.decodeResponse(resp, &doc) | ||
1085 | 546 | notifications := p.filterNotifications(&doc, &p.state.LastInboxUpdate) | ||
1086 | 547 | max := doc.size() - 2 | ||
1087 | 548 | batch := p.buildPushMessages(notifications, &doc, max, max) | ||
1088 | 549 | c.Assert(batch, NotNil) | ||
1089 | 550 | // we should get all the messages in a batch with Limit=max | ||
1090 | 551 | c.Check(batch.Messages, HasLen, len(notifications)) | ||
1091 | 552 | c.Check(batch.Limit, Equals, max) | ||
1092 | 553 | } | ||
1093 | 554 | |||
1094 | 555 | func (s *S) TestStateFromStorageInitialState(c *C) { | ||
1095 | 556 | state, err := stateFromStorage(32) | ||
1096 | 557 | c.Check(err, NotNil) | ||
1097 | 558 | c.Check(state.LastUpdate, Equals, timeStamp("")) | ||
1098 | 559 | c.Check(state.LastInboxUpdate, Equals, timeStamp("")) | ||
1099 | 560 | } | ||
1100 | 561 | |||
1101 | 562 | func (s *S) TestStateFromStoragePersist(c *C) { | ||
1102 | 563 | filePath := filepath.Join(s.tempDir, "facebook.test/facebook-32.json") | ||
1103 | 564 | state, err := stateFromStorage(32) | ||
1104 | 565 | c.Check(err, NotNil) | ||
1105 | 566 | state.LastInboxUpdate = timeStamp("2014-08-25T18:39:32+0000") | ||
1106 | 567 | state.LastUpdate = timeStamp("2014-08-25T18:39:33+0000") | ||
1107 | 568 | err = state.persist(32) | ||
1108 | 569 | c.Check(err, IsNil) | ||
1109 | 570 | jsonData, err := ioutil.ReadFile(filePath) | ||
1110 | 571 | c.Check(err, IsNil) | ||
1111 | 572 | var data map[string]string | ||
1112 | 573 | json.Unmarshal(jsonData, &data) | ||
1113 | 574 | c.Check(data["last_inbox_update"], Equals, "2014-08-25T18:39:32+0000") | ||
1114 | 575 | c.Check(data["last_notification_update"], Equals, "2014-08-25T18:39:33+0000") | ||
1115 | 576 | } | ||
1116 | 577 | |||
1117 | 578 | func (s *S) TestStateFromStoragePersistFails(c *C) { | ||
1118 | 579 | state := fbState{LastInboxUpdate: "2014-08-25T18:39:32+0000", LastUpdate: "2014-08-25T18:39:33+0000"} | ||
1119 | 580 | plugins.XdgDataEnsure = plugins.XdgDataFind | ||
1120 | 581 | err := state.persist(32) | ||
1121 | 582 | c.Check(err, NotNil) | ||
1122 | 583 | } | ||
1123 | 584 | |||
1124 | 585 | func (s *S) TestStateFromStorage(c *C) { | ||
1125 | 586 | state := fbState{LastInboxUpdate: "2014-08-25T18:39:32+0000", LastUpdate: "2014-08-25T18:39:33+0000"} | ||
1126 | 587 | err := state.persist(32) | ||
1127 | 588 | c.Check(err, IsNil) | ||
1128 | 589 | newState, err := stateFromStorage(32) | ||
1129 | 590 | c.Check(err, IsNil) | ||
1130 | 591 | c.Check(newState.LastUpdate, Equals, timeStamp("2014-08-25T18:39:33+0000")) | ||
1131 | 592 | c.Check(newState.LastInboxUpdate, Equals, timeStamp("2014-08-25T18:39:32+0000")) | ||
1132 | 593 | // bad format | ||
1133 | 594 | state = fbState{LastInboxUpdate: "yesterday", LastUpdate: "2014-08-25T18:39:33+0000"} | ||
1134 | 595 | state.persist(32) | ||
1135 | 596 | _, err = stateFromStorage(32) | ||
1136 | 597 | c.Check(err, NotNil) | ||
1137 | 598 | state = fbState{LastInboxUpdate: "2014-08-25T18:39:33+0000", LastUpdate: "today"} | ||
1138 | 599 | state.persist(32) | ||
1139 | 600 | _, err = stateFromStorage(32) | ||
1140 | 601 | c.Check(err, NotNil) | ||
1141 | 602 | } | ||
1142 | 603 | |||
1143 | 604 | func (s *S) TestNew(c *C) { | ||
1144 | 605 | state := fbState{LastInboxUpdate: "2014-08-25T18:39:32+0000", LastUpdate: "2014-08-25T18:39:33+0000"} | ||
1145 | 606 | state.persist(32) | ||
1146 | 607 | p := New(32) | ||
1147 | 608 | fb := p.(*fbPlugin) | ||
1148 | 609 | c.Check(fb.state, DeepEquals, state) | ||
1149 | 610 | // with bad format | ||
1150 | 611 | state = fbState{LastInboxUpdate: "hola", LastUpdate: "mundo"} | ||
1151 | 612 | state.persist(32) | ||
1152 | 613 | p = New(32) | ||
1153 | 614 | fb = p.(*fbPlugin) | ||
1154 | 615 | c.Check(fb.state, DeepEquals, state) | ||
1155 | 616 | } | ||
1156 | 617 | |||
1157 | 618 | func (s *S) TestApplicationId(c *C) { | ||
1158 | 619 | expected := plugins.ApplicationId("com.ubuntu.developer.webapps.webapp-facebook_webapp-facebook") | ||
1159 | 620 | p := New(32) | ||
1160 | 621 | c.Check(p.ApplicationId(), Equals, expected) | ||
1161 | 622 | } | ||
1162 | 623 | |||
1163 | 624 | func (s *S) TestThreadIsValid(c *C) { | ||
1164 | 625 | t := thread{} | ||
1165 | 626 | t.Unseen = 1 | ||
1166 | 627 | t.Unread = 2 | ||
1167 | 628 | // 10m before Now, the thread should be valid | ||
1168 | 629 | t.UpdatedTime = timeStamp(time.Now().Add(-10 * time.Minute).Format(facebookTime)) | ||
1169 | 630 | tStamp := timeStamp(time.Now().Add(-20 * time.Minute).Format(facebookTime)) | ||
1170 | 631 | c.Check(t.isValid(tStamp), Equals, true) | ||
1171 | 632 | // 2m before Now, the thread should be invalid | ||
1172 | 633 | t.UpdatedTime = timeStamp(time.Now().Add(-2 * time.Minute).Format(facebookTime)) | ||
1173 | 634 | c.Check(t.isValid(tStamp), Equals, false) | ||
1174 | 635 | // unseen = 0 | ||
1175 | 636 | t.Unseen = 0 | ||
1176 | 637 | t.UpdatedTime = timeStamp(time.Now().Add(-10 * time.Minute).Format(facebookTime)) | ||
1177 | 638 | c.Check(t.isValid(tStamp), Equals, false) | ||
1178 | 639 | // unread = 0, unseen = 1 | ||
1179 | 640 | t.Unread = 0 | ||
1180 | 641 | t.Unseen = 1 | ||
1181 | 642 | c.Check(t.isValid(tStamp), Equals, false) | ||
1182 | 643 | } | ||
1183 | 644 | |||
1184 | 645 | func (s *S) TestGetInbox(c *C) { | ||
1185 | 646 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1186 | 647 | return &http.Response{ | ||
1187 | 648 | StatusCode: http.StatusOK, | ||
1188 | 649 | Body: closeWrapper{bytes.NewReader([]byte(inboxBody))}, | ||
1189 | 650 | }, nil | ||
1190 | 651 | } | ||
1191 | 652 | var state fbState | ||
1192 | 653 | authData := accounts.AuthData{} | ||
1193 | 654 | authData.AccessToken = "foo" | ||
1194 | 655 | p := &fbPlugin{state: state, accountId: 32} | ||
1195 | 656 | batch, err := p.getInbox(&authData) | ||
1196 | 657 | c.Assert(err, IsNil) | ||
1197 | 658 | c.Assert(batch, NotNil) | ||
1198 | 659 | msgs := batch.Messages | ||
1199 | 660 | c.Check(msgs, HasLen, 3) | ||
1200 | 661 | } | ||
1201 | 662 | |||
1202 | 663 | func (s *S) TestGetInboxRequestFails(c *C) { | ||
1203 | 664 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1204 | 665 | return &http.Response{ | ||
1205 | 666 | StatusCode: http.StatusBadRequest, | ||
1206 | 667 | Body: closeWrapper{bytes.NewReader([]byte(""))}, | ||
1207 | 668 | }, fmt.Errorf("please, fail") | ||
1208 | 669 | } | ||
1209 | 670 | var state fbState | ||
1210 | 671 | authData := accounts.AuthData{} | ||
1211 | 672 | authData.AccessToken = "foo" | ||
1212 | 673 | p := &fbPlugin{state: state, accountId: 32} | ||
1213 | 674 | _, err := p.getInbox(&authData) | ||
1214 | 675 | c.Check(err, NotNil) | ||
1215 | 676 | } | ||
1216 | 677 | |||
1217 | 678 | func (s *S) TestGetInboxParseResponseFails(c *C) { | ||
1218 | 679 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1219 | 680 | return &http.Response{ | ||
1220 | 681 | StatusCode: http.StatusOK, | ||
1221 | 682 | Body: closeWrapper{bytes.NewReader([]byte("hola" + inboxBody))}, | ||
1222 | 683 | }, nil | ||
1223 | 684 | } | ||
1224 | 685 | var state fbState | ||
1225 | 686 | authData := accounts.AuthData{} | ||
1226 | 687 | authData.AccessToken = "foo" | ||
1227 | 688 | p := &fbPlugin{state: state, accountId: 32} | ||
1228 | 689 | _, err := p.getInbox(&authData) | ||
1229 | 690 | c.Check(err, NotNil) | ||
1230 | 691 | } | ||
1231 | 692 | |||
1232 | 693 | func (s *S) TestGetNotifications(c *C) { | ||
1233 | 694 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1234 | 695 | return &http.Response{ | ||
1235 | 696 | StatusCode: http.StatusOK, | ||
1236 | 697 | Body: closeWrapper{bytes.NewReader([]byte(notificationsBody))}, | ||
1237 | 698 | }, nil | ||
1238 | 699 | } | ||
1239 | 700 | var state fbState | ||
1240 | 701 | authData := accounts.AuthData{} | ||
1241 | 702 | authData.AccessToken = "foo" | ||
1242 | 703 | p := &fbPlugin{state: state, accountId: 32} | ||
1243 | 704 | batch, err := p.getNotifications(&authData) | ||
1244 | 705 | c.Assert(err, IsNil) | ||
1245 | 706 | c.Assert(batch, NotNil) | ||
1246 | 707 | msgs := batch.Messages | ||
1247 | 708 | c.Check(msgs, HasLen, 2) | ||
1248 | 709 | } | ||
1249 | 710 | |||
1250 | 711 | func (s *S) TestGetNotificationsRequestFails(c *C) { | ||
1251 | 712 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1252 | 713 | return &http.Response{ | ||
1253 | 714 | StatusCode: http.StatusBadRequest, | ||
1254 | 715 | Body: closeWrapper{bytes.NewReader([]byte(""))}, | ||
1255 | 716 | }, fmt.Errorf("please, fail") | ||
1256 | 717 | } | ||
1257 | 718 | var state fbState | ||
1258 | 719 | authData := accounts.AuthData{} | ||
1259 | 720 | authData.AccessToken = "foo" | ||
1260 | 721 | p := &fbPlugin{state: state, accountId: 32} | ||
1261 | 722 | _, err := p.getNotifications(&authData) | ||
1262 | 723 | c.Check(err, NotNil) | ||
1263 | 724 | } | ||
1264 | 725 | |||
1265 | 726 | func (s *S) TestGetNotificationsParseResponseFails(c *C) { | ||
1266 | 727 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1267 | 728 | return &http.Response{ | ||
1268 | 729 | StatusCode: http.StatusOK, | ||
1269 | 730 | Body: closeWrapper{bytes.NewReader([]byte("hola" + notificationsBody))}, | ||
1270 | 731 | }, nil | ||
1271 | 732 | } | ||
1272 | 733 | var state fbState | ||
1273 | 734 | authData := accounts.AuthData{} | ||
1274 | 735 | authData.AccessToken = "foo" | ||
1275 | 736 | p := &fbPlugin{state: state, accountId: 32} | ||
1276 | 737 | _, err := p.getNotifications(&authData) | ||
1277 | 738 | c.Check(err, NotNil) | ||
1278 | 739 | } | ||
1279 | 740 | |||
1280 | 741 | func (s *S) TestPoll(c *C) { | ||
1281 | 742 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1282 | 743 | body := inboxBody | ||
1283 | 744 | if url == "me/notifications" { | ||
1284 | 745 | body = notificationsBody | ||
1285 | 746 | } | ||
1286 | 747 | return &http.Response{ | ||
1287 | 748 | StatusCode: http.StatusOK, | ||
1288 | 749 | Body: closeWrapper{bytes.NewReader([]byte(body))}, | ||
1289 | 750 | }, nil | ||
1290 | 751 | } | ||
1291 | 752 | var state fbState | ||
1292 | 753 | authData := accounts.AuthData{} | ||
1293 | 754 | authData.AccessToken = "foo" | ||
1294 | 755 | p := &fbPlugin{state: state, accountId: 32} | ||
1295 | 756 | batches, err := p.Poll(&authData) | ||
1296 | 757 | c.Assert(err, IsNil) | ||
1297 | 758 | c.Assert(batches, NotNil) | ||
1298 | 759 | c.Assert(batches, HasLen, 2) | ||
1299 | 760 | c.Assert(batches[0], NotNil) | ||
1300 | 761 | c.Assert(batches[1], NotNil) | ||
1301 | 762 | c.Check(batches[0].Tag, Equals, "notification") | ||
1302 | 763 | c.Check(batches[0].Messages, HasLen, 2) | ||
1303 | 764 | c.Check(batches[1].Tag, Equals, "inbox") | ||
1304 | 765 | c.Check(batches[1].Messages, HasLen, 3) | ||
1305 | 766 | } | ||
1306 | 767 | |||
1307 | 768 | func (s *S) TestPollSingleError(c *C) { | ||
1308 | 769 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1309 | 770 | body := inboxBody | ||
1310 | 771 | if url == "me/notifications" { | ||
1311 | 772 | body = "hola" + notificationsBody | ||
1312 | 773 | } | ||
1313 | 774 | return &http.Response{ | ||
1314 | 775 | StatusCode: http.StatusOK, | ||
1315 | 776 | Body: closeWrapper{bytes.NewReader([]byte(body))}, | ||
1316 | 777 | }, nil | ||
1317 | 778 | } | ||
1318 | 779 | var state fbState | ||
1319 | 780 | authData := accounts.AuthData{} | ||
1320 | 781 | authData.AccessToken = "foo" | ||
1321 | 782 | p := &fbPlugin{state: state, accountId: 32} | ||
1322 | 783 | batches, err := p.Poll(&authData) | ||
1323 | 784 | c.Assert(err, IsNil) | ||
1324 | 785 | c.Assert(batches, HasLen, 1) | ||
1325 | 786 | c.Assert(batches[0], NotNil) | ||
1326 | 787 | c.Check(batches[0].Messages, HasLen, 3) | ||
1327 | 788 | } | ||
1328 | 789 | |||
1329 | 790 | func (s *S) TestPollError(c *C) { | ||
1330 | 791 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1331 | 792 | body := "hola" + inboxBody | ||
1332 | 793 | if url == "me/notifications" { | ||
1333 | 794 | body = "hola" + notificationsBody | ||
1334 | 795 | } | ||
1335 | 796 | return &http.Response{ | ||
1336 | 797 | StatusCode: http.StatusOK, | ||
1337 | 798 | Body: closeWrapper{bytes.NewReader([]byte(body))}, | ||
1338 | 799 | }, nil | ||
1339 | 800 | } | ||
1340 | 801 | var state fbState | ||
1341 | 802 | authData := accounts.AuthData{} | ||
1342 | 803 | authData.AccessToken = "foo" | ||
1343 | 804 | p := &fbPlugin{state: state, accountId: 32} | ||
1344 | 805 | _, err := p.Poll(&authData) | ||
1345 | 806 | c.Check(err, NotNil) | ||
1346 | 807 | } | ||
1347 | 808 | |||
1348 | 809 | func (s *S) TestPollUseEnvToken(c *C) { | ||
1349 | 810 | doRequest = func(a *accounts.AuthData, url string) (*http.Response, error) { | ||
1350 | 811 | c.Check(a.AccessToken, Equals, "bar") | ||
1351 | 812 | return nil, fmt.Errorf("please, fail") | ||
1352 | 813 | } | ||
1353 | 814 | var state fbState | ||
1354 | 815 | authData := accounts.AuthData{} | ||
1355 | 816 | authData.AccessToken = "foo" | ||
1356 | 817 | os.Setenv("ACCOUNT_POLLD_TOKEN_FACEBOOK", "bar") | ||
1357 | 818 | // defer the unset | ||
1358 | 819 | defer os.Setenv("ACCOUNT_POLLD_TOKEN_FACEBOOK", "") | ||
1359 | 820 | p := &fbPlugin{state: state, accountId: 32} | ||
1360 | 821 | _, err := p.Poll(&authData) | ||
1361 | 822 | c.Check(err, NotNil) | ||
1362 | 823 | } | ||
1363 | 824 | |||
1364 | 825 | func (s *S) TestRequest(c *C) { | ||
1365 | 826 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
1366 | 827 | fmt.Fprintln(w, "Hello, client") | ||
1367 | 828 | })) | ||
1368 | 829 | defer ts.Close() | ||
1369 | 830 | baseUrl, _ = url.Parse(ts.URL) | ||
1370 | 831 | authData := accounts.AuthData{} | ||
1371 | 832 | authData.AccessToken = "foo" | ||
1372 | 833 | _, err := request(&authData, "me/notifications") | ||
1373 | 834 | c.Check(err, IsNil) | ||
1374 | 835 | } |
PASSED: Continuous integration, rev:138 jenkins. qa.ubuntu. com/job/ account- polld-ci/ 89/ jenkins. qa.ubuntu. com/job/ account- polld-vivid- amd64-ci/ 4 jenkins. qa.ubuntu. com/job/ account- polld-vivid- armhf-ci/ 4 jenkins. qa.ubuntu. com/job/ account- polld-vivid- armhf-ci/ 4/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ account- polld-vivid- i386-ci/ 4
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/ 89/rebuild
http://