Merge lp:~elopio/u1-test-utils/smart_scopes_tests into lp:u1-test-utils
- smart_scopes_tests
- Merge into trunk
Proposed by
Leo Arias
Status: | Superseded |
---|---|
Proposed branch: | lp:~elopio/u1-test-utils/smart_scopes_tests |
Merge into: | lp:u1-test-utils |
Diff against target: |
1090 lines (+1004/-0) 17 files modified
bin/vm_session_setup.py (+19/-0) selftest.py (+20/-0) tests/__init__.py (+114/-0) tests/browsers.py (+24/-0) tests/dash.py (+185/-0) tests/goods.py (+9/-0) tests/pay.py (+23/-0) tests/schema.py (+26/-0) tests/settings.py (+16/-0) tests/sso.py (+230/-0) tests/test_dash.py (+40/-0) tests/test_dbus.py (+19/-0) tests/test_purchase_good.py (+84/-0) tests/test_session.py (+19/-0) tests/test_smart_scopes.py (+28/-0) tests/test_users.py (+64/-0) tests/users.py (+84/-0) |
To merge this branch: | bzr merge lp:~elopio/u1-test-utils/smart_scopes_tests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical ISD hackers | Pending | ||
Review via email: mp+170968@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Unmerged revisions
- 68. By Leo Arias
-
Check the URL on the browser.
- 67. By Leo Arias
-
Click the View button.
- 66. By Leo Arias
-
Preview the result.
- 65. By Leo Arias
-
Added the first part of the first scope search test.
- 64. By Leo Arias
-
Assert that there is at least one result displayed.
- 63. By Leo Arias
-
Added a skip because of bug #1185486.
- 62. By Leo Arias
-
Removed the code and comments about the previous workaround.
- 61. By Leo Arias
-
Remove the test for the expired card. We have no way to add an expired credit card.
- 60. By Leo Arias
-
We need to point to the root of the servers.
- 59. By Leo Arias
-
But the one we have to clear is the music scope.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'bin' |
2 | === added file 'bin/vm_session_setup.py' |
3 | --- bin/vm_session_setup.py 1970-01-01 00:00:00 +0000 |
4 | +++ bin/vm_session_setup.py 2013-06-23 05:21:25 +0000 |
5 | @@ -0,0 +1,19 @@ |
6 | +#!/usr/bin/env python |
7 | +""" |
8 | +Setup the gnome session environment to suit test needs. |
9 | +""" |
10 | + |
11 | +from gi.repository import Gio, Gtk |
12 | + |
13 | + |
14 | +def run(args=None): |
15 | + session = Gio.Settings('org.gnome.desktop.session') |
16 | + # Never sleep, we're in a vm |
17 | + session.set_uint('idle-delay', 0) |
18 | + # Never lock the screen either |
19 | + screen_saver = Gio.Settings('org.gnome.desktop.screensaver') |
20 | + screen_saver.set_boolean('lock-enabled', False) |
21 | + |
22 | + |
23 | +if __name__ == "__main__": |
24 | + run() |
25 | |
26 | === added file 'selftest.py' |
27 | --- selftest.py 1970-01-01 00:00:00 +0000 |
28 | +++ selftest.py 2013-06-23 05:21:25 +0000 |
29 | @@ -0,0 +1,20 @@ |
30 | +#!/usr/bin/env python |
31 | + |
32 | +import os |
33 | +import sys |
34 | + |
35 | +import tests |
36 | + |
37 | +# load tests |
38 | +# filter tests |
39 | +# order tests |
40 | +# run tests |
41 | + |
42 | + |
43 | +# We discover tests under './tests', the python 'load_test' protocol can be |
44 | +# used in test modules for more fancy stuff. |
45 | +discover_args = ['discover', |
46 | + '--start-directory', './tests', |
47 | + ] |
48 | +os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' |
49 | +tests.TestProgram(__name__, argv=[sys.argv[0]] + discover_args + sys.argv[1:]) |
50 | |
51 | === added directory 'tests' |
52 | === added file 'tests/__init__.py' |
53 | --- tests/__init__.py 1970-01-01 00:00:00 +0000 |
54 | +++ tests/__init__.py 2013-06-23 05:21:25 +0000 |
55 | @@ -0,0 +1,114 @@ |
56 | +import os |
57 | +import selenium |
58 | +from selenium.webdriver.firefox import ( |
59 | + firefox_binary, |
60 | + webdriver as ff_webdriver, |
61 | + ) |
62 | + |
63 | +from sst import runtests as stests |
64 | +import testtools |
65 | +from testtools import ( |
66 | + run, |
67 | + testcase, |
68 | + ) |
69 | +import unittest |
70 | +import unity.tests as utests |
71 | + |
72 | + |
73 | +class TestCase(testcase.TestCase): |
74 | + pass |
75 | + |
76 | + |
77 | +class FirefoxNoRemote(firefox_binary.FirefoxBinary): |
78 | + """A firefox that can be called by all apps. |
79 | + |
80 | + Selelnium provides a firefox instance working in isolation. While that's |
81 | + the most common case, we need a variant here as we don't control how the |
82 | + dash or any scope will call the web browser. |
83 | + """ |
84 | + |
85 | + def __init__(self, firefox_path=None): |
86 | + super(FirefoxNoRemote, self).__init__(firefox_path) |
87 | + if self._firefox_env.get('MOZ_NO_REMOTE', None) is not None: |
88 | + del self._firefox_env['MOZ_NO_REMOTE'] |
89 | + |
90 | + if selenium.__version__ < '2.32': |
91 | + # FIXME: The constructor above is all that is needed for selenium 2.32, |
92 | + # but it hasn't been pushed in all places so far so the method below |
93 | + # achieve the same trick for previous selenium releases |
94 | + def _start_from_profile_path(self, path): |
95 | + import platform |
96 | + from subprocess import Popen, PIPE, STDOUT |
97 | + self._firefox_env["XRE_PROFILE_PATH"] = path |
98 | + self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1" |
99 | + self._firefox_env["NO_EM_RESTART"] = "1" |
100 | + |
101 | + if platform.system().lower() == 'linux': |
102 | + self._modify_link_library_path() |
103 | + command = [self._start_cmd, "-silent"] |
104 | + if self.command_line is not None: |
105 | + for cli in self.command_line: |
106 | + command.append(cli) |
107 | + |
108 | + Popen(command, stdout=PIPE, stderr=STDOUT, |
109 | + env=self._firefox_env).communicate() |
110 | + command[1] = '-foreground' |
111 | + self.process = Popen( |
112 | + command, stdout=PIPE, stderr=STDOUT, |
113 | + env=self._firefox_env) |
114 | + |
115 | + |
116 | +class WebDriverNoRemote(ff_webdriver.WebDriver): |
117 | + """A selenium webdriver using FirefoxNoRemote. |
118 | + |
119 | + See FirefoxNoRemote for rationale. |
120 | + """ |
121 | + |
122 | + def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30, |
123 | + capabilities=None, proxy=None): |
124 | + super(WebDriverNoRemote, self).__init__( |
125 | + firefox_profile, FirefoxNoRemote(), timeout, capabilities, proxy) |
126 | + |
127 | + |
128 | +class FirefoxNoRemoteFactory(stests.FirefoxFactory): |
129 | + """A browser factory using FirefoxNoRemote. |
130 | + |
131 | + See FirefoxNoRemote for rationale. |
132 | + """ |
133 | + |
134 | + webdriver_class = WebDriverNoRemote |
135 | + |
136 | + |
137 | +class UnitySSTestcase(utests.UnityTestCase, stests.SSTTestCase): |
138 | + |
139 | + def setUp(self): |
140 | + # We need to setup a firefox instance that capture all web connections |
141 | + self.browser_factory = FirefoxNoRemoteFactory() |
142 | + utests.UnityTestCase.setUp(self) |
143 | + # FIXME: Urgh, for an unclear reason, the following occurs in |
144 | + # autopilot.testcase.LoggedTestCase.setUp when super(LoggedTestCase, |
145 | + # self).setUp() is called. The joy of multiple inheritance... One more |
146 | + # reason to avoid it but sst doesn't provide a way to compose instead |
147 | + # of inheriting in our case. If we leave the following call in place, |
148 | + # we end up calling SSTTestCase.setUp twice which is utterly wrong. One |
149 | + # workaround may be to make SSTTestCase detect double calls and avoid |
150 | + # them but that's even dirtier than the actual behavior and may lead to |
151 | + # even more obscure failures -- vila 2013-03-27 |
152 | + # stests.SSTTestCase.setUp(self) |
153 | + |
154 | + # Check that the browser started with a single window opened |
155 | + self.assertEqual(1, len(self.browser.window_handles)) |
156 | + self.assertEqual('about:blank', self.browser.current_url) |
157 | + |
158 | + # FIXME: sst overrides testtools definition, that breaks autopilot |
159 | + # assumption that shortDescription can be used to generate a unique log |
160 | + # file. -- vila 2013-03-27 |
161 | + shortDescription = testcase.TestCase.shortDescription |
162 | + |
163 | +class TestProgram(testtools.run.TestProgram): |
164 | + |
165 | + def __init__(self, module, argv, stdout=None, testRunner=None, exit=True): |
166 | + if testRunner is None: |
167 | + testRunner = unittest.TextTestRunner |
168 | + super(TestProgram, self).__init__(module, argv=argv, stdout=stdout, |
169 | + testRunner=testRunner, exit=exit) |
170 | |
171 | === added file 'tests/browsers.py' |
172 | --- tests/browsers.py 1970-01-01 00:00:00 +0000 |
173 | +++ tests/browsers.py 2013-06-23 05:21:25 +0000 |
174 | @@ -0,0 +1,24 @@ |
175 | +from sst import actions |
176 | + |
177 | + |
178 | +class Browser(object): |
179 | + |
180 | + def __init__(self, test): |
181 | + super(Browser, self).__init__() |
182 | + self.browser = test.browser |
183 | + self.nb_windows = len(self.browser.window_handles) |
184 | + |
185 | + def switch_to_new_window(self, test): |
186 | + def new_window_appeared(): |
187 | + return len(self.browser.window_handles) > 1 |
188 | + actions.wait_for(new_window_appeared) |
189 | + test.assertEqual(2, len(self.browser.window_handles)) |
190 | + self.browser.switch_to_window(self.browser.window_handles[1]) |
191 | + |
192 | + @property |
193 | + def url(self): |
194 | + return self.browser.current_url |
195 | + |
196 | + @property |
197 | + def title(self): |
198 | + return self.browser.title |
199 | |
200 | === added file 'tests/dash.py' |
201 | --- tests/dash.py 1970-01-01 00:00:00 +0000 |
202 | +++ tests/dash.py 2013-06-23 05:21:25 +0000 |
203 | @@ -0,0 +1,185 @@ |
204 | +import time |
205 | + |
206 | +from autopilot.matchers import Eventually |
207 | +from testtools.matchers import ( |
208 | + Equals, |
209 | + GreaterThan, |
210 | + NotEquals, |
211 | +) |
212 | + |
213 | + |
214 | +def search_home_scope(test, query): |
215 | + test.addCleanup(test.unity.dash.ensure_hidden) |
216 | + test.unity.dash.ensure_visible() |
217 | + test.addCleanup(test.unity.dash.clear_search) |
218 | + test.keyboard.type(query) |
219 | + _wait_for_result_settle(test) |
220 | + |
221 | + |
222 | +# Copied from unity tests. TODO move it to the unity emulator. |
223 | +# --elopio -2013-06-22 |
224 | +def _wait_for_result_settle(test): |
225 | + """wait for row count to settle""" |
226 | + old_row_count = -1 |
227 | + new_row_count = test.unity.dash.get_num_rows() |
228 | + while(old_row_count != new_row_count): |
229 | + time.sleep(1) |
230 | + old_row_count = new_row_count |
231 | + new_row_count = test.unity.dash.get_num_rows() |
232 | + |
233 | + |
234 | +def search_music_scope(test, query): |
235 | + test.addCleanup(test.unity.dash.ensure_hidden) |
236 | + test.unity.dash.reveal_music_scope() |
237 | + test.addCleanup(clear_music_scope_search, test) |
238 | + test.keyboard.type(query) |
239 | + music_scope = test.unity.dash.get_current_scope() |
240 | + # Wait for a category to appear. |
241 | + test.assertThat( |
242 | + music_scope.get_num_visible_categories, |
243 | + Eventually(GreaterThan(0))) |
244 | + |
245 | + |
246 | +def clear_music_scope_search(test): |
247 | + dash = test.unity.dash |
248 | + if (not dash.visible) or (dash.get_current_scope() != 'music.scope'): |
249 | + dash.reveal_music_scope() |
250 | + dash.clear_search() |
251 | + |
252 | + |
253 | +def get_first_result_from_category(test, category_name): |
254 | + current_scope = test.unity.dash.get_current_scope() |
255 | + category = current_scope.get_category_by_name(category_name) |
256 | + test.assertTrue(category.is_visible) |
257 | + get_number_of_results = lambda: len(category.get_results()) |
258 | + test.assertThat(get_number_of_results, Eventually(GreaterThan(0))) |
259 | + return category.get_results()[0] |
260 | + |
261 | + |
262 | +def preview_result(test, result): |
263 | + current_scope = test.unity.dash.get_current_scope() |
264 | + test.assertThat(current_scope.get_num_visible_categories(), GreaterThan(0)) |
265 | + result.preview() |
266 | + _wait_for_preview_animation(test) |
267 | + |
268 | + |
269 | +def _wait_for_preview_animation(test): |
270 | + test.assertThat( |
271 | + test.unity.dash.view.get_preview_container, |
272 | + Eventually(NotEquals(None))) |
273 | + preview_container = test.unity.dash.view.get_preview_container() |
274 | + test.assertThat( |
275 | + preview_container.animating, Eventually(Equals(False))) |
276 | + |
277 | + |
278 | +def ensure_preview_closed(test): |
279 | + if test.unity.dash.preview_displaying: |
280 | + close_preview(test) |
281 | + |
282 | + |
283 | +def close_preview(test): |
284 | + test.assertTrue(test.unity.dash.preview_displaying) |
285 | + test.keyboard.press_and_release('Escape') |
286 | + |
287 | + |
288 | +def get_current_preview(test): |
289 | + test.assertThat( |
290 | + test.unity.dash.view.get_preview_container, |
291 | + Eventually(NotEquals(None))) |
292 | + container = test.unity.dash.view.get_preview_container() |
293 | + test.assertThat(container.waiting_preview, Eventually(Equals(False))) |
294 | + return container.current_preview |
295 | + |
296 | + |
297 | +class Window(object): |
298 | + """A dash window with a content, including buttons and links.""" |
299 | + |
300 | + def __init__(self, user): |
301 | + self.user = user |
302 | + |
303 | +class Home(Window): |
304 | + """The first window opened when the dash is activated.""" |
305 | + |
306 | + def get_result(self, test): |
307 | + # XXX Currently we can't force the Music Store to return a result, |
308 | + # so for now we will just use the first one. --elopio - 2013-05-19 |
309 | + return get_first_result_from_category(test, 'More suggestions') |
310 | + |
311 | + def get_preview(self, test, good): |
312 | + search_music_scope(test, good.search) |
313 | + result = self.get_result(test) |
314 | + test.addCleanup(ensure_preview_closed, test) |
315 | + preview_result(test, result) |
316 | + # We have the preview now |
317 | + return Preview(self.user, good) |
318 | + |
319 | + |
320 | +class WindowWithGood(Window): |
321 | + |
322 | + def __init__(self, user, good): |
323 | + super(WindowWithGood, self).__init__(user) |
324 | + self.good = good |
325 | + |
326 | + def good_displayed(self): |
327 | + # FIXME: There should be a way to fail ;) -- vila 2013-03-26 |
328 | + return True |
329 | + |
330 | + |
331 | +class Preview(WindowWithGood): |
332 | + |
333 | + _DOWNLOAD_BUTTON_ID = 'show_purchase_preview' |
334 | + |
335 | + def __init__(self, user, good): |
336 | + super(Preview, self).__init__(user, good) |
337 | + |
338 | + def get_message_replacing_action(self, test): |
339 | + preview = get_current_preview() |
340 | + # FIXME: Far from pretty but at least we can get it. There are two |
341 | + # issues below: [2] may break if more StaticCairoTexts are added and |
342 | + # the 'get_properties()['text'] may be brittle (but that would mean |
343 | + # StaticCairoText implementation changed which should have other |
344 | + # fallouts anyway). Ideally, this helper should be provided by the |
345 | + # MusicPreview object from the dash. -- vila 2013-05-02 |
346 | + message = preview.text_boxes[2].get_properties()['text'] |
347 | + return message |
348 | + |
349 | + def get_download_button(self, test): |
350 | + preview = get_current_preview(test) |
351 | + download = preview.get_action_by_id(self._DOWNLOAD_BUTTON_ID) |
352 | + return download |
353 | + |
354 | + def click_download(self, test): |
355 | + preview = get_current_preview(test) |
356 | + preview.execute_action_by_id(self._DOWNLOAD_BUTTON_ID) |
357 | + # FIXME: The current implementation diverges from the design spec by |
358 | + # going straight to the web without presenting a window explaining why |
359 | + # the good can't be bought while also displaying the good. As of today, |
360 | + # the workaround is to display a message instead of the button. But |
361 | + # this is only implemented for the music scope, not the home |
362 | + # scope. -- vila 2013-03-28 |
363 | + return GotoU1(self.user, self.good) |
364 | + |
365 | + |
366 | +class GotoU1(WindowWithGood): |
367 | + |
368 | + def __init__(self, user, good): |
369 | + super(GotoU1, self).__init__(user, good) |
370 | + if self.user.is_logged_in(): |
371 | + if self.user.get_payment_type() == 'expired': |
372 | + self.message = ('Your card has expired.' |
373 | + 'To add a new payment method, please visit...') |
374 | + else: |
375 | + self.message = 'To add a payment method, please visit...' |
376 | + else: |
377 | + self.message = ("You don't have an Ubuntu One account," |
378 | + " or you are not logged in.") |
379 | + |
380 | + def click_goto_u1(self): |
381 | + from tests import browsers |
382 | + browser = browsers.get_default() |
383 | + # Fake the app response by forcing the url in the browser |
384 | + if self.user.logged_in: |
385 | + browser.current_url = 'https://pay.ubuntu.com' |
386 | + else: |
387 | + browser.current_url = 'https://one.ubuntu.com' |
388 | + return browser |
389 | |
390 | === added file 'tests/goods.py' |
391 | --- tests/goods.py 1970-01-01 00:00:00 +0000 |
392 | +++ tests/goods.py 2013-06-23 05:21:25 +0000 |
393 | @@ -0,0 +1,9 @@ |
394 | +class Good(object): |
395 | + |
396 | + def __init__(self, name, search): |
397 | + # Name is used to lightly check the result we get from the |
398 | + # search. Lightly here means that we ensure that the result name |
399 | + # contains the good name. |
400 | + self.name = name |
401 | + # How to search for this good from the dash |
402 | + self.search = search |
403 | |
404 | === added file 'tests/pay.py' |
405 | --- tests/pay.py 1970-01-01 00:00:00 +0000 |
406 | +++ tests/pay.py 2013-06-23 05:21:25 +0000 |
407 | @@ -0,0 +1,23 @@ |
408 | +from sst import actions as sst_actions |
409 | +from u1testutils.pay import api, data |
410 | +from u1testutils.sso import sst as sso_website |
411 | + |
412 | + |
413 | +def get_payment_preferences_with_server_api(user, pay_server_url): |
414 | + api_client = api.APIClient(pay_server_url) |
415 | + return api_client.client.account_preferences(open_id=user.openid) |
416 | + |
417 | + |
418 | +def add_new_credit_card_with_server_api( |
419 | + user, unattended, pay_server_url, consumer_id): |
420 | + credit_card = data.CreditCard.make_test_visa_card() |
421 | + billing_address = data.Address.make_unique() |
422 | + api_client = api.APIClient(pay_server_url) |
423 | + api_client.add_new_credit_card( |
424 | + consumer_id, user, credit_card, billing_address, unattended) |
425 | + |
426 | + |
427 | +def log_in_to_website(user, pay_server_url): |
428 | + sst_actions.go_to(pay_server_url) |
429 | + sst_actions.click_link('login-link') |
430 | + sso_website.sign_in(user, is_site_recognized=False) |
431 | |
432 | === added file 'tests/schema.py' |
433 | --- tests/schema.py 1970-01-01 00:00:00 +0000 |
434 | +++ tests/schema.py 2013-06-23 05:21:25 +0000 |
435 | @@ -0,0 +1,26 @@ |
436 | +from configglue import schema |
437 | + |
438 | + |
439 | +class InDashPaymentsSchema(schema.Schema): |
440 | + |
441 | + class openid(schema.Section): |
442 | + openid_sso_server_url = schema.StringOption() |
443 | + |
444 | + class upay(schema.Section): |
445 | + pay_server_url = schema.StringOption() |
446 | + consumer_id = schema.StringOption() |
447 | + pay_api_username = schema.StringOption() |
448 | + pay_api_password = schema.StringOption() |
449 | + |
450 | + class ubuntuone(schema.Section): |
451 | + ubuntuone_server_url = schema.StringOption() |
452 | + |
453 | + class musicsearch(schema.Section): |
454 | + musicsearch_server_url = schema.StringOption() |
455 | + |
456 | + class smartscopes(schema.Section): |
457 | + smartscopes_server_url = schema.StringOption() |
458 | + smartscopes_geo_store = schema.StringOption() |
459 | + |
460 | + |
461 | +schema = InDashPaymentsSchema |
462 | |
463 | === added file 'tests/settings.py' |
464 | --- tests/settings.py 1970-01-01 00:00:00 +0000 |
465 | +++ tests/settings.py 2013-06-23 05:21:25 +0000 |
466 | @@ -0,0 +1,16 @@ |
467 | +import os |
468 | + |
469 | +from django_configglue.utils import configglue |
470 | + |
471 | +from tests.schema import schema |
472 | + |
473 | + |
474 | +# Get location of local cfg files. |
475 | +local_configs = os.environ.get('CONFIGGLUE_LOCAL_CONFIG', ['local.cfg']) |
476 | + |
477 | +# Get absolute path for config files |
478 | +current_dir = os.path.dirname(os.path.abspath(__file__)) |
479 | +config_files = map(lambda x: os.path.join(current_dir, x), local_configs) |
480 | + |
481 | +# Glue everything together. |
482 | +configglue(schema, config_files, __name__) |
483 | |
484 | === added file 'tests/sso.py' |
485 | --- tests/sso.py 1970-01-01 00:00:00 +0000 |
486 | +++ tests/sso.py 2013-06-23 05:21:25 +0000 |
487 | @@ -0,0 +1,230 @@ |
488 | +import os |
489 | +import subprocess |
490 | +import threading |
491 | +import time |
492 | + |
493 | +import dbus |
494 | +import dbus.mainloop.glib |
495 | +import gobject |
496 | + |
497 | +from autopilot.emulators import input |
498 | +from autopilot import introspection |
499 | +from u1testutils.sso import api |
500 | + |
501 | + |
502 | +def create_new_account_with_server_api(user, sso_server_url): |
503 | + api_client = api.APIClient(sso_server_url) |
504 | + api_client.create_new_account( |
505 | + user, captcha_id='Not used', captcha_solution='Not used') |
506 | + |
507 | + |
508 | +def log_in_with_desktop_client(user, sso_server_url, ubuntuone_server_url): |
509 | + desktop_client = UbuntuSSODesktopClient( |
510 | + sso_server_url, ubuntuone_server_url) |
511 | + desktop_client.log_in(user) |
512 | + |
513 | + |
514 | +def _start_ubuntu_sso_service_on_desktop(sso_server_url): |
515 | + os.environ['USSOC_SERVICE_URL'] = '{0}/api/1.0/'.format(sso_server_url) |
516 | + os.system('pkill ubuntu-sso') |
517 | + subprocess.Popen('/usr/lib/ubuntu-sso-client/ubuntu-sso-login') |
518 | + |
519 | + |
520 | +def log_out_with_dbus_api(sso_server_url): |
521 | + _start_ubuntu_sso_service_on_desktop(sso_server_url) |
522 | + interface = _get_sso_dbus_interface() |
523 | + interface.clear_credentials('Ubuntu One', {}) |
524 | + |
525 | + |
526 | +def is_logged_in_on_desktop(sso_server_url): |
527 | + _start_ubuntu_sso_service_on_desktop(sso_server_url) |
528 | + credentials = _get_credentials() |
529 | + return credentials is not None |
530 | + |
531 | + |
532 | +def _get_credentials(): |
533 | + signaled = threading.Event() |
534 | + # XXX is there a way to share a variable without the globals? Without that |
535 | + # the signal callbacks don't set the value on the right variable. |
536 | + # -- elopio - 2013-05-15 |
537 | + global _credentials |
538 | + _credentials = None |
539 | + global _credentials_error |
540 | + _credentials_error = None |
541 | + |
542 | + def credentials_found(app_name, credentials=None): |
543 | + if app_name == 'Ubuntu One': |
544 | + global _credentials |
545 | + _credentials = credentials |
546 | + loop.quit() |
547 | + signaled.set() |
548 | + |
549 | + def credentials_error(app_name, error): |
550 | + if app_name == 'Ubuntu One': |
551 | + global _credentials_error |
552 | + _credentials_error = error |
553 | + loop.quit() |
554 | + signaled.set() |
555 | + |
556 | + interface = _get_sso_dbus_interface() |
557 | + interface.connect_to_signal('CredentialsFound', credentials_found) |
558 | + interface.connect_to_signal('CredentialsNotFound', credentials_found) |
559 | + interface.connect_to_signal('CredentialsError', credentials_error) |
560 | + interface.find_credentials('Ubuntu One', {}) |
561 | + loop = gobject.MainLoop() |
562 | + loop.run() |
563 | + signaled.wait() |
564 | + if _credentials_error: |
565 | + raise dbus.DBusException(_credentials_error['message']) |
566 | + else: |
567 | + return _credentials |
568 | + |
569 | + |
570 | +def _get_sso_dbus_interface(): |
571 | + dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
572 | + session_bus = dbus.SessionBus() |
573 | + proxy = session_bus.get_object( |
574 | + 'com.ubuntu.sso', '/com/ubuntu/sso/credentials') |
575 | + return dbus.Interface( |
576 | + proxy, dbus_interface='com.ubuntu.sso.CredentialsManagement') |
577 | + |
578 | + |
579 | +class UbuntuSSODesktopClient(object): |
580 | + |
581 | + def __init__(self, sso_server_url, ubuntuone_server_url): |
582 | + self.sso_server_url = sso_server_url |
583 | + self.ubuntuone_server_url = ubuntuone_server_url |
584 | + |
585 | + def launch_application(self): |
586 | + self._prepare_environment() |
587 | + process = subprocess.Popen( |
588 | + '/usr/lib/ubuntu-sso-client/ubuntu-sso-login-qt') |
589 | + self.application = ( |
590 | + introspection.get_autopilot_proxy_object_for_process(process)) |
591 | + |
592 | + def _prepare_environment(self): |
593 | + os.environ['TESTABILITY'] = '1' |
594 | + os.environ['SSO_AUTH_BASE_URL'] = self.sso_server_url |
595 | + os.environ['SSO_UONE_BASE_URL'] = self.ubuntuone_server_url |
596 | + _start_ubuntu_sso_service_on_desktop(self.sso_server_url) |
597 | + |
598 | + def log_in(self, user): |
599 | + self.launch_application() |
600 | + log_in_page = self._go_to_log_in_page() |
601 | + success_page = log_in_page.log_in_with_successful_authentication(user) |
602 | + assert (success_page.get_message() == |
603 | + 'You are now logged into Ubuntu One.') |
604 | + success_page.finish() |
605 | + |
606 | + def _go_to_log_in_page(self): |
607 | + sign_up_page = SignUpPage(self.application) |
608 | + sign_up_page.wait_for_action_to_complete() |
609 | + return sign_up_page.go_to_log_in() |
610 | + |
611 | + |
612 | +class Page(object): |
613 | + |
614 | + def __init__(self, application, page_type): |
615 | + self.application = application |
616 | + self.page = self.application.select_single(page_type) |
617 | + self.keyboard = input.get_keyboard() |
618 | + self.mouse = input.get_mouse() |
619 | + |
620 | + def wait_for_action_to_complete(self): |
621 | + """Wait for the last action to complete. |
622 | + |
623 | + This method waits for the loading overlay to dissapear. |
624 | + |
625 | + """ |
626 | + # If autopilot executes too fast, we might look for the loading overlay |
627 | + # before it is shown. We should wait a little to give it time to |
628 | + # appear, but it's not possible to wait for it, because if autopilot |
629 | + # executes too slow, we might look for it after it dissapears. |
630 | + # Thus, the only solution that comes to mind is a sleep. |
631 | + # -- elopio - 2013-05-03 |
632 | + time.sleep(3) |
633 | + loading_overlay = self.application.select_single('LoadingOverlay') |
634 | + loading_overlay.visible.wait_for(False) |
635 | + |
636 | + # TODO propose this to autopilot. -- elopio - 2013-05-03 |
637 | + def get_child_by_type(self, desired_type, **kwargs): |
638 | + children = self.page.get_children_by_type(desired_type, **kwargs) |
639 | + assert len(children) > 0, 'No child found.' |
640 | + assert len(children) == 1, 'More than one child found.' |
641 | + return children[0] |
642 | + |
643 | + |
644 | +class SignUpPage(Page): |
645 | + |
646 | + def __init__(self, application): |
647 | + super(SignUpPage, self).__init__(application, 'SetupAccountPage') |
648 | + |
649 | + def go_to_log_in(self): |
650 | + self.wait_for_action_to_complete() |
651 | + sign_in_label = self.get_child_by_type( |
652 | + 'QLabel', objectName='sign_in_label') |
653 | + self.mouse.move_to_object(sign_in_label) |
654 | + self.mouse.click() |
655 | + self.wait_for_action_to_complete() |
656 | + return LogInPage(self.application) |
657 | + |
658 | + |
659 | +class LogInPage(Page): |
660 | + |
661 | + def __init__(self, application): |
662 | + super(LogInPage, self).__init__(application, 'CurrentUserSignInPage') |
663 | + |
664 | + def log_in_with_successful_authentication(self, user): |
665 | + self._log_in(user.email, user.password) |
666 | + assert self.get_form_errors() is None |
667 | + return SuccessPage(self.application) |
668 | + |
669 | + def _log_in(self, email, password): |
670 | + self._fill_log_in_form(email, password) |
671 | + sign_in_button = self.get_child_by_type( |
672 | + 'QPushButton', objectName='sign_in_button') |
673 | + self.mouse.move_to_object(sign_in_button) |
674 | + self.mouse.click() |
675 | + self.wait_for_action_to_complete() |
676 | + |
677 | + def _fill_log_in_form(self, email, password): |
678 | + email_edit = self.get_child_by_type( |
679 | + 'QLineEdit', objectName='email_edit') |
680 | + self.mouse.move_to_object(email_edit) |
681 | + self.mouse.click() |
682 | + self.keyboard.type(email) |
683 | + password_edit = self.get_child_by_type( |
684 | + 'QLineEdit', objectName='password_edit') |
685 | + self.mouse.move_to_object(password_edit) |
686 | + self.mouse.click() |
687 | + self.keyboard.type(password) |
688 | + |
689 | + def get_form_errors(self): |
690 | + form_errors = self.get_child_by_type( |
691 | + 'QLabel', objectName='form_errors') |
692 | + if form_errors.visible: |
693 | + return form_errors.text |
694 | + else: |
695 | + return None |
696 | + |
697 | + |
698 | +class SuccessPage(Page): |
699 | + |
700 | + def __init__(self, application): |
701 | + super(SuccessPage, self).__init__(application, 'SuccessPage') |
702 | + |
703 | + def get_message(self): |
704 | + label = self.get_child_by_type( |
705 | + 'QLabel', objectName='success_message_body') |
706 | + return label.text |
707 | + |
708 | + def finish(self): |
709 | + # TODO when we have the emulator, this should be on the wizard, not the |
710 | + # page. -- elopio - 2013-05-03 |
711 | + # Bug reported to QWizard because the buttons don't have objectName: |
712 | + # https://bugreports.qt-project.org/browse/QTBUG-29924 |
713 | + # TODO update when the bug is fixed. -- elopio - 2013-05-03 |
714 | + finish_button = self.application.select_single( |
715 | + 'QPushButton', text='&Finish') |
716 | + self.mouse.move_to_object(finish_button) |
717 | + self.mouse.click() |
718 | |
719 | === added file 'tests/test_dash.py' |
720 | --- tests/test_dash.py 1970-01-01 00:00:00 +0000 |
721 | +++ tests/test_dash.py 2013-06-23 05:21:25 +0000 |
722 | @@ -0,0 +1,40 @@ |
723 | +import unity.tests as utests |
724 | + |
725 | +from autopilot.matchers import Eventually |
726 | +from testtools.matchers import Equals |
727 | + |
728 | +from tests import dash |
729 | + |
730 | + |
731 | +class TestDash(utests.UnityTestCase): |
732 | + |
733 | + def setUp(self): |
734 | + super(TestDash, self).setUp() |
735 | + self.addCleanup(self.unity.dash.ensure_hidden) |
736 | + |
737 | + def test_open_music_scope(self): |
738 | + self.unity.dash.reveal_music_scope() |
739 | + current_scope = self.unity.dash.get_current_scope() |
740 | + self.assertEquals(current_scope.name, 'music.scope') |
741 | + |
742 | + def test_search_music_scope(self): |
743 | + dash.search_music_scope(self, 'Hendrix') |
744 | + first_result = dash.get_first_result_from_category( |
745 | + self, 'More suggestions') |
746 | + self.assertIn('Hendrix', first_result.name) |
747 | + |
748 | + def test_open_and_close_music_result(self): |
749 | + dash.search_music_scope(self, 'Hendrix') |
750 | + first_result = dash.get_first_result_from_category( |
751 | + self, 'More suggestions') |
752 | + dash.preview_result(self, first_result) |
753 | + self.assertThat( |
754 | + self.unity.dash.preview_displaying, Eventually(Equals(True))) |
755 | + preview = dash.get_current_preview(self) |
756 | + self.assertIn('Hendrix', preview.text_boxes[0].text) |
757 | + self.assertEquals(preview.get_num_actions(), 1) |
758 | + download_button = preview.get_action_by_id('show_purchase_preview') |
759 | + self.assertEqual(download_button.label, 'Download') |
760 | + dash.close_preview(self) |
761 | + self.assertThat( |
762 | + self.unity.dash.preview_displaying, Eventually(Equals(False))) |
763 | |
764 | === added file 'tests/test_dbus.py' |
765 | --- tests/test_dbus.py 1970-01-01 00:00:00 +0000 |
766 | +++ tests/test_dbus.py 2013-06-23 05:21:25 +0000 |
767 | @@ -0,0 +1,19 @@ |
768 | +import tests |
769 | + |
770 | +import dbus |
771 | +from gi.repository import Gio, Gtk |
772 | + |
773 | +class TestDBUS(tests.TestCase): |
774 | + |
775 | + def setUp(self): |
776 | + super(TestDBUS, self).setUp() |
777 | + self.bus = dbus.SessionBus() |
778 | + # Register and clean up our own bus. |
779 | + self.bus.request_name('canonical.online-services.selftest') |
780 | + self.addCleanup(self.bus.release_name, |
781 | + 'canonical.online-services.selftest') |
782 | + |
783 | + def test_it(self): |
784 | + # FIXME: We need some real tests :0) But the setUp above will be |
785 | + # exercised even by an eempty test and represent useful knowledge. |
786 | + pass |
787 | |
788 | === added file 'tests/test_purchase_good.py' |
789 | --- tests/test_purchase_good.py 1970-01-01 00:00:00 +0000 |
790 | +++ tests/test_purchase_good.py 2013-06-23 05:21:25 +0000 |
791 | @@ -0,0 +1,84 @@ |
792 | +import os |
793 | +import subprocess |
794 | + |
795 | +from sst import actions as sactions |
796 | + |
797 | +# FIXME do not use django config to get the server settings. |
798 | +# -- elopio - 2013-05-15 |
799 | +from django import conf |
800 | + |
801 | +import tests |
802 | +from tests import ( |
803 | + browsers, |
804 | + dash, |
805 | + goods, |
806 | + users, |
807 | +) |
808 | + |
809 | + |
810 | +class TestPurchaseGood(tests.UnitySSTestcase): |
811 | + |
812 | + def setUp(self): |
813 | + super(TestPurchaseGood, self).setUp() |
814 | + # A user will purchase a good |
815 | + self.user = users.User.make_unique() |
816 | + # Set the urls of the servers that will be used by the user. |
817 | + self.user.set_servers_data( |
818 | + sso_server_url=conf.settings.OPENID_SSO_SERVER_URL, |
819 | + pay_server_url=conf.settings.PAY_SERVER_URL, |
820 | + consumer_id=conf.settings.CONSUMER_ID, |
821 | + ubuntuone_server_url=conf.settings.UBUNTUONE_SERVER_URL) |
822 | + # Arbitrarily using a good that is known to work |
823 | + self.good = goods.Good(u'Jimi Hendrix', 'hendrix') |
824 | + self._run_music_lens() |
825 | + |
826 | + def _run_music_lens(self): |
827 | + os.environ['MUSICSTORE_URI'] = ( |
828 | + conf.settings.MUSICSEARCH_SERVER_URL + '/v1/') |
829 | + os.environ['U1_STAGING_AUTHENTICATION'] = ( |
830 | + conf.settings.OPENID_SSO_SERVER_URL + '/') |
831 | + os.environ['U1_STAGING_WEBAPI'] = ( |
832 | + conf.settings.UBUNTUONE_SERVER_URL + '/') |
833 | + os.system('pkill unity-music') |
834 | + subprocess.Popen( |
835 | + '/usr/lib/x86_64-linux-gnu/unity-lens-music/' |
836 | + 'unity-musicstore-daemon') |
837 | + |
838 | + def preview_good(self): |
839 | + home = dash.Home(self.user) |
840 | + self.preview = home.get_preview(self, self.good) |
841 | + # user get a preview screen and a button to click on |
842 | + self.assertTrue(self.preview.good_displayed()) |
843 | + |
844 | + def purchase_good(self): |
845 | + # Attempt to purchase the good |
846 | + return self.preview.click_download(self) |
847 | + |
848 | + |
849 | +class TestUserCannotPay(TestPurchaseGood): |
850 | + |
851 | + def test_user_not_logged_in(self): |
852 | + self.assertFalse(self.user.is_logged_in()) |
853 | + self.preview_good() |
854 | + self.purchase_good() |
855 | + browser = browsers.Browser(self) |
856 | + # We've been redirected to the web |
857 | + browser.switch_to_new_window(self) |
858 | + sactions.wait_for( |
859 | + sactions.assert_url_contains, conf.settings.OPENID_SSO_SERVER_URL) |
860 | + self.assertEqual('Log in', browser.title) |
861 | + |
862 | + def test_no_payment_method_stored(self): |
863 | + # Log in with a newly created user, and set no payment method to him. |
864 | + self.user.login(self) |
865 | + self.assertIs(self.user.get_payment_type(), None) |
866 | + self.preview_good() |
867 | + goto_u1 = self.purchase_good() |
868 | + self.assertTrue(goto_u1.good_displayed()) |
869 | + self.assertIn('add a payment method', goto_u1.message) |
870 | + self.skipTest('We are blocked at this point because of bug #1185486.') |
871 | + # The default browser is launched to propose paying |
872 | + #browser = goto_u1.click_goto_u1() |
873 | + # User is sent to the web |
874 | + #self.assertTrue(browser.is_running()) |
875 | + #self.assertEqual(conf.settings.PAY_SERVER_URL, browser.current_url) |
876 | |
877 | === added file 'tests/test_session.py' |
878 | --- tests/test_session.py 1970-01-01 00:00:00 +0000 |
879 | +++ tests/test_session.py 2013-06-23 05:21:25 +0000 |
880 | @@ -0,0 +1,19 @@ |
881 | +import tests |
882 | + |
883 | +from gi.repository import Gio |
884 | + |
885 | +class TestGSettings(tests.TestCase): |
886 | + |
887 | + def test_session(self): |
888 | + s = Gio.Settings('org.gnome.desktop.session') |
889 | + orig = s.get_uint('idle-delay') |
890 | + self.addCleanup(s.set_uint, 'idle-delay', orig) |
891 | + s.set_uint('idle-delay', orig + 2) |
892 | + self.assertEqual(orig + 2, s.get_uint('idle-delay')) |
893 | + |
894 | + def test_screensaver(self): |
895 | + s = Gio.Settings('org.gnome.desktop.screensaver') |
896 | + orig = s.get_boolean('lock-enabled') |
897 | + self.addCleanup(s.set_boolean, 'lock-enabled', orig) |
898 | + s.set_boolean('lock-enabled', not orig) |
899 | + self.assertEqual(not orig, s.get_boolean('lock-enabled')) |
900 | |
901 | === added file 'tests/test_smart_scopes.py' |
902 | --- tests/test_smart_scopes.py 1970-01-01 00:00:00 +0000 |
903 | +++ tests/test_smart_scopes.py 2013-06-23 05:21:25 +0000 |
904 | @@ -0,0 +1,28 @@ |
905 | +import os |
906 | +import subprocess |
907 | + |
908 | +# FIXME do not use django config to get the server settings. |
909 | +# -- elopio - 2013-05-15 |
910 | +from django import conf |
911 | + |
912 | +import tests |
913 | +from tests import dash |
914 | + |
915 | + |
916 | +class SmartScopesSearchTestCase(tests.UnitySSTestcase): |
917 | + |
918 | + def setUp(self): |
919 | + super(SmartScopesSearchTestCase, self).setUp() |
920 | + self._run_home_scope() |
921 | + |
922 | + def _run_home_scope(self): |
923 | + os.environ['SMART_SCOPES_SERVER"'] = ( |
924 | + conf.settings.SMARTSCOPES_SERVER_URL) |
925 | + os.environ['SMART_SCOPES_GEO_STORE'] = ( |
926 | + conf.settings.SMARTSCOPES_GEO_STORE) |
927 | + os.system('pkill unity-scope-home*') |
928 | + subprocess.Popen( |
929 | + '/usr/lib/x86_64-linux-gnu/unity-scope-home/unity-scope-home') |
930 | + |
931 | + def test_search_wikipedia(self): |
932 | + dash.search_home_scope(self, 'wikipedia:Test') |
933 | |
934 | === added file 'tests/test_users.py' |
935 | --- tests/test_users.py 1970-01-01 00:00:00 +0000 |
936 | +++ tests/test_users.py 2013-06-23 05:21:25 +0000 |
937 | @@ -0,0 +1,64 @@ |
938 | +import uuid |
939 | + |
940 | +# FIXME do not use django config to get the server settings. |
941 | +# -- elopio - 2013-05-15 |
942 | +from django import conf |
943 | +from sst import runtests |
944 | + |
945 | + |
946 | +import tests |
947 | +from tests import users |
948 | + |
949 | + |
950 | +class UsersTest(runtests.SSTTestCase): |
951 | + |
952 | + xserver_headless = True |
953 | + |
954 | + def test_make_unique(self): |
955 | + unique_id = str(uuid.uuid1()) |
956 | + user = users.User.make_unique(unique_id) |
957 | + user.set_servers_data( |
958 | + sso_server_url=conf.settings.OPENID_SSO_SERVER_URL, |
959 | + pay_server_url=conf.settings.PAY_SERVER_URL) |
960 | + self.assertEqual( |
961 | + user.user_data.full_name, 'Test user {0}'.format(unique_id)[:30]) |
962 | + self.assertEqual( |
963 | + user.user_data.email, 'test+{0}@example.com'.format(unique_id)) |
964 | + self.assertEqual( |
965 | + user.user_data.password, 'Hola123*') |
966 | + self.assertFalse(user.is_logged_in()) |
967 | + self.assertIs(user.get_payment_type(), None) |
968 | + self.assertIs(user.get_allow_unattended_payments(), None) |
969 | + |
970 | + def test_set_credit_card_payment_method(self): |
971 | + expected_type = 'Credit Card' |
972 | + user = users.User.make_unique() |
973 | + user.set_servers_data( |
974 | + sso_server_url=conf.settings.OPENID_SSO_SERVER_URL, |
975 | + pay_server_url=conf.settings.PAY_SERVER_URL, consumer_id = 'TEST') |
976 | + user.set_payment_method(expected_type) |
977 | + self.assertEqual(user.get_payment_type(), expected_type) |
978 | + self.assertIs(user.get_allow_unattended_payments(), None) |
979 | + |
980 | + def test_set_unattended_credit_card_payment_method(self): |
981 | + expected_type = 'Credit Card' |
982 | + user = users.User.make_unique() |
983 | + user.set_servers_data( |
984 | + sso_server_url=conf.settings.OPENID_SSO_SERVER_URL, |
985 | + pay_server_url=conf.settings.PAY_SERVER_URL, consumer_id = 'TEST') |
986 | + user.set_payment_method(expected_type, unattended=True) |
987 | + self.assertEqual(user.get_payment_type(), expected_type) |
988 | + self.assertTrue(user.get_allow_unattended_payments()) |
989 | + |
990 | + |
991 | +class SessionTest(tests.TestCase): |
992 | + |
993 | + def test_log_in_and_out(self): |
994 | + user = users.User.make_unique() |
995 | + user.set_servers_data( |
996 | + sso_server_url=conf.settings.OPENID_SSO_SERVER_URL, |
997 | + ubuntuone_server_url=conf.settings.UBUNTUONE_SERVER_URL) |
998 | + user.login(self) |
999 | + self.assertTrue(user.is_logged_in()) |
1000 | + user.logout() |
1001 | + self.assertFalse(user.is_logged_in()) |
1002 | |
1003 | === added file 'tests/users.py' |
1004 | --- tests/users.py 1970-01-01 00:00:00 +0000 |
1005 | +++ tests/users.py 2013-06-23 05:21:25 +0000 |
1006 | @@ -0,0 +1,84 @@ |
1007 | +from u1testutils.sso import data |
1008 | + |
1009 | +from tests import pay, sso |
1010 | + |
1011 | + |
1012 | +class User(object): |
1013 | + |
1014 | + def __init__(self, user_data): |
1015 | + self.user_data = user_data |
1016 | + self._user_created_in_sso = False |
1017 | + self._user_created_in_pay = False |
1018 | + self.payment_method = None |
1019 | + |
1020 | + def set_servers_data( |
1021 | + self, sso_server_url=None, pay_server_url=None, consumer_id=None, |
1022 | + ubuntuone_server_url=None): |
1023 | + self.sso_server_url = sso_server_url |
1024 | + self.pay_server_url = pay_server_url |
1025 | + self.consumer_id = consumer_id |
1026 | + self.ubuntuone_server_url = ubuntuone_server_url |
1027 | + |
1028 | + @classmethod |
1029 | + def make_unique(cls, unique_id=None): |
1030 | + user_data = data.User.make_unique(unique_id) |
1031 | + user_data.full_name = user_data.full_name[:30] |
1032 | + return cls(user_data) |
1033 | + |
1034 | + def is_logged_in(self): |
1035 | + return sso.is_logged_in_on_desktop(self.sso_server_url) |
1036 | + |
1037 | + |
1038 | + def login(self, test): |
1039 | + self._ensure_user_created_in_sso() |
1040 | + test.addCleanup(self.logout) |
1041 | + sso.log_in_with_desktop_client( |
1042 | + self.user_data, self.sso_server_url, self.ubuntuone_server_url) |
1043 | + |
1044 | + def _ensure_user_created_in_sso(self): |
1045 | + if not self._user_created_in_sso: |
1046 | + # Currently we are using a local server, so we don't need to delete |
1047 | + # the user after the test. The same applies to the staging server. |
1048 | + # When we start testing against the production server, we should |
1049 | + # either use a stock user, or delete it after the test. |
1050 | + # -- elopio - 2013-05-15 |
1051 | + sso.create_new_account_with_server_api( |
1052 | + self.user_data, self.sso_server_url) |
1053 | + self._user_created_in_sso = True |
1054 | + |
1055 | + def logout(self): |
1056 | + sso.log_out_with_dbus_api(self.sso_server_url) |
1057 | + |
1058 | + def _ensure_user_created_in_pay(self): |
1059 | + self._ensure_user_created_in_sso() |
1060 | + # Make sure the user is registered in pay. Otherwise, the API calls |
1061 | + # will give a 500 error. |
1062 | + # TODO update this when bug http://pad.lv/1144523 is fixed. |
1063 | + if not self._user_created_in_pay: |
1064 | + pay.log_in_to_website(self.user_data, self.pay_server_url) |
1065 | + self._user_created_in_pay = True |
1066 | + |
1067 | + def set_payment_method(self, payment_type, unattended=False): |
1068 | + self._ensure_user_created_in_pay() |
1069 | + if payment_type == 'Credit Card': |
1070 | + pay.add_new_credit_card_with_server_api( |
1071 | + self.user_data, unattended, self.pay_server_url, |
1072 | + self.consumer_id) |
1073 | + else: |
1074 | + raise NotImplementedError |
1075 | + |
1076 | + def get_payment_type(self): |
1077 | + self._ensure_user_created_in_pay() |
1078 | + preferences = pay.get_payment_preferences_with_server_api( |
1079 | + self.user_data, self.pay_server_url) |
1080 | + default_payment_method = preferences['default_payment_method'] |
1081 | + if default_payment_method is not None: |
1082 | + return default_payment_method['type'] |
1083 | + else: |
1084 | + return None |
1085 | + |
1086 | + def get_allow_unattended_payments(self): |
1087 | + self._ensure_user_created_in_pay() |
1088 | + preferences = pay.get_payment_preferences_with_server_api( |
1089 | + self.user_data, self.pay_server_url) |
1090 | + return preferences['allow_unattended_payments'] |