Merge lp:~alecu/unity-lens-music/musicstore-purchase into lp:~unity-team/unity-lens-music/libunity7-compatible
- musicstore-purchase
- Merge into libunity7-compatible
Status: | Merged |
---|---|
Approved by: | Didier Roche-Tolomelli |
Approved revision: | 146 |
Merged at revision: | 141 |
Proposed branch: | lp:~alecu/unity-lens-music/musicstore-purchase |
Merge into: | lp:~unity-team/unity-lens-music/libunity7-compatible |
Diff against target: |
1017 lines (+402/-188) 9 files modified
configure.ac (+1/-0) debian/changelog (+7/-2) debian/control (+1/-0) src/Makefile.am (+2/-1) src/album.vala (+1/-0) src/musicstore-collection.vala (+12/-0) src/musicstore-scope.vala (+218/-4) src/ubuntuone-webservices.vala (+94/-65) tests/unit/test-ubuntuone-purchases.vala (+66/-116) |
To merge this branch: | bzr merge lp:~alecu/unity-lens-music/musicstore-purchase |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Didier Roche-Tolomelli | Approve | ||
PS Jenkins bot (community) | continuous-integration | Approve | |
Michal Hruby (community) | Approve | ||
Review via email: mp+154483@code.launchpad.net |
Commit message
- Use payment preview for music purchases (LP: #1154176).
Description of the change
Use the payment preview for music purchases in the musicstore scope.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 143. By Alejandro J. Cura
-
Remove FIXME comment
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:143
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 144. By Alejandro J. Cura
-
Apply missing fixes
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:144
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michal Hruby (mhr3) wrote : | # |
329 + var cover_pixbuf = new Pixbuf.from_stream (cover_file.read ());
Doing a blocking read on an http connection before actually returning a preview is a very bad idea. Please move this elsewhere.
- 145. By Alejandro J. Cura
-
Don't set notification icon before it's needed
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:145
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 146. By Alejandro J. Cura
-
fixed pay url
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:146
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild:
http://
Didier Roche-Tolomelli (didrocks) wrote : | # |
Online services QA gave its +1, let's get it merged
Preview Diff
1 | === modified file 'configure.ac' |
2 | --- configure.ac 2013-03-14 18:45:38 +0000 |
3 | +++ configure.ac 2013-03-22 04:32:21 +0000 |
4 | @@ -75,6 +75,7 @@ |
5 | sqlite3 >= 3.7.7 |
6 | gee-1.0 |
7 | json-glib-1.0 |
8 | + libnotify |
9 | libsoup-2.4 |
10 | oauth |
11 | unity >= 6.90.0 |
12 | |
13 | === modified file 'debian/changelog' |
14 | --- debian/changelog 2013-03-15 13:22:25 +0000 |
15 | +++ debian/changelog 2013-03-22 04:32:21 +0000 |
16 | @@ -1,11 +1,16 @@ |
17 | -unity-lens-music (6.8.1daily13.03.04-0ubuntu2) UNRELEASED; urgency=low |
18 | +unity-lens-music (6.8.1daily13.03.04-0ubuntu3) UNRELEASED; urgency=low |
19 | |
20 | + [ Didier Roche ] |
21 | * debian/control: |
22 | - bump build-deps to new libunity |
23 | * debian/*install: |
24 | - installing/renaming files |
25 | |
26 | - -- Didier Roche <didrocks@ubuntu.com> Fri, 15 Mar 2013 14:21:36 +0100 |
27 | + [ Alejandro J. Cura ] |
28 | + * debian/control: |
29 | + - added libnotify-dev build dependency |
30 | + |
31 | + -- Alejandro J. Cura <alecu@canonical.com> Tue, 19 Mar 2013 18:45:38 -0300 |
32 | |
33 | unity-lens-music (6.8.1daily13.03.04-0ubuntu1) raring; urgency=low |
34 | |
35 | |
36 | === modified file 'debian/control' |
37 | --- debian/control 2013-03-15 13:11:43 +0000 |
38 | +++ debian/control 2013-03-22 04:32:21 +0000 |
39 | @@ -11,6 +11,7 @@ |
40 | libglib2.0-dev (>= 2.32), |
41 | libgee-dev, |
42 | libjson-glib-dev, |
43 | + libnotify-dev, |
44 | libsoup2.4-dev, |
45 | liboauth-dev, |
46 | libdee-dev (>= 1.0.7), |
47 | |
48 | === modified file 'src/Makefile.am' |
49 | --- src/Makefile.am 2013-03-13 12:14:27 +0000 |
50 | +++ src/Makefile.am 2013-03-22 04:32:21 +0000 |
51 | @@ -72,8 +72,9 @@ |
52 | |
53 | unity_musicstore_daemon_VALAFLAGS = \ |
54 | --pkg json-glib-1.0 \ |
55 | + --pkg libnotify \ |
56 | + --pkg libsoup-2.4 \ |
57 | --pkg oauth \ |
58 | - --pkg libsoup-2.4 \ |
59 | $(unity_music_daemon_VALAFLAGS) |
60 | |
61 | unity_musicstore_daemon_LDADD = \ |
62 | |
63 | === modified file 'src/album.vala' |
64 | --- src/album.vala 2012-09-11 12:50:34 +0000 |
65 | +++ src/album.vala 2013-03-22 04:32:21 +0000 |
66 | @@ -28,5 +28,6 @@ |
67 | public string uri { get; set; } |
68 | public string artwork_path { get; set; } |
69 | public string formatted_price { get; set; } |
70 | + public string purchase_sku { get; set; } |
71 | } |
72 | } |
73 | \ No newline at end of file |
74 | |
75 | === modified file 'src/musicstore-collection.vala' |
76 | --- src/musicstore-collection.vala 2013-03-13 12:14:27 +0000 |
77 | +++ src/musicstore-collection.vala 2013-03-22 04:32:21 +0000 |
78 | @@ -104,6 +104,18 @@ |
79 | track.duration = (int)root_obj.get_member ("duration").get_int (); |
80 | tracks.append (track); |
81 | } |
82 | + |
83 | + if (root_obj.has_member ("id")) |
84 | + { |
85 | + album.purchase_sku = root_obj.get_string_member ("id"); |
86 | + } |
87 | + else |
88 | + { |
89 | + // U1MS does not allow purchases of an album with no purchase_sku |
90 | + album.purchase_sku = ""; |
91 | + warning ("Json has no purchase_sku in '%s'", details_uri); |
92 | + } |
93 | + |
94 | } |
95 | else |
96 | { |
97 | |
98 | === modified file 'src/musicstore-scope.vala' |
99 | --- src/musicstore-scope.vala 2013-03-13 12:14:27 +0000 |
100 | +++ src/musicstore-scope.vala 2013-03-22 04:32:21 +0000 |
101 | @@ -18,14 +18,27 @@ |
102 | */ |
103 | |
104 | using GLib; |
105 | +using Notify; |
106 | +using Unity; |
107 | +using Gdk; |
108 | +using Ubuntuone.Webservice; |
109 | |
110 | namespace Unity.MusicLens { |
111 | + private const string FORGOTTEN_PASSWORD_URL = "https://login.ubuntu.com/+forgot_password"; |
112 | + private const string CHANGE_PAYMENT_METHOD_URL = "https://pay.ubuntu.com/account/"; |
113 | + private const string ERROR_MESSAGE_NOT_LOGGED_IN = _("It seems you don't have an Ubuntu One account, or you are not logged in. To continue, please login and visit the Ubuntu One online store."); |
114 | + private const string ERROR_MESSAGE_NO_PAYMENT_METHOD = _("It seems you haven't set yet your preferred Ubuntu One payment method. To add a payment method, please visit the Ubuntu One online store."); |
115 | + private const string ERROR_MESSAGE_TECHNICAL_PROBLEM = _("Sorry, we have encountered a technical problem. No money has been taken from your account. To try your purchase again, please visit the Ubuntu One online store."); |
116 | + private const string U1_SSO_UI = "u1_login_register"; |
117 | |
118 | public class MusicStoreScopeProxy : SimpleScope |
119 | { |
120 | private MusicStoreCollection collection; |
121 | private Unity.MusicPreview? music_preview; |
122 | private PreferencesManager preferences = PreferencesManager.get_default (); |
123 | + private HashTable<string, Album> album_map; |
124 | + private PurchaseService purchase_service; |
125 | + private Notification notification; |
126 | |
127 | public MusicStoreScopeProxy () |
128 | { |
129 | @@ -41,7 +54,11 @@ |
130 | |
131 | base.initialize (); |
132 | |
133 | + Notify.init ("Music Store Scope"); |
134 | + notification = new Notification("Album name", _("Purchase started"), ""); |
135 | collection = new MusicStoreCollection (); |
136 | + album_map = new HashTable<string, Album>(str_hash, str_equal); |
137 | + purchase_service = new PurchaseService (); |
138 | |
139 | preferences.notify["remote-content-search"].connect((obj, pspec) => { scope.queue_search_changed(SearchType.DEFAULT); }); |
140 | |
141 | @@ -66,11 +83,11 @@ |
142 | else |
143 | { |
144 | warning ("Failed to generate preview for %s", uri); |
145 | - return download_album (uri); |
146 | + return open_uri (uri); |
147 | } |
148 | } |
149 | |
150 | - public Unity.ActivationResponse download_album (string uri) |
151 | + public Unity.ActivationResponse open_uri (string uri) |
152 | { |
153 | /* launch the music store streaming client or the webstore or whatevz */ |
154 | try { |
155 | @@ -90,6 +107,7 @@ |
156 | |
157 | if (album != null) |
158 | { |
159 | + album_map.insert (uri, album); |
160 | File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri |
161 | var cover = new FileIcon (cover_file); |
162 | |
163 | @@ -110,10 +128,10 @@ |
164 | } |
165 | |
166 | GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg")); |
167 | - var download_action = new Unity.PreviewAction ("download_album", _("Download"), icon); |
168 | + var download_action = new Unity.PreviewAction ("show_purchase_preview", _("Download"), icon); |
169 | if (album.formatted_price != null) |
170 | download_action.extra_text = album.formatted_price; |
171 | - download_action.activated.connect (download_album); |
172 | + download_action.activated.connect (show_purchase_preview); |
173 | music_preview.add_action (download_action); |
174 | } |
175 | return music_preview; |
176 | @@ -146,6 +164,202 @@ |
177 | } catch (IOError e) { |
178 | warning ("Failed to search for '%s': %s", search.search_string, e.message); |
179 | } |
180 | + |
181 | + try { |
182 | + yield purchase_service.fetch_account_info (); |
183 | + debug ("retrieved account info: %s %s", purchase_service.nickname, purchase_service.email); |
184 | + } catch (PurchaseError e) { |
185 | + debug ("can't get account info: %s", e.message); |
186 | + } |
187 | + } |
188 | + |
189 | + delegate Unity.ActivationResponse LinkHandler (string uri); |
190 | + |
191 | + private Unity.ActivationResponse build_error_preview (string uri, string error_header, string link_text, LinkHandler link_handler) |
192 | + { |
193 | + Album album = null; |
194 | + SList<Track> tracks = null; |
195 | + collection.get_album_details (uri, out album, out tracks); |
196 | + debug ("album art uri: %s", album.artwork_path); |
197 | + File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri |
198 | + var cover = new FileIcon (cover_file); |
199 | + var error_preview = new Unity.PaymentPreview.for_error(album.title, album.artist, cover); |
200 | + error_preview.header = error_header; |
201 | + var error_action = new Unity.PreviewAction ("open_u1_link", link_text, null); |
202 | + error_action.activated.connect (() => link_handler (uri)); |
203 | + error_preview.add_action (error_action); |
204 | + return new Unity.ActivationResponse.with_preview (error_preview); |
205 | + } |
206 | + |
207 | + public Unity.ActivationResponse open_forgot_password_url (string uri) |
208 | + { |
209 | + return open_uri (FORGOTTEN_PASSWORD_URL); |
210 | + } |
211 | + |
212 | + public Unity.ActivationResponse change_payment_method (string uri) |
213 | + { |
214 | + return open_uri (CHANGE_PAYMENT_METHOD_URL); |
215 | + } |
216 | + |
217 | + public Unity.ActivationResponse cancel_purchase (string uri) |
218 | + { |
219 | + return new Unity.ActivationResponse.with_preview (preview (uri)); |
220 | + } |
221 | + |
222 | + public Unity.ActivationResponse open_sso_login (string uri) |
223 | + { |
224 | + Album album = null; |
225 | + SList<Track> tracks = null; |
226 | + collection.get_album_details (uri, out album, out tracks); |
227 | + |
228 | + var ui_path = Environment.find_program_in_path (U1_SSO_UI); |
229 | + GLib.Pid child_pid; |
230 | + string argv[11] = { |
231 | + ui_path, |
232 | + "--album", album.title, |
233 | + "--artist", album.artist, |
234 | + "--price", album.formatted_price, |
235 | + "--picture", album.artwork_path, |
236 | + "--url", uri |
237 | + }; |
238 | + if (ui_path != null) { |
239 | + try { |
240 | + bool was_started = Process.spawn_async (null, argv, null, 0, null, out child_pid); |
241 | + // hide dash |
242 | + if (was_started) { |
243 | + return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH); |
244 | + } |
245 | + } catch (GLib.SpawnError e) { |
246 | + debug ("Failed to start u1_sso_ui for uri %s", uri); |
247 | + } |
248 | + } |
249 | + return open_uri (uri); |
250 | + } |
251 | + |
252 | + public Unity.ActivationResponse show_purchase_preview (string uri) |
253 | + { |
254 | + if (purchase_service.got_credentials () == false) |
255 | + { |
256 | + debug ("no credentials available, opening sso login. %s", uri); |
257 | + return build_error_preview (uri, ERROR_MESSAGE_NOT_LOGGED_IN, _("Continue"), open_sso_login); |
258 | + } |
259 | + |
260 | + Album album = null; |
261 | + SList<Track> tracks = null; |
262 | + collection.get_album_details (uri, out album, out tracks); |
263 | + if (album != null) |
264 | + { |
265 | + try { |
266 | + purchase_service.fetch_payment_info (album.purchase_sku); |
267 | + debug ("retrieved payment method: %s", purchase_service.selected_payment_method); |
268 | + return new Unity.ActivationResponse.with_preview (purchase_preview (uri, null)); |
269 | + } catch (PurchaseError e) { |
270 | + debug ("can't get default payment method: %s", e.message); |
271 | + return build_error_preview (uri, ERROR_MESSAGE_NO_PAYMENT_METHOD, _("Choose Payment Method"), change_payment_method); |
272 | + } |
273 | + } |
274 | + return open_uri (uri); |
275 | + } |
276 | + |
277 | + public Unity.ActivationResponse purchase_album (Unity.PreviewAction action, string uri) |
278 | + { |
279 | + var password = action.hints["password"].get_string(); |
280 | + |
281 | + if (password == null) { |
282 | + var preview = purchase_preview (uri, _("Please enter your password")); |
283 | + debug ("empty password."); |
284 | + return new Unity.ActivationResponse.with_preview (preview); |
285 | + } |
286 | + |
287 | + var album = album_map.get (uri); |
288 | + |
289 | + File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri |
290 | + try { |
291 | + var cover_pixbuf = new Pixbuf.from_stream (cover_file.read ()); |
292 | + notification.set_icon_from_pixbuf (cover_pixbuf); |
293 | + } catch (GLib.Error e) { |
294 | + debug ("Cannot set notification icon from uri %s", uri); |
295 | + } |
296 | + |
297 | + notification.summary = album.title; |
298 | + notification.body = _("Authorizing purchase"); |
299 | + try { |
300 | + notification.show (); |
301 | + } catch (GLib.Error e) { |
302 | + debug ("Error while showing notification: %s", e.message); |
303 | + } |
304 | + |
305 | + try { |
306 | + purchase_service.purchase (album.purchase_sku, password); |
307 | + debug ("purchase completed."); |
308 | + notification.update (album.title, _("Purchase completed"), ""); |
309 | + try { |
310 | + notification.show (); |
311 | + } catch (GLib.Error e) { |
312 | + debug ("Error while showing notification: %s", e.message); |
313 | + } |
314 | + return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH); |
315 | + } catch (PurchaseError e) { |
316 | + if (e is PurchaseError.WRONG_PASSWORD_ERROR) { |
317 | + debug ("wrong password error: %s", e.message); |
318 | + return new Unity.ActivationResponse.with_preview (purchase_preview (uri, _("Wrong password"))); |
319 | + } else { |
320 | + debug ("got purchase error: %s", e.message); |
321 | + return build_error_preview (uri, ERROR_MESSAGE_TECHNICAL_PROBLEM, _("Continue"), open_uri); |
322 | + } |
323 | + } |
324 | + } |
325 | + |
326 | + public Unity.Preview purchase_preview (string uri, string? error_message) |
327 | + { |
328 | + Unity.PaymentPreview album_purchase_preview = null; |
329 | + Album album = null; |
330 | + SList<Track> tracks = null; |
331 | + collection.get_album_details (uri, out album, out tracks); |
332 | + |
333 | + if (album != null) |
334 | + { |
335 | + File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri |
336 | + |
337 | + var cover = new FileIcon (cover_file); |
338 | + album_purchase_preview = new Unity.PaymentPreview.for_music(album.title, album.artist, cover); |
339 | + |
340 | + album_purchase_preview.header = _("Hi %s, you purchased in the past from Ubuntu One," |
341 | + + " would you like to use the same payment details? Please review your order.").printf (purchase_service.nickname); |
342 | + album_purchase_preview.email = purchase_service.email; |
343 | + album_purchase_preview.payment_method = purchase_service.selected_payment_method; |
344 | + album_purchase_preview.purchase_prize = album.formatted_price; |
345 | + album_purchase_preview.purchase_type = _("Digital CD"); |
346 | + |
347 | + // data |
348 | + |
349 | + var data = new HashTable<string, Variant>(str_hash, str_equal); |
350 | + if (error_message != null) { |
351 | + data["error_message"] = error_message; |
352 | + } |
353 | + |
354 | + InfoHint info_hint = new InfoHint.with_variant("album_purchase_preview", "", null, data); |
355 | + album_purchase_preview.add_info(info_hint); |
356 | + |
357 | + // actions |
358 | + |
359 | + var purchase_action = new Unity.PreviewAction ("purchase_album", _("Purchase"), null); |
360 | + purchase_action.activated.connect (purchase_album); |
361 | + album_purchase_preview.add_action (purchase_action); |
362 | + |
363 | + var forgot_password_action = new Unity.PreviewAction ("forgot_password", _("forgotten your Ubuntu One password?"), null); |
364 | + forgot_password_action.activated.connect (open_forgot_password_url); |
365 | + album_purchase_preview.add_action (forgot_password_action); |
366 | + |
367 | + var cancel_action = new Unity.PreviewAction ("cancel_purchase", _("Cancel"), null); |
368 | + cancel_action.activated.connect (cancel_purchase); |
369 | + album_purchase_preview.add_action (cancel_action); |
370 | + |
371 | + var change_payment_method_action = new Unity.PreviewAction ("change_payment_method", _("change payment method"), null); |
372 | + change_payment_method_action.activated.connect (change_payment_method); |
373 | + album_purchase_preview.add_action (change_payment_method_action); |
374 | + } |
375 | + return album_purchase_preview; |
376 | } |
377 | |
378 | } |
379 | |
380 | === modified file 'src/ubuntuone-webservices.vala' |
381 | --- src/ubuntuone-webservices.vala 2012-12-06 15:39:38 +0000 |
382 | +++ src/ubuntuone-webservices.vala 2013-03-22 04:32:21 +0000 |
383 | @@ -28,19 +28,20 @@ |
384 | public abstract void find_credentials () throws IOError; |
385 | } |
386 | |
387 | -const string WEBAPI_URI = "https://edge.one.ubuntu.com/"; |
388 | -const string ACCOUNT_URI = WEBAPI_URI + "api/account"; |
389 | -const string PAYMENT_METHOD_URI = WEBAPI_URI + "music-store/api/1/user/retrieve-payment-method?purchase_sku=%s"; |
390 | -const string PURCHASE_WITH_DEFAULT_PAYMENT_URI = WEBAPI_URI + "music-store/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s"; |
391 | -const string AUTHENTICATION_URI = "https://login.staging.ubuntu.com/api/1.1/authentications"; |
392 | +const string WEBAPI_SERVER = "https://one.ubuntu.com/"; |
393 | +const string ACCOUNT_PATH = "api/account"; |
394 | +const string PAYMENT_METHOD_PATH = "music-store-up/api/1/user/retrieve-payment-method?purchase_sku=%s"; |
395 | +const string PURCHASE_WITH_DEFAULT_PAYMENT_PATH = "music-store-up/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s"; |
396 | +const string AUTHENTICATION_SERVER = "https://login.ubuntu.com/"; |
397 | +const string AUTHENTICATION_PATH = "api/1.1/authentications"; |
398 | const string AUTHENTICATE_PARAMS = "ws.op=authenticate&token_name=Purchase_Token"; |
399 | -const int PASSWORD_CACHE_SECONDS = 300; |
400 | -const int PAYMENT_METHOD_CACHE_SECONDS = 120; |
401 | + |
402 | |
403 | namespace Ubuntuone.Webservice |
404 | { |
405 | public errordomain PurchaseError |
406 | { |
407 | + MISSING_CREDENTIALS_ERROR, |
408 | PURCHASE_ERROR, |
409 | WRONG_PASSWORD_ERROR, |
410 | UNSPECIFIED_ERROR |
411 | @@ -56,17 +57,13 @@ |
412 | public string selected_payment_method { get; internal set; default = null; } |
413 | public string consumer_key { get; private set; default = null; } |
414 | public string token { get; private set; default = null; } |
415 | - internal DateTime _selected_payment_method_expiry; |
416 | - DateTime _password_expiry; |
417 | + public string open_url { get; private set; default = null; } |
418 | internal HashTable <string, string> _ubuntuone_credentials = null; |
419 | |
420 | construct { |
421 | http_session = build_http_session (); |
422 | http_session_sso = build_http_session (); |
423 | |
424 | - reset_payment_method_cache (0); |
425 | - reset_password_cache (0); |
426 | - |
427 | credentials_management = build_credentials_management (); |
428 | } |
429 | |
430 | @@ -77,24 +74,40 @@ |
431 | return session; |
432 | } |
433 | |
434 | - internal DateTime now () |
435 | - { |
436 | - return new DateTime.now_utc (); |
437 | - } |
438 | - |
439 | - internal bool expired_payment_method_cache () |
440 | - { |
441 | - return _selected_payment_method_expiry.compare (now ()) <= 0; |
442 | - } |
443 | - |
444 | - internal void reset_payment_method_cache (int seconds_from_now) |
445 | - { |
446 | - _selected_payment_method_expiry = now ().add_seconds (seconds_from_now); |
447 | - } |
448 | - |
449 | - internal void reset_password_cache (int seconds_from_now) |
450 | - { |
451 | - _password_expiry = now ().add_seconds (seconds_from_now); |
452 | + string webapi_server () |
453 | + { |
454 | + string staging_webapi = Environment.get_variable ("U1_STAGING_WEBAPI"); |
455 | + return staging_webapi != null ? staging_webapi : WEBAPI_SERVER; |
456 | + } |
457 | + |
458 | + string account_uri () |
459 | + { |
460 | + return webapi_server() + ACCOUNT_PATH; |
461 | + } |
462 | + |
463 | + string payment_method_uri () |
464 | + { |
465 | + return webapi_server() + PAYMENT_METHOD_PATH; |
466 | + } |
467 | + |
468 | + string purchase_with_default_payment_uri () |
469 | + { |
470 | + return webapi_server() + PURCHASE_WITH_DEFAULT_PAYMENT_PATH; |
471 | + } |
472 | + |
473 | + string authentication_server () |
474 | + { |
475 | + string staging_authentication = Environment.get_variable ("U1_STAGING_AUTHENTICATION"); |
476 | + return staging_authentication != null ? staging_authentication : AUTHENTICATION_SERVER; |
477 | + } |
478 | + |
479 | + string authentication_uri () |
480 | + { |
481 | + return authentication_server() + AUTHENTICATION_PATH; |
482 | + } |
483 | + |
484 | + public bool got_credentials () { |
485 | + return _ubuntuone_credentials != null; |
486 | } |
487 | |
488 | internal virtual CredentialsManagement build_credentials_management () |
489 | @@ -125,25 +138,33 @@ |
490 | email = root_object.get_string_member("email"); |
491 | } |
492 | |
493 | - internal void parse_payment_method_json (string json_string) throws GLib.Error |
494 | + internal void parse_payment_method_json (string json_string) throws GLib.Error, PurchaseError |
495 | { |
496 | var root_object = parse_json (json_string); |
497 | - Json.Object payload = root_object.get_object_member("payload"); |
498 | - selected_payment_method = payload.get_string_member("selected_payment_method"); |
499 | + if (root_object.has_member ("selected_payment_method")) { |
500 | + selected_payment_method = root_object.get_string_member("selected_payment_method"); |
501 | + } else { |
502 | + open_url = root_object.get_string_member ("open_url"); |
503 | + var error_message = root_object.get_string_member ("error_message"); |
504 | + throw new PurchaseError.PURCHASE_ERROR (error_message); |
505 | + } |
506 | } |
507 | |
508 | internal void parse_authentication_json (string json_string) throws GLib.Error |
509 | { |
510 | var root_object = parse_json (json_string); |
511 | - consumer_key = root_object.get_string_member("consumer_key"); |
512 | - token = root_object.get_string_member("token"); |
513 | + consumer_key = root_object.get_string_member ("consumer_key"); |
514 | + token = root_object.get_string_member ("token"); |
515 | } |
516 | |
517 | internal string parse_purchase_json (string json_string) throws GLib.Error |
518 | { |
519 | var root_object = parse_json (json_string); |
520 | - Json.Object payload = root_object.get_object_member("payload"); |
521 | - return payload.get_string_member("open_url"); |
522 | + if (root_object.has_member ("open_url")) { |
523 | + return root_object.get_string_member("open_url"); |
524 | + } else { |
525 | + return ""; |
526 | + } |
527 | } |
528 | |
529 | internal virtual async void fetch_credentials () throws PurchaseError |
530 | @@ -156,7 +177,7 @@ |
531 | fetch_credentials.callback (); |
532 | }); |
533 | ulong error_handler = credentials_management.credentials_error.connect (() => { |
534 | - error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens."); |
535 | + error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens."); |
536 | fetch_credentials.callback (); |
537 | }); |
538 | |
539 | @@ -164,7 +185,7 @@ |
540 | credentials_management.find_credentials (); |
541 | yield; |
542 | } catch (IOError e) { |
543 | - error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens: %s", e.message); |
544 | + error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens: %s", e.message); |
545 | } |
546 | |
547 | credentials_management.disconnect (found_handler); |
548 | @@ -208,7 +229,7 @@ |
549 | internal virtual async void fetch_account () throws PurchaseError |
550 | { |
551 | string response; |
552 | - PurchaseError error = yield call_api ("GET", ACCOUNT_URI, out response); |
553 | + PurchaseError error = yield call_api ("GET", account_uri(), out response); |
554 | |
555 | if (error != null) { |
556 | debug ("Error while fetching U1 account: %s.", error.message); |
557 | @@ -224,43 +245,39 @@ |
558 | } |
559 | } |
560 | |
561 | - internal virtual async void fetch_payment_method (string purchase_sku) throws PurchaseError |
562 | + internal virtual void fetch_payment_method (string purchase_sku) throws PurchaseError |
563 | { |
564 | - string response; |
565 | - var uri = PAYMENT_METHOD_URI.printf (purchase_sku); |
566 | - PurchaseError error = yield call_api ("GET", uri, out response); |
567 | + var uri = payment_method_uri().printf (purchase_sku); |
568 | |
569 | - if (error != null) { |
570 | - debug ("Error while fetching payment method: %s.", error.message); |
571 | - throw error; |
572 | + var message = send_signed_webservice_call ("GET", uri); |
573 | + if (message.status_code != Soup.KnownStatusCode.OK) { |
574 | + debug ("Purchase request failed: HTTP %u", message.status_code); |
575 | + debug ("Reason: %s", message.reason_phrase); |
576 | + try { |
577 | + message.response_body.flatten (); |
578 | + debug ("body: ------\n%s\n------\n", (string) message.response_body.data); |
579 | + } catch (Error e) { |
580 | + } |
581 | + throw new PurchaseError.PURCHASE_ERROR ("Retrieve payment method failed: %s".printf (message.reason_phrase)); |
582 | } |
583 | - |
584 | try { |
585 | - parse_payment_method_json (response); |
586 | - debug ("got payment method"); |
587 | + message.response_body.flatten (); |
588 | + var result = (string) message.response_body.data; |
589 | + parse_payment_method_json (result); |
590 | } catch (GLib.Error e) { |
591 | - debug ("Error while getting payment method: %s.", e.message); |
592 | throw new PurchaseError.PURCHASE_ERROR (e.message); |
593 | } |
594 | } |
595 | |
596 | - internal virtual async void refetch_payment_info (string purchase_sku) throws PurchaseError |
597 | + public virtual async void fetch_account_info () throws PurchaseError |
598 | { |
599 | yield fetch_credentials (); |
600 | yield fetch_account (); |
601 | - yield fetch_payment_method (purchase_sku); |
602 | - reset_payment_method_cache (PAYMENT_METHOD_CACHE_SECONDS); |
603 | } |
604 | |
605 | - public async void fetch_payment_info (string purchase_sku) throws PurchaseError |
606 | + public virtual void fetch_payment_info (string purchase_sku) throws PurchaseError |
607 | { |
608 | - if (expired_payment_method_cache ()) |
609 | - { |
610 | - debug ("refetching"); |
611 | - yield refetch_payment_info (purchase_sku); |
612 | - } else { |
613 | - debug ("in cache"); |
614 | - } |
615 | + fetch_payment_method (purchase_sku); |
616 | } |
617 | |
618 | internal virtual void _do_sso_webcall (Soup.Message message, string password) |
619 | @@ -286,6 +303,11 @@ |
620 | if (message.status_code == Soup.KnownStatusCode.UNAUTHORIZED) { |
621 | throw new PurchaseError.WRONG_PASSWORD_ERROR ("Wrong password"); |
622 | } |
623 | + try { |
624 | + message.response_body.flatten (); |
625 | + debug ("body: ------\n%s\n------\n", (string) message.response_body.data); |
626 | + } catch (Error e) { |
627 | + } |
628 | throw new PurchaseError.PURCHASE_ERROR (message.reason_phrase); |
629 | } |
630 | message.response_body.flatten (); |
631 | @@ -294,7 +316,7 @@ |
632 | |
633 | internal virtual string get_purchase_token (string password) throws PurchaseError |
634 | { |
635 | - var result = authenticated_sso_webcall ("POST", AUTHENTICATION_URI, AUTHENTICATE_PARAMS, password); |
636 | + var result = authenticated_sso_webcall ("POST", authentication_uri(), AUTHENTICATE_PARAMS, password); |
637 | try { |
638 | parse_authentication_json (result); |
639 | } catch (GLib.Error e) { |
640 | @@ -311,14 +333,19 @@ |
641 | return message; |
642 | } |
643 | |
644 | - internal virtual void purchase_with_default_payment (string sku, string purchase_token) throws PurchaseError |
645 | + internal virtual void purchase_with_default_payment (string album_id, string purchase_token) throws PurchaseError |
646 | { |
647 | - var uri = PURCHASE_WITH_DEFAULT_PAYMENT_URI.printf (sku, purchase_token); |
648 | + var uri = purchase_with_default_payment_uri().printf (album_id, purchase_token); |
649 | var message = send_signed_webservice_call ("GET", uri); |
650 | |
651 | if (message.status_code != Soup.KnownStatusCode.OK) { |
652 | debug ("Purchase request failed: HTTP %u", message.status_code); |
653 | debug ("Reason: %s", message.reason_phrase); |
654 | + try { |
655 | + message.response_body.flatten (); |
656 | + debug ("body: ------\n%s\n------\n", (string) message.response_body.data); |
657 | + } catch (Error e) { |
658 | + } |
659 | throw new PurchaseError.PURCHASE_ERROR ("Purchase failed: %s".printf (message.reason_phrase)); |
660 | } |
661 | try { |
662 | @@ -336,7 +363,9 @@ |
663 | public void purchase (string album_id, string password) throws PurchaseError |
664 | { |
665 | var purchase_token = get_purchase_token (password); |
666 | + debug ("purchasing..."); |
667 | purchase_with_default_payment (album_id, purchase_token); |
668 | + debug ("purchase completed."); |
669 | } |
670 | } |
671 | } |
672 | |
673 | === modified file 'tests/unit/test-ubuntuone-purchases.vala' |
674 | --- tests/unit/test-ubuntuone-purchases.vala 2012-12-06 15:39:38 +0000 |
675 | +++ tests/unit/test-ubuntuone-purchases.vala 2013-03-22 04:32:21 +0000 |
676 | @@ -25,7 +25,7 @@ |
677 | |
678 | const string FAKE_URL = "http://fake/url"; |
679 | const string FAKE_PASSWORD = "PezEspada"; |
680 | -const string FAKE_SKU = "fake_store:fake_album:id"; |
681 | +const string FAKE_SKU = "7digital:fake_album:id"; |
682 | const string FAKE_TOKEN = "a fake token"; |
683 | |
684 | const string BROKEN_JSON = """ |
685 | @@ -45,14 +45,17 @@ |
686 | |
687 | const string FAKE_JSON_PAYMENT_METHOD = """ |
688 | { |
689 | - "meta": { |
690 | - "status": "Ok" |
691 | - }, |
692 | - "payload": { |
693 | - "open_url": "", |
694 | - "user_email": "mr@be.an", |
695 | - "selected_payment_method": "Visa 1234" |
696 | - } |
697 | + "open_url": "", |
698 | + "user_email": "mr@be.an", |
699 | + "selected_payment_method": "Visa 1234" |
700 | + } |
701 | +"""; |
702 | + |
703 | + |
704 | +const string FAKE_JSON_PAYMENT_METHOD_ERROR = """ |
705 | + { |
706 | + "open_url": "http://somewhere/else", |
707 | + "error_message": "No default payment method selected." |
708 | } |
709 | """; |
710 | |
711 | @@ -69,30 +72,18 @@ |
712 | |
713 | const string FAKE_JSON_PURCHASE = """ |
714 | { |
715 | - "meta": { |
716 | - "status": "Ok" |
717 | - }, |
718 | - "payload": { |
719 | - "order_id": "1111", |
720 | - "order_status": "OK", |
721 | - "order_detail": "TBD", |
722 | - "open_url": "" |
723 | - } |
724 | + "order_id": "1111", |
725 | + "order_status": "OK" |
726 | } |
727 | """; |
728 | |
729 | |
730 | const string FAKE_JSON_PURCHASE_FAILURE = """ |
731 | { |
732 | - "meta": { |
733 | - "status": "Ok" |
734 | - }, |
735 | - "payload": { |
736 | - "order_id": "1111", |
737 | - "order_status": "PAYMENT_FAILURE", |
738 | - "order_detail": "TBD", |
739 | - "open_url": "http://slashdot.org/" |
740 | - } |
741 | + "order_id": "1111", |
742 | + "order_status": "PAYMENT_FAILURE", |
743 | + "order_detail": "TBD", |
744 | + "open_url": "http://slashdot.org/" |
745 | } |
746 | """; |
747 | |
748 | @@ -113,12 +104,11 @@ |
749 | Test.add_data_func ("/Unit/PurchaseChecker/ParseAccount", test_parse_account); |
750 | Test.add_data_func ("/Unit/PurchaseChecker/ParseBrokenAccount", test_parse_broken_account); |
751 | Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethod", test_parse_payment_method); |
752 | + Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethodError", test_parse_payment_method_error); |
753 | Test.add_data_func ("/Unit/PurchaseChecker/ParseAuthentication", test_parse_authentication); |
754 | Test.add_data_func ("/Unit/PurchaseChecker/ReadyToPurchase", test_ready_to_purchase); |
755 | - Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInvalidated", test_fetch_payment_invalidated); |
756 | - Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentCached", test_fetch_payment_cached); |
757 | - Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPayment", test_refetch_payment); |
758 | - Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPaymentSetsCache", test_refetch_payment_sets_cache); |
759 | + Test.add_data_func ("/Unit/PurchaseChecker/FetchAccountInfo", test_fetch_account_info); |
760 | + Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInfo", test_fetch_payment_info); |
761 | Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentials", test_fetch_credentials); |
762 | Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFails", test_fetch_credentials_fails); |
763 | Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFailsExtra", test_fetch_credentials_fails_extra); |
764 | @@ -163,11 +153,12 @@ |
765 | async void irl_test_async () |
766 | { |
767 | var purchase_service = new PurchaseService(); |
768 | - var purchase_sku = "1"; |
769 | + var purchase_sku = "7digital:album:1347423:WORLD"; |
770 | try { |
771 | - yield purchase_service.fetch_payment_info (purchase_sku); |
772 | + yield purchase_service.fetch_account_info (); |
773 | + purchase_service.fetch_payment_info (purchase_sku); |
774 | debug ("data was available: %s %s %s", purchase_service.nickname, purchase_service.email, purchase_service.selected_payment_method); |
775 | - var real_user_password = "****fake_password_in_bzr***"; |
776 | + var real_user_password = FAKE_PASSWORD; |
777 | purchase_service.purchase (purchase_sku, real_user_password); |
778 | debug ("purchase completed."); |
779 | } catch (PurchaseError e) { |
780 | @@ -186,7 +177,7 @@ |
781 | }); |
782 | return false; |
783 | }); |
784 | - assert (run_with_timeout (loop, 10000)); |
785 | + assert (run_with_timeout (loop, 60000)); |
786 | } |
787 | |
788 | class FakeCredentialsManagement : GLib.Object, CredentialsManagement |
789 | @@ -290,6 +281,19 @@ |
790 | assert_cmpstr (purchase_service.selected_payment_method, OperatorType.EQUAL, "Visa 1234"); |
791 | } |
792 | |
793 | + private static void test_parse_payment_method_error () |
794 | + { |
795 | + var purchase_service = new BaseTestPurchaseService (); |
796 | + try |
797 | + { |
798 | + purchase_service.parse_payment_method_json (FAKE_JSON_PAYMENT_METHOD_ERROR); |
799 | + assert_not_reached (); |
800 | + } catch (PurchaseError e) |
801 | + { |
802 | + assert_cmpstr (purchase_service.open_url, OperatorType.EQUAL, "http://somewhere/else"); |
803 | + } |
804 | + } |
805 | + |
806 | private static void test_parse_authentication () |
807 | { |
808 | var purchase_service = new BaseTestPurchaseService (); |
809 | @@ -313,30 +317,6 @@ |
810 | assert (purchase_service.ready_to_purchase == true); |
811 | } |
812 | |
813 | - class TestFetchPurchaseService : BaseTestPurchaseService { |
814 | - internal bool fetch_called = false; |
815 | - internal override async void refetch_payment_info (string purchase_sku) |
816 | - { |
817 | - fetch_called = true; |
818 | - } |
819 | - } |
820 | - |
821 | - private static void test_fetch_payment_invalidated () |
822 | - { |
823 | - var purchase_service = new TestFetchPurchaseService (); |
824 | - purchase_service._selected_payment_method_expiry = new DateTime.now_utc (); |
825 | - purchase_service.fetch_payment_info ("fake_sku"); |
826 | - assert (purchase_service.fetch_called); |
827 | - } |
828 | - |
829 | - private static void test_fetch_payment_cached () |
830 | - { |
831 | - var purchase_service = new TestFetchPurchaseService (); |
832 | - purchase_service._selected_payment_method_expiry = new DateTime.now_utc ().add_seconds (60); |
833 | - purchase_service.fetch_payment_info ("fake_sku"); |
834 | - assert (purchase_service.fetch_called == false); |
835 | - } |
836 | - |
837 | class TestRefetchPurchaseService : BaseTestPurchaseService { |
838 | internal bool credentials_fetched = false; |
839 | internal bool account_fetched = false; |
840 | @@ -360,26 +340,21 @@ |
841 | }); |
842 | yield; |
843 | } |
844 | - internal override async void fetch_payment_method (string purchase_sku) |
845 | + internal override void fetch_payment_method (string purchase_sku) |
846 | { |
847 | - Idle.add (() => { |
848 | - payment_method_fetched = true; |
849 | - fetch_payment_method.callback (); |
850 | - return false; |
851 | - }); |
852 | - yield; |
853 | + payment_method_fetched = true; |
854 | } |
855 | } |
856 | |
857 | - private static void test_refetch_payment () |
858 | + private static void test_fetch_account_info () |
859 | { |
860 | var purchase_service = new TestRefetchPurchaseService (); |
861 | |
862 | MainLoop mainloop = new MainLoop (); |
863 | - purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => { |
864 | + purchase_service.fetch_account_info.begin((obj, res) => { |
865 | mainloop.quit (); |
866 | try { |
867 | - purchase_service.refetch_payment_info.end (res); |
868 | + purchase_service.fetch_account_info.end (res); |
869 | } catch (PurchaseError e) { |
870 | error ("Can't fetch payment info: %s", e.message); |
871 | } |
872 | @@ -388,29 +363,19 @@ |
873 | |
874 | assert (purchase_service.credentials_fetched); |
875 | assert (purchase_service.account_fetched); |
876 | + } |
877 | + |
878 | + private static void test_fetch_payment_info () |
879 | + { |
880 | + var purchase_service = new TestRefetchPurchaseService (); |
881 | + try { |
882 | + purchase_service.fetch_payment_info("fake_sku"); |
883 | + } catch (PurchaseError e) { |
884 | + error ("Can't fetch payment info: %s", e.message); |
885 | + } |
886 | assert (purchase_service.payment_method_fetched); |
887 | } |
888 | |
889 | - private static void test_refetch_payment_sets_cache () |
890 | - { |
891 | - var purchase_service = new TestRefetchPurchaseService (); |
892 | - |
893 | - assert (true == purchase_service.expired_payment_method_cache ()); |
894 | - |
895 | - MainLoop mainloop = new MainLoop (); |
896 | - purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => { |
897 | - mainloop.quit (); |
898 | - try { |
899 | - purchase_service.refetch_payment_info.end (res); |
900 | - } catch (PurchaseError e) { |
901 | - error ("Can't fetch payment info: %s", e.message); |
902 | - } |
903 | - }); |
904 | - assert (run_with_timeout (mainloop, 1000)); |
905 | - |
906 | - assert (false == purchase_service.expired_payment_method_cache ()); |
907 | - } |
908 | - |
909 | private static void test_fetch_credentials () |
910 | { |
911 | var purchase_service = new BaseTestPurchaseService (); |
912 | @@ -431,7 +396,7 @@ |
913 | |
914 | private static void test_fetch_credentials_fails () |
915 | { |
916 | - bool failed = false; |
917 | + PurchaseError failure = null; |
918 | var purchase_service = new FailingCredentialsPurchaseService (); |
919 | |
920 | MainLoop mainloop = new MainLoop (); |
921 | @@ -440,17 +405,16 @@ |
922 | try { |
923 | purchase_service.fetch_credentials.end (res); |
924 | } catch (PurchaseError e) { |
925 | - failed = true; |
926 | + failure = e; |
927 | } |
928 | }); |
929 | assert (run_with_timeout (mainloop, 1000)); |
930 | - |
931 | - assert (failed); |
932 | + assert (failure is PurchaseError.MISSING_CREDENTIALS_ERROR); |
933 | } |
934 | |
935 | private static void test_fetch_credentials_fails_extra () |
936 | { |
937 | - bool failed = false; |
938 | + PurchaseError failure = null; |
939 | var purchase_service = new ExtraFailingCredentialsPurchaseService (); |
940 | |
941 | MainLoop mainloop = new MainLoop (); |
942 | @@ -459,12 +423,11 @@ |
943 | try { |
944 | purchase_service.fetch_credentials.end (res); |
945 | } catch (PurchaseError e) { |
946 | - failed = true; |
947 | + failure = e; |
948 | } |
949 | }); |
950 | assert (run_with_timeout (mainloop, 1000)); |
951 | - |
952 | - assert (failed); |
953 | + assert (failure is PurchaseError.MISSING_CREDENTIALS_ERROR); |
954 | } |
955 | |
956 | class FakeWebcallPurchaseService : BaseTestPurchaseService |
957 | @@ -565,21 +528,7 @@ |
958 | { |
959 | var purchase_service = new FakeWebcallPurchaseService (_status_code, _found_json); |
960 | purchase_service._ubuntuone_credentials = new HashTable <string, string> (str_hash, str_equal); |
961 | - |
962 | - PurchaseError error = null; |
963 | - MainLoop mainloop = new MainLoop (); |
964 | - purchase_service.fetch_payment_method.begin("fake_sku", (obj, res) => { |
965 | - mainloop.quit (); |
966 | - try { |
967 | - purchase_service.fetch_payment_method.end (res); |
968 | - } catch (PurchaseError e) { |
969 | - error = e; |
970 | - } |
971 | - }); |
972 | - assert (run_with_timeout (mainloop, 1000)); |
973 | - if (error != null) { |
974 | - throw error; |
975 | - } |
976 | + purchase_service.fetch_payment_method("fake_sku"); |
977 | return purchase_service; |
978 | } |
979 | |
980 | @@ -616,7 +565,7 @@ |
981 | class TestPurchaseService : BaseTestPurchaseService |
982 | { |
983 | internal string password; |
984 | - internal string sku; |
985 | + internal string album_id; |
986 | internal string purchase_token; |
987 | |
988 | internal override string get_purchase_token (string password) |
989 | @@ -624,9 +573,9 @@ |
990 | this.password = password; |
991 | return FAKE_TOKEN; |
992 | } |
993 | - internal override void purchase_with_default_payment (string sku, string purchase_token) |
994 | + internal override void purchase_with_default_payment (string album_id, string purchase_token) |
995 | { |
996 | - this.sku = sku; |
997 | + this.album_id = album_id; |
998 | this.purchase_token = purchase_token; |
999 | } |
1000 | } |
1001 | @@ -637,7 +586,7 @@ |
1002 | var purchase_service = new TestPurchaseService (); |
1003 | purchase_service.purchase (FAKE_SKU, FAKE_PASSWORD); |
1004 | assert_cmpstr (purchase_service.password, OperatorType.EQUAL, FAKE_PASSWORD); |
1005 | - assert_cmpstr (purchase_service.sku, OperatorType.EQUAL, FAKE_SKU); |
1006 | + assert_cmpstr (purchase_service.album_id, OperatorType.EQUAL, FAKE_SKU); |
1007 | assert_cmpstr (purchase_service.purchase_token, OperatorType.EQUAL, FAKE_TOKEN); |
1008 | } catch (Error e) { |
1009 | assert_not_reached (); |
1010 | @@ -722,6 +671,7 @@ |
1011 | try { |
1012 | purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN); |
1013 | } catch (PurchaseError e) { |
1014 | + warning (e.message); |
1015 | assert_not_reached (); |
1016 | } |
1017 | } |
PASSED: Continuous integration, rev:142 jenkins. qa.ubuntu. com/job/ unity-team- unity-lens- music-libunity7 -compatible- ci/3/ jenkins. qa.ubuntu. com/job/ unity-team- unity-lens- music-libunity7 -compatible- raring- amd64-ci/ 3
http://
Executed test runs:
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ unity-team- unity-lens- music-libunity7 -compatible- ci/3/rebuild
http://