Merge lp:~alecu/unity-lens-music/ubuntuone-webservices into lp:unity-lens-music

Proposed by Alejandro J. Cura
Status: Merged
Approved by: Michal Hruby
Approved revision: 122
Merged at revision: 122
Proposed branch: lp:~alecu/unity-lens-music/ubuntuone-webservices
Merge into: lp:unity-lens-music
Diff against target: 1247 lines (+1152/-3)
9 files modified
configure.ac (+3/-1)
debian/changelog (+7/-0)
debian/control (+2/-0)
po/POTFILES.skip (+2/-0)
src/Makefile.am (+5/-1)
src/oauth.vapi (+19/-0)
src/ubuntuone-webservices.vala (+342/-0)
tests/unit/Makefile.am (+11/-1)
tests/unit/test-ubuntuone-purchases.vala (+761/-0)
To merge this branch: bzr merge lp:~alecu/unity-lens-music/ubuntuone-webservices
Reviewer Review Type Date Requested Status
Michal Hruby (community) Approve
PS Jenkins bot continuous-integration Pending
Review via email: mp+129203@code.launchpad.net

Commit message

Call every webservice with libsoup, using OAuth when needed

Description of the change

This branch adds code to call the Ubuntu One webservices needed by dash payments, using libsoup and OAuth. It adds a few unit tests too.

To post a comment you must log in.
Revision history for this message
Michal Hruby (mhr3) wrote :

56 + public unowned string sign_url2(string url, string? postargs,

Looking at the docs, it says that you need to free the return, so s/unowned//, plus the postargs should be "out string postargs" as it's also a return (out params are nullable by default).

118 + internal Soup.SessionAsync http_session;

"internal" is a special keyword used when building libraries in vala (internal symbols are callable by other parts of the library, but not by the consumers), pls use private/public as appropriate.

177 + public string nickname {
178 + get { return _nickname; }
179 + }
+ other instances

No need for the extra private variable, use "public string x { get; private set; }"

243 + credentials_management.find_credentials ();
244 + yield;

Wouldn't it be better if find_credentials() itself was async? Then you wouldn't need the signals and you could just return the credentials / throw an error in the async method.

275 + internal virtual async void fetch_account () throws PurchaseError

Nothing wrong here, nice async method ;)

285 + var result = (string) message.response_body.data;

278 + queue_signed_webservice_call ("GET", ACCOUNT_URI, (session, message) => {

You're using this multiple times, perhaps add a helper async method that takes method + uri and returns flattened Soup.MessageBody?

.data could be null, no? Or does the flatten() cause possible null to become ""?

576 + return 0;

You should return result of Test.run ()

852 + MainLoop mainloop = new MainLoop ();
853 + purchase_service.refetch_payment_info.begin((obj, res) => {
854 + mainloop.quit ();
855 + try {
856 + purchase_service.refetch_payment_info.end (res);
857 + } catch (PurchaseError e) {
858 + error ("Can't fetch payment info: %s", e.message);
859 + }
860 + });
861 + mainloop.run ();

I remember you mentioned on IRC that you weren't sure how to do async tests with vala, so yep, this is the way, usually we just have a wrapper for the mainloop.run() which also adds a timeout, so the test fails eventually if there's no response. Usually using "assert (run_with_timeout (mainloop));"

review: Needs Fixing
116. By Alejandro J. Cura

merged with trunk

117. By Alejandro J. Cura

fixes requested in merge proposal

118. By Alejandro J. Cura

use purchase sku to get payment method

Revision history for this message
Alejandro J. Cura (alecu) wrote :
Download full text (3.2 KiB)

Thanks for your through review!

> 56 + public unowned string sign_url2(string url, string? postargs,
>
> Looking at the docs, it says that you need to free the return, so s/unowned//,
> plus the postargs should be "out string postargs" as it's also a return (out
> params are nullable by default).

Fixed as requested.

> 118 + internal Soup.SessionAsync http_session;
>
> "internal" is a special keyword used when building libraries in vala (internal
> symbols are callable by other parts of the library, but not by the consumers),
> pls use private/public as appropriate.

Fixed all unnecessary uses of "internal". A few remain, as needed in some tests.

> 177 + public string nickname {
> 178 + get { return _nickname; }
> 179 + }
> + other instances
>
> No need for the extra private variable, use "public string x { get; private
> set; }"

Fixed as requested.

> 243 + credentials_management.find_credentials ();
> 244 + yield;
>
> Wouldn't it be better if find_credentials() itself was async? Then you
> wouldn't need the signals and you could just return the credentials / throw an
> error in the async method.

find_credentials is defined in lp:ubuntu-sso-client, and used via dbus. We found some timeout problems in the python dbus bindings that were solved by using signals instead, so most of the client code in Ubuntu SSO Client and Ubuntu One already uses this pattern. I agree that it would simplify things, but too much code depends on this dbus API as is, and it's not easy to change just for this case.

> 278 + queue_signed_webservice_call ("GET", ACCOUNT_URI,
> (session, message) => {
>
> You're using this multiple times, perhaps add a helper async method that takes
> method + uri and returns flattened Soup.MessageBody?

As suggested, I created a helper async method for the repeated instances. There are still at least two places which did not follow the same pattern (one used different http object and error codes, the other was not async), so I kept the existing code for those.

> 285 + var result = (string) message.response_body.data;
>
> .data could be null, no? Or does the flatten() cause possible null to become
> ""?

I just checked, and if .data is null, flatten() does the right thing and turns it into an empty string.

> 576 + return 0;
>
> You should return result of Test.run ()

Fixed.

> 852 + MainLoop mainloop = new MainLoop ();
> 853 + purchase_service.refetch_payment_info.begin((obj, res) => {
> 854 + mainloop.quit ();
> 855 + try {
> 856 + purchase_service.refetch_payment_info.end (res);
> 857 + } catch (PurchaseError e) {
> 858 + error ("Can't fetch payment info: %s", e.message);
> 859 + }
> 860 + });
> 861 + mainloop.run ();
>
> I remember you mentioned on IRC that you weren't sure how to do async tests
> with vala, so yep, this is the way, usually we just have a wrapper for the
> mainloop.run() which also adds a timeout, so the test fails eventually if
> there's no response. Usuall...

Read more...

Revision history for this message
Michal Hruby (mhr3) wrote :

Awesome, /me happy now :)

review: Approve
119. By Alejandro J. Cura

Link bug #1088935

120. By Alejandro J. Cura

Fixes requested for the packaging

121. By Alejandro J. Cura

merged with trunk

122. By Alejandro J. Cura

Packaging fixes requested on the review

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2012-11-14 14:45:52 +0000
3+++ configure.ac 2012-12-12 00:01:27 +0000
4@@ -63,7 +63,9 @@
5 dee-1.0 >= 1.0.7
6 sqlite3 >= 3.7.7
7 gee-1.0
8- json-glib-1.0
9+ json-glib-1.0
10+ libsoup-2.4
11+ oauth
12 unity >= 6.90.0
13 unity-extras >= 6.90.0
14 tdb >= 1.2.6)
15
16=== modified file 'debian/changelog'
17--- debian/changelog 2012-12-10 16:34:35 +0000
18+++ debian/changelog 2012-12-12 00:01:27 +0000
19@@ -1,3 +1,10 @@
20+unity-lens-music (6.8.1daily12.12.05-0ubuntu2) UNRELEASED; urgency=low
21+
22+ * debian/control:
23+ - added libsoup2.4-dev and liboauth-dev build dependencies
24+
25+ -- Alejandro J. Cura <alecu@canonical.com> Tue, 11 Dec 2012 20:54:55 -0300
26+
27 unity-lens-music (6.8.1daily12.12.05-0ubuntu1) raring; urgency=low
28
29 [ Michael Terry ]
30
31=== modified file 'debian/control'
32--- debian/control 2012-12-10 16:34:35 +0000
33+++ debian/control 2012-12-12 00:01:27 +0000
34@@ -11,6 +11,8 @@
35 libglib2.0-dev (>= 2.27),
36 libgee-dev,
37 libjson-glib-dev,
38+ libsoup2.4-dev,
39+ liboauth-dev,
40 libdee-dev (>= 1.0.7),
41 libsqlite3-dev (>= 3.7.7),
42 libunity-dev (>= 6.90.0),
43
44=== modified file 'po/POTFILES.skip'
45--- po/POTFILES.skip 2012-08-17 07:56:11 +0000
46+++ po/POTFILES.skip 2012-12-12 00:01:27 +0000
47@@ -5,3 +5,5 @@
48 src/banshee-scope.c
49 src/rhythmbox-scope.c
50 src/musicstore-scope.c
51+src/ubuntuone-webservices.c
52+tests/unit/ubuntuone-webservices.c
53
54=== modified file 'src/Makefile.am'
55--- src/Makefile.am 2012-11-14 14:45:52 +0000
56+++ src/Makefile.am 2012-12-12 00:01:27 +0000
57@@ -69,6 +69,8 @@
58
59 unity_musicstore_daemon_VALAFLAGS = \
60 --pkg json-glib-1.0 \
61+ --pkg oauth \
62+ --pkg libsoup-2.4 \
63 $(unity_music_daemon_VALAFLAGS)
64
65 unity_musicstore_daemon_LDADD = \
66@@ -88,7 +90,9 @@
67 musicstore-filter-parser-genre.vala \
68 musicstore-scope.vala \
69 simple-scope.vala \
70- track.vala
71+ track.vala \
72+ ubuntuone-webservices.vala \
73+ $(NULL)
74
75 unity_musicstore_daemon_SOURCES = \
76 $(unity_musicstore_daemon_VALASOURCES:.vala=.c) \
77
78=== added file 'src/oauth.vapi'
79--- src/oauth.vapi 1970-01-01 00:00:00 +0000
80+++ src/oauth.vapi 2012-12-12 00:01:27 +0000
81@@ -0,0 +1,19 @@
82+/* Minimal vapi for OAuth */
83+[CCode (cheader_filename="oauth.h")]
84+namespace OAuth {
85+
86+ [CCode (cheader_filename="oauth.h", cprefix="OA_")]
87+ public enum Method {
88+ HMAC,
89+ RSA,
90+ PLAINTEXT
91+ }
92+
93+ [CCode (cheader_filename="oauth.h", cprefix="oauth_")]
94+ public string sign_url2(string url, out string postargs,
95+ OAuth.Method method,
96+ string? http_method,
97+ string c_key, string c_secret,
98+ string t_key, string t_secret);
99+
100+}
101
102=== added file 'src/ubuntuone-webservices.vala'
103--- src/ubuntuone-webservices.vala 1970-01-01 00:00:00 +0000
104+++ src/ubuntuone-webservices.vala 2012-12-12 00:01:27 +0000
105@@ -0,0 +1,342 @@
106+/*
107+ * Copyright (C) 2012 Canonical Ltd
108+ *
109+ * This program is free software: you can redistribute it and/or modify
110+ * it under the terms of the GNU General Public License version 3 as
111+ * published by the Free Software Foundation.
112+ *
113+ * This program is distributed in the hope that it will be useful,
114+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
115+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
116+ * GNU General Public License for more details.
117+ *
118+ * You should have received a copy of the GNU General Public License
119+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
120+ *
121+ * Authored by Alejandro J. Cura <alecu@canonical.com>
122+ *
123+ */
124+
125+using Soup;
126+
127+[DBus (name = "com.ubuntuone.CredentialsManagement")]
128+interface CredentialsManagement : GLib.Object {
129+ public signal void credentials_found (HashTable <string, string> info);
130+ public signal void credentials_error ();
131+
132+ [DBus (name = "find_credentials")]
133+ public abstract void find_credentials () throws IOError;
134+}
135+
136+const string WEBAPI_URI = "https://edge.one.ubuntu.com/";
137+const string ACCOUNT_URI = WEBAPI_URI + "api/account";
138+const string PAYMENT_METHOD_URI = WEBAPI_URI + "music-store/api/1/user/retrieve-payment-method?purchase_sku=%s";
139+const string PURCHASE_WITH_DEFAULT_PAYMENT_URI = WEBAPI_URI + "music-store/api/1/user/purchase-with-default-payment?purchase_sku=%s&authentication=%s";
140+const string AUTHENTICATION_URI = "https://login.staging.ubuntu.com/api/1.1/authentications";
141+const string AUTHENTICATE_PARAMS = "ws.op=authenticate&token_name=Purchase_Token";
142+const int PASSWORD_CACHE_SECONDS = 300;
143+const int PAYMENT_METHOD_CACHE_SECONDS = 120;
144+
145+namespace Ubuntuone.Webservice
146+{
147+ public errordomain PurchaseError
148+ {
149+ PURCHASE_ERROR,
150+ WRONG_PASSWORD_ERROR,
151+ UNSPECIFIED_ERROR
152+ }
153+
154+ public class PurchaseService : GLib.Object
155+ {
156+ internal Soup.SessionAsync http_session;
157+ Soup.SessionAsync http_session_sso;
158+ CredentialsManagement credentials_management;
159+ public string nickname { get; private set; default = null; }
160+ public string email { get; private set; default = null; }
161+ public string selected_payment_method { get; internal set; default = null; }
162+ public string consumer_key { get; private set; default = null; }
163+ public string token { get; private set; default = null; }
164+ internal DateTime _selected_payment_method_expiry;
165+ DateTime _password_expiry;
166+ internal HashTable <string, string> _ubuntuone_credentials = null;
167+
168+ construct {
169+ http_session = build_http_session ();
170+ http_session_sso = build_http_session ();
171+
172+ reset_payment_method_cache (0);
173+ reset_password_cache (0);
174+
175+ credentials_management = build_credentials_management ();
176+ }
177+
178+ internal Soup.SessionAsync build_http_session ()
179+ {
180+ var session = new Soup.SessionAsync ();
181+ session.user_agent = "%s/%s (libsoup)".printf("UbuntuOneMusicstoreLens", "1.0");
182+ return session;
183+ }
184+
185+ internal DateTime now ()
186+ {
187+ return new DateTime.now_utc ();
188+ }
189+
190+ internal bool expired_payment_method_cache ()
191+ {
192+ return _selected_payment_method_expiry.compare (now ()) <= 0;
193+ }
194+
195+ internal void reset_payment_method_cache (int seconds_from_now)
196+ {
197+ _selected_payment_method_expiry = now ().add_seconds (seconds_from_now);
198+ }
199+
200+ internal void reset_password_cache (int seconds_from_now)
201+ {
202+ _password_expiry = now ().add_seconds (seconds_from_now);
203+ }
204+
205+ internal virtual CredentialsManagement build_credentials_management ()
206+ {
207+ try {
208+ return Bus.get_proxy_sync (BusType.SESSION, "com.ubuntuone.Credentials",
209+ "/credentials", DBusProxyFlags.DO_NOT_AUTO_START);
210+ } catch (IOError e) {
211+ error ("Can't connect to DBus: %s", e.message);
212+ }
213+ }
214+
215+ public bool ready_to_purchase {
216+ get { return selected_payment_method != null; }
217+ }
218+
219+ internal Json.Object parse_json (string json_string) throws GLib.Error
220+ {
221+ var parser = new Json.Parser();
222+ parser.load_from_data(json_string, -1);
223+ return parser.get_root().get_object();
224+ }
225+
226+ internal void parse_account_json (string json_string) throws GLib.Error
227+ {
228+ var root_object = parse_json (json_string);
229+ nickname = root_object.get_string_member("nickname");
230+ email = root_object.get_string_member("email");
231+ }
232+
233+ internal void parse_payment_method_json (string json_string) throws GLib.Error
234+ {
235+ var root_object = parse_json (json_string);
236+ Json.Object payload = root_object.get_object_member("payload");
237+ selected_payment_method = payload.get_string_member("selected_payment_method");
238+ }
239+
240+ internal void parse_authentication_json (string json_string) throws GLib.Error
241+ {
242+ var root_object = parse_json (json_string);
243+ consumer_key = root_object.get_string_member("consumer_key");
244+ token = root_object.get_string_member("token");
245+ }
246+
247+ internal string parse_purchase_json (string json_string) throws GLib.Error
248+ {
249+ var root_object = parse_json (json_string);
250+ Json.Object payload = root_object.get_object_member("payload");
251+ return payload.get_string_member("open_url");
252+ }
253+
254+ internal virtual async void fetch_credentials () throws PurchaseError
255+ {
256+ PurchaseError error = null;
257+
258+ ulong found_handler = credentials_management.credentials_found.connect ((credentials) => {
259+ _ubuntuone_credentials = credentials;
260+ debug ("got credentials");
261+ fetch_credentials.callback ();
262+ });
263+ ulong error_handler = credentials_management.credentials_error.connect (() => {
264+ error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens.");
265+ fetch_credentials.callback ();
266+ });
267+
268+ try {
269+ credentials_management.find_credentials ();
270+ yield;
271+ } catch (IOError e) {
272+ error = new PurchaseError.PURCHASE_ERROR ("Can't get Ubuntu One tokens: %s", e.message);
273+ }
274+
275+ credentials_management.disconnect (found_handler);
276+ credentials_management.disconnect (error_handler);
277+
278+ if (error != null) {
279+ debug ("Can't get Ubuntu One tokens: %s", error.message);
280+ throw error;
281+ }
282+ }
283+
284+ string oauth_sign (string uri)
285+ {
286+ return OAuth.sign_url2(uri, null,
287+ OAuth.Method.PLAINTEXT, "GET",
288+ _ubuntuone_credentials["consumer_key"],
289+ _ubuntuone_credentials["consumer_secret"],
290+ _ubuntuone_credentials["token"],
291+ _ubuntuone_credentials["token_secret"]);
292+ }
293+
294+ internal virtual async PurchaseError call_api (string method, string uri, out string response)
295+ {
296+ PurchaseError error = null;
297+ var signed_uri = oauth_sign (uri);
298+ var message = new Soup.Message (method, signed_uri);
299+ http_session.queue_message (message, (session, message) => {
300+ if (message.status_code != Soup.KnownStatusCode.OK) {
301+ debug ("Web request failed: HTTP %u %s - %s",
302+ message.status_code, message.reason_phrase, uri);
303+ error = new PurchaseError.PURCHASE_ERROR (message.reason_phrase);
304+ }
305+ call_api.callback ();
306+ });
307+ yield;
308+ message.response_body.flatten ();
309+ response = (string) message.response_body.data;
310+ return error;
311+ }
312+
313+ internal virtual async void fetch_account () throws PurchaseError
314+ {
315+ string response;
316+ PurchaseError error = yield call_api ("GET", ACCOUNT_URI, out response);
317+
318+ if (error != null) {
319+ debug ("Error while fetching U1 account: %s.", error.message);
320+ throw error;
321+ }
322+
323+ try {
324+ parse_account_json (response);
325+ debug ("got account");
326+ } catch (GLib.Error e) {
327+ debug ("Error while parsing U1 account: %s.", e.message);
328+ throw new PurchaseError.PURCHASE_ERROR (e.message);
329+ }
330+ }
331+
332+ internal virtual async void fetch_payment_method (string purchase_sku) throws PurchaseError
333+ {
334+ string response;
335+ var uri = PAYMENT_METHOD_URI.printf (purchase_sku);
336+ PurchaseError error = yield call_api ("GET", uri, out response);
337+
338+ if (error != null) {
339+ debug ("Error while fetching payment method: %s.", error.message);
340+ throw error;
341+ }
342+
343+ try {
344+ parse_payment_method_json (response);
345+ debug ("got payment method");
346+ } catch (GLib.Error e) {
347+ debug ("Error while getting payment method: %s.", e.message);
348+ throw new PurchaseError.PURCHASE_ERROR (e.message);
349+ }
350+ }
351+
352+ internal virtual async void refetch_payment_info (string purchase_sku) throws PurchaseError
353+ {
354+ yield fetch_credentials ();
355+ yield fetch_account ();
356+ yield fetch_payment_method (purchase_sku);
357+ reset_payment_method_cache (PAYMENT_METHOD_CACHE_SECONDS);
358+ }
359+
360+ public async void fetch_payment_info (string purchase_sku) throws PurchaseError
361+ {
362+ if (expired_payment_method_cache ())
363+ {
364+ debug ("refetching");
365+ yield refetch_payment_info (purchase_sku);
366+ } else {
367+ debug ("in cache");
368+ }
369+ }
370+
371+ internal virtual void _do_sso_webcall (Soup.Message message, string password)
372+ {
373+ var handler = http_session_sso.authenticate.connect ((session, message, auth, retrying) => {
374+ if (!retrying) {
375+ auth.authenticate (email, password);
376+ }
377+ });
378+ http_session_sso.send_message (message);
379+ http_session_sso.disconnect (handler);
380+ }
381+
382+ internal virtual string authenticated_sso_webcall (string method, string uri, string operation, string password)
383+ throws PurchaseError
384+ {
385+ var message = new Soup.Message (method, uri);
386+ message.set_request ("application/x-www-form-urlencoded", Soup.MemoryUse.COPY, operation.data);
387+ _do_sso_webcall (message, password);
388+ if (message.status_code != Soup.KnownStatusCode.OK) {
389+ debug ("Authentication request failed: HTTP %u", message.status_code);
390+ debug ("Reason: %s", message.reason_phrase);
391+ if (message.status_code == Soup.KnownStatusCode.UNAUTHORIZED) {
392+ throw new PurchaseError.WRONG_PASSWORD_ERROR ("Wrong password");
393+ }
394+ throw new PurchaseError.PURCHASE_ERROR (message.reason_phrase);
395+ }
396+ message.response_body.flatten ();
397+ return (string) message.response_body.data;
398+ }
399+
400+ internal virtual string get_purchase_token (string password) throws PurchaseError
401+ {
402+ var result = authenticated_sso_webcall ("POST", AUTHENTICATION_URI, AUTHENTICATE_PARAMS, password);
403+ try {
404+ parse_authentication_json (result);
405+ } catch (GLib.Error e) {
406+ throw new PurchaseError.PURCHASE_ERROR (e.message);
407+ }
408+ return "%s:%s".printf (consumer_key, token);
409+ }
410+
411+ internal virtual Soup.Message send_signed_webservice_call (string method, string uri)
412+ {
413+ var signed_uri = oauth_sign (uri);
414+ var message = new Soup.Message (method, signed_uri);
415+ http_session.send_message (message);
416+ return message;
417+ }
418+
419+ internal virtual void purchase_with_default_payment (string sku, string purchase_token) throws PurchaseError
420+ {
421+ var uri = PURCHASE_WITH_DEFAULT_PAYMENT_URI.printf (sku, purchase_token);
422+ var message = send_signed_webservice_call ("GET", uri);
423+
424+ if (message.status_code != Soup.KnownStatusCode.OK) {
425+ debug ("Purchase request failed: HTTP %u", message.status_code);
426+ debug ("Reason: %s", message.reason_phrase);
427+ throw new PurchaseError.PURCHASE_ERROR ("Purchase failed: %s".printf (message.reason_phrase));
428+ }
429+ try {
430+ message.response_body.flatten ();
431+ var result = (string) message.response_body.data;
432+ var open_url = parse_purchase_json (result);
433+ if (open_url != "") {
434+ throw new PurchaseError.PURCHASE_ERROR (open_url);
435+ }
436+ } catch (GLib.Error e) {
437+ throw new PurchaseError.PURCHASE_ERROR (e.message);
438+ }
439+ }
440+
441+ public void purchase (string album_id, string password) throws PurchaseError
442+ {
443+ var purchase_token = get_purchase_token (password);
444+ purchase_with_default_payment (album_id, purchase_token);
445+ }
446+ }
447+}
448
449=== modified file 'tests/unit/Makefile.am'
450--- tests/unit/Makefile.am 2012-08-13 10:36:17 +0000
451+++ tests/unit/Makefile.am 2012-12-12 00:01:27 +0000
452@@ -1,4 +1,4 @@
453-check_PROGRAMS = test-rhythmbox-parser
454+check_PROGRAMS = test-rhythmbox-parser test-ubuntuone-purchases
455
456 TESTS = $(check_PROGRAMS)
457
458@@ -16,6 +16,9 @@
459 --pkg gio-2.0 \
460 --pkg gio-unix-2.0 \
461 --pkg glib-2.0 \
462+ --pkg json-glib-1.0 \
463+ --pkg oauth \
464+ --pkg libsoup-2.4 \
465 --vapidir $(top_srcdir)/src \
466 --pkg tdb \
467 --pkg unity \
468@@ -41,6 +44,13 @@
469 $(top_srcdir)/src/track.vala \
470 $(NULL)
471
472+test_ubuntuone_purchases_LDADD = $(test_libs)
473+
474+test_ubuntuone_purchases_SOURCES = \
475+ test-ubuntuone-purchases.vala \
476+ $(top_srcdir)/src/ubuntuone-webservices.vala \
477+ $(NULL)
478+
479 EXTRA_DIST = assertions.vapi
480 CLEANFILES = *.stamp
481
482
483=== added file 'tests/unit/test-ubuntuone-purchases.vala'
484--- tests/unit/test-ubuntuone-purchases.vala 1970-01-01 00:00:00 +0000
485+++ tests/unit/test-ubuntuone-purchases.vala 2012-12-12 00:01:27 +0000
486@@ -0,0 +1,761 @@
487+/*
488+ * Copyright (C) 2012 Canonical Ltd
489+ *
490+ * This program is free software: you can redistribute it and/or modify
491+ * it under the terms of the GNU General Public License version 3 as
492+ * published by the Free Software Foundation.
493+ *
494+ * This program is distributed in the hope that it will be useful,
495+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
496+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
497+ * GNU General Public License for more details.
498+ *
499+ * You should have received a copy of the GNU General Public License
500+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
501+ *
502+ * Authored by Alejandro J. Cura <alecu@canonical.com>
503+ *
504+ */
505+
506+using Assertions;
507+using Gee;
508+using GLib;
509+using Ubuntuone.Webservice;
510+
511+
512+const string FAKE_URL = "http://fake/url";
513+const string FAKE_PASSWORD = "PezEspada";
514+const string FAKE_SKU = "fake_store:fake_album:id";
515+const string FAKE_TOKEN = "a fake token";
516+
517+const string BROKEN_JSON = """
518+ {
519+ "a": "b",
520+ $!@$# BROKEN!
521+""";
522+
523+const string FAKE_JSON_ACCOUNT = """
524+ {
525+ "username": "mrbean",
526+ "nickname": "Mr. Bean",
527+ "email": "mr@be.an"
528+ }
529+""";
530+
531+
532+const string FAKE_JSON_PAYMENT_METHOD = """
533+ {
534+ "meta": {
535+ "status": "Ok"
536+ },
537+ "payload": {
538+ "open_url": "",
539+ "user_email": "mr@be.an",
540+ "selected_payment_method": "Visa 1234"
541+ }
542+ }
543+""";
544+
545+
546+const string FAKE_JSON_AUTHENTICATION = """
547+ {
548+ "consumer_secret": "fake_secret",
549+ "token": "fake_token",
550+ "consumer_key": "fake_consumer_key",
551+ "name": "FAKE_TOKEN_NAME",
552+ "token_secret": "fake_token_secret"
553+ }
554+""";
555+
556+const string FAKE_JSON_PURCHASE = """
557+ {
558+ "meta": {
559+ "status": "Ok"
560+ },
561+ "payload": {
562+ "order_id": "1111",
563+ "order_status": "OK",
564+ "order_detail": "TBD",
565+ "open_url": ""
566+ }
567+ }
568+""";
569+
570+
571+const string FAKE_JSON_PURCHASE_FAILURE = """
572+ {
573+ "meta": {
574+ "status": "Ok"
575+ },
576+ "payload": {
577+ "order_id": "1111",
578+ "order_status": "PAYMENT_FAILURE",
579+ "order_detail": "TBD",
580+ "open_url": "http://slashdot.org/"
581+ }
582+ }
583+""";
584+
585+
586+public class Main
587+{
588+ public static int main (string[] args)
589+ {
590+ int result = run_tests (args);
591+ // irl_test ();
592+ return result;
593+ }
594+
595+ static int run_tests (string[] args)
596+ {
597+ Test.init (ref args);
598+
599+ Test.add_data_func ("/Unit/PurchaseChecker/ParseAccount", test_parse_account);
600+ Test.add_data_func ("/Unit/PurchaseChecker/ParseBrokenAccount", test_parse_broken_account);
601+ Test.add_data_func ("/Unit/PurchaseChecker/ParsePaymentMethod", test_parse_payment_method);
602+ Test.add_data_func ("/Unit/PurchaseChecker/ParseAuthentication", test_parse_authentication);
603+ Test.add_data_func ("/Unit/PurchaseChecker/ReadyToPurchase", test_ready_to_purchase);
604+ Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentInvalidated", test_fetch_payment_invalidated);
605+ Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentCached", test_fetch_payment_cached);
606+ Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPayment", test_refetch_payment);
607+ Test.add_data_func ("/Unit/PurchaseChecker/ReFetchPaymentSetsCache", test_refetch_payment_sets_cache);
608+ Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentials", test_fetch_credentials);
609+ Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFails", test_fetch_credentials_fails);
610+ Test.add_data_func ("/Unit/PurchaseChecker/FetchCredentialsFailsExtra", test_fetch_credentials_fails_extra);
611+ Test.add_data_func ("/Unit/PurchaseChecker/FetchAccount", test_fetch_account);
612+ Test.add_data_func ("/Unit/PurchaseChecker/FetchAccountFails", test_fetch_account_http_failure);
613+ Test.add_data_func ("/Unit/PurchaseChecker/FetchAccountBrokenJson", test_fetch_account_wrong_json);
614+ Test.add_data_func ("/Unit/PurchaseChecker/FetchPayment", test_fetch_payment);
615+ Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentFails", test_fetch_payment_http_failure);
616+ Test.add_data_func ("/Unit/PurchaseChecker/FetchPaymentBrokenJson", test_fetch_payment_wrong_json);
617+ Test.add_data_func ("/Unit/PurchaseChecker/Purchase", test_purchase);
618+ Test.add_data_func ("/Unit/PurchaseChecker/GetPurchaseToken", test_get_purchase_token);
619+ Test.add_data_func ("/Unit/PurchaseChecker/GetPurchaseTokenBrokenJson", test_get_purchase_token_broken_json);
620+ Test.add_data_func ("/Unit/PurchaseChecker/SsoWebcall", test_authenticated_sso_webcall);
621+ Test.add_data_func ("/Unit/PurchaseChecker/SsoWebcallFails", test_authenticated_sso_webcall_fails);
622+ Test.add_data_func ("/Unit/PurchaseChecker/SsoWebcallWrongPassword", test_authenticated_sso_webcall_wrong_password);
623+ Test.add_data_func ("/Unit/PurchaseChecker/PurchaseDefaultPayment", test_purchase_with_default_payment);
624+ Test.add_data_func ("/Unit/PurchaseChecker/PurchaseDefaultPaymentBroken", test_purchase_with_default_payment_broken_json);
625+ Test.add_data_func ("/Unit/PurchaseChecker/PurchaseConnectionFails", test_purchase_with_default_payment_connection_fails);
626+ Test.add_data_func ("/Unit/PurchaseChecker/PurchaseDefaultPaymentFails", test_purchase_with_default_payment_fails);
627+
628+ return Test.run ();
629+ }
630+
631+ public static bool run_with_timeout (MainLoop ml, uint timeout_ms)
632+ {
633+ bool timeout_reached = false;
634+ var t_id = Timeout.add (timeout_ms, () => {
635+ timeout_reached = true;
636+ debug ("Timeout reached");
637+ ml.quit ();
638+ return false;
639+ });
640+
641+ ml.run ();
642+
643+ if (!timeout_reached) Source.remove (t_id);
644+
645+ return !timeout_reached;
646+ }
647+
648+
649+ async void irl_test_async ()
650+ {
651+ var purchase_service = new PurchaseService();
652+ var purchase_sku = "1";
653+ try {
654+ yield purchase_service.fetch_payment_info (purchase_sku);
655+ debug ("data was available: %s %s %s", purchase_service.nickname, purchase_service.email, purchase_service.selected_payment_method);
656+ var real_user_password = "****fake_password_in_bzr***";
657+ purchase_service.purchase (purchase_sku, real_user_password);
658+ debug ("purchase completed.");
659+ } catch (PurchaseError e) {
660+ debug ("got purchase error: %s", e.message);
661+ }
662+ }
663+
664+ static void irl_test ()
665+ {
666+ var loop = new GLib.MainLoop ();
667+ var m = new Main();
668+ Idle.add (() => {
669+ m.irl_test_async.begin ((obj, res) => {
670+ m.irl_test_async.end (res);
671+ loop.quit ();
672+ });
673+ return false;
674+ });
675+ assert (run_with_timeout (loop, 10000));
676+ }
677+
678+ class FakeCredentialsManagement : GLib.Object, CredentialsManagement
679+ {
680+ private HashTable <string, string> fake_credentials;
681+
682+ construct
683+ {
684+ fake_credentials = new HashTable <string, string> (str_hash, str_equal);
685+ fake_credentials["token"] = "fake_token";
686+ }
687+
688+ public void find_credentials () throws IOError
689+ {
690+ Idle.add (() => {
691+ credentials_found (fake_credentials);
692+ return false;
693+ });
694+ }
695+ }
696+
697+ class FailingCredentialsManagement : GLib.Object, CredentialsManagement
698+ {
699+ public void find_credentials () throws IOError
700+ {
701+ Idle.add (() => {
702+ credentials_error ();
703+ return false;
704+ });
705+ }
706+ }
707+
708+ class ExtraFailingCredentialsManagement : GLib.Object, CredentialsManagement
709+ {
710+ public void find_credentials () throws IOError
711+ {
712+ throw new IOError.INVALID_DATA ("Miscellanea Errora");
713+ }
714+ }
715+
716+ class BaseTestPurchaseService : PurchaseService
717+ {
718+ internal override CredentialsManagement build_credentials_management ()
719+ {
720+ return new FakeCredentialsManagement ();
721+ }
722+ }
723+
724+ class FailingCredentialsPurchaseService : PurchaseService
725+ {
726+ internal override CredentialsManagement build_credentials_management ()
727+ {
728+ return new FailingCredentialsManagement ();
729+ }
730+ }
731+
732+ class ExtraFailingCredentialsPurchaseService : PurchaseService
733+ {
734+ internal override CredentialsManagement build_credentials_management ()
735+ {
736+ return new ExtraFailingCredentialsManagement ();
737+ }
738+ }
739+
740+ private static void test_parse_account ()
741+ {
742+ var purchase_service = new BaseTestPurchaseService ();
743+ try {
744+ purchase_service.parse_account_json (FAKE_JSON_ACCOUNT);
745+ } catch (GLib.Error e)
746+ {
747+ assert_not_reached ();
748+ }
749+ assert_cmpstr (purchase_service.nickname, OperatorType.EQUAL, "Mr. Bean");
750+ assert_cmpstr (purchase_service.email, OperatorType.EQUAL, "mr@be.an");
751+ }
752+
753+ private static void test_parse_broken_account ()
754+ {
755+ var purchase_service = new BaseTestPurchaseService ();
756+ try
757+ {
758+ purchase_service.parse_account_json (BROKEN_JSON);
759+ assert_not_reached ();
760+ } catch (GLib.Error e)
761+ {
762+ // This is *just* the error we are expecting, so do nothing!
763+ }
764+ }
765+
766+ private static void test_parse_payment_method ()
767+ {
768+ var purchase_service = new BaseTestPurchaseService ();
769+ try
770+ {
771+ purchase_service.parse_payment_method_json (FAKE_JSON_PAYMENT_METHOD);
772+ } catch (GLib.Error e)
773+ {
774+ assert_not_reached ();
775+ }
776+ assert_cmpstr (purchase_service.selected_payment_method, OperatorType.EQUAL, "Visa 1234");
777+ }
778+
779+ private static void test_parse_authentication ()
780+ {
781+ var purchase_service = new BaseTestPurchaseService ();
782+
783+ try
784+ {
785+ purchase_service.parse_authentication_json (FAKE_JSON_AUTHENTICATION);
786+ } catch (GLib.Error e)
787+ {
788+ assert_not_reached ();
789+ }
790+ assert_cmpstr (purchase_service.consumer_key, OperatorType.EQUAL, "fake_consumer_key");
791+ assert_cmpstr (purchase_service.token, OperatorType.EQUAL, "fake_token");
792+ }
793+
794+ private static void test_ready_to_purchase ()
795+ {
796+ var purchase_service = new BaseTestPurchaseService ();
797+ assert (purchase_service.ready_to_purchase == false);
798+ purchase_service.selected_payment_method = "visa 1234";
799+ assert (purchase_service.ready_to_purchase == true);
800+ }
801+
802+ class TestFetchPurchaseService : BaseTestPurchaseService {
803+ internal bool fetch_called = false;
804+ internal override async void refetch_payment_info (string purchase_sku)
805+ {
806+ fetch_called = true;
807+ }
808+ }
809+
810+ private static void test_fetch_payment_invalidated ()
811+ {
812+ var purchase_service = new TestFetchPurchaseService ();
813+ purchase_service._selected_payment_method_expiry = new DateTime.now_utc ();
814+ purchase_service.fetch_payment_info ("fake_sku");
815+ assert (purchase_service.fetch_called);
816+ }
817+
818+ private static void test_fetch_payment_cached ()
819+ {
820+ var purchase_service = new TestFetchPurchaseService ();
821+ purchase_service._selected_payment_method_expiry = new DateTime.now_utc ().add_seconds (60);
822+ purchase_service.fetch_payment_info ("fake_sku");
823+ assert (purchase_service.fetch_called == false);
824+ }
825+
826+ class TestRefetchPurchaseService : BaseTestPurchaseService {
827+ internal bool credentials_fetched = false;
828+ internal bool account_fetched = false;
829+ internal bool payment_method_fetched = false;
830+
831+ internal override async void fetch_credentials ()
832+ {
833+ Idle.add (() => {
834+ credentials_fetched = true;
835+ fetch_credentials.callback ();
836+ return false;
837+ });
838+ yield;
839+ }
840+ internal override async void fetch_account ()
841+ {
842+ Idle.add (() => {
843+ account_fetched = true;
844+ fetch_account.callback ();
845+ return false;
846+ });
847+ yield;
848+ }
849+ internal override async void fetch_payment_method (string purchase_sku)
850+ {
851+ Idle.add (() => {
852+ payment_method_fetched = true;
853+ fetch_payment_method.callback ();
854+ return false;
855+ });
856+ yield;
857+ }
858+ }
859+
860+ private static void test_refetch_payment ()
861+ {
862+ var purchase_service = new TestRefetchPurchaseService ();
863+
864+ MainLoop mainloop = new MainLoop ();
865+ purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => {
866+ mainloop.quit ();
867+ try {
868+ purchase_service.refetch_payment_info.end (res);
869+ } catch (PurchaseError e) {
870+ error ("Can't fetch payment info: %s", e.message);
871+ }
872+ });
873+ assert (run_with_timeout (mainloop, 1000));
874+
875+ assert (purchase_service.credentials_fetched);
876+ assert (purchase_service.account_fetched);
877+ assert (purchase_service.payment_method_fetched);
878+ }
879+
880+ private static void test_refetch_payment_sets_cache ()
881+ {
882+ var purchase_service = new TestRefetchPurchaseService ();
883+
884+ assert (true == purchase_service.expired_payment_method_cache ());
885+
886+ MainLoop mainloop = new MainLoop ();
887+ purchase_service.refetch_payment_info.begin("fake_sku", (obj, res) => {
888+ mainloop.quit ();
889+ try {
890+ purchase_service.refetch_payment_info.end (res);
891+ } catch (PurchaseError e) {
892+ error ("Can't fetch payment info: %s", e.message);
893+ }
894+ });
895+ assert (run_with_timeout (mainloop, 1000));
896+
897+ assert (false == purchase_service.expired_payment_method_cache ());
898+ }
899+
900+ private static void test_fetch_credentials ()
901+ {
902+ var purchase_service = new BaseTestPurchaseService ();
903+
904+ MainLoop mainloop = new MainLoop ();
905+ purchase_service.fetch_credentials.begin((obj, res) => {
906+ mainloop.quit ();
907+ try {
908+ purchase_service.fetch_credentials.end (res);
909+ } catch (PurchaseError e) {
910+ error ("Can't fetch credentials: %s", e.message);
911+ }
912+ });
913+ assert (run_with_timeout (mainloop, 1000));
914+
915+ assert (purchase_service._ubuntuone_credentials != null);
916+ }
917+
918+ private static void test_fetch_credentials_fails ()
919+ {
920+ bool failed = false;
921+ var purchase_service = new FailingCredentialsPurchaseService ();
922+
923+ MainLoop mainloop = new MainLoop ();
924+ purchase_service.fetch_credentials.begin((obj, res) => {
925+ mainloop.quit ();
926+ try {
927+ purchase_service.fetch_credentials.end (res);
928+ } catch (PurchaseError e) {
929+ failed = true;
930+ }
931+ });
932+ assert (run_with_timeout (mainloop, 1000));
933+
934+ assert (failed);
935+ }
936+
937+ private static void test_fetch_credentials_fails_extra ()
938+ {
939+ bool failed = false;
940+ var purchase_service = new ExtraFailingCredentialsPurchaseService ();
941+
942+ MainLoop mainloop = new MainLoop ();
943+ purchase_service.fetch_credentials.begin((obj, res) => {
944+ mainloop.quit ();
945+ try {
946+ purchase_service.fetch_credentials.end (res);
947+ } catch (PurchaseError e) {
948+ failed = true;
949+ }
950+ });
951+ assert (run_with_timeout (mainloop, 1000));
952+
953+ assert (failed);
954+ }
955+
956+ class FakeWebcallPurchaseService : BaseTestPurchaseService
957+ {
958+ internal string used_uri = null;
959+ internal int status_code;
960+ internal string found_json;
961+
962+ internal FakeWebcallPurchaseService (int _status_code, string _found_json) {
963+ status_code = _status_code;
964+ found_json = _found_json;
965+ }
966+
967+ internal override async PurchaseError call_api (string method, string uri, out string response)
968+ {
969+ PurchaseError error = null;
970+ Idle.add (() => {
971+ call_api.callback ();
972+ return false;
973+ });
974+ yield;
975+ response = found_json;
976+ if (status_code != Soup.KnownStatusCode.OK) {
977+ error = new PurchaseError.PURCHASE_ERROR ("fake error");
978+ }
979+ return error;
980+ }
981+
982+ internal override void _do_sso_webcall (Soup.Message message, string password)
983+ {
984+ message.response_body.append (Soup.MemoryUse.STATIC, found_json.data);
985+ message.status_code = status_code;
986+ }
987+
988+ internal override Soup.Message send_signed_webservice_call (string method, string uri)
989+ {
990+ var message = new Soup.Message (method, uri);
991+ message.response_body.append (Soup.MemoryUse.STATIC, found_json.data);
992+ message.status_code = status_code;
993+ return message;
994+ }
995+ }
996+
997+ private static FakeWebcallPurchaseService _test_fetch_account (int _status_code, string _found_json) throws PurchaseError
998+ {
999+ var purchase_service = new FakeWebcallPurchaseService (_status_code, _found_json);
1000+ purchase_service._ubuntuone_credentials = new HashTable <string, string> (str_hash, str_equal);
1001+
1002+ PurchaseError error = null;
1003+ MainLoop mainloop = new MainLoop ();
1004+ purchase_service.fetch_account.begin((obj, res) => {
1005+ mainloop.quit ();
1006+ try {
1007+ purchase_service.fetch_account.end (res);
1008+ } catch (PurchaseError e) {
1009+ error = e;
1010+ }
1011+ });
1012+ assert (run_with_timeout (mainloop, 1000));
1013+ if (error != null) {
1014+ throw error;
1015+ }
1016+ return purchase_service;
1017+ }
1018+
1019+ private static void test_fetch_account ()
1020+ {
1021+ try {
1022+ var purchase_service = _test_fetch_account (Soup.KnownStatusCode.OK, FAKE_JSON_ACCOUNT);
1023+ assert_cmpstr (purchase_service.nickname, OperatorType.EQUAL, "Mr. Bean");
1024+ assert_cmpstr (purchase_service.email, OperatorType.EQUAL, "mr@be.an");
1025+ } catch (PurchaseError e) {
1026+ assert_not_reached ();
1027+ }
1028+ }
1029+
1030+ private static void test_fetch_account_http_failure ()
1031+ {
1032+ try {
1033+ _test_fetch_account (Soup.KnownStatusCode.INTERNAL_SERVER_ERROR, FAKE_JSON_ACCOUNT);
1034+ assert_not_reached ();
1035+ } catch (PurchaseError e) {
1036+ // Expected error
1037+ }
1038+ }
1039+
1040+ private static void test_fetch_account_wrong_json ()
1041+ {
1042+ try {
1043+ _test_fetch_account (Soup.KnownStatusCode.OK, BROKEN_JSON);
1044+ assert_not_reached ();
1045+ } catch (PurchaseError e) {
1046+ // Expected error
1047+ }
1048+ }
1049+
1050+ private static FakeWebcallPurchaseService _test_fetch_payment (int _status_code, string _found_json) throws PurchaseError
1051+ {
1052+ var purchase_service = new FakeWebcallPurchaseService (_status_code, _found_json);
1053+ purchase_service._ubuntuone_credentials = new HashTable <string, string> (str_hash, str_equal);
1054+
1055+ PurchaseError error = null;
1056+ MainLoop mainloop = new MainLoop ();
1057+ purchase_service.fetch_payment_method.begin("fake_sku", (obj, res) => {
1058+ mainloop.quit ();
1059+ try {
1060+ purchase_service.fetch_payment_method.end (res);
1061+ } catch (PurchaseError e) {
1062+ error = e;
1063+ }
1064+ });
1065+ assert (run_with_timeout (mainloop, 1000));
1066+ if (error != null) {
1067+ throw error;
1068+ }
1069+ return purchase_service;
1070+ }
1071+
1072+ private static void test_fetch_payment ()
1073+ {
1074+ try {
1075+ var purchase_service = _test_fetch_payment (Soup.KnownStatusCode.OK, FAKE_JSON_PAYMENT_METHOD);
1076+ assert_cmpstr (purchase_service.selected_payment_method, OperatorType.EQUAL, "Visa 1234");
1077+ } catch (PurchaseError e) {
1078+ assert_not_reached ();
1079+ }
1080+ }
1081+
1082+ private static void test_fetch_payment_http_failure ()
1083+ {
1084+ try {
1085+ _test_fetch_payment (Soup.KnownStatusCode.INTERNAL_SERVER_ERROR, FAKE_JSON_PAYMENT_METHOD);
1086+ assert_not_reached ();
1087+ } catch (PurchaseError e) {
1088+ // Expected error
1089+ }
1090+ }
1091+
1092+ private static void test_fetch_payment_wrong_json ()
1093+ {
1094+ try {
1095+ _test_fetch_payment (Soup.KnownStatusCode.OK, BROKEN_JSON);
1096+ assert_not_reached ();
1097+ } catch (PurchaseError e) {
1098+ // Expected error
1099+ }
1100+ }
1101+
1102+ class TestPurchaseService : BaseTestPurchaseService
1103+ {
1104+ internal string password;
1105+ internal string sku;
1106+ internal string purchase_token;
1107+
1108+ internal override string get_purchase_token (string password)
1109+ {
1110+ this.password = password;
1111+ return FAKE_TOKEN;
1112+ }
1113+ internal override void purchase_with_default_payment (string sku, string purchase_token)
1114+ {
1115+ this.sku = sku;
1116+ this.purchase_token = purchase_token;
1117+ }
1118+ }
1119+
1120+ private static void test_purchase ()
1121+ {
1122+ try {
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.purchase_token, OperatorType.EQUAL, FAKE_TOKEN);
1128+ } catch (Error e) {
1129+ assert_not_reached ();
1130+ }
1131+ }
1132+
1133+ class FakePurchaseTokenService : BaseTestPurchaseService
1134+ {
1135+ string found_json;
1136+
1137+ internal FakePurchaseTokenService (string _found_json)
1138+ {
1139+ found_json = _found_json;
1140+ }
1141+
1142+ internal override string authenticated_sso_webcall (string method, string uri, string operation, string password)
1143+ throws PurchaseError
1144+ {
1145+ return found_json;
1146+ }
1147+ }
1148+
1149+ private static void test_get_purchase_token ()
1150+ {
1151+ var purchase_service = new FakePurchaseTokenService (FAKE_JSON_AUTHENTICATION);
1152+ var expected = "fake_consumer_key:fake_token";
1153+ try {
1154+ var result = purchase_service.get_purchase_token (FAKE_PASSWORD);
1155+ assert_cmpstr (expected, OperatorType.EQUAL, result);
1156+ } catch (PurchaseError e) {
1157+ assert_not_reached ();
1158+ }
1159+ }
1160+
1161+ private static void test_get_purchase_token_broken_json ()
1162+ {
1163+ var purchase_service = new FakePurchaseTokenService (BROKEN_JSON);
1164+ try {
1165+ purchase_service.get_purchase_token (FAKE_PASSWORD);
1166+ assert_not_reached ();
1167+ } catch (PurchaseError e) {
1168+ assert (e is PurchaseError.PURCHASE_ERROR);
1169+ }
1170+ }
1171+
1172+ private static void test_authenticated_sso_webcall ()
1173+ {
1174+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.OK, FAKE_JSON_AUTHENTICATION);
1175+ try {
1176+ var result = purchase_service.authenticated_sso_webcall ("FAKE", "http://some/where", "operation=wolf", FAKE_PASSWORD);
1177+ assert_cmpstr (FAKE_JSON_AUTHENTICATION, OperatorType.EQUAL, result);
1178+ } catch (PurchaseError e) {
1179+ assert_not_reached ();
1180+ }
1181+ }
1182+
1183+ private static void test_authenticated_sso_webcall_fails ()
1184+ {
1185+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.INTERNAL_SERVER_ERROR, FAKE_JSON_AUTHENTICATION);
1186+ try {
1187+ purchase_service.authenticated_sso_webcall ("FAKE", "http://some/where", "operation=wolf", FAKE_PASSWORD);
1188+ assert_not_reached ();
1189+ } catch (Error e) {
1190+ assert (e is PurchaseError.PURCHASE_ERROR);
1191+ }
1192+ }
1193+
1194+ private static void test_authenticated_sso_webcall_wrong_password ()
1195+ {
1196+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.UNAUTHORIZED, FAKE_JSON_AUTHENTICATION);
1197+ try {
1198+ purchase_service.authenticated_sso_webcall ("FAKE", "http://some/where", "operation=wolf", FAKE_PASSWORD);
1199+ assert_not_reached ();
1200+ } catch (Error e) {
1201+ assert (e is PurchaseError.WRONG_PASSWORD_ERROR);
1202+ }
1203+ }
1204+
1205+ private static void test_purchase_with_default_payment ()
1206+ {
1207+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.OK, FAKE_JSON_PURCHASE);
1208+ try {
1209+ purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);
1210+ } catch (PurchaseError e) {
1211+ assert_not_reached ();
1212+ }
1213+ }
1214+
1215+ private static void test_purchase_with_default_payment_broken_json ()
1216+ {
1217+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.OK, BROKEN_JSON);
1218+ try {
1219+ purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);
1220+ assert_not_reached ();
1221+ } catch (PurchaseError e) {
1222+ assert (e is PurchaseError.PURCHASE_ERROR);
1223+ }
1224+ }
1225+
1226+ private static void test_purchase_with_default_payment_fails ()
1227+ {
1228+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.OK, FAKE_JSON_PURCHASE_FAILURE);
1229+ try {
1230+ purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);
1231+ assert_not_reached ();
1232+ } catch (PurchaseError e) {
1233+ assert (e is PurchaseError.PURCHASE_ERROR);
1234+ }
1235+ }
1236+
1237+ private static void test_purchase_with_default_payment_connection_fails ()
1238+ {
1239+ var purchase_service = new FakeWebcallPurchaseService (Soup.KnownStatusCode.INTERNAL_SERVER_ERROR, FAKE_JSON_PURCHASE_FAILURE);
1240+ try {
1241+ purchase_service.purchase_with_default_payment (FAKE_SKU, FAKE_TOKEN);
1242+ assert_not_reached ();
1243+ } catch (PurchaseError e) {
1244+ assert (e is PurchaseError.PURCHASE_ERROR);
1245+ }
1246+ }
1247+}

Subscribers

People subscribed via source and target branches

to all changes: