Merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.4 into lp:ubuntuone-control-panel/stable-3-0
- stable-3-0-update-2.99.4
- Merge into stable-3-0
Proposed by
Natalia Bidart
Status: | Merged | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Natalia Bidart | ||||||||||||||||||||||||
Approved revision: | 249 | ||||||||||||||||||||||||
Merged at revision: | 248 | ||||||||||||||||||||||||
Proposed branch: | lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.4 | ||||||||||||||||||||||||
Merge into: | lp:ubuntuone-control-panel/stable-3-0 | ||||||||||||||||||||||||
Diff against target: |
2307 lines (+540/-1126) 30 files modified
run-tests (+20/-15) run-tests.bat (+25/-3) setup.py (+1/-1) ubuntuone/controlpanel/backend.py (+46/-27) ubuntuone/controlpanel/gui/__init__.py (+33/-50) ubuntuone/controlpanel/gui/gtk/gui.py (+6/-4) ubuntuone/controlpanel/gui/gtk/tests/__init__.py (+1/-10) ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py (+7/-8) ubuntuone/controlpanel/gui/qt/folders.py (+2/-7) ubuntuone/controlpanel/gui/qt/gotoweb.py (+2/-12) ubuntuone/controlpanel/gui/qt/gui.py (+3/-3) ubuntuone/controlpanel/gui/qt/main/__init__.py (+5/-14) ubuntuone/controlpanel/gui/qt/main/linux.py (+11/-3) ubuntuone/controlpanel/gui/qt/main/windows.py (+21/-0) ubuntuone/controlpanel/gui/qt/systray.py (+4/-5) ubuntuone/controlpanel/gui/qt/tests/__init__.py (+29/-21) ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py (+5/-52) ubuntuone/controlpanel/gui/qt/tests/test_signin.py (+2/-5) ubuntuone/controlpanel/gui/qt/tests/test_start.py (+89/-91) ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+0/-2) ubuntuone/controlpanel/gui/tests/test_url_sign.py (+0/-102) ubuntuone/controlpanel/logger.py (+2/-2) ubuntuone/controlpanel/tests/test_backend.py (+66/-1) ubuntuone/controlpanel/tests/test_web_client.py (+107/-84) ubuntuone/controlpanel/web_client.py (+53/-61) ubuntuone/controlpanel/web_client/libsoup.py (+0/-133) ubuntuone/controlpanel/web_client/tests/__init__.py (+0/-19) ubuntuone/controlpanel/web_client/tests/test_libsoup.py (+0/-106) ubuntuone/controlpanel/web_client/tests/test_txwebclient.py (+0/-177) ubuntuone/controlpanel/web_client/txwebclient.py (+0/-108) |
||||||||||||||||||||||||
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.4 | ||||||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Review via email: mp+93090@code.launchpad.net |
Commit message
- Updating from trunk up to revno 259:
[ Natalia B. Bidart <email address hidden> ]
- Install the uniqueapp module (LP: #930269).
- Replaced custom webclient with the one from ubuntu-sso-client
(LP: #926311).
- Removed the dependency on qt4reactor for Linux implementation.
- Avoid TypeError when fetching credentials in the Gtk OverviewPanel
(LP: #927743).
- Run the whole test suite with a single command (LP: #927770).
- Do proper cleanup when dealing with UIs (LP: #925617).
[ Manuel de la Pena <email address hidden> ]
- Forward extra params to u1trial (LP: #924384).
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'run-tests' |
2 | --- run-tests 2012-01-18 17:40:33 +0000 |
3 | +++ run-tests 2012-02-14 22:15:21 +0000 |
4 | @@ -18,17 +18,11 @@ |
5 | |
6 | QT_TESTS_PATH=ubuntuone/controlpanel/gui/qt/tests |
7 | GTK_TESTS_PATH=ubuntuone/controlpanel/gui/gtk/tests |
8 | +DBUS_TESTS_PATH=ubuntuone/controlpanel/dbustests |
9 | WINDOWS_TESTS=test_windows.py |
10 | |
11 | set -e |
12 | |
13 | -if [ "$1" == "-qt" ]; then |
14 | - USE_QT=1 |
15 | - shift |
16 | -else |
17 | - USE_QT=0 |
18 | -fi |
19 | - |
20 | if [ $# -ne 0 ]; then |
21 | # run specific module given by the caller |
22 | MODULE="$@" |
23 | @@ -45,16 +39,27 @@ |
24 | echo "Please install the 'pep8' package." |
25 | fi |
26 | } |
27 | - |
28 | unset GTK_MODULES |
29 | |
30 | +XVFB_CMDLINE="" |
31 | +XVFB=$(which xvfb-run) |
32 | +if [ $XVFB ]; then |
33 | + XVFB_CMDLINE="$XVFB -a" |
34 | +fi |
35 | + |
36 | +echo "*** Running test suite for ""$MODULE"" ***" |
37 | +u1trial --reactor=gi -p "$DBUS_TESTS_PATH, $QT_TESTS_PATH, $GTK_TESTS_PATH" -i "$WINDOWS_TESTS" "$MODULE" |
38 | + |
39 | +echo "*** Running DBus test suite ***" |
40 | +u1trial --reactor=glib "$DBUS_TESTS_PATH" |
41 | + |
42 | +echo "*** Running GTK test suite ***" |
43 | +$XVFB_CMDLINE u1trial --reactor=glib "$GTK_TESTS_PATH" |
44 | + |
45 | +echo "*** Running QT test suite for ""$MODULE"" ***" |
46 | ./setup.py build |
47 | -echo "Running test suite for ""$MODULE" |
48 | -if [ "$USE_QT" -eq 0 ]; then |
49 | - `which xvfb-run` u1trial --ignore-paths "$QT_TESTS_PATH" -i "$WINDOWS_TESTS" "$MODULE" |
50 | -else |
51 | - `which xvfb-run` u1trial --ignore-paths "$GTK_TESTS_PATH" -i "$WINDOWS_TESTS" --reactor=qt4 --gui "$MODULE" |
52 | -fi |
53 | -style_check |
54 | +$XVFB_CMDLINE u1trial -p "$GTK_TESTS_PATH" -i "$WINDOWS_TESTS" --reactor=qt4 --gui "$MODULE" |
55 | rm -rf _trial_temp |
56 | rm -rf build |
57 | + |
58 | +style_check |
59 | |
60 | === modified file 'run-tests.bat' |
61 | --- run-tests.bat 2012-01-19 15:34:44 +0000 |
62 | +++ run-tests.bat 2012-02-14 22:15:21 +0000 |
63 | @@ -21,14 +21,36 @@ |
64 | SET IGNORE_PATHS="ubuntuone\controlpanel\gui\gtk, ubuntuone\controlpanel\dbustests" |
65 | SET IGNORE_MODULES="test_linux.py, test_libsoup.py" |
66 | |
67 | +:: throw the first parameter away if is /skip-lint, |
68 | +:: the way we do this is to ensure that /skip-lint |
69 | +:: is the first parameter and copy all the rest in a loop |
70 | +:: the main reason for that is that %* is not affected |
71 | +:: by SHIFT, that is, it allways have all passed parameters |
72 | + |
73 | +SET PARAMS=%* |
74 | SET SKIPLINT=0 |
75 | -:: lets check if we pass the /skip-lint param, if we did we remember and use shift |
76 | -if "%1" == "/skip-lint" SET SKIPLINT=1 |
77 | +IF "%1" == "/skip-lint" ( |
78 | + SET SKIPLINT=1 |
79 | + GOTO :CLEANPARAMS |
80 | +)ELSE ( |
81 | + GOTO :CONTINUEBATCH) |
82 | +:CLEANPARAMS |
83 | + |
84 | +SHIFT |
85 | +SET PARAMS=%1 |
86 | +:GETREST |
87 | +SHIFT |
88 | +if [%1]==[] ( |
89 | + GOTO CONTINUEBATCH) |
90 | +SET PARAMS=%PARAMS% %1 |
91 | +GOTO GETREST |
92 | +:CONTINUEBATCH |
93 | + |
94 | |
95 | "%PYTHONEXEPATH%\python.exe" setup.py build |
96 | ECHO Running tests |
97 | :: execute the tests with a number of ignored linux only modules |
98 | -"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1trial" --reactor=qt4 --gui -p %IGNORE_PATHS% -i %IGNORE_MODULES% %MODULE% |
99 | +"%PYTHONEXEPATH%\python.exe" "%PYTHONEXEPATH%\Scripts\u1trial" --reactor=qt4 --gui -p %IGNORE_PATHS% -i %IGNORE_MODULES% %PARAMS% %MODULE% |
100 | :: Clean the build from the setupt.py |
101 | ECHO Cleaning the generated code |
102 | "%PYTHONEXEPATH%\python.exe" setup.py clean |
103 | |
104 | === modified file 'setup.py' |
105 | --- setup.py 2012-02-02 15:25:58 +0000 |
106 | +++ setup.py 2012-02-14 22:15:21 +0000 |
107 | @@ -245,9 +245,9 @@ |
108 | 'ubuntuone.controlpanel.gui.qt', |
109 | 'ubuntuone.controlpanel.gui.qt.main', |
110 | 'ubuntuone.controlpanel.gui.qt.ui', |
111 | + 'ubuntuone.controlpanel.gui.qt.uniqueapp', |
112 | 'ubuntuone.controlpanel.sd_client', |
113 | 'ubuntuone.controlpanel.utils', |
114 | - 'ubuntuone.controlpanel.web_client', |
115 | ], |
116 | extra_path='ubuntuone-control-panel', |
117 | data_files=[ |
118 | |
119 | === modified file 'ubuntuone/controlpanel/backend.py' |
120 | --- ubuntuone/controlpanel/backend.py 2012-01-25 14:49:59 +0000 |
121 | +++ ubuntuone/controlpanel/backend.py 2012-02-14 22:15:21 +0000 |
122 | @@ -34,41 +34,47 @@ |
123 | |
124 | from ubuntuone.controlpanel import sd_client, replication_client |
125 | from ubuntuone.controlpanel.logger import setup_logging, log_call |
126 | -# pylint: disable=W0611 |
127 | -from ubuntuone.controlpanel.web_client import (UnauthorizedError, |
128 | - web_client_factory, WebClientError) |
129 | -# pylint: enable=W0611 |
130 | +from ubuntuone.controlpanel.web_client import ( |
131 | + UnauthorizedError, |
132 | + WebClient, |
133 | + WebClientError, |
134 | +) |
135 | |
136 | logger = setup_logging('backend') |
137 | |
138 | -ACCOUNT_API = "account/" |
139 | -QUOTA_API = "quota/" |
140 | -DEVICES_API = "1.0/devices/" |
141 | -DEVICE_REMOVE_API = "1.0/devices/remove/%s/%s" |
142 | -DEVICE_TYPE_PHONE = "Phone" |
143 | -DEVICE_TYPE_COMPUTER = "Computer" |
144 | -AUTOCONNECT_KEY = 'autoconnect' |
145 | -SHOW_ALL_NOTIFICATIONS_KEY = 'show_all_notifications' |
146 | -SHARE_AUTOSUBSCRIBE_KEY = 'share_autosubscribe' |
147 | -UDF_AUTOSUBSCRIBE_KEY = 'udf_autosubscribe' |
148 | -LIMIT_BW_KEY = 'limit_bandwidth' |
149 | -UPLOAD_KEY = "max_upload_speed" |
150 | -DOWNLOAD_KEY = "max_download_speed" |
151 | - |
152 | -FILE_SYNC_DISABLED = 'file-sync-disabled' |
153 | -FILE_SYNC_DISCONNECTED = 'file-sync-disconnected' |
154 | -FILE_SYNC_ERROR = 'file-sync-error' |
155 | -FILE_SYNC_IDLE = 'file-sync-idle' |
156 | -FILE_SYNC_STARTING = 'file-sync-starting' |
157 | -FILE_SYNC_STOPPED = 'file-sync-stopped' |
158 | -FILE_SYNC_SYNCING = 'file-sync-syncing' |
159 | -FILE_SYNC_UNKNOWN = 'file-sync-unknown' |
160 | +ACCOUNT_API = u"account/" |
161 | +DEVICES_API = u"1.0/devices/" |
162 | +DEVICE_REMOVE_API = u"1.0/devices/remove/%s/%s" |
163 | +QUOTA_API = u"quota/" |
164 | + |
165 | +DEVICE_TYPE_PHONE = u"Phone" |
166 | +DEVICE_TYPE_COMPUTER = u"Computer" |
167 | + |
168 | +AUTOCONNECT_KEY = u'autoconnect' |
169 | +SHOW_ALL_NOTIFICATIONS_KEY = u'show_all_notifications' |
170 | +SHARE_AUTOSUBSCRIBE_KEY = u'share_autosubscribe' |
171 | +UDF_AUTOSUBSCRIBE_KEY = u'udf_autosubscribe' |
172 | +LIMIT_BW_KEY = u'limit_bandwidth' |
173 | +UPLOAD_KEY = u'max_upload_speed' |
174 | +DOWNLOAD_KEY = u'max_download_speed' |
175 | + |
176 | +FILE_SYNC_DISABLED = u'file-sync-disabled' |
177 | +FILE_SYNC_DISCONNECTED = u'file-sync-disconnected' |
178 | +FILE_SYNC_ERROR = u'file-sync-error' |
179 | +FILE_SYNC_IDLE = u'file-sync-idle' |
180 | +FILE_SYNC_STARTING = u'file-sync-starting' |
181 | +FILE_SYNC_STOPPED = u'file-sync-stopped' |
182 | +FILE_SYNC_SYNCING = u'file-sync-syncing' |
183 | +FILE_SYNC_UNKNOWN = u'file-sync-unknown' |
184 | |
185 | MSG_KEY = 'message' |
186 | STATUS_KEY = 'status' |
187 | |
188 | CONTACTS_PKG = 'thunderbird-couchdb' |
189 | |
190 | +UBUNTUONE_FROM_OAUTH = u'https://one.ubuntu.com/api/1.0/from_oauth/' |
191 | +UBUNTUONE_LINK = u'https://one.ubuntu.com/' |
192 | + |
193 | |
194 | def append_path_sep(path): |
195 | """If 'path' does not end with the path separator, append it.""" |
196 | @@ -135,7 +141,7 @@ |
197 | |
198 | self.login_client = CredentialsManagementTool() |
199 | self.sd_client = sd_client.SyncDaemonClient() |
200 | - self.wc = web_client_factory(self.get_credentials) |
201 | + self.wc = WebClient(self.get_credentials) |
202 | |
203 | logger.info('ControlBackend: instance started.') |
204 | |
205 | @@ -304,6 +310,19 @@ |
206 | returnValue(path) |
207 | |
208 | @inlineCallbacks |
209 | + def build_signed_iri(self, iri): |
210 | + """Returned an OAuth signed iri.""" |
211 | + credentials = None |
212 | + if iri.startswith(UBUNTUONE_LINK): |
213 | + credentials = yield self.get_credentials() |
214 | + |
215 | + if credentials: |
216 | + parameters = {'next': iri} |
217 | + iri = yield self.wc.build_signed_iri(UBUNTUONE_FROM_OAUTH, |
218 | + parameters) |
219 | + returnValue(iri) |
220 | + |
221 | + @inlineCallbacks |
222 | def get_credentials(self): |
223 | """Find credentials.""" |
224 | if not self._credentials: |
225 | |
226 | === modified file 'ubuntuone/controlpanel/gui/__init__.py' |
227 | --- ubuntuone/controlpanel/gui/__init__.py 2011-10-06 20:38:39 +0000 |
228 | +++ ubuntuone/controlpanel/gui/__init__.py 2012-02-14 22:15:21 +0000 |
229 | @@ -21,56 +21,55 @@ |
230 | |
231 | import gettext |
232 | |
233 | -from oauth import oauth |
234 | +from ubuntuone.controlpanel.backend import UBUNTUONE_LINK |
235 | + |
236 | |
237 | _ = gettext.gettext |
238 | |
239 | |
240 | -ERROR_COLOR = 'red' |
241 | +ERROR_COLOR = u'red' |
242 | KILOBYTES = 1024 |
243 | NO_OP = lambda *a, **kw: None |
244 | # http://design.canonical.com/the-toolkit/ubuntu-logo-and-circle-of-friends/ |
245 | -ORANGE = '#DD4814' |
246 | +ORANGE = u'#DD4814' |
247 | QUOTA_THRESHOLD = 0.95 |
248 | SHARES_MIN_SIZE_FULL = 1048576 |
249 | -SUCCESS_COLOR = 'green' |
250 | +SUCCESS_COLOR = u'green' |
251 | |
252 | ERROR_ICON = u'✘' |
253 | SYNCING_ICON = u'⇅' |
254 | IDLE_ICON = u'✔' |
255 | |
256 | -CONTACT_ICON_NAME = 'avatar-default' |
257 | -FOLDER_ICON_NAME = 'folder' |
258 | -SHARE_ICON_NAME = 'folder-remote' |
259 | -MUSIC_ICON_NAME = 'audio-x-generic' |
260 | - |
261 | -CONTACTS_ICON = 'contacts.png' |
262 | -FACEBOOK_LOGO = 'facebook.png' |
263 | -FILES_ICON = 'files.png' |
264 | -OVERVIEW_BANNER = 'overview.png' |
265 | -TWITTER_LOGO = 'twitter.png' |
266 | -MUSIC_STORE_ICON = 'music-store.png' |
267 | -MUSIC_STREAM_ICON = 'music-stream.png' |
268 | -NOTES_ICON = 'notes.png' |
269 | -SERVICES_CONTACTS_ICON = 'services-contacts.png' |
270 | -SERVICES_FILES_EXAMPLE = 'services-files-example.png' |
271 | -SERVICES_FILES_ICON = 'services-files.png' |
272 | - |
273 | -FILE_URI_PREFIX = 'file://' |
274 | -UBUNTUONE_FROM_OAUTH = 'https://one.ubuntu.com/api/1.0/from_oauth/' |
275 | -UBUNTUONE_LINK = 'https://one.ubuntu.com/' |
276 | +CONTACT_ICON_NAME = u'avatar-default' |
277 | +FOLDER_ICON_NAME = u'folder' |
278 | +SHARE_ICON_NAME = u'folder-remote' |
279 | +MUSIC_ICON_NAME = u'audio-x-generic' |
280 | + |
281 | +CONTACTS_ICON = u'contacts.png' |
282 | +FACEBOOK_LOGO = u'facebook.png' |
283 | +FILES_ICON = u'files.png' |
284 | +OVERVIEW_BANNER = u'overview.png' |
285 | +TWITTER_LOGO = u'twitter.png' |
286 | +MUSIC_STORE_ICON = u'music-store.png' |
287 | +MUSIC_STREAM_ICON = u'music-stream.png' |
288 | +NOTES_ICON = u'notes.png' |
289 | +SERVICES_CONTACTS_ICON = u'services-contacts.png' |
290 | +SERVICES_FILES_EXAMPLE = u'services-files-example.png' |
291 | +SERVICES_FILES_ICON = u'services-files.png' |
292 | + |
293 | +FILE_URI_PREFIX = u'file://' |
294 | |
295 | CONTACTS_LINK = UBUNTUONE_LINK |
296 | -EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + 'account/' |
297 | -EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + 'machines/' |
298 | -EDIT_PROFILE_LINK = 'https://login.ubuntu.com/' |
299 | -EDIT_SERVICES_LINK = UBUNTUONE_LINK + 'services' |
300 | -FACEBOOK_LINK = 'http://www.facebook.com/ubuntuone/' |
301 | -GET_SUPPORT_LINK = UBUNTUONE_LINK + 'support/' |
302 | +EDIT_ACCOUNT_LINK = UBUNTUONE_LINK + u'account/' |
303 | +EDIT_DEVICES_LINK = EDIT_ACCOUNT_LINK + u'machines/' |
304 | +EDIT_PROFILE_LINK = u'https://login.ubuntu.com/' |
305 | +EDIT_SERVICES_LINK = UBUNTUONE_LINK + u'services' |
306 | +FACEBOOK_LINK = u'http://www.facebook.com/ubuntuone/' |
307 | +GET_SUPPORT_LINK = UBUNTUONE_LINK + u'support/' |
308 | LEARN_MORE_LINK = UBUNTUONE_LINK |
309 | -MANAGE_FILES_LINK = UBUNTUONE_LINK + 'files/' |
310 | -RESET_PASSWORD_LINK = EDIT_PROFILE_LINK + '+forgot_password' |
311 | -TWITTER_LINK = 'http://twitter.com/ubuntuone/' |
312 | +MANAGE_FILES_LINK = UBUNTUONE_LINK + u'files/' |
313 | +RESET_PASSWORD_LINK = EDIT_PROFILE_LINK + u'+forgot_password' |
314 | +TWITTER_LINK = u'http://twitter.com/ubuntuone/' |
315 | |
316 | ALWAYS_SUBSCRIBED = _('Always in sync') |
317 | CONNECT_BUTTON_LABEL = _('Connect to Ubuntu One') |
318 | @@ -86,7 +85,7 @@ |
319 | 'previous values were restored.') |
320 | DEVICE_CONFIRM_REMOVE = _('Are you sure you want to remove this device ' |
321 | 'from Ubuntu One?') |
322 | -DEVICE_REMOVABLE_PREFIX = 'Ubuntu One @ ' |
323 | +DEVICE_REMOVABLE_PREFIX = u'Ubuntu One @ ' |
324 | DEVICE_REMOVAL_ERROR = _('The device could not be removed.') |
325 | DEVICES_BUTTON_TOOLTIP = _('Manage devices registered with your personal ' |
326 | 'cloud') |
327 | @@ -185,19 +184,3 @@ |
328 | str_bytes = str_bytes.rstrip('0') |
329 | str_bytes = str_bytes.rstrip('.') |
330 | return '%s %s' % (str_bytes, units[unit]) |
331 | - |
332 | - |
333 | -def sign_url(url, credentials, timestamp=None): |
334 | - """Sign the URL using the currently available credentials.""" |
335 | - consumer = oauth.OAuthConsumer(credentials["consumer_key"], |
336 | - credentials["consumer_secret"]) |
337 | - token = oauth.OAuthToken(credentials["token"], |
338 | - credentials["token_secret"]) |
339 | - parameters = {'next': url, 'oauth_timestamp': timestamp} |
340 | - request = oauth.OAuthRequest.from_consumer_and_token( |
341 | - http_url=UBUNTUONE_FROM_OAUTH, http_method='GET', |
342 | - oauth_consumer=consumer, token=token, |
343 | - parameters=parameters) |
344 | - sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1() |
345 | - request.sign_request(sig_method, consumer, token) |
346 | - return request.to_url() |
347 | |
348 | === modified file 'ubuntuone/controlpanel/gui/gtk/gui.py' |
349 | --- ubuntuone/controlpanel/gui/gtk/gui.py 2012-01-23 20:15:09 +0000 |
350 | +++ ubuntuone/controlpanel/gui/gtk/gui.py 2012-02-14 22:15:21 +0000 |
351 | @@ -178,6 +178,9 @@ |
352 | label.set_markup(WARNING_MARKUP % message) |
353 | label.show() |
354 | |
355 | + def destroy(self): |
356 | + """Cleanup.""" |
357 | + |
358 | |
359 | class UbuntuOneBin(gtk.VBox): |
360 | """A Ubuntu One bin.""" |
361 | @@ -248,7 +251,7 @@ |
362 | |
363 | __gsignals__ = { |
364 | 'credentials-found': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, |
365 | - (gobject.TYPE_BOOLEAN, gobject.TYPE_PYOBJECT)), |
366 | + (gobject.TYPE_BOOLEAN,)), |
367 | } |
368 | |
369 | def __init__(self, main_window): |
370 | @@ -345,7 +348,7 @@ |
371 | def on_credentials_found(self, credentials): |
372 | """Credentials backend notifies of credentials found.""" |
373 | self.set_property('greyed', False) |
374 | - self.emit('credentials-found', self._credentials_are_new, credentials) |
375 | + self.emit('credentials-found', self._credentials_are_new) |
376 | |
377 | @log_call(logger.info) |
378 | def on_credentials_not_found(self): |
379 | @@ -1568,8 +1571,7 @@ |
380 | """Show the overview panel.""" |
381 | self.set_current_page(0) |
382 | |
383 | - def on_show_management_panel(self, widget=None, |
384 | - credentials_are_new=False, token=None): |
385 | + def on_show_management_panel(self, widget=None, credentials_are_new=False): |
386 | """Show the notebook (main panel).""" |
387 | if self.get_current_page() == 0: |
388 | self.management.load() |
389 | |
390 | === modified file 'ubuntuone/controlpanel/gui/gtk/tests/__init__.py' |
391 | --- ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2011-10-24 21:48:27 +0000 |
392 | +++ ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2012-02-14 22:15:21 +0000 |
393 | @@ -202,21 +202,12 @@ |
394 | |
395 | if self.klass is not None: |
396 | self.ui = self.klass(**self.kwargs) |
397 | + self.addCleanup(self.ui.destroy) |
398 | |
399 | self.memento = MementoHandler() |
400 | self.memento.setLevel(logging.DEBUG) |
401 | gui.logger.addHandler(self.memento) |
402 | |
403 | - @defer.inlineCallbacks |
404 | - def tearDown(self): |
405 | - try: |
406 | - self.ui.hide() |
407 | - del self.ui |
408 | - self.ui = None |
409 | - except AttributeError: |
410 | - pass |
411 | - yield super(BaseTestCase, self).tearDown() |
412 | - |
413 | def assert_image_equal(self, image, filename): |
414 | """Check that expected and actual represent the same image.""" |
415 | pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename)) |
416 | |
417 | === modified file 'ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py' |
418 | --- ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py 2011-11-21 13:32:44 +0000 |
419 | +++ ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py 2012-02-14 22:15:21 +0000 |
420 | @@ -278,7 +278,7 @@ |
421 | |
422 | """ |
423 | self.patch(self.ui.management, 'load', self._set_called) |
424 | - self.ui.overview.emit('credentials-found', False, object()) |
425 | + self.ui.overview.emit('credentials-found', False) |
426 | |
427 | self.assert_current_tab_correct(self.ui.management) |
428 | self.assertEqual(self.ui.management.notebook.get_current_page(), |
429 | @@ -291,8 +291,7 @@ |
430 | If first signal parameter is True, visible tab should be services. |
431 | |
432 | """ |
433 | - a_token = object() |
434 | - self.ui.overview.emit('credentials-found', True, a_token) |
435 | + self.ui.overview.emit('credentials-found', True) |
436 | |
437 | self.assert_current_tab_correct(self.ui.management) |
438 | self.assertEqual(self.ui.management.notebook.get_current_page(), |
439 | @@ -301,19 +300,19 @@ |
440 | def test_credentials_found_connects_syncdaemon(self): |
441 | """On 'credentials-found' signal, ask syncdaemon to connect.""" |
442 | # credentials are new |
443 | - self.ui.overview.emit('credentials-found', True, object()) |
444 | + self.ui.overview.emit('credentials-found', True) |
445 | self.assert_backend_called('connect_files') |
446 | |
447 | def test_local_device_removed_shows_overview_panel(self): |
448 | """On 'local-device-removed' signal, the overview panel is shown.""" |
449 | - self.ui.overview.emit('credentials-found', True, object()) |
450 | + self.ui.overview.emit('credentials-found', True) |
451 | self.ui.management.emit('local-device-removed') |
452 | |
453 | self.assert_current_tab_correct(self.ui.overview) |
454 | |
455 | def test_unauthorized_shows_overview_panel(self): |
456 | """On 'unauthorized' signal, the overview panel is shown.""" |
457 | - self.ui.overview.emit('credentials-found', True, object()) |
458 | + self.ui.overview.emit('credentials-found', True) |
459 | self.ui.management.emit('unauthorized') |
460 | |
461 | self.assert_current_tab_correct(self.ui.overview) |
462 | @@ -628,7 +627,7 @@ |
463 | |
464 | self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.') |
465 | # assume credentials were in local keyring |
466 | - self.assertEqual(self._called, ((self.ui, False, TOKEN), {})) |
467 | + self.assertEqual(self._called, ((self.ui, False), {})) |
468 | |
469 | def test_on_credentials_found_when_creds_are_not_new(self): |
470 | """Callback 'on_credentials_found' distinguish if creds are new.""" |
471 | @@ -641,7 +640,7 @@ |
472 | |
473 | self.assertFalse(self.ui.get_property('greyed'), 'Must not be greyed.') |
474 | # assume credentials were not in local keyring |
475 | - self.assertEqual(self._called, ((self.ui, True, TOKEN), {})) |
476 | + self.assertEqual(self._called, ((self.ui, True), {})) |
477 | |
478 | def test_on_credentials_not_found(self): |
479 | """Callback 'on_credentials_not_found' is correct.""" |
480 | |
481 | === modified file 'ubuntuone/controlpanel/gui/qt/folders.py' |
482 | --- ubuntuone/controlpanel/gui/qt/folders.py 2012-01-18 17:40:33 +0000 |
483 | +++ ubuntuone/controlpanel/gui/qt/folders.py 2012-02-14 22:15:21 +0000 |
484 | @@ -1,8 +1,6 @@ |
485 | # -*- coding: utf-8 -*- |
486 | - |
487 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
488 | # |
489 | -# Copyright 2011 Canonical Ltd. |
490 | +# Copyright 2011-2012 Canonical Ltd. |
491 | # |
492 | # This program is free software: you can redistribute it and/or modify it |
493 | # under the terms of the GNU General Public License version 3, as published |
494 | @@ -85,10 +83,7 @@ |
495 | def on_folder_created(self, new_folder): |
496 | """Reload folder info after folder creation.""" |
497 | self.is_processing = True |
498 | - # hack to ensure that syncdaemon updates the folder list. |
499 | - # pylint: disable=W0404, E1101 |
500 | - from twisted.internet import reactor |
501 | - reactor.callLater(2, self.load) |
502 | + self.load() |
503 | |
504 | # pylint: disable=E0202 |
505 | @defer.inlineCallbacks |
506 | |
507 | === modified file 'ubuntuone/controlpanel/gui/qt/gotoweb.py' |
508 | --- ubuntuone/controlpanel/gui/qt/gotoweb.py 2011-10-05 23:12:23 +0000 |
509 | +++ ubuntuone/controlpanel/gui/qt/gotoweb.py 2012-02-14 22:15:21 +0000 |
510 | @@ -23,8 +23,7 @@ |
511 | from twisted.internet import defer |
512 | |
513 | from ubuntuone.controlpanel import cache |
514 | -from ubuntuone.controlpanel.gui import qt, sign_url, UBUNTUONE_LINK |
515 | -from ubuntuone.controlpanel.web_client.txwebclient import timestamp_checker |
516 | +from ubuntuone.controlpanel.gui import qt |
517 | |
518 | |
519 | class GoToWebButton(cache.Cache, QtGui.QPushButton): |
520 | @@ -44,14 +43,5 @@ |
521 | def on_clicked(self): |
522 | """Open self.uri if not None, do nothing otherwise.""" |
523 | if self.uri is not None: |
524 | - |
525 | - credentials = None |
526 | - if self.uri.startswith(UBUNTUONE_LINK): |
527 | - credentials = yield self.backend.get_credentials() |
528 | - |
529 | - uri = self.uri |
530 | - if credentials: |
531 | - timestamp = yield timestamp_checker.get_faithful_time() |
532 | - uri = yield sign_url(uri, credentials, timestamp) |
533 | - |
534 | + uri = yield self.backend.build_signed_iri(self.uri) |
535 | qt.uri_hook(uri) |
536 | |
537 | === modified file 'ubuntuone/controlpanel/gui/qt/gui.py' |
538 | --- ubuntuone/controlpanel/gui/qt/gui.py 2011-09-15 14:42:24 +0000 |
539 | +++ ubuntuone/controlpanel/gui/qt/gui.py 2012-02-14 22:15:21 +0000 |
540 | @@ -52,20 +52,20 @@ |
541 | # pylint: enable=C0103 |
542 | |
543 | |
544 | -def start(stop, minimized=False, with_icon=False): |
545 | +def start(close_callback, minimized=False, with_icon=False): |
546 | """Show the UI elements.""" |
547 | # pylint: disable=W0404, F0401 |
548 | if not minimized: |
549 | if with_icon or minimized: |
550 | window = MainWindow() |
551 | else: |
552 | - window = MainWindow(close_callback=stop) |
553 | + window = MainWindow(close_callback=close_callback) |
554 | window.show() |
555 | else: |
556 | window = None |
557 | if with_icon or minimized: |
558 | QtGui.QApplication.instance().setQuitOnLastWindowClosed(False) |
559 | - icon = TrayIcon(window=window) |
560 | + icon = TrayIcon(window=window, close_callback=close_callback) |
561 | else: |
562 | icon = None |
563 | return icon, window |
564 | |
565 | === modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py' |
566 | --- ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-01-19 19:00:54 +0000 |
567 | +++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-02-14 22:15:21 +0000 |
568 | @@ -14,7 +14,7 @@ |
569 | # You should have received a copy of the GNU General Public License along |
570 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
571 | |
572 | -"""Provide the correct reactor and ui integration.""" |
573 | +"""Provide the correct ui main module.""" |
574 | |
575 | import sys |
576 | |
577 | @@ -37,32 +37,23 @@ |
578 | |
579 | |
580 | def main(switch_to='', alert=False, minimized=False, with_icon=False): |
581 | - """Start the Qt reactor and open the main window.""" |
582 | + """Start the Qt mainloop and open the main window.""" |
583 | # The following cannot be imported outside this function |
584 | # because u1trial already provides a reactor. |
585 | |
586 | - # The main loop MUST be initialized before importing the reactor |
587 | app = UniqueApplication(sys.argv, "ubuntuone-control-panel") |
588 | source.main(app) |
589 | + |
590 | qss = QtCore.QResource(":/ubuntuone.qss") |
591 | app.setStyleSheet(qss.data()) |
592 | |
593 | - # Reimport 'qt4reactor', 'reactor', 'start', pylint: disable=W0404, F0401 |
594 | - import qt4reactor |
595 | - qt4reactor.install() |
596 | - |
597 | - from twisted.internet import reactor |
598 | from ubuntuone.controlpanel.gui.qt.gui import start |
599 | - # pylint: enable=W0404, F0401 |
600 | - |
601 | - # Module 'reactor' has no 'run'/'stop' member, pylint: disable=E1101 |
602 | |
603 | # Unused variable 'window', 'icon', pylint: disable=W0612 |
604 | - icon, window = start(reactor.stop, |
605 | + icon, window = start(lambda: source.main_quit(app), |
606 | minimized=minimized, with_icon=with_icon) |
607 | # pylint: enable=W0612 |
608 | if icon: |
609 | app.new_instance.connect(icon.restore_window) |
610 | |
611 | - reactor.run() |
612 | - # pylint: enable=E1101 |
613 | + source.main_start(app) |
614 | |
615 | === modified file 'ubuntuone/controlpanel/gui/qt/main/linux.py' |
616 | --- ubuntuone/controlpanel/gui/qt/main/linux.py 2012-01-26 12:41:08 +0000 |
617 | +++ ubuntuone/controlpanel/gui/qt/main/linux.py 2012-02-14 22:15:21 +0000 |
618 | @@ -16,10 +16,18 @@ |
619 | |
620 | """Main method to be used on linux.""" |
621 | |
622 | -from dbus.mainloop.qt import DBusQtMainLoop |
623 | - |
624 | |
625 | def main(app): |
626 | """Apply style sheet.""" |
627 | - # The DBus main loop MUST be initialized before importing the reactor |
628 | + from dbus.mainloop.qt import DBusQtMainLoop |
629 | DBusQtMainLoop(set_as_default=True) |
630 | + |
631 | + |
632 | +def main_start(app): |
633 | + """Start the mainloop.""" |
634 | + app.exec_() |
635 | + |
636 | + |
637 | +def main_quit(app): |
638 | + """Stop the mainloop.""" |
639 | + app.exit() |
640 | |
641 | === modified file 'ubuntuone/controlpanel/gui/qt/main/windows.py' |
642 | --- ubuntuone/controlpanel/gui/qt/main/windows.py 2012-01-26 12:41:08 +0000 |
643 | +++ ubuntuone/controlpanel/gui/qt/main/windows.py 2012-02-14 22:15:21 +0000 |
644 | @@ -24,3 +24,24 @@ |
645 | # Apply font to the entire application |
646 | QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-R.ttf') |
647 | QtGui.QFontDatabase.addApplicationFont(':/Ubuntu-B.ttf') |
648 | + |
649 | + import qt4reactor |
650 | + qt4reactor.install() |
651 | + |
652 | + |
653 | +# Module 'reactor' has no 'run'/'stop' member, pylint: disable=E1101 |
654 | + |
655 | + |
656 | +def main_start(app): |
657 | + """Start the mainloop.""" |
658 | + from twisted.internet import reactor |
659 | + reactor.run() |
660 | + |
661 | + |
662 | +def main_quit(app): |
663 | + """Stop the mainloop.""" |
664 | + from twisted.internet import reactor |
665 | + reactor.stop() |
666 | + |
667 | + |
668 | +# pylint: enable=E1101 |
669 | |
670 | === modified file 'ubuntuone/controlpanel/gui/qt/systray.py' |
671 | --- ubuntuone/controlpanel/gui/qt/systray.py 2011-12-27 13:54:49 +0000 |
672 | +++ ubuntuone/controlpanel/gui/qt/systray.py 2012-02-14 22:15:21 +0000 |
673 | @@ -25,7 +25,7 @@ |
674 | |
675 | """System notification icon.""" |
676 | |
677 | - def __init__(self, window=None): |
678 | + def __init__(self, window=None, close_callback=lambda: None): |
679 | super(TrayIcon, self).__init__(None) |
680 | self.setIcon(QtGui.QIcon(":/u1icon.png")) |
681 | self.setVisible(True) |
682 | @@ -41,6 +41,8 @@ |
683 | self.context_menu.addAction(self.quit) |
684 | self.setContextMenu(self.context_menu) |
685 | |
686 | + self.close_callback = close_callback |
687 | + |
688 | def on_activated(self, reason): |
689 | """The user activated the icon.""" |
690 | if reason == self.Trigger: # Left-click |
691 | @@ -73,7 +75,4 @@ |
692 | except: |
693 | # Maybe it was not running? |
694 | pass |
695 | - # pylint: disable=W0404 |
696 | - from twisted.internet import reactor |
697 | - # pylint: enable=W0404 |
698 | - reactor.stop() |
699 | + self.close_callback() |
700 | |
701 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/__init__.py' |
702 | --- ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-01-18 19:52:25 +0000 |
703 | +++ ubuntuone/controlpanel/gui/qt/tests/__init__.py 2012-02-14 22:15:21 +0000 |
704 | @@ -20,7 +20,6 @@ |
705 | |
706 | import logging |
707 | import os |
708 | -import urllib |
709 | |
710 | from PyQt4 import QtGui, QtCore |
711 | from twisted.internet import defer |
712 | @@ -28,8 +27,7 @@ |
713 | |
714 | from ubuntuone.controlpanel import backend, cache |
715 | from ubuntuone.controlpanel.tests import TestCase, EXPECTED_ACCOUNT_INFO, TOKEN |
716 | -from ubuntuone.controlpanel.gui import qt, UBUNTUONE_FROM_OAUTH |
717 | -from ubuntuone.controlpanel.gui.qt import gotoweb |
718 | +from ubuntuone.controlpanel.gui import qt |
719 | from ubuntuone.controlpanel.gui.tests import FakedObject, USER_HOME |
720 | |
721 | # Attribute 'yyy' defined outside __init__, access to a protected member |
722 | @@ -107,19 +105,31 @@ |
723 | |
724 | next_result = [] |
725 | exposed_methods = [ |
726 | - 'account_info', # account |
727 | - 'devices_info', 'device_names_info', # devices |
728 | - 'change_device_settings', 'remove_device', |
729 | - 'volumes_info', 'change_volume_settings', # volumes |
730 | - 'create_folder', 'validate_path_for_folder', |
731 | - 'replications_info', 'change_replication_settings', # replications |
732 | - 'file_sync_status', 'enable_files', 'disable_files', # files |
733 | - 'connect_files', 'disconnect_files', |
734 | - 'restart_files', 'start_files', 'stop_files', |
735 | + 'account_info', |
736 | + 'build_signed_iri', |
737 | + 'change_device_settings', |
738 | + 'change_file_sync_settings', |
739 | + 'change_replication_settings', |
740 | + 'change_volume_settings', |
741 | + 'connect_files', |
742 | + 'create_folder', |
743 | + 'device_names_info', |
744 | + 'devices_info', |
745 | + 'disable_files', |
746 | + 'disconnect_files', |
747 | + 'enable_files', |
748 | 'file_sync_settings_info', |
749 | - 'change_file_sync_settings', |
750 | + 'file_sync_status', |
751 | + 'login', |
752 | + 'remove_device', |
753 | + 'replications_info', |
754 | + 'restart_files', |
755 | 'restore_file_sync_settings', |
756 | - 'shutdown', 'login', |
757 | + 'shutdown', |
758 | + 'start_files', |
759 | + 'stop_files', |
760 | + 'validate_path_for_folder', |
761 | + 'volumes_info', |
762 | ] |
763 | |
764 | def get_credentials(self): |
765 | @@ -131,6 +141,9 @@ |
766 | """Fake home return.""" |
767 | return USER_HOME |
768 | |
769 | + def build_signed_iri(self, iri): |
770 | + """Fake iri signing.""" |
771 | + |
772 | |
773 | class CrashyBackendException(Exception): |
774 | """A faked backend crash.""" |
775 | @@ -284,19 +297,14 @@ |
776 | |
777 | def assert_uri_hook_called(self, button, url): |
778 | """Check that uri_hook was called with 'url' when clicking 'button'.""" |
779 | - fake_time = 12345678 |
780 | + self.patch(self.ui.backend, 'next_result', url) |
781 | self.patch(qt, 'uri_hook', self._set_called) |
782 | - self.patch(gotoweb.timestamp_checker, "get_faithful_time", |
783 | - lambda: defer.succeed(fake_time)) |
784 | button.click() |
785 | |
786 | self.assertEqual(len(self._called), 2, 'uri_hook must be called.') |
787 | self.assertEqual(len(self._called[0]), 1, 'uri_hook must be called.') |
788 | actual_url = self._called[0][0] |
789 | - if actual_url.startswith(UBUNTUONE_FROM_OAUTH): |
790 | - self.assertIn(urllib.urlencode({'next': url}), actual_url) |
791 | - else: |
792 | - self.assertEqual(actual_url, url) |
793 | + self.assertEqual(actual_url, url) |
794 | |
795 | def test_init_loads_ui(self, expected_setup_ui=None): |
796 | """The __init__ method loads the ui.""" |
797 | |
798 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py' |
799 | --- ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2011-10-06 20:33:42 +0000 |
800 | +++ ubuntuone/controlpanel/gui/qt/tests/test_gotoweb.py 2012-02-14 22:15:21 +0000 |
801 | @@ -20,7 +20,7 @@ |
802 | |
803 | from twisted.internet import defer |
804 | |
805 | -from ubuntuone.controlpanel.gui import qt, UBUNTUONE_LINK |
806 | +from ubuntuone.controlpanel.gui import qt |
807 | from ubuntuone.controlpanel.gui.qt import gotoweb as gui |
808 | from ubuntuone.controlpanel.gui.qt.tests import ( |
809 | BaseTestCase, |
810 | @@ -53,11 +53,14 @@ |
811 | |
812 | def test_open_uri_when_clicked(self): |
813 | """When clicking the button, the uri is opened.""" |
814 | + expected_iri = 'foo-bar-baz' |
815 | + self.patch(self.ui.backend, 'build_signed_iri', lambda i: expected_iri) |
816 | self.patch(qt, 'uri_hook', self._set_called) |
817 | self.ui.uri = 'yadda-yadda-yoo' |
818 | + |
819 | self.ui.click() |
820 | |
821 | - self.assertEqual(self._called, ((self.ui.uri,), {})) |
822 | + self.assertEqual(self._called, ((expected_iri,), {})) |
823 | |
824 | def test_do_nothing_on_clicked_if_uri_is_none(self): |
825 | """When clicking the button, if the uri is None, do nothing.""" |
826 | @@ -66,53 +69,3 @@ |
827 | self.ui.click() |
828 | |
829 | self.assertEqual(self._called, False) |
830 | - |
831 | - |
832 | -class SignUrlTestCase(GoToWebButtonTestCase): |
833 | - """The test suite for the sign url management.""" |
834 | - |
835 | - @defer.inlineCallbacks |
836 | - def setUp(self): |
837 | - yield super(SignUrlTestCase, self).setUp() |
838 | - self.patch(qt, 'uri_hook', lambda url: None) |
839 | - self.patch(gui, 'sign_url', self._set_called) |
840 | - self.creds = yield self.ui.backend.get_credentials() |
841 | - assert len(self.creds) > 0 |
842 | - |
843 | - def test_without_ubuntuone_prefix(self): |
844 | - """If given url is not an ubuntuone url, don't sign it.""" |
845 | - self.ui.uri = 'bad_prefix' + UBUNTUONE_LINK |
846 | - self.ui.click() |
847 | - |
848 | - self.assertFalse(self._called) |
849 | - |
850 | - def test_with_ubuntuone_prefix(self): |
851 | - """If given url is an ubuntuone url, sign it.""" |
852 | - fake_time = 12345 |
853 | - self.ui.uri = UBUNTUONE_LINK + 'foo' |
854 | - self.patch(gui.timestamp_checker, "get_faithful_time", |
855 | - lambda: defer.succeed(fake_time)) |
856 | - self.ui.click() |
857 | - |
858 | - expected_call = ((self.ui.uri, self.creds, fake_time), {}) |
859 | - self.assertEqual(self._called, expected_call) |
860 | - |
861 | - |
862 | -class SignUrlNoCredsTestCase(SignUrlTestCase): |
863 | - """The test suite for the sign url management when there are no creds.""" |
864 | - |
865 | - @defer.inlineCallbacks |
866 | - def setUp(self): |
867 | - yield super(SignUrlNoCredsTestCase, self).setUp() |
868 | - self.patch(self.ui.backend, 'get_credentials', lambda: {}) |
869 | - |
870 | - def test_with_ubuntuone_prefix(self): |
871 | - """If given url is an ubuntuone url, don't sign it. |
872 | - |
873 | - Since we have no credentials, the url should not be signed. |
874 | - |
875 | - """ |
876 | - self.ui.uri = UBUNTUONE_LINK + 'foo' |
877 | - self.ui.click() |
878 | - |
879 | - self.assertFalse(self._called) |
880 | |
881 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_signin.py' |
882 | --- ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2011-09-07 17:56:17 +0000 |
883 | +++ ubuntuone/controlpanel/gui/qt/tests/test_signin.py 2012-02-14 22:15:21 +0000 |
884 | @@ -20,7 +20,6 @@ |
885 | |
886 | from twisted.internet import defer |
887 | |
888 | -from ubuntuone.controlpanel.gui import qt |
889 | from ubuntuone.controlpanel.gui.qt import signin as gui |
890 | from ubuntuone.controlpanel.gui.qt.tests import ( |
891 | CrashyBackend, |
892 | @@ -107,10 +106,8 @@ |
893 | |
894 | def test_forgot_password_button(self): |
895 | """When clicking the forgot passsword btn, the proper url is opened.""" |
896 | - self.patch(qt, 'uri_hook', self._set_called) |
897 | - self.ui.ui.forgot_password_button.click() |
898 | - |
899 | - self.assertEqual(self._called, ((gui.RESET_PASSWORD_LINK,), {})) |
900 | + self.assert_uri_hook_called(self.ui.ui.forgot_password_button, |
901 | + gui.RESET_PASSWORD_LINK) |
902 | |
903 | |
904 | class SignInButtonPanelTestCase(BaseSignInPanelTestCase): |
905 | |
906 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_start.py' |
907 | --- ubuntuone/controlpanel/gui/qt/tests/test_start.py 2011-09-15 15:37:04 +0000 |
908 | +++ ubuntuone/controlpanel/gui/qt/tests/test_start.py 2012-02-14 22:15:21 +0000 |
909 | @@ -1,91 +1,89 @@ |
910 | -# -*- coding: utf-8 -*- |
911 | - |
912 | -# Author: Roberto Alsina <roberto.alsina@canonical.com> |
913 | -# |
914 | -# Copyright 2011 Canonical Ltd. |
915 | -# |
916 | -# This program is free software: you can redistribute it and/or modify it |
917 | -# under the terms of the GNU General Public License version 3, as published |
918 | -# by the Free Software Foundation. |
919 | -# |
920 | -# This program is distributed in the hope that it will be useful, but |
921 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
922 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
923 | -# PURPOSE. See the GNU General Public License for more details. |
924 | -# |
925 | -# You should have received a copy of the GNU General Public License along |
926 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
927 | - |
928 | -"""Tests for the start function.""" |
929 | - |
930 | -from twisted.internet import defer |
931 | - |
932 | -from ubuntuone.controlpanel.gui.qt import gui |
933 | -from ubuntuone.controlpanel.gui.qt.tests import NO_OP |
934 | -from ubuntuone.controlpanel.tests import TestCase |
935 | - |
936 | - |
937 | -class FakeThing(object): |
938 | - |
939 | - """A fake thing.""" |
940 | - |
941 | - def __init__(self): |
942 | - self.args = [] |
943 | - self.shown = False |
944 | - |
945 | - def __call__(self, *args, **kwargs): |
946 | - self.args.append((args, kwargs)) |
947 | - return self |
948 | - |
949 | - def show(self): |
950 | - """Show.""" |
951 | - self.shown = True |
952 | - |
953 | - |
954 | -class FakeReactor(object): |
955 | - """A fake reactor.""" |
956 | - |
957 | - def run(self): |
958 | - """Start.""" |
959 | - |
960 | - def stop(self): |
961 | - """Stop.""" |
962 | - |
963 | - |
964 | -class StartTestCase(TestCase): |
965 | - """Test the qt control panel.""" |
966 | - |
967 | - @defer.inlineCallbacks |
968 | - def setUp(self): |
969 | - yield super(StartTestCase, self).setUp() |
970 | - self.main_window = FakeThing() |
971 | - self.tray_icon = FakeThing() |
972 | - self.patch(gui, "MainWindow", self.main_window) |
973 | - self.patch(gui, "TrayIcon", self.tray_icon) |
974 | - |
975 | - def test_minimized(self): |
976 | - """Test behaviour with minimized=True.""" |
977 | - gui.start(NO_OP, minimized=True, with_icon=True) |
978 | - self.assertEqual(self.tray_icon.args, [((), {'window': None})]) |
979 | - self.assertEqual(self.main_window.args, []) |
980 | - |
981 | - def test_with_icon(self): |
982 | - """Test behaviour with with_icon=True.""" |
983 | - gui.start(NO_OP, with_icon=True, minimized=False) |
984 | - self.assertEqual(self.main_window.args, [((), {})]) |
985 | - self.assertEqual(self.tray_icon.args, [((), |
986 | - {'window': self.main_window})]) |
987 | - |
988 | - def test_both_false(self): |
989 | - """Test behaviour when with_icon and minimized are False.""" |
990 | - gui.start(NO_OP, with_icon=False, minimized=False) |
991 | - # Should be called |
992 | - self.assertNotEqual(self.main_window.args, []) |
993 | - # Should not be called |
994 | - self.assertEqual(self.tray_icon.args, []) |
995 | - |
996 | - def test_both_true(self): |
997 | - """Test behaviour when with_icon and minimized are True.""" |
998 | - gui.start(NO_OP, with_icon=True, minimized=True) |
999 | - self.assertEqual(self.tray_icon.args, [((), {'window': None})]) |
1000 | - self.assertEqual(self.main_window.args, []) |
1001 | +# -*- coding: utf-8 -*- |
1002 | + |
1003 | +# Author: Roberto Alsina <roberto.alsina@canonical.com> |
1004 | +# |
1005 | +# Copyright 2011 Canonical Ltd. |
1006 | +# |
1007 | +# This program is free software: you can redistribute it and/or modify it |
1008 | +# under the terms of the GNU General Public License version 3, as published |
1009 | +# by the Free Software Foundation. |
1010 | +# |
1011 | +# This program is distributed in the hope that it will be useful, but |
1012 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
1013 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1014 | +# PURPOSE. See the GNU General Public License for more details. |
1015 | +# |
1016 | +# You should have received a copy of the GNU General Public License along |
1017 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
1018 | + |
1019 | +"""Tests for the start function.""" |
1020 | + |
1021 | +from twisted.internet import defer |
1022 | + |
1023 | +from ubuntuone.controlpanel.gui.qt import gui |
1024 | +from ubuntuone.controlpanel.tests import TestCase |
1025 | + |
1026 | + |
1027 | +class FakeThing(object): |
1028 | + |
1029 | + """A fake thing.""" |
1030 | + |
1031 | + def __init__(self): |
1032 | + self.args = [] |
1033 | + self.shown = False |
1034 | + |
1035 | + def __call__(self, *args, **kwargs): |
1036 | + self.args.append((args, kwargs)) |
1037 | + return self |
1038 | + |
1039 | + def show(self): |
1040 | + """Show.""" |
1041 | + self.shown = True |
1042 | + |
1043 | + |
1044 | +class StartTestCase(TestCase): |
1045 | + """Test the qt control panel.""" |
1046 | + |
1047 | + @defer.inlineCallbacks |
1048 | + def setUp(self): |
1049 | + yield super(StartTestCase, self).setUp() |
1050 | + self.main_window = FakeThing() |
1051 | + self.tray_icon = FakeThing() |
1052 | + self.patch(gui, "MainWindow", self.main_window) |
1053 | + self.patch(gui, "TrayIcon", self.tray_icon) |
1054 | + |
1055 | + def close_cb(self): |
1056 | + """A dummy close callback.""" |
1057 | + |
1058 | + def test_minimized(self): |
1059 | + """Test behaviour with minimized=True.""" |
1060 | + gui.start(close_callback=self.close_cb, |
1061 | + minimized=True, with_icon=True) |
1062 | + kwargs = {'close_callback': self.close_cb, 'window': None} |
1063 | + self.assertEqual(self.tray_icon.args, [((), kwargs)]) |
1064 | + self.assertEqual(self.main_window.args, []) |
1065 | + |
1066 | + def test_with_icon(self): |
1067 | + """Test behaviour with with_icon=True.""" |
1068 | + gui.start(close_callback=self.close_cb, |
1069 | + with_icon=True, minimized=False) |
1070 | + kwargs = {'close_callback': self.close_cb, 'window': self.main_window} |
1071 | + self.assertEqual(self.main_window.args, [((), {})]) |
1072 | + self.assertEqual(self.tray_icon.args, [((), kwargs)]) |
1073 | + |
1074 | + def test_both_false(self): |
1075 | + """Test behaviour when with_icon and minimized are False.""" |
1076 | + gui.start(close_callback=self.close_cb, |
1077 | + with_icon=False, minimized=False) |
1078 | + # Should be called |
1079 | + self.assertNotEqual(self.main_window.args, []) |
1080 | + # Should not be called |
1081 | + self.assertEqual(self.tray_icon.args, []) |
1082 | + |
1083 | + def test_both_true(self): |
1084 | + """Test behaviour when with_icon and minimized are True.""" |
1085 | + gui.start(close_callback=self.close_cb, |
1086 | + with_icon=True, minimized=True) |
1087 | + kwargs = {'close_callback': self.close_cb, 'window': None} |
1088 | + self.assertEqual(self.tray_icon.args, [((), kwargs)]) |
1089 | + self.assertEqual(self.main_window.args, []) |
1090 | |
1091 | === modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py' |
1092 | --- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-01-02 19:38:14 +0000 |
1093 | +++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-02-14 22:15:21 +0000 |
1094 | @@ -19,7 +19,6 @@ |
1095 | """Tests for the notification area icon.""" |
1096 | |
1097 | from PyQt4 import QtGui |
1098 | -from twisted.internet import reactor |
1099 | from twisted.internet.defer import inlineCallbacks |
1100 | |
1101 | from ubuntuone.controlpanel.gui.qt import systray |
1102 | @@ -65,7 +64,6 @@ |
1103 | """Quit should call SyncDaemonTool.quit().""" |
1104 | st = FakeSDTool() |
1105 | self.patch(systray, "SyncDaemonTool", lambda: st) |
1106 | - self.patch(reactor, "stop", lambda: None) |
1107 | tray = systray.TrayIcon() |
1108 | yield tray.stop() |
1109 | self.assertTrue(st.called) |
1110 | |
1111 | === removed file 'ubuntuone/controlpanel/gui/tests/test_url_sign.py' |
1112 | --- ubuntuone/controlpanel/gui/tests/test_url_sign.py 2011-10-05 23:12:23 +0000 |
1113 | +++ ubuntuone/controlpanel/gui/tests/test_url_sign.py 1970-01-01 00:00:00 +0000 |
1114 | @@ -1,102 +0,0 @@ |
1115 | -# -*- coding: utf-8 -*- |
1116 | - |
1117 | -# Authors: Roberto Alsina <roberto.alsina@canonical.com> |
1118 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
1119 | -# |
1120 | -# Copyright 2011 Canonical Ltd. |
1121 | -# |
1122 | -# This program is free software: you can redistribute it and/or modify it |
1123 | -# under the terms of the GNU General Public License version 3, as published |
1124 | -# by the Free Software Foundation. |
1125 | -# |
1126 | -# This program is distributed in the hope that it will be useful, but |
1127 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
1128 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1129 | -# PURPOSE. See the GNU General Public License for more details. |
1130 | -# |
1131 | -# You should have received a copy of the GNU General Public License along |
1132 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1133 | - |
1134 | -"""Tests for the url signing function.""" |
1135 | - |
1136 | -from urlparse import urlparse, parse_qs |
1137 | - |
1138 | -from ubuntuone.controlpanel.tests import TestCase |
1139 | -from ubuntuone.controlpanel.gui import ( |
1140 | - sign_url, |
1141 | - UBUNTUONE_FROM_OAUTH, |
1142 | -) |
1143 | - |
1144 | -TOKEN = {u'consumer_key': u'consumer_key', |
1145 | - u'consumer_secret': u'consumer_secret', |
1146 | - u'token_name': u'test_token', |
1147 | - u'token': u'GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo', |
1148 | - u'token_secret': u'qFYImEtlczPbsCnYyuwLoPDlPEnvNcIktZphPQklAWrvyfFMV'} |
1149 | - |
1150 | -SAMPLE_SIGNED = UBUNTUONE_FROM_OAUTH + '?oauth_nonce=' \ |
1151 | - '36886134&oauth_timestamp=1310671062&oauth_consumer_key=consumer_key&' \ |
1152 | - 'oauth_signature_method=HMAC-SHA1&next=%2Fblah&oauth_version=1.0&' \ |
1153 | - 'oauth_token=GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo&' \ |
1154 | - 'oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo%3D' |
1155 | - |
1156 | -#pylint: disable=E1101 |
1157 | - |
1158 | - |
1159 | -class SignURLTestCase(TestCase): |
1160 | - |
1161 | - """Test cases for the URL signing function.""" |
1162 | - |
1163 | - def test_is_correct_domain(self): |
1164 | - """Test that we are using the right domain.""" |
1165 | - signed = sign_url("/blah", TOKEN) |
1166 | - parsed_signed = urlparse(signed) |
1167 | - parsed_sample = urlparse(SAMPLE_SIGNED) |
1168 | - self.assertEqual(parsed_signed.netloc, parsed_sample.netloc) |
1169 | - |
1170 | - def test_is_correct_path(self): |
1171 | - """Test that we are using the right path in the URL.""" |
1172 | - signed = sign_url("/blah", TOKEN) |
1173 | - parsed_signed = urlparse(signed) |
1174 | - parsed_sample = urlparse(SAMPLE_SIGNED) |
1175 | - self.assertEqual(parsed_signed.path, parsed_sample.path) |
1176 | - |
1177 | - def test_is_correct_scheme(self): |
1178 | - """Test that we are using the right scheme.""" |
1179 | - signed = sign_url("/blah", TOKEN) |
1180 | - parsed_signed = urlparse(signed) |
1181 | - parsed_sample = urlparse(SAMPLE_SIGNED) |
1182 | - |
1183 | - self.assertEqual(parsed_signed.scheme, parsed_sample.scheme) |
1184 | - |
1185 | - def test_correct_query(self): |
1186 | - """Test the invariant parts of the signed URL.""" |
1187 | - signed = sign_url("/blah", TOKEN) |
1188 | - parsed_signed = urlparse(signed) |
1189 | - parsed_sample = urlparse(SAMPLE_SIGNED) |
1190 | - signed_query = parse_qs(parsed_signed.query) |
1191 | - sample_query = parse_qs(parsed_sample.query) |
1192 | - |
1193 | - for key in ('next', |
1194 | - 'oauth_consumer_key', |
1195 | - 'oauth_signature_method', |
1196 | - 'oauth_token', |
1197 | - 'oauth_version'): |
1198 | - self.assertEqual("%s=%s" % (key, signed_query[key]), |
1199 | - "%s=%s" % (key, sample_query[key])) |
1200 | - |
1201 | - def test_url_with_query(self): |
1202 | - """Test that we are using the right scheme.""" |
1203 | - signed = sign_url("/blah?foo=bar", TOKEN) |
1204 | - parsed_signed = urlparse(signed) |
1205 | - signed_query = parse_qs(parsed_signed.query) |
1206 | - |
1207 | - self.assertEqual(signed_query['next'], ['/blah?foo=bar']) |
1208 | - |
1209 | - def test_uses_timestamper(self): |
1210 | - """Test that the signed url is using the server-relative timestamp.""" |
1211 | - timestamp = 999 |
1212 | - signed = sign_url("/blah?foo=bar", TOKEN, timestamp) |
1213 | - parsed_signed = urlparse(signed) |
1214 | - signed_query = parse_qs(parsed_signed.query) |
1215 | - |
1216 | - self.assertEqual(signed_query['oauth_timestamp'], [str(timestamp)]) |
1217 | |
1218 | === modified file 'ubuntuone/controlpanel/logger.py' |
1219 | --- ubuntuone/controlpanel/logger.py 2011-08-30 16:10:36 +0000 |
1220 | +++ ubuntuone/controlpanel/logger.py 2012-02-14 22:15:21 +0000 |
1221 | @@ -34,7 +34,7 @@ |
1222 | LOG_LEVEL = logging.DEBUG |
1223 | else: |
1224 | # Only log this level and above |
1225 | - LOG_LEVEL = logging.DEBUG # before final release, switch to INFO |
1226 | + LOG_LEVEL = logging.INFO |
1227 | |
1228 | FILE_NAME = os.path.join(ubuntuone_log_dir, 'controlpanel.log') |
1229 | MAIN_HANDLER = RotatingFileHandler(FILE_NAME, |
1230 | @@ -53,7 +53,7 @@ |
1231 | logger = logging.getLogger('ubuntuone.controlpanel.%s' % log_domain) |
1232 | logger.setLevel(LOG_LEVEL) |
1233 | logger.addHandler(MAIN_HANDLER) |
1234 | - if os.environ.get('DEBUG'): |
1235 | + if os.environ.get('U1_DEBUG'): |
1236 | debug_handler = logging.StreamHandler(sys.stderr) |
1237 | debug_handler.setFormatter(basic_formatter) |
1238 | logger.addHandler(debug_handler) |
1239 | |
1240 | === modified file 'ubuntuone/controlpanel/tests/test_backend.py' |
1241 | --- ubuntuone/controlpanel/tests/test_backend.py 2012-01-26 12:44:10 +0000 |
1242 | +++ ubuntuone/controlpanel/tests/test_backend.py 2012-02-14 22:15:21 +0000 |
1243 | @@ -43,6 +43,8 @@ |
1244 | FILE_SYNC_SYNCING, |
1245 | FILE_SYNC_UNKNOWN, |
1246 | MSG_KEY, STATUS_KEY, |
1247 | + UBUNTUONE_FROM_OAUTH, |
1248 | + UBUNTUONE_LINK, |
1249 | ) |
1250 | from ubuntuone.controlpanel.tests import (TestCase, |
1251 | EMPTY_DESCRIPTION_JSON, |
1252 | @@ -65,6 +67,12 @@ |
1253 | USER_HOME, |
1254 | ) |
1255 | |
1256 | +SAMPLE_SIGNED = UBUNTUONE_FROM_OAUTH + '?oauth_nonce=' \ |
1257 | + '36886134&oauth_timestamp=1310671062&oauth_consumer_key=consumer_key&' \ |
1258 | + 'oauth_signature_method=HMAC-SHA1&next=%2Fblah&oauth_version=1.0&' \ |
1259 | + 'oauth_token=GkInOfSMGwTXAUoVQwLUoPxElEEUdhsLVNTPhxHJDUIeHCPNEo&' \ |
1260 | + 'oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo%3D' |
1261 | + |
1262 | |
1263 | # pylint: disable=E1101, W0201, W0212 |
1264 | |
1265 | @@ -104,6 +112,13 @@ |
1266 | result = simplejson.loads(self.results[method]) |
1267 | return defer.succeed(result) |
1268 | |
1269 | + @defer.inlineCallbacks |
1270 | + def build_signed_iri(self, iri, params): |
1271 | + """Fake the IRI signing.""" |
1272 | + creds = yield self.get_credentials() |
1273 | + result = u'%s-%s-%s' % (iri, unicode(creds), unicode(params)) |
1274 | + defer.returnValue(result) |
1275 | + |
1276 | |
1277 | class MockLoginClient(CallRecorder): |
1278 | """A mock login_client module.""" |
1279 | @@ -347,7 +362,7 @@ |
1280 | @defer.inlineCallbacks |
1281 | def setUp(self): |
1282 | yield super(BackendBasicTestCase, self).setUp() |
1283 | - self.patch(backend, "web_client_factory", MockWebClient) |
1284 | + self.patch(backend, "WebClient", MockWebClient) |
1285 | self.patch(backend, "CredentialsManagementTool", MockLoginClient) |
1286 | self.patch(backend.sd_client, "SyncDaemonClient", MockSDClient) |
1287 | self.patch(backend, "replication_client", MockReplicationClient()) |
1288 | @@ -368,12 +383,14 @@ |
1289 | @inlineCallbacks |
1290 | def test_get_token(self): |
1291 | """The get_token method returns the right token.""" |
1292 | + self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN)) |
1293 | token = yield self.be.get_token() |
1294 | self.assertEqual(token, TOKEN["token"]) |
1295 | |
1296 | @inlineCallbacks |
1297 | def test_device_is_local(self): |
1298 | """The device_is_local returns the right result.""" |
1299 | + self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN)) |
1300 | result = yield self.be.device_is_local(self.local_token) |
1301 | self.assertTrue(result) |
1302 | |
1303 | @@ -408,6 +425,54 @@ |
1304 | self.assertIs(self.be.sd_client, self.be.sd_client) |
1305 | |
1306 | |
1307 | +class SignIriTestCase(BackendBasicTestCase): |
1308 | + """Test cases for the IRI signing function.""" |
1309 | + |
1310 | + @defer.inlineCallbacks |
1311 | + def setUp(self): |
1312 | + yield super(SignIriTestCase, self).setUp() |
1313 | + self.patch(self.be, 'get_credentials', lambda: defer.succeed(TOKEN)) |
1314 | + |
1315 | + @inlineCallbacks |
1316 | + def test_without_ubuntuone_prefix(self): |
1317 | + """If given url is not an ubuntuone url, don't sign it.""" |
1318 | + iri = 'bad_prefix' + UBUNTUONE_LINK |
1319 | + result = yield self.be.build_signed_iri(iri) |
1320 | + |
1321 | + self.assertEqual(result, iri) |
1322 | + |
1323 | + @inlineCallbacks |
1324 | + def test_with_ubuntuone_prefix(self): |
1325 | + """If given url is an ubuntuone url, sign it.""" |
1326 | + iri = UBUNTUONE_LINK + 'foo' |
1327 | + result = yield self.be.build_signed_iri(iri) |
1328 | + |
1329 | + expected = yield self.be.wc.build_signed_iri(UBUNTUONE_FROM_OAUTH, |
1330 | + {'next': iri}) |
1331 | + self.assertEqual(expected, result) |
1332 | + |
1333 | + |
1334 | +class SignIriNoCredsTestCase(SignIriTestCase): |
1335 | + """The test suite for the sign url management when there are no creds.""" |
1336 | + |
1337 | + @defer.inlineCallbacks |
1338 | + def setUp(self): |
1339 | + yield super(SignIriNoCredsTestCase, self).setUp() |
1340 | + self.patch(self.be, 'get_credentials', lambda: defer.succeed({})) |
1341 | + |
1342 | + @inlineCallbacks |
1343 | + def test_with_ubuntuone_prefix(self): |
1344 | + """If given url is an ubuntuone url, don't sign it. |
1345 | + |
1346 | + Since we have no credentials, the url should not be signed. |
1347 | + |
1348 | + """ |
1349 | + iri = UBUNTUONE_LINK + 'foo' |
1350 | + result = yield self.be.build_signed_iri(iri) |
1351 | + |
1352 | + self.assertEqual(result, iri) |
1353 | + |
1354 | + |
1355 | class BackendCredentialsTestCase(BackendBasicTestCase): |
1356 | """Credentials tests for the backend.""" |
1357 | |
1358 | |
1359 | === modified file 'ubuntuone/controlpanel/tests/test_web_client.py' |
1360 | --- ubuntuone/controlpanel/tests/test_web_client.py 2011-10-07 14:36:14 +0000 |
1361 | +++ ubuntuone/controlpanel/tests/test_web_client.py 2012-02-14 22:15:21 +0000 |
1362 | @@ -1,9 +1,6 @@ |
1363 | # -*- coding: utf-8 -*- |
1364 | - |
1365 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
1366 | -# Authors: Natalia B. Bidart <nataliabidart@canonical.com> |
1367 | # |
1368 | -# Copyright 2010 Canonical Ltd. |
1369 | +# Copyright 2011-2012 Canonical Ltd. |
1370 | # |
1371 | # This program is free software: you can redistribute it and/or modify it |
1372 | # under the terms of the GNU General Public License version 3, as published |
1373 | @@ -17,26 +14,38 @@ |
1374 | # You should have received a copy of the GNU General Public License along |
1375 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
1376 | |
1377 | -"""Integration tests for the control panel backend webservice client.""" |
1378 | - |
1379 | -from twisted.application import internet, service |
1380 | +"""Integration tests for the webclient.""" |
1381 | + |
1382 | +from urlparse import urlparse, parse_qs |
1383 | + |
1384 | from twisted.internet import defer |
1385 | -from twisted.internet.defer import inlineCallbacks |
1386 | -from twisted.web import server, resource |
1387 | - |
1388 | -from ubuntuone.controlpanel import web_client |
1389 | +from twisted.web import resource |
1390 | + |
1391 | +from ubuntu_sso.utils.webclient.tests import BaseMockWebServer |
1392 | + |
1393 | from ubuntuone.controlpanel.tests import TestCase |
1394 | +from ubuntuone.controlpanel.web_client import ( |
1395 | + UnauthorizedError, |
1396 | + WebClient, |
1397 | + WebClientError, |
1398 | +) |
1399 | |
1400 | |
1401 | SAMPLE_KEY = "result" |
1402 | SAMPLE_VALUE = "sample result" |
1403 | SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE) |
1404 | SAMPLE_CREDENTIALS = dict( |
1405 | - consumer_key="consumer key", |
1406 | - consumer_secret="consumer secret", |
1407 | - token="the real token", |
1408 | - token_secret="the token secret", |
1409 | + consumer_key="consumer_key", |
1410 | + consumer_secret="consumer_secret", |
1411 | + token="the_real_token", |
1412 | + token_secret="the_token_secret", |
1413 | ) |
1414 | +SAMPLE_TARGET = u'http://example.com/' |
1415 | +SAMPLE_SIGNED = SAMPLE_TARGET + u'?oauth_nonce=36886134&' \ |
1416 | + 'oauth_timestamp=1328544832&oauth_consumer_key=%s&' \ |
1417 | + 'oauth_signature_method=HMAC-SHA1&next=%%2Fblah&oauth_version=1.0&' \ |
1418 | + 'oauth_token=%s&oauth_signature=s6h0LRBiWchTADrTJWaJUSuaGpo3D' % \ |
1419 | + (SAMPLE_CREDENTIALS['consumer_key'], SAMPLE_CREDENTIALS['token']) |
1420 | |
1421 | |
1422 | def sample_get_credentials(): |
1423 | @@ -63,11 +72,11 @@ |
1424 | return self.contents |
1425 | |
1426 | |
1427 | -class MockWebService(object): |
1428 | - """A mock webservice for testing""" |
1429 | +class MockWebServer(BaseMockWebServer): |
1430 | + """A mock webserver for the webclient tests.""" |
1431 | |
1432 | - def __init__(self): |
1433 | - """Start up this instance.""" |
1434 | + def get_root_resource(self): |
1435 | + """Get the root resource with all the children.""" |
1436 | root = resource.Resource() |
1437 | devices_resource = MockResource() |
1438 | devices_resource.contents = SAMPLE_RESOURCE |
1439 | @@ -77,91 +86,105 @@ |
1440 | "Unauthrorized", "Unauthrorized") |
1441 | root.putChild("unauthorized", unauthorized) |
1442 | |
1443 | - site = server.Site(root) |
1444 | - application = service.Application('web') |
1445 | - self.service_collection = service.IServiceCollection(application) |
1446 | - #pylint: disable=E1101 |
1447 | - self.tcpserver = internet.TCPServer(0, site) |
1448 | - self.tcpserver.setServiceParent(self.service_collection) |
1449 | - self.service_collection.startService() |
1450 | - |
1451 | - def get_url(self): |
1452 | - """Build the url for this mock server.""" |
1453 | - #pylint: disable=W0212 |
1454 | - port_num = self.tcpserver._port.getHost().port |
1455 | - return "http://localhost:%d/" % port_num |
1456 | - |
1457 | - def stop(self): |
1458 | - """Shut it down.""" |
1459 | - #pylint: disable=E1101 |
1460 | - return self.service_collection.stopService() |
1461 | + return root |
1462 | |
1463 | |
1464 | class WebClientTestCase(TestCase): |
1465 | """Test for the webservice client.""" |
1466 | |
1467 | - timeout = 8 |
1468 | + timeout = 5 |
1469 | |
1470 | - @inlineCallbacks |
1471 | + @defer.inlineCallbacks |
1472 | def setUp(self): |
1473 | yield super(WebClientTestCase, self).setUp() |
1474 | - self.ws = MockWebService() |
1475 | - test_base_url = self.ws.get_url() |
1476 | - self.wc = web_client.web_client_factory(sample_get_credentials, |
1477 | - test_base_url) |
1478 | + self.ws = MockWebServer() |
1479 | + self.addCleanup(self.ws.stop) |
1480 | + self.base_iri = self.ws.get_iri() |
1481 | + |
1482 | + self.wc = WebClient(sample_get_credentials, base_url=self.base_iri) |
1483 | self.addCleanup(self.wc.shutdown) |
1484 | - self.addCleanup(self.ws.stop) |
1485 | - web_module = web_client.web_client_module() |
1486 | - if getattr(web_module, "timestamp_checker", None): |
1487 | - fake_timestamp = 12345678 |
1488 | - self.patch(web_module.timestamp_checker, "get_faithful_time", |
1489 | - lambda: defer.succeed(fake_timestamp)) |
1490 | |
1491 | - @inlineCallbacks |
1492 | + @defer.inlineCallbacks |
1493 | def test_get_url(self): |
1494 | """A method is successfully called in the mock webservice.""" |
1495 | result = yield self.wc.call_api("devices") |
1496 | self.assertIn(SAMPLE_KEY, result) |
1497 | self.assertEqual(SAMPLE_VALUE, result[SAMPLE_KEY]) |
1498 | |
1499 | - @inlineCallbacks |
1500 | + @defer.inlineCallbacks |
1501 | def test_get_url_error(self): |
1502 | """The errback is called when there's some error.""" |
1503 | yield self.assertFailure(self.wc.call_api("throwerror"), |
1504 | - web_client.WebClientError) |
1505 | + WebClientError) |
1506 | |
1507 | - @inlineCallbacks |
1508 | + @defer.inlineCallbacks |
1509 | def test_unauthorized(self): |
1510 | """Detect when a request failed with UNAUTHORIZED.""" |
1511 | yield self.assertFailure(self.wc.call_api("unauthorized"), |
1512 | - web_client.UnauthorizedError) |
1513 | - |
1514 | - |
1515 | -class OAuthTestCase(TestCase): |
1516 | - """Test for the oauth signing code.""" |
1517 | - |
1518 | - def test_build_oauth_headers(self): |
1519 | - """Build the oauth headers for a sample request.""" |
1520 | - |
1521 | - sample_method = "GET" |
1522 | - sample_url = "http://one.ubuntu.com/" |
1523 | - timestamp = 1 |
1524 | - headers = web_client.build_oauth_headers(sample_method, sample_url, |
1525 | - SAMPLE_CREDENTIALS, timestamp) |
1526 | - self.assertIn("Authorization", headers) |
1527 | - |
1528 | - def test_add_oauth_headers(self): |
1529 | - """Add the OAuth headers to a request.""" |
1530 | - |
1531 | - def sample_build_headers(*a): |
1532 | - """Build some sample headers.""" |
1533 | - return {"header1": "h1", "header2": "h2"} |
1534 | - |
1535 | - self.patch(web_client, "build_oauth_headers", sample_build_headers) |
1536 | - test_request_headers = {} |
1537 | - append_method = test_request_headers.__setitem__ |
1538 | - timestamp = 12345 |
1539 | - web_client.add_oauth_headers(append_method, "GET", "http://this", {}, |
1540 | - timestamp) |
1541 | - self.assertIn("header1", test_request_headers) |
1542 | - self.assertIn("header2", test_request_headers) |
1543 | + UnauthorizedError) |
1544 | + |
1545 | + |
1546 | +class WebClientBuildSignedIriTestCase(WebClientTestCase): |
1547 | + """Test for the webservice client when signing iris.""" |
1548 | + |
1549 | + # Instance of 'ParseResult' has no 'foo' member |
1550 | + # pylint: disable=E1101 |
1551 | + |
1552 | + @defer.inlineCallbacks |
1553 | + def test_is_correct_domain(self): |
1554 | + """Test that we are using the right domain.""" |
1555 | + signed = yield self.wc.build_signed_iri(SAMPLE_TARGET) |
1556 | + parsed_signed = urlparse(signed) |
1557 | + parsed_sample = urlparse(SAMPLE_SIGNED) |
1558 | + self.assertEqual(parsed_signed.netloc, parsed_sample.netloc) |
1559 | + |
1560 | + @defer.inlineCallbacks |
1561 | + def test_is_correct_path(self): |
1562 | + """Test that we are using the right path in the URL.""" |
1563 | + signed = yield self.wc.build_signed_iri(SAMPLE_TARGET) |
1564 | + parsed_signed = urlparse(signed) |
1565 | + parsed_sample = urlparse(SAMPLE_SIGNED) |
1566 | + self.assertEqual(parsed_signed.path, parsed_sample.path) |
1567 | + |
1568 | + @defer.inlineCallbacks |
1569 | + def test_is_correct_scheme(self): |
1570 | + """Test that we are using the right scheme.""" |
1571 | + signed = yield self.wc.build_signed_iri(SAMPLE_TARGET) |
1572 | + parsed_signed = urlparse(signed) |
1573 | + parsed_sample = urlparse(SAMPLE_SIGNED) |
1574 | + |
1575 | + self.assertEqual(parsed_signed.scheme, parsed_sample.scheme) |
1576 | + |
1577 | + @defer.inlineCallbacks |
1578 | + def test_correct_query(self): |
1579 | + """Test the invariant parts of the signed URL.""" |
1580 | + signed = yield self.wc.build_signed_iri(SAMPLE_TARGET, |
1581 | + params={'next': u'/blah'}) |
1582 | + parsed_signed = urlparse(signed) |
1583 | + parsed_sample = urlparse(SAMPLE_SIGNED) |
1584 | + signed_query = parse_qs(parsed_signed.query) |
1585 | + sample_query = parse_qs(parsed_sample.query) |
1586 | + |
1587 | + for key in ('next', 'oauth_consumer_key', 'oauth_version', |
1588 | + 'oauth_signature_method', 'oauth_token'): |
1589 | + self.assertEqual(map(unicode, signed_query[key]), |
1590 | + sample_query[key]) |
1591 | + |
1592 | + @defer.inlineCallbacks |
1593 | + def test_url_with_query(self): |
1594 | + """Test that we are using the right scheme.""" |
1595 | + signed = yield self.wc.build_signed_iri(SAMPLE_TARGET, |
1596 | + params={'next': u'/blah?foo=bar'}) |
1597 | + parsed_signed = urlparse(signed) |
1598 | + signed_query = parse_qs(parsed_signed.query) |
1599 | + |
1600 | + self.assertEqual(signed_query['next'], [u'/blah?foo=bar']) |
1601 | + |
1602 | + @defer.inlineCallbacks |
1603 | + def test_uses_timestamper(self): |
1604 | + """Test that the signed url is using the serverrelative timestamp.""" |
1605 | + signed = yield self.wc.build_signed_iri(u'/blah?foo=bar') |
1606 | + parsed_signed = urlparse(signed) |
1607 | + signed_query = parse_qs(parsed_signed.query) |
1608 | + |
1609 | + self.assertTrue(signed_query['oauth_timestamp'] is not None) |
1610 | |
1611 | === removed directory 'ubuntuone/controlpanel/web_client' |
1612 | === renamed file 'ubuntuone/controlpanel/web_client/__init__.py' => 'ubuntuone/controlpanel/web_client.py' |
1613 | --- ubuntuone/controlpanel/web_client/__init__.py 2011-10-07 14:36:14 +0000 |
1614 | +++ ubuntuone/controlpanel/web_client.py 2012-02-14 22:15:21 +0000 |
1615 | @@ -1,9 +1,6 @@ |
1616 | # -*- coding: utf-8 -*- |
1617 | - |
1618 | -# Authors: Natalia B Bidart <natalia.bidart@canonical.com> |
1619 | -# Alejandro J. Cura <alecu@canonical.com> |
1620 | # |
1621 | -# Copyright 2011 Canonical Ltd. |
1622 | +# Copyright 2011-2012 Canonical Ltd. |
1623 | # |
1624 | # This program is free software: you can redistribute it and/or modify it |
1625 | # under the terms of the GNU General Public License version 3, as published |
1626 | @@ -19,60 +16,55 @@ |
1627 | |
1628 | """The web client.""" |
1629 | |
1630 | -from oauth import oauth |
1631 | - |
1632 | - |
1633 | -# pylint: disable=W0401, W0614 |
1634 | - |
1635 | - |
1636 | -class WebClientError(Exception): |
1637 | - """An http error happened while calling the webservice.""" |
1638 | - |
1639 | - |
1640 | -class UnauthorizedError(WebClientError): |
1641 | - """The request ended with bad_request, unauthorized or forbidden.""" |
1642 | - |
1643 | - |
1644 | -def build_oauth_headers(method, url, credentials, timestamp): |
1645 | - """Build an oauth request given some credentials.""" |
1646 | - consumer = oauth.OAuthConsumer(credentials["consumer_key"], |
1647 | - credentials["consumer_secret"]) |
1648 | - token = oauth.OAuthToken(credentials["token"], |
1649 | - credentials["token_secret"]) |
1650 | - parameters = {} |
1651 | - if timestamp: |
1652 | - parameters["oauth_timestamp"] = timestamp |
1653 | - request = oauth.OAuthRequest.from_consumer_and_token( |
1654 | - http_url=url, |
1655 | - http_method=method, |
1656 | - parameters=parameters, |
1657 | - oauth_consumer=consumer, |
1658 | - token=token) |
1659 | - sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1() |
1660 | - request.sign_request(sig_method, consumer, token) |
1661 | - return request.to_header() |
1662 | - |
1663 | - |
1664 | -def add_oauth_headers(append_method, method, url, credentials, timestamp=None): |
1665 | - """Sign a libsoup message with oauth headers.""" |
1666 | - headers = build_oauth_headers(method, url, credentials, timestamp) |
1667 | - for key, value in headers.items(): |
1668 | - append_method(key, value) |
1669 | - |
1670 | - |
1671 | -def web_client_module(): |
1672 | - """Choose the module of the web client.""" |
1673 | - # the reactor can only be imported after Qt is initialized |
1674 | - # pylint: disable=W0404 |
1675 | - from twisted.internet import reactor |
1676 | - if getattr(reactor, "qApp", None): |
1677 | - from ubuntuone.controlpanel.web_client import txwebclient as web_module |
1678 | - else: |
1679 | - from ubuntuone.controlpanel.web_client import libsoup as web_module |
1680 | - return web_module |
1681 | - |
1682 | - |
1683 | -def web_client_factory(*args, **kwargs): |
1684 | - """Choose the type of the web client dynamically.""" |
1685 | - web_module = web_client_module() |
1686 | - return web_module.WebClient(*args, **kwargs) |
1687 | +import simplejson |
1688 | + |
1689 | +from twisted.internet import defer |
1690 | +# need to export the exceptions to avoid API breakage |
1691 | +# pylint: disable=W0611 |
1692 | +from ubuntu_sso.utils.webclient import ( |
1693 | + UnauthorizedError, |
1694 | + WebClientError, |
1695 | + webclient_factory, |
1696 | +) |
1697 | +# pylint: enable=W0611 |
1698 | + |
1699 | +from ubuntuone.controlpanel import WEBSERVICE_BASE_URL |
1700 | +from ubuntuone.controlpanel.logger import setup_logging |
1701 | + |
1702 | + |
1703 | +logger = setup_logging('webclient') |
1704 | + |
1705 | + |
1706 | +class WebClient(object): |
1707 | + """A client for the u1 webservice.""" |
1708 | + |
1709 | + def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL): |
1710 | + """Initialize the webclient.""" |
1711 | + self.base_url = base_url |
1712 | + self.get_credentials = get_credentials |
1713 | + self.wc = webclient_factory() |
1714 | + logger.debug("WebClient created: base_url is %r, inner client is %r.", |
1715 | + self.base_url, self.wc) |
1716 | + |
1717 | + @defer.inlineCallbacks |
1718 | + def call_api(self, api_name, extra_headers=None): |
1719 | + """Call the webservice.""" |
1720 | + # this may log device ID's, but only for removals, which is OK |
1721 | + logger.debug("calling api: %r", api_name) |
1722 | + iri = self.base_url + api_name |
1723 | + credentials = yield self.get_credentials() |
1724 | + response = yield self.wc.request(iri, extra_headers=extra_headers, |
1725 | + oauth_credentials=credentials) |
1726 | + result = simplejson.loads(response.content) |
1727 | + defer.returnValue(result) |
1728 | + |
1729 | + @defer.inlineCallbacks |
1730 | + def build_signed_iri(self, iri, params=None): |
1731 | + """Build an OAuth iri.""" |
1732 | + credentials = yield self.get_credentials() |
1733 | + result = yield self.wc.build_signed_iri(iri, credentials, params) |
1734 | + defer.returnValue(result) |
1735 | + |
1736 | + def shutdown(self): |
1737 | + """Shutdown and cleanup.""" |
1738 | + return self.wc.shutdown() |
1739 | |
1740 | === removed file 'ubuntuone/controlpanel/web_client/libsoup.py' |
1741 | --- ubuntuone/controlpanel/web_client/libsoup.py 2011-10-06 19:56:38 +0000 |
1742 | +++ ubuntuone/controlpanel/web_client/libsoup.py 1970-01-01 00:00:00 +0000 |
1743 | @@ -1,133 +0,0 @@ |
1744 | -# -*- coding: utf-8 -*- |
1745 | - |
1746 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
1747 | -# Authors: Natalia B. Bidart <nataliabidart@canonical.com> |
1748 | -# |
1749 | -# Copyright 2010 Canonical Ltd. |
1750 | -# |
1751 | -# This program is free software: you can redistribute it and/or modify it |
1752 | -# under the terms of the GNU General Public License version 3, as published |
1753 | -# by the Free Software Foundation. |
1754 | -# |
1755 | -# This program is distributed in the hope that it will be useful, but |
1756 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
1757 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1758 | -# PURPOSE. See the GNU General Public License for more details. |
1759 | -# |
1760 | -# You should have received a copy of the GNU General Public License along |
1761 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1762 | - |
1763 | -"""The control panel backend webservice client.""" |
1764 | - |
1765 | -import simplejson |
1766 | - |
1767 | -# pylint: disable=E0611 |
1768 | -from gi.repository import Soup, SoupGNOME |
1769 | -from twisted.internet import defer |
1770 | - |
1771 | -from ubuntuone.controlpanel import WEBSERVICE_BASE_URL |
1772 | -from ubuntuone.controlpanel.web_client import (add_oauth_headers, |
1773 | - WebClientError, |
1774 | - UnauthorizedError) |
1775 | -from ubuntuone.controlpanel.logger import setup_logging |
1776 | -from ubuntuone.storageprotocol.utils import BaseTimestampChecker |
1777 | - |
1778 | -logger = setup_logging('webclient') |
1779 | - |
1780 | -# full list of status codes |
1781 | -# http://library.gnome.org/devel/libsoup/stable/libsoup-2.4-soup-status.html |
1782 | - |
1783 | - |
1784 | -class LibsoupTimestampChecker(BaseTimestampChecker): |
1785 | - """Specialized TimestampChecker using libsoup.""" |
1786 | - |
1787 | - def __init__(self): |
1788 | - """Initialize this instance.""" |
1789 | - super(LibsoupTimestampChecker, self).__init__() |
1790 | - self.session = Soup.SessionAsync() |
1791 | - self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME) |
1792 | - |
1793 | - def _handler(self, session, msg, d): |
1794 | - """Handle the result of an http message.""" |
1795 | - logger.debug("got http response %d for uri %r", |
1796 | - msg.status_code, msg.get_uri().to_string(False)) |
1797 | - if msg.status_code == 200: |
1798 | - date = msg.response_headers.get("Date") |
1799 | - d.callback(date) |
1800 | - else: |
1801 | - e = WebClientError(msg.status_code, "") |
1802 | - d.errback(e) |
1803 | - |
1804 | - def get_server_date_header(self, server_url): |
1805 | - """Get the server date using twisted webclient.""" |
1806 | - method = "HEAD" |
1807 | - msg = Soup.Message.new(method, server_url) |
1808 | - msg.request_headers.append("Cache-Control", "no-cache") |
1809 | - d = defer.Deferred() |
1810 | - self.session.queue_message(msg, self._handler, d) |
1811 | - return d |
1812 | - |
1813 | - def shutdown(self): |
1814 | - """End the soup session for this webclient.""" |
1815 | - self.session.abort() |
1816 | - |
1817 | - |
1818 | -# pylint: disable=C0103 |
1819 | -timestamp_checker = LibsoupTimestampChecker() |
1820 | - |
1821 | - |
1822 | -class WebClient(object): |
1823 | - """A client for the u1 webservice.""" |
1824 | - |
1825 | - def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL): |
1826 | - """Initialize the webclient.""" |
1827 | - self.base_url = base_url |
1828 | - self.session = Soup.SessionAsync() |
1829 | - self.session.add_feature_by_type(SoupGNOME.ProxyResolverGNOME) |
1830 | - self.get_credentials = get_credentials |
1831 | - |
1832 | - def _handler(self, session, msg, d): |
1833 | - """Handle the result of an http message.""" |
1834 | - logger.debug("got http response %d for uri %r", |
1835 | - msg.status_code, msg.get_uri().to_string(False)) |
1836 | - data = msg.response_body.data |
1837 | - if msg.status_code == 200: |
1838 | - result = simplejson.loads(data) |
1839 | - d.callback(result) |
1840 | - else: |
1841 | - if msg.status_code in (401,): |
1842 | - e = UnauthorizedError(msg.status_code, data) |
1843 | - else: |
1844 | - e = WebClientError(msg.status_code, data) |
1845 | - d.errback(e) |
1846 | - |
1847 | - def _call_api_inner(self, credentials, api_name, timestamp): |
1848 | - """Call the webservice with credentials and timestamp.""" |
1849 | - url = (self.base_url + api_name).encode('utf-8') |
1850 | - method = "GET" |
1851 | - logger.debug("getting url: %s, %s", method, url) |
1852 | - msg = Soup.Message.new(method, url) |
1853 | - add_oauth_headers(msg.request_headers.append, method, url, |
1854 | - credentials, timestamp) |
1855 | - d = defer.Deferred() |
1856 | - self.session.queue_message(msg, self._handler, d) |
1857 | - return d |
1858 | - |
1859 | - @defer.inlineCallbacks |
1860 | - def _call_api_add_timestamp(self, credentials, api_name): |
1861 | - """Add the timestamp to the api call.""" |
1862 | - timestamp = yield timestamp_checker.get_faithful_time() |
1863 | - result = yield self._call_api_inner(credentials, api_name, timestamp) |
1864 | - defer.returnValue(result) |
1865 | - |
1866 | - def call_api(self, api_name): |
1867 | - """Call the webservice.""" |
1868 | - # this may log device ID's, but only for removals, which is OK |
1869 | - logger.debug("calling api: %s", api_name) |
1870 | - d = self.get_credentials() |
1871 | - d.addCallback(self._call_api_add_timestamp, api_name) |
1872 | - return d |
1873 | - |
1874 | - def shutdown(self): |
1875 | - """End the soup session for this webclient.""" |
1876 | - self.session.abort() |
1877 | |
1878 | === removed directory 'ubuntuone/controlpanel/web_client/tests' |
1879 | === removed file 'ubuntuone/controlpanel/web_client/tests/__init__.py' |
1880 | --- ubuntuone/controlpanel/web_client/tests/__init__.py 2011-09-08 23:52:27 +0000 |
1881 | +++ ubuntuone/controlpanel/web_client/tests/__init__.py 1970-01-01 00:00:00 +0000 |
1882 | @@ -1,19 +0,0 @@ |
1883 | -# -*- coding: utf-8 -*- |
1884 | - |
1885 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
1886 | -# |
1887 | -# Copyright 2011 Canonical Ltd. |
1888 | -# |
1889 | -# This program is free software: you can redistribute it and/or modify it |
1890 | -# under the terms of the GNU General Public License version 3, as published |
1891 | -# by the Free Software Foundation. |
1892 | -# |
1893 | -# This program is distributed in the hope that it will be useful, but |
1894 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
1895 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1896 | -# PURPOSE. See the GNU General Public License for more details. |
1897 | -# |
1898 | -# You should have received a copy of the GNU General Public License along |
1899 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1900 | - |
1901 | -"""Unit tests for the control panel backend webservice clients.""" |
1902 | |
1903 | === removed file 'ubuntuone/controlpanel/web_client/tests/test_libsoup.py' |
1904 | --- ubuntuone/controlpanel/web_client/tests/test_libsoup.py 2011-10-06 22:27:57 +0000 |
1905 | +++ ubuntuone/controlpanel/web_client/tests/test_libsoup.py 1970-01-01 00:00:00 +0000 |
1906 | @@ -1,106 +0,0 @@ |
1907 | -# -*- coding: utf-8 -*- |
1908 | - |
1909 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
1910 | -# |
1911 | -# Copyright 2011 Canonical Ltd. |
1912 | -# |
1913 | -# This program is free software: you can redistribute it and/or modify it |
1914 | -# under the terms of the GNU General Public License version 3, as published |
1915 | -# by the Free Software Foundation. |
1916 | -# |
1917 | -# This program is distributed in the hope that it will be useful, but |
1918 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
1919 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
1920 | -# PURPOSE. See the GNU General Public License for more details. |
1921 | -# |
1922 | -# You should have received a copy of the GNU General Public License along |
1923 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
1924 | - |
1925 | -"""Integration tests for the libsoup based webservice client.""" |
1926 | - |
1927 | -import time |
1928 | - |
1929 | -from twisted.application import internet, service |
1930 | -from twisted.internet import defer |
1931 | -from twisted.web import http, resource, server |
1932 | - |
1933 | -from ubuntuone.controlpanel.tests import TestCase |
1934 | -from ubuntuone.controlpanel.web_client.libsoup import LibsoupTimestampChecker |
1935 | - |
1936 | - |
1937 | -class MockResource(resource.Resource): |
1938 | - """A mock resource.""" |
1939 | - |
1940 | - isLeaf = True |
1941 | - |
1942 | - def __init__(self): |
1943 | - """Initialize this mock instance.""" |
1944 | - resource.Resource.__init__(self) |
1945 | - self.request_headers = [] |
1946 | - |
1947 | - # pylint: disable=C0103 |
1948 | - def render_GET(self, request): |
1949 | - """Render some content.""" |
1950 | - self.request_headers.append(request.requestHeaders) |
1951 | - return "hello!" |
1952 | - |
1953 | - |
1954 | -class MockWebService(object): |
1955 | - """A mock webservice for testing""" |
1956 | - |
1957 | - def __init__(self): |
1958 | - """Start up this instance.""" |
1959 | - self.root = MockResource() |
1960 | - site = server.Site(self.root) |
1961 | - application = service.Application('web') |
1962 | - self.service_collection = service.IServiceCollection(application) |
1963 | - #pylint: disable=E1101 |
1964 | - self.tcpserver = internet.TCPServer(0, site) |
1965 | - self.tcpserver.setServiceParent(self.service_collection) |
1966 | - self.service_collection.startService() |
1967 | - |
1968 | - def get_url(self): |
1969 | - """Build the url for this mock server.""" |
1970 | - #pylint: disable=W0212 |
1971 | - port_num = self.tcpserver._port.getHost().port |
1972 | - return "http://localhost:%d/" % port_num |
1973 | - |
1974 | - def stop(self): |
1975 | - """Shut down the service.""" |
1976 | - #pylint: disable=E1101 |
1977 | - self.service_collection.stopService() |
1978 | - |
1979 | - |
1980 | -class LibsoupTimestampCheckerTestCase(TestCase): |
1981 | - """Tests for LibsoupTimestampChecker.""" |
1982 | - |
1983 | - timeout = 3 |
1984 | - |
1985 | - @defer.inlineCallbacks |
1986 | - def setUp(self): |
1987 | - yield super(LibsoupTimestampCheckerTestCase, self).setUp() |
1988 | - self.ws = MockWebService() |
1989 | - self.addCleanup(self.ws.stop) |
1990 | - |
1991 | - @defer.inlineCallbacks |
1992 | - def test_gets_server_date(self): |
1993 | - """The server date is gotten right.""" |
1994 | - fake_time = 1 |
1995 | - self.patch(time, "time", lambda: fake_time) |
1996 | - checker = LibsoupTimestampChecker() |
1997 | - self.addCleanup(checker.shutdown) |
1998 | - d = checker.get_server_date_header(self.ws.get_url()) |
1999 | - result = yield d |
2000 | - result_time = http.stringToDatetime(result) |
2001 | - self.assertEqual(result_time, fake_time) |
2002 | - |
2003 | - @defer.inlineCallbacks |
2004 | - def test_server_date_sends_nocache_headers(self): |
2005 | - """Getting the server date sends the no-cache headers.""" |
2006 | - checker = LibsoupTimestampChecker() |
2007 | - self.addCleanup(checker.shutdown) |
2008 | - yield checker.get_server_date_header(self.ws.get_url()) |
2009 | - self.assertEqual(len(self.ws.root.request_headers), 1) |
2010 | - headers = self.ws.root.request_headers[0] |
2011 | - result = headers.getRawHeaders("Cache-Control") |
2012 | - self.assertEqual(result, ["no-cache"]) |
2013 | |
2014 | === removed file 'ubuntuone/controlpanel/web_client/tests/test_txwebclient.py' |
2015 | --- ubuntuone/controlpanel/web_client/tests/test_txwebclient.py 2011-11-21 13:32:44 +0000 |
2016 | +++ ubuntuone/controlpanel/web_client/tests/test_txwebclient.py 1970-01-01 00:00:00 +0000 |
2017 | @@ -1,177 +0,0 @@ |
2018 | -# -*- coding: utf-8 -*- |
2019 | - |
2020 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
2021 | -# |
2022 | -# Copyright 2011 Canonical Ltd. |
2023 | -# |
2024 | -# This program is free software: you can redistribute it and/or modify it |
2025 | -# under the terms of the GNU General Public License version 3, as published |
2026 | -# by the Free Software Foundation. |
2027 | -# |
2028 | -# This program is distributed in the hope that it will be useful, but |
2029 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
2030 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2031 | -# PURPOSE. See the GNU General Public License for more details. |
2032 | -# |
2033 | -# You should have received a copy of the GNU General Public License along |
2034 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
2035 | - |
2036 | -"""Unit tests for the control panel backend twisted webservice client.""" |
2037 | - |
2038 | -import time |
2039 | - |
2040 | -from twisted.application import internet, service |
2041 | -from twisted.internet import defer, reactor |
2042 | -from twisted.internet.defer import inlineCallbacks |
2043 | -from twisted.web import server, resource |
2044 | -from ubuntuone.devtools.testcases import skipIfOS |
2045 | - |
2046 | -from ubuntuone.controlpanel.web_client import txwebclient |
2047 | -from ubuntuone.controlpanel.tests import TestCase |
2048 | - |
2049 | - |
2050 | -SAMPLE_KEY = "result" |
2051 | -SAMPLE_VALUE = "sample result" |
2052 | -SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE) |
2053 | -SAMPLE_CREDENTIALS = dict( |
2054 | - consumer_key="consumer key", |
2055 | - consumer_secret="consumer secret", |
2056 | - token="the real token", |
2057 | - token_secret="the token secret", |
2058 | -) |
2059 | - |
2060 | - |
2061 | -def sample_get_credentials(): |
2062 | - """Will return the sample credentials right now.""" |
2063 | - return defer.succeed(SAMPLE_CREDENTIALS) |
2064 | - |
2065 | - |
2066 | -class MockResource(resource.Resource): |
2067 | - """A simple web resource.""" |
2068 | - isLeaf = True |
2069 | - contents = "" |
2070 | - |
2071 | - # pylint: disable=C0103 |
2072 | - # t.w.resource methods have freeform cased names |
2073 | - |
2074 | - def getChild(self, name, request): |
2075 | - """Get a given child resource.""" |
2076 | - if name == '': |
2077 | - return self |
2078 | - return resource.Resource.getChild(self, name, request) |
2079 | - |
2080 | - def render_GET(self, request): |
2081 | - """Make a bit of html out of these resource's content.""" |
2082 | - return self.contents |
2083 | - |
2084 | - |
2085 | -class MockWebService(object): |
2086 | - """A mock webservice for testing""" |
2087 | - |
2088 | - def __init__(self): |
2089 | - """Start up this instance.""" |
2090 | - root = resource.Resource() |
2091 | - devices_resource = MockResource() |
2092 | - devices_resource.contents = SAMPLE_RESOURCE |
2093 | - root.putChild("devices", devices_resource) |
2094 | - root.putChild("throwerror", resource.NoResource()) |
2095 | - unauthorized = resource.ErrorPage(resource.http.UNAUTHORIZED, |
2096 | - "Unauthrorized", "Unauthrorized") |
2097 | - root.putChild("unauthorized", unauthorized) |
2098 | - |
2099 | - site = server.Site(root) |
2100 | - application = service.Application('web') |
2101 | - self.service_collection = service.IServiceCollection(application) |
2102 | - #pylint: disable=E1101 |
2103 | - self.tcpserver = internet.TCPServer(0, site) |
2104 | - self.tcpserver.setServiceParent(self.service_collection) |
2105 | - self.service_collection.startService() |
2106 | - |
2107 | - def get_url(self): |
2108 | - """Build the url for this mock server.""" |
2109 | - #pylint: disable=W0212 |
2110 | - port_num = self.tcpserver._port.getHost().port |
2111 | - return "http://localhost:%d/" % port_num |
2112 | - |
2113 | - def stop(self): |
2114 | - """Shut it down.""" |
2115 | - #pylint: disable=E1101 |
2116 | - return self.service_collection.stopService() |
2117 | - |
2118 | - |
2119 | -class FakeAsyncTimestamper(object): |
2120 | - """A fake timestamp.""" |
2121 | - |
2122 | - def __init__(self): |
2123 | - """Initialize this instance.""" |
2124 | - self.called = False |
2125 | - |
2126 | - def get_faithful_time(self): |
2127 | - """Return the server checked timestamp.""" |
2128 | - self.called = True |
2129 | - return defer.succeed(time.time()) |
2130 | - |
2131 | - |
2132 | -class WebClientTestCase(TestCase): |
2133 | - """Test for the webservice client.""" |
2134 | - |
2135 | - timeout = 8 |
2136 | - |
2137 | - @defer.inlineCallbacks |
2138 | - def setUp(self): |
2139 | - yield super(WebClientTestCase, self).setUp() |
2140 | - self.ws = MockWebService() |
2141 | - test_base_url = self.ws.get_url() |
2142 | - self.wc = txwebclient.WebClient(sample_get_credentials, test_base_url) |
2143 | - self.timestamper = FakeAsyncTimestamper() |
2144 | - self.patch(txwebclient, "timestamp_checker", self.timestamper) |
2145 | - self.addCleanup(self.wc.shutdown) |
2146 | - self.addCleanup(self.ws.stop) |
2147 | - |
2148 | - @inlineCallbacks |
2149 | - def test_get_url(self): |
2150 | - """A method is successfully called in the mock webservice.""" |
2151 | - result = yield self.wc.call_api("devices") |
2152 | - self.assertIn(SAMPLE_KEY, result) |
2153 | - self.assertEqual(SAMPLE_VALUE, result[SAMPLE_KEY]) |
2154 | - |
2155 | - @inlineCallbacks |
2156 | - def test_call_api_uses_timestamp(self): |
2157 | - """Check that call_api uses the timestamp.""" |
2158 | - yield self.wc.call_api("devices") |
2159 | - self.assertTrue(self.timestamper.called, |
2160 | - "The timestamper must be used.") |
2161 | - |
2162 | - @inlineCallbacks |
2163 | - def test_get_url_error(self): |
2164 | - """The errback is called when there's some error.""" |
2165 | - yield self.assertFailure(self.wc.call_api("throwerror"), |
2166 | - txwebclient.WebClientError) |
2167 | - |
2168 | - @inlineCallbacks |
2169 | - def test_unauthorized(self): |
2170 | - """Detect when a request failed with UNAUTHORIZED.""" |
2171 | - yield self.assertFailure(self.wc.call_api("unauthorized"), |
2172 | - txwebclient.UnauthorizedError) |
2173 | - |
2174 | - |
2175 | -class WebClientShutdownTestCase(TestCase): |
2176 | - """The webclient behaviour during shutdown.""" |
2177 | - |
2178 | - @skipIfOS('win32', 'Failing on windows, see LP: #851158.') |
2179 | - @inlineCallbacks |
2180 | - def test_shutdown(self): |
2181 | - """The webclient behaves well during shutdown.""" |
2182 | - self.patch(txwebclient, "timestamp_checker", FakeAsyncTimestamper()) |
2183 | - d3 = defer.Deferred() |
2184 | - # pylint: disable=E1101 |
2185 | - reactor.callLater(1, d3.callback, None) |
2186 | - ws = MockWebService() |
2187 | - test_base_url = ws.get_url() |
2188 | - wc = txwebclient.WebClient(sample_get_credentials, test_base_url) |
2189 | - d1 = wc.call_api("throwerror") |
2190 | - d2 = ws.stop() |
2191 | - wc.shutdown() |
2192 | - yield d2 |
2193 | - yield defer.DeferredList([d1, d3], fireOnOneCallback=True, |
2194 | - fireOnOneErrback=True) |
2195 | |
2196 | === removed file 'ubuntuone/controlpanel/web_client/txwebclient.py' |
2197 | --- ubuntuone/controlpanel/web_client/txwebclient.py 2011-10-05 23:12:23 +0000 |
2198 | +++ ubuntuone/controlpanel/web_client/txwebclient.py 1970-01-01 00:00:00 +0000 |
2199 | @@ -1,108 +0,0 @@ |
2200 | -# -*- coding: utf-8 -*- |
2201 | - |
2202 | -# Authors: Alejandro J. Cura <alecu@canonical.com> |
2203 | -# |
2204 | -# Copyright 2011 Canonical Ltd. |
2205 | -# |
2206 | -# This program is free software: you can redistribute it and/or modify it |
2207 | -# under the terms of the GNU General Public License version 3, as published |
2208 | -# by the Free Software Foundation. |
2209 | -# |
2210 | -# This program is distributed in the hope that it will be useful, but |
2211 | -# WITHOUT ANY WARRANTY; without even the implied warranties of |
2212 | -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
2213 | -# PURPOSE. See the GNU General Public License for more details. |
2214 | -# |
2215 | -# You should have received a copy of the GNU General Public License along |
2216 | -# with this program. If not, see <http://www.gnu.org/licenses/>. |
2217 | - |
2218 | -"""The control panel backend webservice client using twisted.web.""" |
2219 | - |
2220 | -import simplejson |
2221 | - |
2222 | -from twisted.internet import defer, reactor |
2223 | -from twisted.web import client, error, http |
2224 | - |
2225 | -from ubuntuone.controlpanel import WEBSERVICE_BASE_URL |
2226 | -from ubuntuone.controlpanel.web_client import (add_oauth_headers, |
2227 | - WebClientError, |
2228 | - UnauthorizedError) |
2229 | - |
2230 | -from ubuntuone.controlpanel.logger import setup_logging |
2231 | -from ubuntuone.storageprotocol.client import TwistedTimestampChecker |
2232 | - |
2233 | -logger = setup_logging('webclient') |
2234 | -# pylint: disable=C0103 |
2235 | -timestamp_checker = TwistedTimestampChecker() |
2236 | - |
2237 | - |
2238 | -class WebClient(object): |
2239 | - """A client for the u1 webservice.""" |
2240 | - |
2241 | - def __init__(self, get_credentials, base_url=WEBSERVICE_BASE_URL): |
2242 | - """Initialize the webclient.""" |
2243 | - self.base_url = base_url |
2244 | - self.get_credentials = get_credentials |
2245 | - self.running = True |
2246 | - # pylint: disable=E1101 |
2247 | - self.trigger_id = reactor.addSystemEventTrigger("before", "shutdown", |
2248 | - self.shutdown) |
2249 | - |
2250 | - def _handle_response(self, result): |
2251 | - """Handle the response of the webservice call.""" |
2252 | - return simplejson.loads(result) |
2253 | - |
2254 | - def _handle_error(self, failure): |
2255 | - """Handle an error while calling the webservice.""" |
2256 | - if failure.type == error.Error: |
2257 | - exception = failure.value |
2258 | - if exception.status == str(http.UNAUTHORIZED): |
2259 | - raise UnauthorizedError(exception.status, exception.response) |
2260 | - else: |
2261 | - raise WebClientError(exception.status, exception.response) |
2262 | - else: |
2263 | - raise WebClientError(-1, failure) |
2264 | - |
2265 | - def _call_api_inner(self, credentials, api_name, timestamp): |
2266 | - """Call the webservice with credentials and timestamp.""" |
2267 | - url = (self.base_url + api_name).encode('utf-8') |
2268 | - method = "GET" |
2269 | - logger.debug("getting url: %s, %s", method, url) |
2270 | - headers = {} |
2271 | - add_oauth_headers(headers.__setitem__, method, url, credentials, |
2272 | - timestamp) |
2273 | - d = client.getPage(url, headers=headers) |
2274 | - d.addCallback(self._handle_response) |
2275 | - d.addErrback(self._handle_error) |
2276 | - return d |
2277 | - |
2278 | - @defer.inlineCallbacks |
2279 | - def _call_api_add_timestamp(self, credentials, api_name): |
2280 | - """Add the timestamp to the api call.""" |
2281 | - timestamp = yield timestamp_checker.get_faithful_time() |
2282 | - result = yield self._call_api_inner(credentials, api_name, timestamp) |
2283 | - defer.returnValue(result) |
2284 | - |
2285 | - def call_api(self, api_name): |
2286 | - """Call the webservice.""" |
2287 | - # this may log device ID's, but only for removals, which is OK |
2288 | - logger.debug("calling api: %s", api_name) |
2289 | - d = self.get_credentials() |
2290 | - d.addErrback(self._handle_error) |
2291 | - d.addCallback(self._call_api_add_timestamp, api_name) |
2292 | - d2 = defer.Deferred() |
2293 | - d.addCallback(d2.callback) |
2294 | - |
2295 | - def mask_errors_on_shutdown(failure): |
2296 | - """Do not fire the errbacks if we are shutting down.""" |
2297 | - if self.running: |
2298 | - d2.errback(failure) |
2299 | - |
2300 | - d.addErrback(mask_errors_on_shutdown) |
2301 | - return d2 |
2302 | - |
2303 | - def shutdown(self): |
2304 | - """End the pending webclient calls.""" |
2305 | - self.running = False |
2306 | - # pylint: disable=E1101 |
2307 | - reactor.removeSystemEventTrigger(self.trigger_id) |
+1