Merge lp:~mandel/unity-lens-music/musicstore-purchase into lp:unity-lens-music

Proposed by Manuel de la Peña
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
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

Description of the change

Adds support for music purchase from the musicstore scope.

To post a comment you must log in.
135. By Manuel de la Peña

Add u1 icon to action.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:135
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://code.launchpad.net/~mandel/unity-lens-music/musicstore-purchase/+merge/156513/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-lens-music-ci/2/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-music-raring-amd64-ci/2
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-music-raring-armhf-ci/2
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-music-raring-i386-ci/2

Click here to trigger a rebuild:
http://s-jenkins:8080/job/unity-lens-music-ci/2/rebuild

review: Needs Fixing (continuous-integration)
136. By Manuel de la Peña

Removed cached password.

Revision history for this message
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://code.launchpad.net/~mandel/unity-lens-music/musicstore-purchase/+merge/156513/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/unity-lens-music-ci/3/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-music-raring-amd64-ci/3
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-music-raring-armhf-ci/3
    SUCCESS: http://jenkins.qa.ubuntu.com/job/unity-lens-music-raring-i386-ci/3

Click here to trigger a rebuild:
http://s-jenkins:8080/job/unity-lens-music-ci/3/rebuild

review: Needs Fixing (continuous-integration)

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'configure.ac'
--- configure.ac 2013-02-15 13:06:28 +0000
+++ configure.ac 2013-04-17 10:13:28 +0000
@@ -75,6 +75,7 @@
75 sqlite3 >= 3.7.775 sqlite3 >= 3.7.7
76 gee-1.076 gee-1.0
77 json-glib-1.077 json-glib-1.0
78 libnotify
78 libsoup-2.479 libsoup-2.4
79 oauth80 oauth
80 unity >= 6.90.081 unity >= 6.90.0
8182
=== modified file 'debian/changelog'
--- debian/changelog 2013-03-04 04:01:17 +0000
+++ debian/changelog 2013-04-17 10:13:28 +0000
@@ -1,3 +1,11 @@
1unity-lens-music (6.8.1daily13.03.04-0ubuntu3) UNRELEASED; urgency=low
2
3 [ Alejandro J. Cura ]
4 * debian/control:
5 - added libnotify-dev build dependency
6
7 -- Alejandro J. Cura <alecu@canonical.com> Tue, 19 Mar 2013 18:45:38 -0300
8
1unity-lens-music (6.8.1daily13.03.04-0ubuntu1) raring; urgency=low9unity-lens-music (6.8.1daily13.03.04-0ubuntu1) raring; urgency=low
210
3 [ Pawel Stolowski ]11 [ Pawel Stolowski ]
412
=== modified file 'debian/control'
--- debian/control 2012-12-12 16:12:30 +0000
+++ debian/control 2013-04-17 10:13:28 +0000
@@ -11,11 +11,12 @@
11 libglib2.0-dev (>= 2.32),11 libglib2.0-dev (>= 2.32),
12 libgee-dev,12 libgee-dev,
13 libjson-glib-dev,13 libjson-glib-dev,
14 libnotify-dev,
14 libsoup2.4-dev,15 libsoup2.4-dev,
15 liboauth-dev,16 liboauth-dev,
16 libdee-dev (>= 1.0.7),17 libdee-dev (>= 1.0.7),
17 libsqlite3-dev (>= 3.7.7),18 libsqlite3-dev (>= 3.7.7),
18 libunity-dev (>= 6.90.0),19 libunity-dev (>= 6.9),
19 libtdb-dev (>= 1.2.6),20 libtdb-dev (>= 1.2.6),
20 libgstreamer1.0-dev,21 libgstreamer1.0-dev,
21 libgstreamer-plugins-base1.0-dev22 libgstreamer-plugins-base1.0-dev
2223
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2013-02-15 13:06:28 +0000
+++ src/Makefile.am 2013-04-17 10:13:28 +0000
@@ -72,8 +72,9 @@
7272
73unity_musicstore_daemon_VALAFLAGS = \73unity_musicstore_daemon_VALAFLAGS = \
74 --pkg json-glib-1.0 \74 --pkg json-glib-1.0 \
75 --pkg libnotify \
76 --pkg libsoup-2.4 \
75 --pkg oauth \77 --pkg oauth \
76 --pkg libsoup-2.4 \
77 $(unity_music_daemon_VALAFLAGS)78 $(unity_music_daemon_VALAFLAGS)
7879
79unity_musicstore_daemon_LDADD = \80unity_musicstore_daemon_LDADD = \
8081
=== modified file 'src/album.vala'
--- src/album.vala 2012-09-11 12:50:34 +0000
+++ src/album.vala 2013-04-17 10:13:28 +0000
@@ -28,5 +28,6 @@
28 public string uri { get; set; }28 public string uri { get; set; }
29 public string artwork_path { get; set; }29 public string artwork_path { get; set; }
30 public string formatted_price { get; set; }30 public string formatted_price { get; set; }
31 public string purchase_sku { get; set; }
31 }32 }
32}33}
33\ No newline at end of file34\ No newline at end of file
3435
=== modified file 'src/musicstore-collection.vala'
--- src/musicstore-collection.vala 2012-12-06 19:29:07 +0000
+++ src/musicstore-collection.vala 2013-04-17 10:13:28 +0000
@@ -104,6 +104,18 @@
104 track.duration = (int)root_obj.get_member ("duration").get_int ();104 track.duration = (int)root_obj.get_member ("duration").get_int ();
105 tracks.append (track);105 tracks.append (track);
106 }106 }
107
108 if (root_obj.has_member ("id"))
109 {
110 album.purchase_sku = root_obj.get_string_member ("id");
111 }
112 else
113 {
114 // U1MS does not allow purchases of an album with no purchase_sku
115 album.purchase_sku = "";
116 warning ("Json has no purchase_sku in '%s'", details_uri);
117 }
118
107 }119 }
108 else120 else
109 {121 {
@@ -125,6 +137,7 @@
125 {137 {
126 var timer = new Timer ();138 var timer = new Timer ();
127 debug ("Searching %s", file.get_uri ());139 debug ("Searching %s", file.get_uri ());
140 var empty_asv = new Variant.array (VariantType.VARDICT.element (), {});
128141
129 try {142 try {
130 var stream = yield file.read_async (Priority.DEFAULT, cancellable);143 var stream = yield file.read_async (Priority.DEFAULT, cancellable);
131144
=== modified file 'src/musicstore-daemon.vala'
--- src/musicstore-daemon.vala 2012-09-04 09:53:02 +0000
+++ src/musicstore-daemon.vala 2013-04-17 10:13:28 +0000
@@ -25,6 +25,8 @@
25 static Application? app = null;25 static Application? app = null;
26 static MusicStoreScopeProxy? daemon = null;26 static MusicStoreScopeProxy? daemon = null;
2727
28 static const string BUS_NAME = "com.canonical.Unity.Scope.MusicStore";
29
28 /* Check if a given well known DBus is owned.30 /* Check if a given well known DBus is owned.
29 * WARNING: This does sync IO! */31 * WARNING: This does sync IO! */
30 public static bool dbus_name_has_owner (string name)32 public static bool dbus_name_has_owner (string name)
@@ -65,7 +67,7 @@
65 * making it race against GDBus' worker thread to export our67 * making it race against GDBus' worker thread to export our
66 * objects on the bus before/after owning our name and receiving68 * objects on the bus before/after owning our name and receiving
67 * method calls on our objects (which may not yet be up!)*/69 * method calls on our objects (which may not yet be up!)*/
68 if (dbus_name_has_owner ("com.canonical.Unity.Scope.MusicStore"))70 if (dbus_name_has_owner (BUS_NAME))
69 {71 {
70 print ("Another instance of the UbuntuOne Music Daemon " +72 print ("Another instance of the UbuntuOne Music Daemon " +
71 "already appears to be running.\nBailing out.\n");73 "already appears to be running.\nBailing out.\n");
@@ -75,10 +77,14 @@
75 /* Now register our DBus objects *before* acquiring the name!77 /* Now register our DBus objects *before* acquiring the name!
76 * See above for reasons */78 * See above for reasons */
77 daemon = new MusicStoreScopeProxy ();79 daemon = new MusicStoreScopeProxy ();
80 try {
81 daemon.scope.export ();
82 } catch (GLib.IOError e) {
83 stdout.printf ("error %s\n", e.message);
84 }
78 85
79 /* Use GApplication directly for single instance app functionality */86 /* Use GApplication directly for single instance app functionality */
80 app = new Application ("com.canonical.Unity.Scope.MusicStore",87 app = new Application (BUS_NAME, ApplicationFlags.IS_SERVICE);
81 ApplicationFlags.IS_SERVICE);
82 try {88 try {
83 app.register ();89 app.register ();
84 } catch (Error e) {90 } catch (Error e) {
8591
=== modified file 'src/musicstore-scope.vala'
--- src/musicstore-scope.vala 2013-02-28 09:48:46 +0000
+++ src/musicstore-scope.vala 2013-04-17 10:13:28 +0000
@@ -18,15 +18,30 @@
18 */18 */
1919
20using GLib;20using GLib;
21using Notify;
22using Unity;
23using Gdk;
24using Ubuntuone.Webservice;
2125
22namespace Unity.MusicLens {26namespace Unity.MusicLens {
27 private const string FORGOTTEN_PASSWORD_URL = "https://login.ubuntu.com/+forgot_password";
28 private const string CHANGE_PAYMENT_METHOD_URL = "https://pay.ubuntu.com/account/";
29 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.");
30 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.");
31 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.");
32 private const string U1_SSO_UI = "u1_login_register";
2333
24 public class MusicStoreScopeProxy : SimpleScope34 public class MusicStoreScopeProxy : SimpleScope
25 {35 {
36 const string NO_CREDENTIALS_LABEL_TEXT = _("Before you can purchase music you need to log in to the Ubuntu One app.");
26 private MusicStoreCollection collection;37 private MusicStoreCollection collection;
27 private Unity.Extras.PreviewPlayerController preview_player;38 private Unity.Extras.PreviewPlayerController preview_player;
28 private Unity.MusicPreview? music_preview;39 private Unity.MusicPreview? music_preview;
29 private PreferencesManager preferences = PreferencesManager.get_default ();40 private PreferencesManager preferences = PreferencesManager.get_default ();
41 private HashTable<string, Album> album_map;
42 private PurchaseService purchase_service;
43 private Notification notification;
44 private bool have_credentials = false;
3045
31 public MusicStoreScopeProxy ()46 public MusicStoreScopeProxy ()
32 {47 {
@@ -40,7 +55,11 @@
4055
41 base.initialize ();56 base.initialize ();
4257
58 Notify.init ("Music Store Scope");
59 notification = new Notification("Album name", _("Purchase started"), "");
43 collection = new MusicStoreCollection ();60 collection = new MusicStoreCollection ();
61 album_map = new HashTable<string, Album>(str_hash, str_equal);
62 purchase_service = new PurchaseService ();
4463
45 preferences.notify["remote-content-search"].connect((obj, pspec) => { scope.queue_search_changed(SearchType.DEFAULT); });64 preferences.notify["remote-content-search"].connect((obj, pspec) => { scope.queue_search_changed(SearchType.DEFAULT); });
4665
@@ -65,11 +84,11 @@
65 else84 else
66 {85 {
67 warning ("Failed to generate preview for %s", uri);86 warning ("Failed to generate preview for %s", uri);
68 return download_album (uri);87 return open_uri (uri);
69 }88 }
70 }89 }
7190
72 public Unity.ActivationResponse download_album (string uri)91 public Unity.ActivationResponse open_uri (string uri)
73 {92 {
74 /* launch the music store streaming client or the webstore or whatevz */93 /* launch the music store streaming client or the webstore or whatevz */
75 try {94 try {
@@ -89,6 +108,7 @@
89108
90 if (album != null)109 if (album != null)
91 {110 {
111 album_map.insert (uri, album);
92 File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri112 File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
93 var cover = new FileIcon (cover_file);113 var cover = new FileIcon (cover_file);
94114
@@ -111,12 +131,19 @@
111 }131 }
112 }132 }
113133
114 GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg"));134 if (have_credentials) {
115 var download_action = new Unity.PreviewAction ("download_album", _("Download"), icon);135 GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg"));
116 if (album.formatted_price != null)136 var download_action = new Unity.PreviewAction ("show_purchase_preview", _("Download"), icon);
117 download_action.extra_text = album.formatted_price;137 if (album.formatted_price != null)
118 download_action.activated.connect (download_album);138 download_action.extra_text = album.formatted_price;
119 music_preview.add_action (download_action);139 download_action.activated.connect (show_purchase_preview);
140 music_preview.add_action (download_action);
141 } else {
142 var data = new HashTable<string, Variant>(str_hash, str_equal);
143 data["no_credentials_label"] = NO_CREDENTIALS_LABEL_TEXT;
144 InfoHint info_hint = new InfoHint.with_variant("music_preview", "", null, data);
145 music_preview.add_info(info_hint);
146 }
120 }147 }
121 return music_preview;148 return music_preview;
122 }149 }
@@ -142,12 +169,227 @@
142 }169 }
143170
144 try {171 try {
145 debug ("model has %u rows before search", search.results_model.get_n_rows ());172 yield purchase_service.fetch_credentials ();
146 yield collection.search (search, search_type, (owned) filters, max_results, cancellable);173 have_credentials = true;
147 debug ("model has %u rows after search", search.results_model.get_n_rows ());174 } catch (PurchaseError e) {
175 // this is not a serious error, just missing credentials
176 have_credentials = false;
177 }
178
179 try {
180 debug ("model has %u rows before search", search.results_model.get_n_rows ());
181 yield collection.search (search, search_type, (owned) filters, max_results, cancellable);
182 debug ("model has %u rows after search", search.results_model.get_n_rows ());
148 } catch (IOError e) {183 } catch (IOError e) {
149 warning ("Failed to search for '%s': %s", search.search_string, e.message);184 warning ("Failed to search for '%s': %s", search.search_string, e.message);
150 }185 }
186
187 try {
188 yield purchase_service.fetch_account_info ();
189 debug ("retrieved account info: %s %s", purchase_service.nickname, purchase_service.email);
190 } catch (PurchaseError e) {
191 debug ("can't get account info: %s", e.message);
192 }
193 }
194
195 delegate Unity.ActivationResponse LinkHandler (string uri);
196
197 private Unity.ActivationResponse build_error_preview (string uri, string error_header, string link_text, LinkHandler link_handler)
198 {
199 Album album = null;
200 SList<Track> tracks = null;
201 collection.get_album_details (uri, out album, out tracks);
202 debug ("album art uri: %s", album.artwork_path);
203 File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
204 var cover = new FileIcon (cover_file);
205 var error_preview = new Unity.PaymentPreview.for_error(album.title, album.artist, cover);
206 error_preview.header = error_header;
207
208 var cancel_action = new Unity.PreviewAction ("cancel", _("Cancel"), null);
209 cancel_action.activated.connect (() => cancel_purchase (uri));
210 error_preview.add_action (cancel_action);
211
212 GLib.Icon? icon = new GLib.FileIcon (File.new_for_path (Config.DATADIR + "/icons/unity-icon-theme/places/svg/service-u1.svg"));
213 var error_action = new Unity.PreviewAction ("go_to_u1", link_text, icon);
214 error_action.activated.connect (() => link_handler (uri));
215 error_preview.add_action (error_action);
216 return new Unity.ActivationResponse.with_preview (error_preview);
217 }
218
219 public Unity.ActivationResponse open_forgot_password_url (string uri)
220 {
221 return open_uri (FORGOTTEN_PASSWORD_URL);
222 }
223
224 public Unity.ActivationResponse change_payment_method (string uri)
225 {
226 if (purchase_service.open_url != null)
227 {
228 debug ("Open url is %s", purchase_service.open_url);
229 return open_uri (purchase_service.open_url);
230 }
231 return open_uri (CHANGE_PAYMENT_METHOD_URL);
232 }
233
234 public Unity.ActivationResponse cancel_purchase (string uri)
235 {
236 return new Unity.ActivationResponse.with_preview (preview (uri));
237 }
238
239 public Unity.ActivationResponse open_sso_login (string uri)
240 {
241 Album album = null;
242 SList<Track> tracks = null;
243 collection.get_album_details (uri, out album, out tracks);
244
245 var ui_path = Environment.find_program_in_path (U1_SSO_UI);
246 GLib.Pid child_pid;
247 string argv[11] = {
248 ui_path,
249 "--album", album.title,
250 "--artist", album.artist,
251 "--price", album.formatted_price,
252 "--picture", album.artwork_path,
253 "--url", uri
254 };
255 if (ui_path != null) {
256 try {
257 bool was_started = Process.spawn_async (null, argv, null, 0, null, out child_pid);
258 // hide dash
259 if (was_started) {
260 return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
261 }
262 } catch (GLib.SpawnError e) {
263 debug ("Failed to start u1_sso_ui for uri %s", uri);
264 }
265 }
266 return open_uri (uri);
267 }
268
269 public Unity.ActivationResponse show_purchase_preview (string uri)
270 {
271 if (purchase_service.got_credentials () == false)
272 {
273 debug ("no credentials available, opening sso login. %s", uri);
274 return build_error_preview (uri, ERROR_MESSAGE_NOT_LOGGED_IN, _("Continue"), open_sso_login);
275 }
276
277 Album album = null;
278 SList<Track> tracks = null;
279 collection.get_album_details (uri, out album, out tracks);
280 if (album != null)
281 {
282 try {
283 purchase_service.fetch_payment_info (album.purchase_sku);
284 debug ("retrieved payment method: %s", purchase_service.selected_payment_method);
285 return new Unity.ActivationResponse.with_preview (purchase_preview (uri, null));
286 } catch (PurchaseError e) {
287 debug ("can't get default payment method: %s", e.message);
288 return build_error_preview (uri, ERROR_MESSAGE_NO_PAYMENT_METHOD, _("Choose Payment Method"), change_payment_method);
289 }
290 }
291 return open_uri (uri);
292 }
293
294 public Unity.ActivationResponse purchase_album (Unity.PreviewAction action, string uri)
295 {
296 var password = action.hints["password"].get_string();
297
298 if (password == null) {
299 var preview = purchase_preview (uri, _("Please enter your password"));
300 debug ("empty password.");
301 return new Unity.ActivationResponse.with_preview (preview);
302 }
303
304 var album = album_map.get (uri);
305
306 File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
307 try {
308 var cover_pixbuf = new Pixbuf.from_stream (cover_file.read ());
309 notification.set_icon_from_pixbuf (cover_pixbuf);
310 } catch (GLib.Error e) {
311 debug ("Cannot set notification icon from uri %s", uri);
312 }
313
314 notification.summary = album.title;
315 notification.body = _("Authorizing purchase");
316 try {
317 notification.show ();
318 } catch (GLib.Error e) {
319 debug ("Error while showing notification: %s", e.message);
320 }
321
322 try {
323 purchase_service.purchase (album.purchase_sku, password);
324 debug ("purchase completed.");
325 notification.update (album.title, _("Purchase completed"), "");
326 try {
327 notification.show ();
328 } catch (GLib.Error e) {
329 debug ("Error while showing notification: %s", e.message);
330 }
331 return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
332 } catch (PurchaseError e) {
333 if (e is PurchaseError.WRONG_PASSWORD_ERROR) {
334 debug ("wrong password error: %s", e.message);
335 return new Unity.ActivationResponse.with_preview (purchase_preview (uri, _("Wrong password")));
336 } else {
337 debug ("got purchase error: %s", e.message);
338 return build_error_preview (uri, ERROR_MESSAGE_TECHNICAL_PROBLEM, _("Continue"), open_uri);
339 }
340 }
341 }
342
343 public Unity.Preview purchase_preview (string uri, string? error_message)
344 {
345 Unity.PaymentPreview album_purchase_preview = null;
346 Album album = null;
347 SList<Track> tracks = null;
348 collection.get_album_details (uri, out album, out tracks);
349
350 if (album != null)
351 {
352 File cover_file = File.new_for_uri (album.artwork_path); //artwork path is a remote uri
353
354 var cover = new FileIcon (cover_file);
355 album_purchase_preview = new Unity.PaymentPreview.for_music(album.title, album.artist, cover);
356
357 album_purchase_preview.header = _("Hi %s, you purchased in the past from Ubuntu One,"
358 + " would you like to use the same payment details? Please review your order.").printf (purchase_service.nickname);
359 album_purchase_preview.email = purchase_service.email;
360 album_purchase_preview.payment_method = purchase_service.selected_payment_method;
361 album_purchase_preview.purchase_prize = album.formatted_price;
362 album_purchase_preview.purchase_type = _("Digital CD");
363
364 // data
365
366 var data = new HashTable<string, Variant>(str_hash, str_equal);
367 if (error_message != null) {
368 data["error_message"] = error_message;
369 }
370
371 InfoHint info_hint = new InfoHint.with_variant("album_purchase_preview", "", null, data);
372 album_purchase_preview.add_info(info_hint);
373
374 // actions
375
376 var purchase_action = new Unity.PreviewAction ("purchase_album", _("Purchase"), null);
377 purchase_action.activated.connect (purchase_album);
378 album_purchase_preview.add_action (purchase_action);
379
380 var forgot_password_action = new Unity.PreviewAction ("forgot_password", _("forgotten your Ubuntu One password?"), null);
381 forgot_password_action.activated.connect (open_forgot_password_url);
382 album_purchase_preview.add_action (forgot_password_action);
383
384 var cancel_action = new Unity.PreviewAction ("cancel_purchase", _("Cancel"), null);
385 cancel_action.activated.connect (cancel_purchase);
386 album_purchase_preview.add_action (cancel_action);
387
388 var change_payment_method_action = new Unity.PreviewAction ("change_payment_method", _("change payment method"), null);
389 change_payment_method_action.activated.connect (change_payment_method);
390 album_purchase_preview.add_action (change_payment_method_action);
391 }
392 return album_purchase_preview;
151 }393 }
152394
153 }395 }
154396
=== modified file 'src/ubuntuone-webservices.vala'
--- src/ubuntuone-webservices.vala 2012-12-06 15:39:38 +0000
+++ src/ubuntuone-webservices.vala 2013-04-17 10:13:28 +0000
@@ -21,26 +21,29 @@
2121
22[DBus (name = "com.ubuntuone.CredentialsManagement")]22[DBus (name = "com.ubuntuone.CredentialsManagement")]
23interface CredentialsManagement : GLib.Object {23interface CredentialsManagement : GLib.Object {
24 public signal void authorization_denied ();
24 public signal void credentials_found (HashTable <string, string> info);25 public signal void credentials_found (HashTable <string, string> info);
26 public signal void credentials_not_found ();
25 public signal void credentials_error ();27 public signal void credentials_error ();
2628
27 [DBus (name = "find_credentials")]29 [DBus (name = "find_credentials")]
28 public abstract void find_credentials () throws IOError;30 public abstract void find_credentials () throws IOError;
29}31}
3032
31const string WEBAPI_URI = "https://edge.one.ubuntu.com/";33const string WEBAPI_SERVER = "https://one.ubuntu.com/";
32const string ACCOUNT_URI = WEBAPI_URI + "api/account";34const string ACCOUNT_PATH = "api/account";
33const string PAYMENT_METHOD_URI = WEBAPI_URI + "music-store/api/1/user/retrieve-payment-method?purchase_sku=%s";35const string PAYMENT_METHOD_PATH = "music-store-up/api/1/user/retrieve-payment-method?purchase_sku=%s";
34const string PURCHASE_WITH_DEFAULT_PAYMENT_URI = WEBAPI_URI + "music-store/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s";36const string PURCHASE_WITH_DEFAULT_PAYMENT_PATH = "music-store-up/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s";
35const string AUTHENTICATION_URI = "https://login.staging.ubuntu.com/api/1.1/authentications";37const string AUTHENTICATION_SERVER = "https://login.ubuntu.com/";
38const string AUTHENTICATION_PATH = "api/1.1/authentications";
36const string AUTHENTICATE_PARAMS = "ws.op=authenticate&token_name=Purchase_Token";39const string AUTHENTICATE_PARAMS = "ws.op=authenticate&token_name=Purchase_Token";
37const int PASSWORD_CACHE_SECONDS = 300;40
38const int PAYMENT_METHOD_CACHE_SECONDS = 120;
3941
40namespace Ubuntuone.Webservice42namespace Ubuntuone.Webservice
41{43{
42 public errordomain PurchaseError44 public errordomain PurchaseError
43 {45 {
46 MISSING_CREDENTIALS_ERROR,
44 PURCHASE_ERROR,47 PURCHASE_ERROR,
45 WRONG_PASSWORD_ERROR,48 WRONG_PASSWORD_ERROR,
46 UNSPECIFIED_ERROR49 UNSPECIFIED_ERROR
@@ -56,17 +59,13 @@
56 public string selected_payment_method { get; internal set; default = null; }59 public string selected_payment_method { get; internal set; default = null; }
57 public string consumer_key { get; private set; default = null; }60 public string consumer_key { get; private set; default = null; }
58 public string token { get; private set; default = null; }61 public string token { get; private set; default = null; }
59 internal DateTime _selected_payment_method_expiry;62 public string open_url { get; private set; default = null; }
60 DateTime _password_expiry;
61 internal HashTable <string, string> _ubuntuone_credentials = null;63 internal HashTable <string, string> _ubuntuone_credentials = null;
6264
63 construct {65 construct {
64 http_session = build_http_session ();66 http_session = build_http_session ();
65 http_session_sso = build_http_session ();67 http_session_sso = build_http_session ();
6668
67 reset_payment_method_cache (0);
68 reset_password_cache (0);
69
70 credentials_management = build_credentials_management ();69 credentials_management = build_credentials_management ();
71 }70 }
7271
@@ -77,24 +76,40 @@
77 return session;76 return session;
78 }77 }
7978
80 internal DateTime now ()79 string webapi_server ()
81 {80 {
82 return new DateTime.now_utc ();81 string staging_webapi = Environment.get_variable ("U1_STAGING_WEBAPI");
83 }82 return staging_webapi != null ? staging_webapi : WEBAPI_SERVER;
8483 }
85 internal bool expired_payment_method_cache ()84
86 {85 string account_uri ()
87 return _selected_payment_method_expiry.compare (now ()) <= 0;86 {
88 }87 return webapi_server() + ACCOUNT_PATH;
8988 }
90 internal void reset_payment_method_cache (int seconds_from_now)89
91 {90 string payment_method_uri ()
92 _selected_payment_method_expiry = now ().add_seconds (seconds_from_now);91 {
93 }92 return webapi_server() + PAYMENT_METHOD_PATH;
9493 }
95 internal void reset_password_cache (int seconds_from_now)94
96 {95 string purchase_with_default_payment_uri ()
97 _password_expiry = now ().add_seconds (seconds_from_now);96 {
97 return webapi_server() + PURCHASE_WITH_DEFAULT_PAYMENT_PATH;
98 }
99
100 string authentication_server ()
101 {
102 string staging_authentication = Environment.get_variable ("U1_STAGING_AUTHENTICATION");
103 return staging_authentication != null ? staging_authentication : AUTHENTICATION_SERVER;
104 }
105
106 string authentication_uri ()
107 {
108 return authentication_server() + AUTHENTICATION_PATH;
109 }
110
111 public bool got_credentials () {
112 return _ubuntuone_credentials != null;
98 }113 }
99114
100 internal virtual CredentialsManagement build_credentials_management ()115 internal virtual CredentialsManagement build_credentials_management ()
@@ -125,28 +140,36 @@
125 email = root_object.get_string_member("email");140 email = root_object.get_string_member("email");
126 }141 }
127142
128 internal void parse_payment_method_json (string json_string) throws GLib.Error143 internal void parse_payment_method_json (string json_string) throws GLib.Error, PurchaseError
129 {144 {
130 var root_object = parse_json (json_string);145 var root_object = parse_json (json_string);
131 Json.Object payload = root_object.get_object_member("payload");146 if (root_object.has_member ("selected_payment_method")) {
132 selected_payment_method = payload.get_string_member("selected_payment_method");147 selected_payment_method = root_object.get_string_member("selected_payment_method");
148 } else {
149 open_url = root_object.get_string_member ("open_url");
150 var error_message = root_object.get_string_member ("error_message");
151 throw new PurchaseError.PURCHASE_ERROR (error_message);
152 }
133 }153 }
134154
135 internal void parse_authentication_json (string json_string) throws GLib.Error155 internal void parse_authentication_json (string json_string) throws GLib.Error
136 {156 {
137 var root_object = parse_json (json_string);157 var root_object = parse_json (json_string);
138 consumer_key = root_object.get_string_member("consumer_key");158 consumer_key = root_object.get_string_member ("consumer_key");
139 token = root_object.get_string_member("token");159 token = root_object.get_string_member ("token");
140 }160 }
141161
142 internal string parse_purchase_json (string json_string) throws GLib.Error162 internal string parse_purchase_json (string json_string) throws GLib.Error
143 {163 {
144 var root_object = parse_json (json_string);164 var root_object = parse_json (json_string);
145 Json.Object payload = root_object.get_object_member("payload");165 if (root_object.has_member ("open_url")) {
146 return payload.get_string_member("open_url");166 return root_object.get_string_member("open_url");
167 } else {
168 return "";
169 }
147 }170 }
148171
149 internal virtual async void fetch_credentials () throws PurchaseError172 public virtual async void fetch_credentials () throws PurchaseError
150 {173 {
151 PurchaseError error = null;174 PurchaseError error = null;
152175
@@ -155,8 +178,12 @@
155 debug ("got credentials");178 debug ("got credentials");
156 fetch_credentials.callback ();179 fetch_credentials.callback ();
157 });180 });
181 ulong not_found_handler = credentials_management.credentials_not_found.connect ((credentials) => {
182 error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens.");
183 fetch_credentials.callback ();
184 });
158 ulong error_handler = credentials_management.credentials_error.connect (() => {185 ulong error_handler = credentials_management.credentials_error.connect (() => {
159 error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens.");186 error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens.");
160 fetch_credentials.callback ();187 fetch_credentials.callback ();
161 });188 });
162189
@@ -164,10 +191,11 @@
164 credentials_management.find_credentials ();191 credentials_management.find_credentials ();
165 yield;192 yield;
166 } catch (IOError e) {193 } catch (IOError e) {
167 error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens: %s", e.message);194 error = new PurchaseError.MISSING_CREDENTIALS_ERROR ("Can't get Ubuntu One tokens: %s", e.message);
168 }195 }
169196
170 credentials_management.disconnect (found_handler);197 credentials_management.disconnect (found_handler);
198 credentials_management.disconnect (not_found_handler);
171 credentials_management.disconnect (error_handler);199 credentials_management.disconnect (error_handler);
172200
173 if (error != null) {201 if (error != null) {
@@ -208,7 +236,7 @@
208 internal virtual async void fetch_account () throws PurchaseError236 internal virtual async void fetch_account () throws PurchaseError
209 {237 {
210 string response;238 string response;
211 PurchaseError error = yield call_api ("GET", ACCOUNT_URI, out response);239 PurchaseError error = yield call_api ("GET", account_uri(), out response);
212240
213 if (error != null) {241 if (error != null) {
214 debug ("Error while fetching U1 account: %s.", error.message);242 debug ("Error while fetching U1 account: %s.", error.message);
@@ -224,43 +252,40 @@
224 }252 }
225 }253 }
226254
227 internal virtual async void fetch_payment_method (string purchase_sku) throws PurchaseError255 internal virtual void fetch_payment_method (string purchase_sku) throws PurchaseError
228 {256 {
229 string response;257 var uri = payment_method_uri().printf (purchase_sku);
230 var uri = PAYMENT_METHOD_URI.printf (purchase_sku);
231 PurchaseError error = yield call_api ("GET", uri, out response);
232258
233 if (error != null) {259 var message = send_signed_webservice_call ("GET", uri);
234 debug ("Error while fetching payment method: %s.", error.message);260 if (message.status_code != Soup.KnownStatusCode.OK) {
235 throw error;261 debug ("Purchase request failed: HTTP %u", message.status_code);
262 debug ("Reason: %s", message.reason_phrase);
263 try {
264 message.response_body.flatten ();
265 debug ("body: ------\n%s\n------\n", (string) message.response_body.data);
266 } catch (Error e) {
267 }
268 throw new PurchaseError.PURCHASE_ERROR ("Retrieve payment method failed: %s".printf (message.reason_phrase));
236 }269 }
237
238 try {270 try {
239 parse_payment_method_json (response);271 message.response_body.flatten ();
240 debug ("got payment method");272 var result = (string) message.response_body.data;
273 parse_payment_method_json (result);
241 } catch (GLib.Error e) {274 } catch (GLib.Error e) {
242 debug ("Error while getting payment method: %s.", e.message);275 debug ("Error while getting payment method: %s.", e.message);
243 throw new PurchaseError.PURCHASE_ERROR (e.message);276 throw new PurchaseError.PURCHASE_ERROR (e.message);
244 }277 }
245 }278 }
246279
247 internal virtual async void refetch_payment_info (string purchase_sku) throws PurchaseError280 public virtual async void fetch_account_info () throws PurchaseError
248 {281 {
249 yield fetch_credentials ();282 yield fetch_credentials ();
250 yield fetch_account ();283 yield fetch_account ();
251 yield fetch_payment_method (purchase_sku);
252 reset_payment_method_cache (PAYMENT_METHOD_CACHE_SECONDS);
253 }284 }
254285
255 public async void fetch_payment_info (string purchase_sku) throws PurchaseError286 public void fetch_payment_info (string purchase_sku) throws PurchaseError
256 {287 {
257 if (expired_payment_method_cache ())288 fetch_payment_method (purchase_sku);
258 {
259 debug ("refetching");
260 yield refetch_payment_info (purchase_sku);
261 } else {
262 debug ("in cache");
263 }
264 }289 }
265290
266 internal virtual void _do_sso_webcall (Soup.Message message, string password)291 internal virtual void _do_sso_webcall (Soup.Message message, string password)
@@ -286,6 +311,11 @@
286 if (message.status_code == Soup.KnownStatusCode.UNAUTHORIZED) {311 if (message.status_code == Soup.KnownStatusCode.UNAUTHORIZED) {
287 throw new PurchaseError.WRONG_PASSWORD_ERROR ("Wrong password");312 throw new PurchaseError.WRONG_PASSWORD_ERROR ("Wrong password");
288 }313 }
314 try {
315 message.response_body.flatten ();
316 debug ("body: ------\n%s\n------\n", (string) message.response_body.data);
317 } catch (Error e) {
318 }
289 throw new PurchaseError.PURCHASE_ERROR (message.reason_phrase);319 throw new PurchaseError.PURCHASE_ERROR (message.reason_phrase);
290 }320 }
291 message.response_body.flatten ();321 message.response_body.flatten ();
@@ -294,12 +324,13 @@
294324
295 internal virtual string get_purchase_token (string password) throws PurchaseError325 internal virtual string get_purchase_token (string password) throws PurchaseError
296 {326 {
297 var result = authenticated_sso_webcall ("POST", AUTHENTICATION_URI, AUTHENTICATE_PARAMS, password);327 var result = authenticated_sso_webcall ("POST", authentication_uri(), AUTHENTICATE_PARAMS, password);
298 try {328 try {
299 parse_authentication_json (result);329 parse_authentication_json (result);
300 } catch (GLib.Error e) {330 } catch (GLib.Error e) {
301 throw new PurchaseError.PURCHASE_ERROR (e.message);331 throw new PurchaseError.PURCHASE_ERROR (e.message);
302 }332 }
333
303 return "%s:%s".printf (consumer_key, token);334 return "%s:%s".printf (consumer_key, token);
304 }335 }
305336
@@ -311,14 +342,20 @@
311 return message;342 return message;
312 }343 }
313344
314 internal virtual void purchase_with_default_payment (string sku, string purchase_token) throws PurchaseError345 internal virtual void purchase_with_default_payment (string album_id, string purchase_token) throws PurchaseError
315 {346 {
316 var uri = PURCHASE_WITH_DEFAULT_PAYMENT_URI.printf (sku, purchase_token);347 var uri = purchase_with_default_payment_uri().printf (album_id, purchase_token);
348
317 var message = send_signed_webservice_call ("GET", uri);349 var message = send_signed_webservice_call ("GET", uri);
318350
319 if (message.status_code != Soup.KnownStatusCode.OK) {351 if (message.status_code != Soup.KnownStatusCode.OK) {
320 debug ("Purchase request failed: HTTP %u", message.status_code);352 debug ("Purchase request failed: HTTP %u", message.status_code);
321 debug ("Reason: %s", message.reason_phrase);353 debug ("Reason: %s", message.reason_phrase);
354 try {
355 message.response_body.flatten ();
356 debug ("body: ------\n%s\n------\n", (string) message.response_body.data);
357 } catch (Error e) {
358 }
322 throw new PurchaseError.PURCHASE_ERROR ("Purchase failed: %s".printf (message.reason_phrase));359 throw new PurchaseError.PURCHASE_ERROR ("Purchase failed: %s".printf (message.reason_phrase));
323 }360 }
324 try {361 try {
@@ -336,7 +373,9 @@
336 public void purchase (string album_id, string password) throws PurchaseError373 public void purchase (string album_id, string password) throws PurchaseError
337 {374 {
338 var purchase_token = get_purchase_token (password);375 var purchase_token = get_purchase_token (password);
376 debug ("purchasing...");
339 purchase_with_default_payment (album_id, purchase_token);377 purchase_with_default_payment (album_id, purchase_token);
378 debug ("purchase completed.");
340 }379 }
341 }380 }
342}381}
343382
=== modified file 'tests/unit/test-ubuntuone-purchases.vala'
--- tests/unit/test-ubuntuone-purchases.vala 2012-12-06 15:39:38 +0000
+++ tests/unit/test-ubuntuone-purchases.vala 2013-04-17 10:13:28 +0000
@@ -25,7 +25,7 @@
2525
26const string FAKE_URL = "http://fake/url";26const string FAKE_URL = "http://fake/url";
27const string FAKE_PASSWORD = "PezEspada";27const string FAKE_PASSWORD = "PezEspada";
28const string FAKE_SKU = "fake_store:fake_album:id";28const string FAKE_SKU = "7digital:fake_album:id";
29const string FAKE_TOKEN = "a fake token";29const string FAKE_TOKEN = "a fake token";
3030
31const string BROKEN_JSON = """31const string BROKEN_JSON = """
@@ -45,14 +45,17 @@
4545
46const string FAKE_JSON_PAYMENT_METHOD = """46const string FAKE_JSON_PAYMENT_METHOD = """
47 {47 {
48 "meta": {48 "open_url": "",
49 "status": "Ok"49 "user_email": "mr@be.an",
50 },50 "selected_payment_method": "Visa 1234"
51 "payload": {51 }
52 "open_url": "",52""";
53 "user_email": "mr@be.an",53
54 "selected_payment_method": "Visa 1234"54
55 }55const string FAKE_JSON_PAYMENT_METHOD_ERROR = """
56 {
57 "open_url": "http://somewhere/else",
58 "error_message": "No default payment method selected."
56 }59 }
57""";60""";
5861
@@ -69,30 +72,18 @@
6972
70const string FAKE_JSON_PURCHASE = """73const string FAKE_JSON_PURCHASE = """
71 {74 {
72 "meta": {75 "order_id": "1111",
73 "status": "Ok"76 "order_status": "OK"
74 },
75 "payload": {
76 "order_id": "1111",
77 "order_status": "OK",
78 "order_detail": "TBD",
79 "open_url": ""
80 }
81 }77 }
82""";78""";
8379
8480
85const string FAKE_JSON_PURCHASE_FAILURE = """81const string FAKE_JSON_PURCHASE_FAILURE = """
86 {82 {
87 "meta": {83 "order_id": "1111",
88 "status": "Ok"84 "order_status": "PAYMENT_FAILURE",
89 },85 "order_detail": "TBD",
90 "payload": {86 "open_url": "http://slashdot.org/"
91 "order_id": "1111",
92 "order_status": "PAYMENT_FAILURE",
93 "order_detail": "TBD",
94 "open_url": "http://slashdot.org/"
95 }
96 }87 }
97""";88""";
9889
@@ -113,12 +104,11 @@
113 Test.add_data_func ("/Unit/PurchaseChecker/ParseAccount", test_parse_account);104 Test.add_data_func ("/Unit/PurchaseChecker/ParseAccount", test_parse_account);
114 Test.add_data_func ("/Unit/PurchaseChecker/ParseBrokenAccount", test_parse_broken_account);105 Test.add_data_func ("/Unit/PurchaseChecker/ParseBrokenAccount", test_parse_broken_account);
115 Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethod", test_parse_payment_method);106 Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethod", test_parse_payment_method);
107 Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethodError", test_parse_payment_method_error);
116 Test.add_data_func ("/Unit/PurchaseChecker/ParseAuthentication", test_parse_authentication);108 Test.add_data_func ("/Unit/PurchaseChecker/ParseAuthentication", test_parse_authentication);
117 Test.add_data_func ("/Unit/PurchaseChecker/ReadyToPurchase", test_ready_to_purchase);109 Test.add_data_func ("/Unit/PurchaseChecker/ReadyToPurchase", test_ready_to_purchase);
118 Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInvalidated", test_fetch_payment_invalidated);110 Test.add_data_func ("/Unit/PurchaseChecker/FetchAccountInfo", test_fetch_account_info);
119 Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentCached", test_fetch_payment_cached);111 Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInfo", test_fetch_payment_info);
120 Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPayment", test_refetch_payment);
121 Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPaymentSetsCache", test_refetch_payment_sets_cache);
122 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentials", test_fetch_credentials);112 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentials", test_fetch_credentials);
123 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFails", test_fetch_credentials_fails);113 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFails", test_fetch_credentials_fails);
124 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFailsExtra", test_fetch_credentials_fails_extra);114 Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFailsExtra", test_fetch_credentials_fails_extra);
@@ -163,11 +153,12 @@
163 async void irl_test_async ()153 async void irl_test_async ()
164 {154 {
165 var purchase_service = new PurchaseService();155 var purchase_service = new PurchaseService();
166 var purchase_sku = "1";156 var purchase_sku = "7digital:album:1347423:WORLD";
167 try {157 try {
168 yield purchase_service.fetch_payment_info (purchase_sku);158 yield purchase_service.fetch_account_info ();
159 purchase_service.fetch_payment_info (purchase_sku);
169 debug ("data was available: %s %s %s", purchase_service.nickname, purchase_service.email, purchase_service.selected_payment_method);160 debug ("data was available: %s %s %s", purchase_service.nickname, purchase_service.email, purchase_service.selected_payment_method);
170 var real_user_password = "****fake_password_in_bzr***";161 var real_user_password = FAKE_PASSWORD;
171 purchase_service.purchase (purchase_sku, real_user_password);162 purchase_service.purchase (purchase_sku, real_user_password);
172 debug ("purchase completed.");163 debug ("purchase completed.");
173 } catch (PurchaseError e) {164 } catch (PurchaseError e) {
@@ -186,7 +177,7 @@
186 });177 });
187 return false;178 return false;
188 });179 });
189 assert (run_with_timeout (loop, 10000));180 assert (run_with_timeout (loop, 60000));
190 }181 }
191182
192 class FakeCredentialsManagement : GLib.Object, CredentialsManagement183 class FakeCredentialsManagement : GLib.Object, CredentialsManagement
@@ -290,6 +281,19 @@
290 assert_cmpstr (purchase_service.selected_payment_method, OperatorType.EQUAL, "Visa 1234");281 assert_cmpstr (purchase_service.selected_payment_method, OperatorType.EQUAL, "Visa 1234");
291 }282 }
292283
284 private static void test_parse_payment_method_error ()
285 {
286 var purchase_service = new BaseTestPurchaseService ();
287 try
288 {
289 purchase_service.parse_payment_method_json (FAKE_JSON_PAYMENT_METHOD_ERROR);
290 assert_not_reached ();
291 } catch (PurchaseError e)
292 {
293 assert_cmpstr (purchase_service.open_url, OperatorType.EQUAL, "http://somewhere/else");
294 }
295 }
296
293 private static void test_parse_authentication ()297 private static void test_parse_authentication ()
294 {298 {
295 var purchase_service = new BaseTestPurchaseService ();299 var purchase_service = new BaseTestPurchaseService ();
@@ -313,30 +317,6 @@
313 assert (purchase_service.ready_to_purchase == true);317 assert (purchase_service.ready_to_purchase == true);
314 }318 }
315319
316 class TestFetchPurchaseService : BaseTestPurchaseService {
317 internal bool fetch_called = false;
318 internal override async void refetch_payment_info (string purchase_sku)
319 {
320 fetch_called = true;
321 }
322 }
323
324 private static void test_fetch_payment_invalidated ()
325 {
326 var purchase_service = new TestFetchPurchaseService ();
327 purchase_service._selected_payment_method_expiry = new DateTime.now_utc ();
328 purchase_service.fetch_payment_info ("fake_sku");
329 assert (purchase_service.fetch_called);
330 }
331
332 private static void test_fetch_payment_cached ()
333 {
334 var purchase_service = new TestFetchPurchaseService ();
335 purchase_service._selected_payment_method_expiry = new DateTime.now_utc ().add_seconds (60);
336 purchase_service.fetch_payment_info ("fake_sku");
337 assert (purchase_service.fetch_called == false);
338 }
339
340 class TestRefetchPurchaseService : BaseTestPurchaseService {320 class TestRefetchPurchaseService : BaseTestPurchaseService {
341 internal bool credentials_fetched = false;321 internal bool credentials_fetched = false;
342 internal bool account_fetched = false;322 internal bool account_fetched = false;
@@ -360,26 +340,21 @@
360 });340 });
361 yield;341 yield;
362 }342 }
363 internal override async void fetch_payment_method (string purchase_sku)343 internal override void fetch_payment_method (string purchase_sku)
364 {344 {
365 Idle.add (() => {345 payment_method_fetched = true;
366 payment_method_fetched = true;
367 fetch_payment_method.callback ();
368 return false;
369 });
370 yield;
371 }346 }
372 }347 }
373348
374 private static void test_refetch_payment ()349 private static void test_fetch_account_info ()
375 {350 {
376 var purchase_service = new TestRefetchPurchaseService ();351 var purchase_service = new TestRefetchPurchaseService ();
377352
378 MainLoop mainloop = new MainLoop ();353 MainLoop mainloop = new MainLoop ();
379 purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => {354 purchase_service.fetch_account_info.begin((obj, res) => {
380 mainloop.quit ();355 mainloop.quit ();
381 try {356 try {
382 purchase_service.refetch_payment_info.end (res);357 purchase_service.fetch_account_info.end (res);
383 } catch (PurchaseError e) {358 } catch (PurchaseError e) {
384 error ("Can't fetch payment info: %s", e.message);359 error ("Can't fetch payment info: %s", e.message);
385 }360 }
@@ -388,29 +363,19 @@
388363
389 assert (purchase_service.credentials_fetched);364 assert (purchase_service.credentials_fetched);
390 assert (purchase_service.account_fetched);365 assert (purchase_service.account_fetched);
366 }
367
368 private static void test_fetch_payment_info ()
369 {
370 var purchase_service = new TestRefetchPurchaseService ();
371 try {
372 purchase_service.fetch_payment_info("fake_sku");
373 } catch (PurchaseError e) {
374 error ("Can't fetch payment info: %s", e.message);
375 }
391 assert (purchase_service.payment_method_fetched);376 assert (purchase_service.payment_method_fetched);
392 }377 }
393378
394 private static void test_refetch_payment_sets_cache ()
395 {
396 var purchase_service = new TestRefetchPurchaseService ();
397
398 assert (true == purchase_service.expired_payment_method_cache ());
399
400 MainLoop mainloop = new MainLoop ();
401 purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => {
402 mainloop.quit ();
403 try {
404 purchase_service.refetch_payment_info.end (res);
405 } catch (PurchaseError e) {
406 error ("Can't fetch payment info: %s", e.message);
407 }
408 });
409 assert (run_with_timeout (mainloop, 1000));
410
411 assert (false == purchase_service.expired_payment_method_cache ());
412 }
413
414 private static void test_fetch_credentials ()379 private static void test_fetch_credentials ()
415 {380 {
416 var purchase_service = new BaseTestPurchaseService ();381 var purchase_service = new BaseTestPurchaseService ();
@@ -431,7 +396,7 @@
431396
432 private static void test_fetch_credentials_fails ()397 private static void test_fetch_credentials_fails ()
433 {398 {
434 bool failed = false;399 PurchaseError failure = null;
435 var purchase_service = new FailingCredentialsPurchaseService ();400 var purchase_service = new FailingCredentialsPurchaseService ();
436401
437 MainLoop mainloop = new MainLoop ();402 MainLoop mainloop = new MainLoop ();
@@ -440,17 +405,16 @@
440 try {405 try {
441 purchase_service.fetch_credentials.end (res);406 purchase_service.fetch_credentials.end (res);
442 } catch (PurchaseError e) {407 } catch (PurchaseError e) {
443 failed = true;408 failure = e;
444 }409 }
445 });410 });
446 assert (run_with_timeout (mainloop, 1000));411 assert (run_with_timeout (mainloop, 1000));
447412 assert (failure is PurchaseError.MISSING_CREDENTIALS_ERROR);
448 assert (failed);
449 }413 }
450414
451 private static void test_fetch_credentials_fails_extra ()415 private static void test_fetch_credentials_fails_extra ()
452 {416 {
453 bool failed = false;417 PurchaseError failure = null;
454 var purchase_service = new ExtraFailingCredentialsPurchaseService ();418 var purchase_service = new ExtraFailingCredentialsPurchaseService ();
455419
456 MainLoop mainloop = new MainLoop ();420 MainLoop mainloop = new MainLoop ();
@@ -459,12 +423,11 @@
459 try {423 try {
460 purchase_service.fetch_credentials.end (res);424 purchase_service.fetch_credentials.end (res);
461 } catch (PurchaseError e) {425 } catch (PurchaseError e) {
462 failed = true;426 failure = e;
463 }427 }
464 });428 });
465 assert (run_with_timeout (mainloop, 1000));429 assert (run_with_timeout (mainloop, 1000));
466430 assert (failure is PurchaseError.MISSING_CREDENTIALS_ERROR);
467 assert (failed);
468 }431 }
469432
470 class FakeWebcallPurchaseService : BaseTestPurchaseService433 class FakeWebcallPurchaseService : BaseTestPurchaseService
@@ -565,21 +528,7 @@
565 {528 {
566 var purchase_service = new FakeWebcallPurchaseService (_status_code, _found_json);529 var purchase_service = new FakeWebcallPurchaseService (_status_code, _found_json);
567 purchase_service._ubuntuone_credentials = new HashTable <string, string> (str_hash, str_equal);530 purchase_service._ubuntuone_credentials = new HashTable <string, string> (str_hash, str_equal);
568531 purchase_service.fetch_payment_method("fake_sku");
569 PurchaseError error = null;
570 MainLoop mainloop = new MainLoop ();
571 purchase_service.fetch_payment_method.begin("fake_sku", (obj, res) => {
572 mainloop.quit ();
573 try {
574 purchase_service.fetch_payment_method.end (res);
575 } catch (PurchaseError e) {
576 error = e;
577 }
578 });
579 assert (run_with_timeout (mainloop, 1000));
580 if (error != null) {
581 throw error;
582 }
583 return purchase_service;532 return purchase_service;
584 }533 }
585534
@@ -616,7 +565,7 @@
616 class TestPurchaseService : BaseTestPurchaseService565 class TestPurchaseService : BaseTestPurchaseService
617 {566 {
618 internal string password;567 internal string password;
619 internal string sku;568 internal string album_id;
620 internal string purchase_token;569 internal string purchase_token;
621570
622 internal override string get_purchase_token (string password)571 internal override string get_purchase_token (string password)
@@ -624,9 +573,9 @@
624 this.password = password;573 this.password = password;
625 return FAKE_TOKEN;574 return FAKE_TOKEN;
626 }575 }
627 internal override void purchase_with_default_payment (string sku, string purchase_token)576 internal override void purchase_with_default_payment (string album_id, string purchase_token)
628 {577 {
629 this.sku = sku;578 this.album_id = album_id;
630 this.purchase_token = purchase_token;579 this.purchase_token = purchase_token;
631 }580 }
632 }581 }
@@ -637,7 +586,7 @@
637 var purchase_service = new TestPurchaseService ();586 var purchase_service = new TestPurchaseService ();
638 purchase_service.purchase (FAKE_SKU, FAKE_PASSWORD);587 purchase_service.purchase (FAKE_SKU, FAKE_PASSWORD);
639 assert_cmpstr (purchase_service.password, OperatorType.EQUAL, FAKE_PASSWORD);588 assert_cmpstr (purchase_service.password, OperatorType.EQUAL, FAKE_PASSWORD);
640 assert_cmpstr (purchase_service.sku, OperatorType.EQUAL, FAKE_SKU);589 assert_cmpstr (purchase_service.album_id, OperatorType.EQUAL, FAKE_SKU);
641 assert_cmpstr (purchase_service.purchase_token, OperatorType.EQUAL, FAKE_TOKEN);590 assert_cmpstr (purchase_service.purchase_token, OperatorType.EQUAL, FAKE_TOKEN);
642 } catch (Error e) {591 } catch (Error e) {
643 assert_not_reached ();592 assert_not_reached ();
@@ -722,6 +671,7 @@
722 try {671 try {
723 purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);672 purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);
724 } catch (PurchaseError e) {673 } catch (PurchaseError e) {
674 warning (e.message);
725 assert_not_reached ();675 assert_not_reached ();
726 }676 }
727 }677 }

Subscribers

People subscribed via source and target branches

to all changes: