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