Merge lp:~dobey/unity-scope-click/merge-trunk into lp:unity-scope-click/devel

Proposed by dobey
Status: Merged
Approved by: Alejandro J. Cura
Approved revision: 484
Merged at revision: 484
Proposed branch: lp:~dobey/unity-scope-click/merge-trunk
Merge into: lp:unity-scope-click/devel
Diff against target: 5459 lines (+3525/-477)
56 files modified
CMakeLists.txt (+2/-2)
data/clickscope.ini.in.in (+0/-1)
data/update_schema.sh (+173/-0)
debian/changelog (+212/-0)
debian/control (+3/-2)
libclickscope/click/configuration.cpp (+27/-0)
libclickscope/click/configuration.h (+6/-1)
libclickscope/click/departments-db.cpp (+3/-7)
libclickscope/click/download-manager.cpp (+17/-2)
libclickscope/click/highlights.cpp (+10/-4)
libclickscope/click/index.cpp (+12/-0)
libclickscope/click/index.h (+4/-0)
libclickscope/click/interface.cpp (+10/-11)
libclickscope/click/package.cpp (+15/-0)
libclickscope/click/package.h (+5/-0)
libclickscope/click/preview.cpp (+168/-78)
libclickscope/click/preview.h (+6/-3)
libclickscope/click/reviews.h (+2/-1)
libclickscope/click/webclient.cpp (+13/-2)
libclickscope/click/webclient.h (+3/-0)
libclickscope/tests/fake_json.cpp (+182/-2)
libclickscope/tests/fake_json.h (+1/-0)
libclickscope/tests/mock_ubuntu_download_manager.h (+5/-0)
libclickscope/tests/mock_webclient.h (+3/-0)
libclickscope/tests/test_bootstrap.cpp (+53/-19)
libclickscope/tests/test_configuration.cpp (+33/-1)
libclickscope/tests/test_departments-db.cpp (+2/-2)
libclickscope/tests/test_interface.cpp (+4/-4)
libclickscope/tests/test_package.cpp (+27/-0)
libclickscope/tests/test_preview.cpp (+84/-6)
libclickscope/tests/test_webclient.cpp (+11/-5)
po/unity-scope-click.pot (+60/-57)
scope/clickstore/pay.cpp (+70/-2)
scope/clickstore/pay.h (+57/-5)
scope/clickstore/store-query.cpp (+65/-58)
scope/clickstore/store-query.h (+4/-1)
scope/clickstore/store-scope.cpp (+1/-1)
scope/tests/CMakeLists.txt (+2/-0)
scope/tests/mock_pay.h (+34/-0)
scope/tests/test_pay.cpp (+201/-21)
scope/tests/test_query.cpp (+116/-41)
tests/CMakeLists.txt (+4/-3)
tests/autopilot/unityclickscope/__init__.py (+77/-70)
tests/autopilot/unityclickscope/fixture_setup.py (+3/-36)
tests/autopilot/unityclickscope/test_click_scope.py (+18/-29)
tests/common/__init__.py (+15/-0)
tests/common/fake_server_fixture.py (+60/-0)
tests/scope-harness/apps-scope-harness.py (+200/-0)
tests/scope-harness/fake_responses/click-package-index/api/v1/departments/games/index.json (+174/-0)
tests/scope-harness/fake_responses/click-package-index/api/v1/index.json (+642/-0)
tests/scope-harness/fake_responses/click-package-index/api/v1/package/com.ubuntu.calendar (+86/-0)
tests/scope-harness/fake_responses/click-package-index/api/v1/search (+76/-0)
tests/scope-harness/fake_responses/ratings-and-reviews/click/api/1.0/reviews/index.json (+210/-0)
tests/scope-harness/fake_responses/software-center-agent/api/2.0/click/purchases/index.json (+8/-0)
tests/scope-harness/run-store-test.sh (+1/-0)
tests/scope-harness/store-scope-harness.py (+245/-0)
To merge this branch: bzr merge lp:~dobey/unity-scope-click/merge-trunk
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Alejandro J. Cura (community) Approve
Review via email: mp+256200@code.launchpad.net

Commit message

Synchronize devel branch with trunk.

To post a comment you must log in.
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Looks good, +1

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-08-01 15:04:19 +0000
3+++ CMakeLists.txt 2015-04-14 19:49:39 +0000
4@@ -22,7 +22,7 @@
5 include(UseGSettings)
6 find_package (PkgConfig REQUIRED)
7
8-pkg_check_modules(UNITY_SCOPES REQUIRED libunity-scopes>=0.6.0 libunity-api>=0.1.3)
9+pkg_check_modules(UNITY_SCOPES REQUIRED libunity-scopes>=0.6.7 libunity-api>=0.1.3)
10 add_definitions(${UNITY_SCOPES_CFLAGS} ${UNITY_SCOPES_CFLAGS_OTHER})
11
12 pkg_check_modules(UBUNTUONE REQUIRED ubuntuoneauth-2.0)
13@@ -48,7 +48,7 @@
14 add_subdirectory(${GMOCK_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/gmock")
15
16 # Add our own subdirectories.
17-add_subdirectory(autopilot)
18+add_subdirectory(tests)
19 add_subdirectory(bin)
20 add_subdirectory(libclickscope)
21 add_subdirectory(scope)
22
23=== modified file 'data/clickscope.ini.in.in'
24--- data/clickscope.ini.in.in 2014-08-19 15:56:29 +0000
25+++ data/clickscope.ini.in.in 2015-04-14 19:49:39 +0000
26@@ -7,5 +7,4 @@
27 _SearchHint=Search apps
28
29 [Appearance]
30-PageHeader.Logo=@APPS_DATA_DIR@/ubuntu-logo.png
31 LogoOverlayColor=#ffffffff
32
33=== modified file 'data/departments.db'
34Binary files data/departments.db 2014-08-18 14:54:00 +0000 and data/departments.db 2015-04-14 19:49:39 +0000 differ
35=== modified file 'data/update_schema.sh'
36--- data/update_schema.sh 2014-08-18 14:36:17 +0000
37+++ data/update_schema.sh 2015-04-14 19:49:39 +0000
38@@ -149,4 +149,177 @@
39 UPDATE meta SET value='3' WHERE name='version';
40 END TRANSACTION;
41 _UPDATE_TO_VER3
42+SCHEMA_VERSION=$(sqlite3 "$DBFILE" "SELECT value FROM meta WHERE name='version'")
43+fi
44+
45+if [ "x$SCHEMA_VERSION" = "x3" ]
46+then
47+ sqlite3 "$DBFILE" << _UPDATE_TO_VER4
48+ BEGIN TRANSACTION;
49+
50+ DELETE FROM "deptnames";
51+ INSERT INTO "deptnames" VALUES('social-networking','en_US','Social Networking');
52+ INSERT INTO "deptnames" VALUES('travel-local','en_US','Travel & Local');
53+ INSERT INTO "deptnames" VALUES('reference','en_US','Reference');
54+ INSERT INTO "deptnames" VALUES('food-drink','en_US','Food & Drink');
55+ INSERT INTO "deptnames" VALUES('communication','en_US','Communication');
56+ INSERT INTO "deptnames" VALUES('accessories','en_US','Utilities');
57+ INSERT INTO "deptnames" VALUES('science-engineering','en_US','Science & Engineering');
58+ INSERT INTO "deptnames" VALUES('personalisation','en_US','Personalisation');
59+ INSERT INTO "deptnames" VALUES('education','en_US','Education');
60+ INSERT INTO "deptnames" VALUES('productivity','en_US','Productivity');
61+ INSERT INTO "deptnames" VALUES('universal-accessaccessibility','en_US','Universal Access/Accessibility');
62+ INSERT INTO "deptnames" VALUES('entertainment','en_US','Entertainment');
63+ INSERT INTO "deptnames" VALUES('sports','en_US','Sports');
64+ INSERT INTO "deptnames" VALUES('health-fitness','en_US','Health & Fitness');
65+ INSERT INTO "deptnames" VALUES('weather','en_US','Weather');
66+ INSERT INTO "deptnames" VALUES('shopping','en_US','Shopping');
67+ INSERT INTO "deptnames" VALUES('finance','en_US','Finance');
68+ INSERT INTO "deptnames" VALUES('business','en_US','Business');
69+ INSERT INTO "deptnames" VALUES('media-video','en_US','Media & Video');
70+ INSERT INTO "deptnames" VALUES('music-audio','en_US','Music & Audio');
71+ INSERT INTO "deptnames" VALUES('news-magazines','en_US','News & Magazines');
72+ INSERT INTO "deptnames" VALUES('graphics','en_US','Graphics');
73+ INSERT INTO "deptnames" VALUES('lifestyle','en_US','Lifestyle');
74+ INSERT INTO "deptnames" VALUES('medical','en_US','Medical');
75+ INSERT INTO "deptnames" VALUES('developer-tools','en_US','Developer Tools');
76+ INSERT INTO "deptnames" VALUES('games','en_US','Games');
77+ INSERT INTO "deptnames" VALUES('books-comics','en_US','Books & Comics');
78+
79+ INSERT INTO "deptnames" VALUES('accessories','ca_ES','Utilitats');
80+ INSERT INTO "deptnames" VALUES('accessories','es_ES','Utilidades');
81+ INSERT INTO "deptnames" VALUES('accessories','eu_ES','Tresnak');
82+ INSERT INTO "deptnames" VALUES('accessories','gl_ES','Utilidades');
83+ INSERT INTO "deptnames" VALUES('books-comics','ca_ES','Llibres i còmics');
84+ INSERT INTO "deptnames" VALUES('books-comics','es_ES','Libros y cómics');
85+ INSERT INTO "deptnames" VALUES('books-comics','eu_ES','Liburuak eta komikiak');
86+ INSERT INTO "deptnames" VALUES('books-comics','gl_ES','Libros e cómics');
87+ INSERT INTO "deptnames" VALUES('communication','ca_ES','Comunicació');
88+ INSERT INTO "deptnames" VALUES('communication','es_ES','Comunicación');
89+ INSERT INTO "deptnames" VALUES('communication','eu_ES','Komunikazioa');
90+ INSERT INTO "deptnames" VALUES('communication','gl_ES','Comunicación');
91+ INSERT INTO "deptnames" VALUES('developer-tools','ca_ES','Eines per a desenvolupadors');
92+ INSERT INTO "deptnames" VALUES('developer-tools','es_ES','Herramientas para desarrolladores');
93+ INSERT INTO "deptnames" VALUES('developer-tools','eu_ES','Garatzaileentzako tresnak');
94+ INSERT INTO "deptnames" VALUES('developer-tools','gl_ES','Ferramentas de desenvolvemento');
95+ INSERT INTO "deptnames" VALUES('education','ca_ES','Educació');
96+ INSERT INTO "deptnames" VALUES('education','es_ES','Educación');
97+ INSERT INTO "deptnames" VALUES('education','eu_ES','Hezkuntza');
98+ INSERT INTO "deptnames" VALUES('education','gl_ES','Educación');
99+ INSERT INTO "deptnames" VALUES('entertainment','ca_ES','Entreteniment');
100+ INSERT INTO "deptnames" VALUES('entertainment','es_ES','Entretenimiento');
101+ INSERT INTO "deptnames" VALUES('entertainment','eu_ES','Entretenimendua');
102+ INSERT INTO "deptnames" VALUES('entertainment','gl_ES','Lecer');
103+ INSERT INTO "deptnames" VALUES('finance','ca_ES','Finances');
104+ INSERT INTO "deptnames" VALUES('finance','es_ES','Finanzas');
105+ INSERT INTO "deptnames" VALUES('finance','eu_ES','Finantzak');
106+ INSERT INTO "deptnames" VALUES('finance','gl_ES','Finanzas');
107+ INSERT INTO "deptnames" VALUES('food-drink','ca_ES','Menjar i begudes');
108+ INSERT INTO "deptnames" VALUES('food-drink','es_ES','Comida y bebida');
109+ INSERT INTO "deptnames" VALUES('food-drink','eu_ES','Jana eta edaria');
110+ INSERT INTO "deptnames" VALUES('food-drink','gl_ES','Comida & bebida');
111+ INSERT INTO "deptnames" VALUES('games','ca_ES','Jocs');
112+ INSERT INTO "deptnames" VALUES('games','es_ES','Juegos');
113+ INSERT INTO "deptnames" VALUES('games','eu_ES','Jokoak');
114+ INSERT INTO "deptnames" VALUES('games','gl_ES','Xogos');
115+ INSERT INTO "deptnames" VALUES('graphics','ca_ES','Gràfics');
116+ INSERT INTO "deptnames" VALUES('graphics','es_ES','Gráficos');
117+ INSERT INTO "deptnames" VALUES('graphics','eu_ES','Grafikoak');
118+ INSERT INTO "deptnames" VALUES('graphics','gl_ES','Gráficos');
119+ INSERT INTO "deptnames" VALUES('lifestyle','ca_ES','Estil de vida');
120+ INSERT INTO "deptnames" VALUES('lifestyle','es_ES','Estilo de vida');
121+ INSERT INTO "deptnames" VALUES('lifestyle','eu_ES','Bizi-estiloa');
122+ INSERT INTO "deptnames" VALUES('lifestyle','gl_ES','Estilo de vida');
123+ INSERT INTO "deptnames" VALUES('media-video','ca_ES','Multimèdia i vídeo');
124+ INSERT INTO "deptnames" VALUES('media-video','es_ES','Multimedia y vídeos');
125+ INSERT INTO "deptnames" VALUES('media-video','eu_ES','Multimedia eta bideoak');
126+ INSERT INTO "deptnames" VALUES('media-video','gl_ES','Multimedia e vídeo');
127+ INSERT INTO "deptnames" VALUES('medical','ca_ES','Medicina');
128+ INSERT INTO "deptnames" VALUES('medical','es_ES','Medicina');
129+ INSERT INTO "deptnames" VALUES('medical','eu_ES','Osasuna');
130+ INSERT INTO "deptnames" VALUES('medical','gl_ES','Medicina');
131+ INSERT INTO "deptnames" VALUES('music-audio','ca_ES','Música i àudio');
132+ INSERT INTO "deptnames" VALUES('music-audio','es_ES','Música y audio');
133+ INSERT INTO "deptnames" VALUES('music-audio','eu_ES','Musika eta audioa');
134+ INSERT INTO "deptnames" VALUES('music-audio','gl_ES','Música e son');
135+ INSERT INTO "deptnames" VALUES('news-magazines','ca_ES','Notícies i revistes');
136+ INSERT INTO "deptnames" VALUES('news-magazines','es_ES','Noticias y revistas');
137+ INSERT INTO "deptnames" VALUES('news-magazines','eu_ES','Albisteak eta aldizkariak');
138+ INSERT INTO "deptnames" VALUES('news-magazines','gl_ES','Novas e revistas');
139+ INSERT INTO "deptnames" VALUES('productivity','ca_ES','Productivitat');
140+ INSERT INTO "deptnames" VALUES('productivity','es_ES','Productividad');
141+ INSERT INTO "deptnames" VALUES('productivity','eu_ES','Produktibitatea');
142+ INSERT INTO "deptnames" VALUES('productivity','gl_ES','Produtividade');
143+ INSERT INTO "deptnames" VALUES('reference','ca_ES','Referència');
144+ INSERT INTO "deptnames" VALUES('reference','es_ES','Referencia');
145+ INSERT INTO "deptnames" VALUES('reference','eu_ES','Erreferentzia');
146+ INSERT INTO "deptnames" VALUES('reference','gl_ES','Referencia');
147+ INSERT INTO "deptnames" VALUES('science-engineering','ca_ES','Ciència i enginyeria');
148+ INSERT INTO "deptnames" VALUES('science-engineering','es_ES','Ciencia e ingeniería');
149+ INSERT INTO "deptnames" VALUES('science-engineering','eu_ES','Zientzia eta ingeniaritza');
150+ INSERT INTO "deptnames" VALUES('science-engineering','gl_ES','Ciencia e enxeñaría');
151+ INSERT INTO "deptnames" VALUES('shopping','ca_ES','Compres');
152+ INSERT INTO "deptnames" VALUES('shopping','es_ES','Tiendas');
153+ INSERT INTO "deptnames" VALUES('shopping','eu_ES','Erosketak');
154+ INSERT INTO "deptnames" VALUES('shopping','gl_ES','Compras');
155+ INSERT INTO "deptnames" VALUES('social-networking','ca_ES','Xarxes socials');
156+ INSERT INTO "deptnames" VALUES('social-networking','es_ES','Redes sociales');
157+ INSERT INTO "deptnames" VALUES('social-networking','eu_ES','Sare sozialak');
158+ INSERT INTO "deptnames" VALUES('social-networking','gl_ES','Redes sociais');
159+ INSERT INTO "deptnames" VALUES('sports','ca_ES','Esports');
160+ INSERT INTO "deptnames" VALUES('sports','es_ES','Deportes');
161+ INSERT INTO "deptnames" VALUES('sports','eu_ES','Kirolak');
162+ INSERT INTO "deptnames" VALUES('sports','gl_ES','Deportes');
163+ INSERT INTO "deptnames" VALUES('travel-local','ca_ES','Viatges i regional');
164+ INSERT INTO "deptnames" VALUES('travel-local','es_ES','Viajes y guías');
165+ INSERT INTO "deptnames" VALUES('travel-local','eu_ES','Bidaiak eta gidak');
166+ INSERT INTO "deptnames" VALUES('travel-local','gl_ES','Viaxes e local');
167+ INSERT INTO "deptnames" VALUES('universal-accessaccessibility','ca_ES','Accés universal i accessibilitat');
168+ INSERT INTO "deptnames" VALUES('universal-accessaccessibility','es_ES','Acceso universal/accesibilidad');
169+ INSERT INTO "deptnames" VALUES('universal-accessaccessibility','eu_ES','Sarbide unibertsala/Erabilerraztasuna');
170+ INSERT INTO "deptnames" VALUES('universal-accessaccessibility','gl_ES','Acceso universal/Accesibilidade');
171+ INSERT INTO "deptnames" VALUES('weather','ca_ES','El temps');
172+ INSERT INTO "deptnames" VALUES('weather','es_ES','Meteorología');
173+ INSERT INTO "deptnames" VALUES('weather','eu_ES','Eguraldia');
174+ INSERT INTO "deptnames" VALUES('weather','gl_ES','O Tempo');
175+
176+ DELETE FROM "pkgmap";
177+ INSERT INTO "pkgmap" VALUES('com.canonical.cincodias','news-magazines');
178+ INSERT INTO "pkgmap" VALUES('com.canonical.elpais','news-magazines');
179+ INSERT INTO "pkgmap" VALUES('com.nokia.heremaps','travel-local');
180+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-amazon','shopping');
181+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-amazon-es','shopping');
182+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-amazon-int','shopping');
183+ INSERT INTO "pkgmap" VALUES('com.ubuntu.dropping-letters','games');
184+ INSERT INTO "pkgmap" VALUES('com.ubuntu.filemanager','accesories');
185+ INSERT INTO "pkgmap" VALUES('com.ubuntu.shorts','news-magazines');
186+ INSERT INTO "pkgmap" VALUES('com.ubuntu.sudoku','games');
187+ INSERT INTO "pkgmap" VALUES('com.ubuntu.telegram','communication');
188+ INSERT INTO "pkgmap" VALUES('com.ubuntu.terminal','accesories');
189+ INSERT INTO "pkgmap" VALUES('com.zeptolab.cuttherope.free','games');
190+ INSERT INTO "pkgmap" VALUES('com.ubuntu.weather','weather');
191+ INSERT INTO "pkgmap" VALUES('com.ubuntu.clock','accessories');
192+ INSERT INTO "pkgmap" VALUES('com.canonical.payui','accessories');
193+ INSERT INTO "pkgmap" VALUES('com.ubuntu.music','music-audio');
194+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-ebay','shopping');
195+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-gmail','communication');
196+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.ken-vandine.pathwind','games');
197+ INSERT INTO "pkgmap" VALUES('com.ubuntu.gallery','accessories');
198+ INSERT INTO "pkgmap" VALUES('com.ubuntu.camera','accessories');
199+ INSERT INTO "pkgmap" VALUES('com.ubuntu.calculator','accessories');
200+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.mzanetti.tagger','accessories');
201+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-facebook','communication');
202+ INSERT INTO "pkgmap" VALUES('com.ubuntu.reminders','accessories');
203+ INSERT INTO "pkgmap" VALUES('com.ubuntu.developer.webapps.webapp-twitter','communication');
204+ INSERT INTO "pkgmap" VALUES('address-book-app.desktop','accessories');
205+ INSERT INTO "pkgmap" VALUES('dialer-app.desktop','communication');
206+ INSERT INTO "pkgmap" VALUES('mediaplayer-app.desktop','sound-video');
207+ INSERT INTO "pkgmap" VALUES('messaging-app.desktop','communication');
208+ INSERT INTO "pkgmap" VALUES('ubuntu-system-settings.desktop','accessories');
209+ INSERT INTO "pkgmap" VALUES('webbrowser-app.desktop','communication');
210+
211+ UPDATE meta SET value='4' WHERE name='version';
212+ END TRANSACTION;
213+_UPDATE_TO_VER4
214+SCHEMA_VERSION=$(sqlite3 "$DBFILE" "SELECT value FROM meta WHERE name='version'")
215 fi
216
217=== modified file 'debian/changelog'
218--- debian/changelog 2014-08-21 20:40:59 +0000
219+++ debian/changelog 2015-04-14 19:49:39 +0000
220@@ -1,3 +1,215 @@
221+unity-scope-click (0.1.1+15.04.20150407-0ubuntu1) vivid; urgency=medium
222+
223+ *
224+
225+ -- CI Train Bot <ci-train-bot@canonical.com> Tue, 07 Apr 2015 09:13:05 +0000
226+
227+unity-scope-click (0.1.1+15.04.20150326-0ubuntu1) vivid; urgency=medium
228+
229+ [ Alejandro J. Cura ]
230+ * Fake webservices for the integration tests
231+ * Fetch the "refundable_until" field from the /purchases endpoint, and
232+ store it as a GMT timestamp
233+ * Use table widget in previews (LP: #1407680)
234+
235+ [ CI Train Bot ]
236+ * New rebuild forced.
237+
238+ [ Pawel Stolowski ]
239+ * Initial set of python integration tests using the scope harness
240+
241+ -- CI Train Bot <ci-train-bot@canonical.com> Thu, 26 Mar 2015 18:49:47 +0000
242+
243+unity-scope-click (0.1.1+15.04.20150310-0ubuntu1) vivid; urgency=medium
244+
245+ [ Rodney Dawes ]
246+ * Update autopilot dependencies for the autopilot tests package. (LP:
247+ #1429158)
248+
249+ -- CI Train Bot <ci-train-bot@canonical.com> Tue, 10 Mar 2015 19:01:19 +0000
250+
251+unity-scope-click (0.1.1+15.04.20150123-0ubuntu1) vivid; urgency=low
252+
253+ [ Ubuntu daily release ]
254+ * New rebuild forced
255+
256+ [ Alejandro J. Cura ]
257+ * Add Spanish translations to the preinstalled departments db (LP:
258+ #1413272)
259+
260+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 23 Jan 2015 03:41:27 +0000
261+
262+unity-scope-click (0.1.1+15.04.20150120-0ubuntu1) vivid; urgency=low
263+
264+ [ Alejandro J. Cura ]
265+ * Put the store version in the result, to be used if uninstalling just
266+ after installing (LP: #1412541)
267+
268+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 20 Jan 2015 17:27:07 +0000
269+
270+unity-scope-click (0.1.1+15.04.20150114-0ubuntu1) vivid; urgency=low
271+
272+ [ Alejandro J. Cura ]
273+ * Check Variant for null value (LP: #1357143)
274+
275+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 14 Jan 2015 19:27:00 +0000
276+
277+unity-scope-click (0.1.1+15.04.20141212.1-0ubuntu1) vivid; urgency=low
278+
279+ [ Pawel Stolowski ]
280+ * Removed the restriction in get_children method that filtered empty
281+ departments out. (LP: #1390191)
282+
283+ [ Leo Arias ]
284+ * Updated the autopilot tests to get then back to green. (LP:
285+ #1398933)
286+
287+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 12 Dec 2014 18:40:43 +0000
288+
289+unity-scope-click (0.1.1+15.04.20141212-0ubuntu1) vivid; urgency=low
290+
291+ [ Pawel Stolowski ]
292+ * Report all departments in the Store scope so that Shell receives
293+ also the siblings of current department and can properly render
294+ departments menu when going to a specific department via 'Ubuntu
295+ store' button of Apps scope. (LP: #1343242)
296+
297+ [ Ubuntu daily release ]
298+ * New rebuild forced
299+
300+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 12 Dec 2014 10:02:19 +0000
301+
302+unity-scope-click (0.1.1+15.04.20141118-0ubuntu1) vivid; urgency=low
303+
304+ [ Ubuntu daily release ]
305+ * New rebuild forced
306+
307+ [ Alejandro J. Cura ]
308+ * Enable purchases by default (LP: #1393410)
309+ * Remove ubuntu logo from apps scope (LP: #1391498)
310+
311+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 18 Nov 2014 15:05:15 +0000
312+
313+unity-scope-click (0.1.1+15.04.20141030-0ubuntu1) vivid; urgency=low
314+
315+ [ Alejandro J. Cura ]
316+ * Remove the background image used in the scope headers (LP: #1387216)
317+
318+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 30 Oct 2014 20:34:22 +0000
319+
320+unity-scope-click (0.1.1+14.10.20141022.1~rtm-0ubuntu1) 14.09; urgency=low
321+
322+ [ Alejandro J. Cura ]
323+ * Fetch status from download manager serially (LP: #1381101)
324+
325+ [ Rodney Dawes ]
326+ * Get the list of purchased apps from the server all at once. (LP:
327+ #1376310)
328+ * Use the locally translated title and description for application
329+ previews. (LP: #1379366)
330+ * Set attributes on the preview header for rating and purchase price.
331+ (LP: #1282460)
332+
333+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 22 Oct 2014 19:32:52 +0000
334+
335+unity-scope-click (0.1.1+14.10.20141014-0ubuntu1) 14.09; urgency=low
336+
337+ [ Ubuntu daily release ]
338+ * New rebuild forced
339+
340+ [ Sergio Schvezov ]
341+ * Updating download manager mock
342+
343+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 14 Oct 2014 17:01:09 +0000
344+
345+unity-scope-click (0.1.1+14.10.20141010-0ubuntu1) utopic; urgency=low
346+
347+ [ Ubuntu daily release ]
348+ * New rebuild forced
349+
350+ [ Rodney Dawes ]
351+ * Bump dependency on libunity-scopes-api for fixes to
352+ OnlineAccountClient API. Use online accounts API to open login UI
353+ directly. (LP: #1350093)
354+
355+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 10 Oct 2014 19:30:55 +0000
356+
357+unity-scope-click (0.1.1+14.10.20141007.1-0ubuntu1) 14.09; urgency=low
358+
359+ [ Ubuntu daily release ]
360+ * New rebuild forced
361+
362+ [ Alejandro J. Cura ]
363+ * Move app-of-the-week and editors-pick to top of results, moved
364+ scopes above apps. (LP: #1359902)
365+ * Use the new background image for the header (LP: #1378321)
366+
367+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 07 Oct 2014 13:38:11 +0000
368+
369+unity-scope-click (0.1.1+14.10.20141002-0ubuntu1) 14.09; urgency=low
370+
371+ [ Rodney Dawes ]
372+ * Add support for multiple currencies based on GeoIP suggestion from
373+ the server. (LP: #1375281)
374+
375+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 02 Oct 2014 19:21:19 +0000
376+
377+unity-scope-click (0.1.1+14.10.20140929-0ubuntu1) 14.09; urgency=low
378+
379+ [ Rodney Dawes ]
380+ * Change the "Uninstall" button to "Confirm" in confirmation preview.
381+ (LP: #1368252)
382+
383+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 29 Sep 2014 19:59:07 +0000
384+
385+unity-scope-click (0.1.1+14.10.20140925-0ubuntu1) 14.09; urgency=low
386+
387+ [ Rodney Dawes ]
388+ * Invalidate the local token and show login error when token is
389+ invalid. (LP: #1373493)
390+
391+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 25 Sep 2014 13:44:55 +0000
392+
393+unity-scope-click (0.1.1+14.10.20140915-0ubuntu1) utopic; urgency=low
394+
395+ [ Rodney Dawes ]
396+ * Define a constant for the currency symbol to use for USD, and use
397+ it. (LP: #1362716)
398+
399+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 15 Sep 2014 22:02:42 +0000
400+
401+unity-scope-click (0.1.1+14.10.20140903.1-0ubuntu1) utopic; urgency=medium
402+
403+ [ Alejandro J. Cura (alecu) ]
404+ * New upstream release.
405+ - Update apps-scope image with new one provided by design.
406+ - Do not show the subtitle for scopes cards. Only show the first attributes
407+ value (price) for scopes. (LP: #1362700)
408+ - Always install the gsettings schema to the cmake install prefix.
409+ - Depend on the native g++ package, so that cross-compiling will work.
410+ (LP: #1362745)
411+ - Split the price and rating attributes into separate lines when surfacing
412+ results. (LP: #1362629)
413+ - Refresh the store scope too on uninstall. (LP: #1328102)
414+ - Updated translations.
415+
416+ [ Ubuntu daily release ]
417+ * New rebuild forced
418+
419+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 03 Sep 2014 19:01:00 +0000
420+
421+unity-scope-click (0.1.1+14.10.20140827-0ubuntu1) utopic; urgency=medium
422+
423+ [ Alejandro J. Cura (alecu) ]
424+ * New upstream release.
425+ - Skip broken entries in click list. (LP: #1356837)
426+ - Do not push preview widgets until all data is available. (LP: #1360384)
427+ - Add the price and rating as attributes in search results. (LP: #1350561)
428+ - Improved card style for categories with just scopes. (LP: #1359900)
429+ - Updated translations.
430+
431+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 27 Aug 2014 19:09:01 +0000
432+
433 unity-scope-click (0.1.1+14.10.20140821.1-0ubuntu1) utopic; urgency=medium
434
435 [ Alejandro J. Cura (alecu) ]
436
437=== modified file 'debian/control'
438--- debian/control 2014-08-28 18:27:23 +0000
439+++ debian/control 2015-04-14 19:49:39 +0000
440@@ -17,7 +17,7 @@
441 libubuntu-download-manager-common-dev (>= 0.3+14.10.20140430-0ubuntu1),
442 libubuntuoneauth-2.0-dev,
443 libunity-api-dev (>= 7.80.7),
444- libunity-scopes-dev (>= 0.6.0),
445+ libunity-scopes-dev (>= 0.6.7~),
446 libgsettings-qt-dev,
447 pkg-config,
448 python3-all:native,
449@@ -76,13 +76,14 @@
450
451 Package: unity-scope-click-autopilot
452 Architecture: all
453-Depends: libautopilot-qt (>= 1.4),
454+Depends: autopilot-qt5 (>= 1.4+14.10.20140820),
455 python3-autopilot,
456 python3-dbus,
457 python3-dbusmock,
458 python3-fixtures,
459 python3-testscenarios,
460 python3-testtools,
461+ qttestability-autopilot,
462 ubuntu-ui-toolkit-autopilot,
463 unity8-autopilot,
464 ${misc:Depends},
465
466=== modified file 'libclickscope/click/configuration.cpp'
467--- libclickscope/click/configuration.cpp 2014-07-31 19:15:59 +0000
468+++ libclickscope/click/configuration.cpp 2015-04-14 19:49:39 +0000
469@@ -54,6 +54,18 @@
470 "zh_TW",
471 };
472
473+/* NOTE: The list of currencies we need to handle mapping symbols of.
474+ * Please keep this list in A-Z order.
475+ */
476+const std::map<const std::string, const std::string> Configuration::CURRENCY_MAP = {
477+ { "CNY", "RMB"},
478+ { "EUR", "€"},
479+ { "GBP", "₤"},
480+ { "HKD", "HK$"},
481+ { "TWD", "TW$"},
482+ { "USD", "US$"},
483+};
484+
485 std::vector<std::string> Configuration::list_folder(const std::string& folder, const std::string& pattern)
486 {
487 std::vector<std::string> result;
488@@ -115,6 +127,21 @@
489 return std::string("1") == env_value;
490 }
491
492+std::string Configuration::get_currency(const std::string& fallback)
493+{
494+ const char* env_value = std::getenv(CURRENCY_ENVVAR);
495+ if (env_value == NULL) {
496+ if (CURRENCY_MAP.count(fallback) == 0) {
497+ return CURRENCY_DEFAULT;
498+ }
499+ return fallback;
500+ }
501+ if (CURRENCY_MAP.count(env_value) == 0) {
502+ return CURRENCY_DEFAULT;
503+ }
504+ return env_value;
505+}
506+
507 std::string Configuration::get_language_base()
508 {
509 std::string language = get_language();
510
511=== modified file 'libclickscope/click/configuration.h'
512--- libclickscope/click/configuration.h 2014-08-01 05:12:28 +0000
513+++ libclickscope/click/configuration.h 2015-04-14 19:49:39 +0000
514@@ -30,6 +30,7 @@
515 #ifndef CONFIGURATION_H
516 #define CONFIGURATION_H
517
518+#include <map>
519 #include <string>
520 #include <vector>
521
522@@ -45,12 +46,16 @@
523 constexpr static const char* ARCH_ENVVAR {"U1_SEARCH_ARCH"};
524 constexpr static const char* LANGUAGE_ENVVAR {"LANGUAGE"};
525 constexpr static const char* PURCHASES_ENVVAR {"CLICK_STORE_ENABLE_PURCHASES"};
526- constexpr static const bool PURCHASES_DEFAULT = false;
527+ constexpr static const bool PURCHASES_DEFAULT = true;
528+ constexpr static const char* CURRENCY_ENVVAR {"U1_SEARCH_CURRENCY"};
529+ constexpr static const char* CURRENCY_DEFAULT {"USD"};
530+ static const std::map<const std::string, const std::string> CURRENCY_MAP;
531 static const std::vector<const char*> FULL_LANG_CODES;
532
533 virtual std::vector<std::string> get_available_frameworks();
534 virtual std::string get_architecture();
535 static bool get_purchases_enabled();
536+ static std::string get_currency(const std::string& fallback = CURRENCY_DEFAULT);
537
538 virtual std::string get_language_base();
539 virtual std::string get_language();
540
541=== modified file 'libclickscope/click/departments-db.cpp'
542--- libclickscope/click/departments-db.cpp 2014-08-19 15:34:58 +0000
543+++ libclickscope/click/departments-db.cpp 2015-04-14 19:49:39 +0000
544@@ -156,7 +156,7 @@
545 //
546 // note: this will fail due to unique constraint, but that's fine; it's expected to succeed only when new database is created; in other
547 // cases the version needs to be bumped in the update_schema.sh script.
548- query.exec("INSERT INTO meta (name, value) VALUES ('version', 3)");
549+ query.exec("INSERT INTO meta (name, value) VALUES ('version', 4)");
550
551 if (!db_.commit())
552 {
553@@ -221,12 +221,8 @@
554 while (select_children_depts_->next())
555 {
556 auto const child_id = select_children_depts_->value(0).toString().toStdString();
557- // only return child department if it's not empty
558- if (!is_empty(child_id))
559- {
560- const DepartmentInfo inf(child_id, select_children_depts_->value(1).toBool());
561- depts.push_back(inf);
562- }
563+ const DepartmentInfo inf(child_id, select_children_depts_->value(1).toBool());
564+ depts.push_back(inf);
565 }
566
567 select_children_depts_->finish();
568
569=== modified file 'libclickscope/click/download-manager.cpp'
570--- libclickscope/click/download-manager.cpp 2014-08-22 17:59:09 +0000
571+++ libclickscope/click/download-manager.cpp 2015-04-14 19:49:39 +0000
572@@ -73,6 +73,11 @@
573 credentialsService->getCredentials();
574 }
575
576+ void invalidateCredentialsFromService()
577+ {
578+ credentialsService->invalidateCredentials();
579+ }
580+
581 QSharedPointer<click::network::AccessManager> nam;
582 QSharedPointer<click::CredentialsService> credentialsService;
583 QSharedPointer<udm::Manager> systemDownloadManager;
584@@ -248,9 +253,19 @@
585
586 void click::DownloadManager::handleNetworkError(QNetworkReply::NetworkError error)
587 {
588- qDebug() << "error in network request for click token: " << error << impl->reply->errorString();
589+ switch (error) {
590+ case QNetworkReply::ContentAccessDenied:
591+ case QNetworkReply::ContentOperationNotPermittedError:
592+ case QNetworkReply::AuthenticationRequiredError:
593+ impl->invalidateCredentialsFromService();
594+ emit credentialsNotFound();
595+ break;
596+ default:
597+ qDebug() << "error in network request for click token: " << error << impl->reply->errorString();
598+ emit clickTokenFetchError(QString("Network Error"));
599+ break;
600+ }
601 impl->reply.reset();
602- emit clickTokenFetchError(QString("Network Error"));
603 }
604
605 void click::DownloadManager::getAllDownloadsWithMetadata(const QString &key, const QString &value,
606
607=== modified file 'libclickscope/click/highlights.cpp'
608--- libclickscope/click/highlights.cpp 2014-08-21 21:15:13 +0000
609+++ libclickscope/click/highlights.cpp 2015-04-14 19:49:39 +0000
610@@ -85,7 +85,12 @@
611 auto slug = item[Highlight::JsonKeys::slug].asString();
612 auto pkg_node = item[Package::JsonKeys::embedded][Package::JsonKeys::ci_package];
613 auto pkgs = package_list_from_json_node(pkg_node);
614- highlights.push_back(Highlight(slug, name, pkgs));
615+ auto hl = Highlight(slug, name, pkgs);
616+ if (slug == "app-of-the-week" || slug == "editors-pick") {
617+ highlights.push_front(hl);
618+ } else {
619+ highlights.push_back(hl);
620+ }
621 }
622 }
623
624@@ -118,12 +123,13 @@
625 }
626 }
627
628+ if (scopes.size() > 0) {
629+ highlights.push_back(Highlight("__all-scopes__", _("Scopes"), scopes, true));
630+ }
631+
632 if (apps.size() > 0) {
633 highlights.push_back(Highlight("__all-apps__", _("Apps"), apps));
634 }
635- if (scopes.size() > 0) {
636- highlights.push_back(Highlight("__all-scopes__", _("Scopes"), scopes, true));
637- }
638 }
639 }
640
641
642=== modified file 'libclickscope/click/index.cpp'
643--- libclickscope/click/index.cpp 2014-09-01 21:23:11 +0000
644+++ libclickscope/click/index.cpp 2015-04-14 19:49:39 +0000
645@@ -180,6 +180,13 @@
646 Json::Reader reader;
647 Json::Value root;
648
649+ // Get the suggested currency from the store.
650+ if (response->has_header(CURRENCY_HEADER)) {
651+ m_suggested_currency = response->get_header(CURRENCY_HEADER);
652+ } else {
653+ m_suggested_currency = Configuration::CURRENCY_DEFAULT;
654+ }
655+
656 click::DepartmentList depts;
657 click::HighlightList highlights;
658 if (reader.parse(reply.toUtf8().constData(), root)) {
659@@ -218,6 +225,11 @@
660 return click::web::Cancellable(response);
661 }
662
663+std::string Index::get_suggested_currency() const
664+{
665+ return m_suggested_currency;
666+}
667+
668 std::string Index::get_base_url ()
669 {
670 const char *env_url = getenv(SEARCH_BASE_URL_ENVVAR.c_str());
671
672=== modified file 'libclickscope/click/index.h'
673--- libclickscope/click/index.h 2014-06-26 18:57:08 +0000
674+++ libclickscope/click/index.h 2015-04-14 19:49:39 +0000
675@@ -54,6 +54,7 @@
676 const std::string QUERY_ARGNAME = "q";
677 const std::string ARCHITECTURE = "architecture:";
678 const std::string DETAILS_PATH = "api/v1/package/";
679+const std::string CURRENCY_HEADER = "X-Suggested-Currency";
680
681 class PackageManager
682 {
683@@ -67,11 +68,13 @@
684 protected:
685 QSharedPointer<web::Client> client;
686 QSharedPointer<Configuration> configuration;
687+ std::string m_suggested_currency;
688 virtual std::string build_index_query(const std::string& query, const std::string& department);
689 virtual std::map<std::string, std::string> build_headers();
690
691 public:
692 enum class Error {NoError, CredentialsError, NetworkError};
693+ Index() {}
694 Index(const QSharedPointer<click::web::Client>& client,
695 const QSharedPointer<Configuration> configuration=QSharedPointer<Configuration>(new Configuration()));
696 virtual std::pair<Packages, Packages> package_lists_from_json(const std::string& json);
697@@ -81,6 +84,7 @@
698 virtual click::web::Cancellable departments(const std::string& department_href, std::function<void(const DepartmentList&, const HighlightList&, Error, int)> callback);
699 virtual ~Index();
700
701+ virtual std::string get_suggested_currency() const;
702 static std::string get_base_url ();
703 };
704
705
706=== modified file 'libclickscope/click/interface.cpp'
707--- libclickscope/click/interface.cpp 2014-08-26 14:57:03 +0000
708+++ libclickscope/click/interface.cpp 2015-04-14 19:49:39 +0000
709@@ -182,17 +182,16 @@
710 QStringList id = app_id.split("_", QString::SkipEmptyParts);
711 app.name = id[0].toUtf8().data();
712 app.version = id[2].toUtf8().data();
713- } else {
714- if (keyFile.has_key(DESKTOP_FILE_GROUP, DESKTOP_FILE_COMMENT)) {
715- app.description = get_translated_string(keyFile,
716- DESKTOP_FILE_GROUP,
717- DESKTOP_FILE_COMMENT,
718- domain);
719- }
720- if (keyFile.has_key(DESKTOP_FILE_GROUP, DESKTOP_FILE_SCREENSHOT)) {
721- app.main_screenshot = keyFile.get_string(DESKTOP_FILE_GROUP,
722- DESKTOP_FILE_SCREENSHOT);
723- }
724+ }
725+ if (keyFile.has_key(DESKTOP_FILE_GROUP, DESKTOP_FILE_COMMENT)) {
726+ app.description = get_translated_string(keyFile,
727+ DESKTOP_FILE_GROUP,
728+ DESKTOP_FILE_COMMENT,
729+ domain);
730+ }
731+ if (keyFile.has_key(DESKTOP_FILE_GROUP, DESKTOP_FILE_SCREENSHOT)) {
732+ app.main_screenshot = keyFile.get_string(DESKTOP_FILE_GROUP,
733+ DESKTOP_FILE_SCREENSHOT);
734 }
735 return app;
736 }
737
738=== modified file 'libclickscope/click/package.cpp'
739--- libclickscope/click/package.cpp 2014-08-22 19:53:38 +0000
740+++ libclickscope/click/package.cpp 2015-04-14 19:49:39 +0000
741@@ -81,11 +81,19 @@
742 p.name = item[Package::JsonKeys::name].asString();
743 p.title = item[Package::JsonKeys::title].asString();
744 p.price = item[Package::JsonKeys::price].asDouble();
745+ if (item.isMember(Package::JsonKeys::prices)) {
746+ auto prices = item[Package::JsonKeys::prices];
747+ auto currencies = prices.getMemberNames();
748+ foreach (auto currency, currencies) {
749+ p.prices[currency] = prices[currency].asDouble();
750+ }
751+ }
752 p.icon_url = item[Package::JsonKeys::icon_url].asString();
753 p.url = item[Package::JsonKeys::links][Package::JsonKeys::self][Package::JsonKeys::href].asString();
754 p.content = item[Package::JsonKeys::content].asString();
755 p.publisher = item[Package::JsonKeys::publisher].asString();
756 p.rating = item[Package::JsonKeys::rating].asDouble();
757+ p.version = item[Package::JsonKeys::version].asString();
758 return p;
759 }
760
761@@ -133,6 +141,13 @@
762 details.package.title = root[Package::JsonKeys::title].asString();
763 details.package.icon_url = root[Package::JsonKeys::icon_url].asString();
764 details.package.price = root[Package::JsonKeys::price].asDouble();
765+ if (root.isMember(Package::JsonKeys::prices)) {
766+ auto prices = root[Package::JsonKeys::prices];
767+ auto currencies = prices.getMemberNames();
768+ foreach (auto currency, currencies) {
769+ details.package.prices[currency] = prices[currency].asDouble();
770+ }
771+ }
772 details.description = root[JsonKeys::description].asString();
773 details.download_url = root[JsonKeys::download_url].asString();
774 details.license = root[JsonKeys::license].asString();
775
776=== modified file 'libclickscope/click/package.h'
777--- libclickscope/click/package.h 2014-08-22 19:53:38 +0000
778+++ libclickscope/click/package.h 2015-04-14 19:49:39 +0000
779@@ -63,6 +63,10 @@
780 constexpr static const char* content{"content"};
781 constexpr static const char* publisher{"publisher"};
782 constexpr static const char* rating{"ratings_average"};
783+ constexpr static const char* version{"version"};
784+
785+ // NOTE: The "price" field is deprecated in favor of "prices"
786+ constexpr static const char* prices{"prices"};
787 };
788
789 Package() = default;
790@@ -102,6 +106,7 @@
791 double rating;
792 void matches (std::string query, std::function<bool> callback);
793 std::string content;
794+ std::map<std::string, double> prices;
795
796 struct hash_name {
797 public :
798
799=== modified file 'libclickscope/click/preview.cpp'
800--- libclickscope/click/preview.cpp 2014-08-26 15:16:25 +0000
801+++ libclickscope/click/preview.cpp 2015-04-14 19:49:39 +0000
802@@ -184,7 +184,9 @@
803 }
804
805 PreviewStrategy::PreviewStrategy(const unity::scopes::Result& result)
806- : result(result)
807+ : result(result),
808+ oa_client("ubuntuone", "ubuntuone", "ubuntuone",
809+ scopes::OnlineAccountClient::MainLoopSelect::CreateInternalMainLoop)
810 {
811 }
812
813@@ -193,7 +195,9 @@
814 result(result),
815 client(client),
816 index(new click::Index(client)),
817- reviews(new click::Reviews(client))
818+ reviews(new click::Reviews(client)),
819+ oa_client("ubuntuone", "ubuntuone", "ubuntuone",
820+ scopes::OnlineAccountClient::MainLoopSelect::CreateInternalMainLoop)
821 {
822 }
823
824@@ -218,28 +222,32 @@
825 submit_operation.cancel();
826 }
827
828-// TODO: replace this big string with a TABLE widget
829-std::string PreviewStrategy::build_other_metadata(const PackageDetails &details)
830+scopes::PreviewWidget PreviewStrategy::build_other_metadata(const PackageDetails &details)
831 {
832- std::stringstream b;
833- b << _("Publisher/Creator") << ": " << details.publisher << std::endl;
834- b << _("Seller") << ": " << details.company_name << std::endl;
835- b << _("Website") << ": " << details.website << std::endl;
836- b << _("Contact") << ": " << details.support_url << std::endl;
837- b << _("License") << ": " << details.license << std::endl;
838- return b.str();
839+ scopes::PreviewWidget widget("other_metadata", "table");
840+ scopes::VariantArray values {
841+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Publisher/Creator")}, scopes::Variant{details.publisher}}},
842+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Seller")}, scopes::Variant{details.company_name}}},
843+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Website")}, scopes::Variant{details.website}}},
844+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Contact")}, scopes::Variant{details.support_url}}},
845+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("License")}, scopes::Variant{details.license}}},
846+ };
847+ widget.add_attribute_value("values", scopes::Variant(values));
848+ return widget;
849 }
850
851-// TODO: replace this big string with a TABLE widget
852-std::string PreviewStrategy::build_updates_table(const PackageDetails& details)
853+scopes::PreviewWidget PreviewStrategy::build_updates_table(const PackageDetails& details)
854 {
855- std::stringstream b;
856- b << _("Version number") << ": " << details.version << std::endl;
857- b << _("Last updated") << ": " << details.last_updated.formatted() << std::endl;
858- b << _("First released") << ": " << details.date_published.formatted() << std::endl;
859- b << _("Size") << ": " <<
860- click::Formatter::human_readable_filesize(details.binary_filesize) << std::endl;
861- return b.str();
862+ scopes::PreviewWidget widget("updates_table", "table");
863+ widget.add_attribute_value("title", scopes::Variant{_("Updates")});
864+ scopes::VariantArray values {
865+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Version number")}, scopes::Variant{details.version}}},
866+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Last updated")}, scopes::Variant{details.last_updated.formatted()}}},
867+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("First released")}, scopes::Variant{details.date_published.formatted()}}},
868+ scopes::Variant{scopes::VariantArray{scopes::Variant{_("Size")}, scopes::Variant{click::Formatter::human_readable_filesize(details.binary_filesize)}}},
869+ };
870+ widget.add_attribute_value("values", scopes::Variant(values));
871+ return widget;
872 }
873
874 std::string PreviewStrategy::build_whats_new(const PackageDetails& details)
875@@ -250,6 +258,22 @@
876 return b.str();
877 }
878
879+void PreviewStrategy::run_under_qt(const std::function<void ()> &task)
880+{
881+ qt::core::world::enter_with_task([task]() {
882+ task();
883+ });
884+}
885+
886+std::string get_string_maybe_null(scopes::Variant variant)
887+{
888+ if (variant.is_null()) {
889+ return "";
890+ } else {
891+ return variant.get_string();
892+ }
893+}
894+
895 // TODO: error handling - once get_details provides errors, we can
896 // return them from populateDetails and check them in the calling code
897 // to decide whether to show error widgets. see bug LP: #1289541
898@@ -258,15 +282,15 @@
899 click::Reviews::Error)> reviews_callback)
900 {
901
902- std::string app_name = result["name"].get_string();
903+ std::string app_name = get_string_maybe_null(result["name"]);
904
905 if (app_name.empty()) {
906 click::PackageDetails details;
907 qDebug() << "in populateDetails(), app_name is empty";
908 details.package.title = result.title();
909 details.package.icon_url = result.art();
910- details.description = result["description"].get_string();
911- details.main_screenshot_url = result["main_screenshot"].get_string();
912+ details.description = get_string_maybe_null(result["description"]);
913+ details.main_screenshot_url = get_string_maybe_null(result["main_screenshot"]);
914 details_callback(details);
915 reviews_callback(click::ReviewList(), click::Reviews::Error::NoError);
916 } else {
917@@ -274,7 +298,7 @@
918 // I think this should not be required when we switch the click::Index over
919 // to using the Qt bridge. With that, the qt dependency becomes an implementation detail
920 // and code using it does not need to worry about threading/event loop topics.
921- qt::core::world::enter_with_task([this, details_callback, reviews_callback, app_name]()
922+ run_under_qt([this, details_callback, reviews_callback, app_name]()
923 {
924 index_operation = index->get_details(app_name, [this, app_name, details_callback, reviews_callback](PackageDetails details, click::Index::Error error){
925 if(error == click::Index::Error::NoError) {
926@@ -285,8 +309,8 @@
927 click::PackageDetails details;
928 details.package.title = result.title();
929 details.package.icon_url = result.art();
930- details.description = result["description"].get_string();
931- details.main_screenshot_url = result["main_screenshot"].get_string();
932+ details.description = get_string_maybe_null(result["description"]);
933+ details.main_screenshot_url = get_string_maybe_null(result["main_screenshot"]);
934 details_callback(details);
935 }
936 reviews_operation = reviews->fetch_reviews(app_name,
937@@ -327,16 +351,53 @@
938 scopes::PreviewWidgetList widgets;
939
940 scopes::PreviewWidget header("hdr", "header");
941- header.add_attribute_value("title", scopes::Variant(details.package.title));
942+ header.add_attribute_value("title", scopes::Variant(result.title()));
943 if (!details.publisher.empty())
944 {
945 header.add_attribute_value("subtitle", scopes::Variant(details.publisher));
946 }
947 if (!details.package.icon_url.empty())
948+ {
949 header.add_attribute_value("mascot", scopes::Variant(details.package.icon_url));
950+ }
951+
952+ if (result.contains("price_area") && result.contains("rating"))
953+ {
954+ // Add the price and rating as attributes.
955+ bool purchased = result["purchased"].get_bool();
956+ std::string price_area{""};
957+
958+ if (details.package.price == 0.00f)
959+ {
960+ price_area = _("FREE");
961+ }
962+ else if (purchased)
963+ {
964+ price_area = _("✔ PURCHASED");
965+ }
966+ else
967+ {
968+ price_area = result["price_area"].get_string();
969+ }
970+ scopes::VariantBuilder builder;
971+ builder.add_tuple({
972+ {"value", scopes::Variant(price_area)},
973+ });
974+ builder.add_tuple({
975+ {"value", scopes::Variant("")},
976+ });
977+ builder.add_tuple({
978+ {"value", result["rating"]},
979+ });
980+ builder.add_tuple({
981+ {"value", scopes::Variant("")},
982+ });
983+ header.add_attribute_value("attributes", builder.end());
984+ }
985+
986 widgets.push_back(header);
987
988- qDebug() << "Pushed widgets for package:" << QString::fromStdString(details.package.title);
989+ qDebug() << "Pushed widgets for package:" << QString::fromStdString(details.package.name);
990 return widgets;
991 }
992
993@@ -347,20 +408,21 @@
994 {
995 scopes::PreviewWidget summary("summary", "text");
996 summary.add_attribute_value("title", scopes::Variant(_("Info")));
997- summary.add_attribute_value("text", scopes::Variant(details.description));
998+ if (result.contains("description") && !result["description"].get_string().empty())
999+ {
1000+ summary.add_attribute_value("text", scopes::Variant(result["description"].get_string()));
1001+ }
1002+ else
1003+ {
1004+ summary.add_attribute_value("text", scopes::Variant(details.description));
1005+ }
1006 widgets.push_back(summary);
1007 }
1008
1009 if (!details.download_url.empty())
1010 {
1011- scopes::PreviewWidget other_metadata("other_metadata", "text");
1012- other_metadata.add_attribute_value("text", scopes::Variant(build_other_metadata(details)));
1013- widgets.push_back(other_metadata);
1014-
1015- scopes::PreviewWidget updates("updates", "text");
1016- updates.add_attribute_value("title", scopes::Variant(_("Updates")));
1017- updates.add_attribute_value("text", scopes::Variant(build_updates_table(details)));
1018- widgets.push_back(updates);
1019+ widgets.push_back(build_other_metadata(details));
1020+ widgets.push_back(build_updates_table(details));
1021
1022 scopes::PreviewWidget whats_new("whats_new", "text");
1023 whats_new.add_attribute_value("title", scopes::Variant(_("What's new")));
1024@@ -404,13 +466,29 @@
1025 scopes::Variant(_("Close")));
1026 }
1027
1028-scopes::PreviewWidgetList PreviewStrategy::loginErrorWidgets()
1029+scopes::PreviewWidgetList PreviewStrategy::loginErrorWidgets(const PackageDetails& details)
1030 {
1031- return errorWidgets(scopes::Variant(_("Login Error")),
1032- scopes::Variant(_("Please log in to your Ubuntu One account.")),
1033- scopes::Variant(click::Preview::Actions::OPEN_ACCOUNTS),
1034- scopes::Variant(_("Go to Accounts")),
1035- scopes::Variant("settings:///system/online-accounts"));
1036+ auto widgets = errorWidgets(scopes::Variant(_("Login Error")),
1037+ scopes::Variant(_("Please log in to your Ubuntu One account.")),
1038+ scopes::Variant(click::Preview::Actions::INSTALL_CLICK),
1039+ scopes::Variant(_("Go to Accounts")));
1040+ auto buttons = widgets.back();
1041+ widgets.pop_back();
1042+
1043+ scopes::VariantBuilder builder;
1044+ builder.add_tuple(
1045+ {
1046+ {"id", scopes::Variant(click::Preview::Actions::INSTALL_CLICK)},
1047+ {"label", scopes::Variant(_("Go to Accounts"))},
1048+ {"download_url", scopes::Variant(details.download_url)},
1049+ {"download_sha512", scopes::Variant(details.download_sha512)},
1050+ });
1051+ buttons.add_attribute_value("actions", builder.end());
1052+ oa_client.register_account_login_item(buttons,
1053+ scopes::OnlineAccountClient::PostLoginAction::ContinueActivation,
1054+ scopes::OnlineAccountClient::PostLoginAction::DoNothing);
1055+ widgets.push_back(buttons);
1056+ return widgets;
1057 }
1058
1059 scopes::PreviewWidgetList PreviewStrategy::errorWidgets(const scopes::Variant& title,
1060@@ -497,30 +575,36 @@
1061 downloader->startDownload(download_url, download_sha512, result["name"].get_string(),
1062 [this, reply] (std::pair<std::string, click::InstallError> rc){
1063 // NOTE: details not needed by fooErrorWidgets, so no need to populateDetails():
1064+ bool login_error = false;
1065 switch (rc.second)
1066 {
1067- case InstallError::CredentialsError:
1068- qWarning() << "InstallingPreview got error in getting credentials during startDownload";
1069- reply->push(loginErrorWidgets());
1070- return;
1071 case InstallError::DownloadInstallError:
1072 qWarning() << "Error received from UDM during startDownload:" << rc.first.c_str();
1073 reply->push(downloadErrorWidgets());
1074 return;
1075+ case InstallError::CredentialsError:
1076+ qWarning() << "InstallingPreview got error in getting credentials during startDownload";
1077+ login_error = true;
1078 default:
1079 std::string object_path = rc.first;
1080 qDebug() << "Successfully created UDM Download.";
1081- populateDetails([this, reply, object_path](const PackageDetails &details) {
1082+ populateDetails([this, reply, object_path, login_error](const PackageDetails &details) {
1083 store_department(details);
1084- pushPackagePreviewWidgets(reply, details, progressBarWidget(object_path));
1085- startLauncherAnimation(details);
1086+ if (login_error) {
1087+ reply->push(loginErrorWidgets(details));
1088+ } else {
1089+ pushPackagePreviewWidgets(reply, details, progressBarWidget(object_path));
1090+ startLauncherAnimation(details);
1091+ }
1092 },
1093- [this, reply](const ReviewList& reviewlist,
1094+ [this, reply, login_error](const ReviewList& reviewlist,
1095 click::Reviews::Error error) {
1096- if (error == click::Reviews::Error::NoError) {
1097- reply->push(reviewsWidgets(reviewlist));
1098- } else {
1099- qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
1100+ if (!login_error) {
1101+ if (error == click::Reviews::Error::NoError) {
1102+ reply->push(reviewsWidgets(reviewlist));
1103+ } else {
1104+ qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
1105+ }
1106 }
1107 reply->finished();
1108 });
1109@@ -800,7 +884,7 @@
1110 });
1111 builder.add_tuple({
1112 {"id", scopes::Variant(click::Preview::Actions::CONFIRM_UNINSTALL)},
1113- {"label", scopes::Variant(_("Uninstall"))}
1114+ {"label", scopes::Variant(_("Confirm"))}
1115 });
1116 buttons.add_attribute_value("actions", builder.end());
1117 widgets.push_back(buttons);
1118@@ -836,45 +920,48 @@
1119 populateDetails([this, reply](const PackageDetails &details){
1120 store_department(details);
1121 found_details = details;
1122+ },
1123+ [this, reply](const ReviewList& reviewlist,
1124+ click::Reviews::Error reviewserror) {
1125 std::string app_name = result["name"].get_string();
1126 get_downloader(nam)->get_download_progress(app_name,
1127- [this, reply](std::string object_path){
1128+ [this, reply, reviewlist, reviewserror](std::string object_path){
1129 found_object_path = object_path;
1130+ scopes::PreviewWidgetList button_widgets;
1131+ if(found_object_path.empty()) {
1132+ button_widgets = uninstalledActionButtonWidgets(found_details);
1133+ } else {
1134+ button_widgets = progressBarWidget(found_object_path);
1135+ }
1136+ pushPackagePreviewWidgets(reply, found_details, button_widgets);
1137+ if (reviewserror == click::Reviews::Error::NoError) {
1138+ reply->push(reviewsWidgets(reviewlist));
1139+ } else {
1140+ qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
1141+ }
1142+ reply->finished();
1143+ qDebug() << "---------- Finished reply for:" << result["name"].get_string().c_str();
1144 });
1145- },
1146- [this, reply](const ReviewList& reviewlist,
1147- click::Reviews::Error error) {
1148- scopes::PreviewWidgetList button_widgets;
1149- if(found_object_path.empty()) {
1150- button_widgets = uninstalledActionButtonWidgets(found_details);
1151- } else {
1152- button_widgets = progressBarWidget(found_object_path);
1153- }
1154- pushPackagePreviewWidgets(reply, found_details, button_widgets);
1155- if (error == click::Reviews::Error::NoError) {
1156- reply->push(reviewsWidgets(reviewlist));
1157- } else {
1158- qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
1159- }
1160- reply->finished();
1161- qDebug() << "---------- Finished reply for:" << result["name"].get_string().c_str();
1162 });
1163 }
1164
1165 scopes::PreviewWidgetList UninstalledPreview::uninstalledActionButtonWidgets(const PackageDetails &details)
1166 {
1167 scopes::PreviewWidgetList widgets;
1168- if (details.package.price > double(0.00)
1169+ auto price = result["price"].get_double();
1170+
1171+ if (price > double(0.00)
1172 && result["purchased"].get_bool() == false) {
1173 scopes::PreviewWidget payments("purchase", "payments");
1174 scopes::VariantMap tuple;
1175- tuple["currency"] = "$";
1176- qDebug() << "Price is" << details.package.price;
1177- tuple["price"] = scopes::Variant(details.package.price);
1178+ tuple["currency"] = result["currency_symbol"].get_string();
1179+ tuple["price"] = scopes::Variant(price);
1180 tuple["store_item_id"] = details.package.name;
1181 tuple["download_url"] = details.download_url;
1182 tuple["download_sha512"] = details.download_sha512;
1183 payments.add_attribute_value("source", scopes::Variant(tuple));
1184+ // NOTE: No need to connect payments button to online-accounts API
1185+ // here, as pay-ui will take care of any login needs.
1186 widgets.push_back(payments);
1187 } else {
1188 scopes::PreviewWidget buttons("buttons", "actions");
1189@@ -887,6 +974,9 @@
1190 {"download_sha512", scopes::Variant(details.download_sha512)},
1191 });
1192 buttons.add_attribute_value("actions", builder.end());
1193+ oa_client.register_account_login_item(buttons,
1194+ scopes::OnlineAccountClient::PostLoginAction::ContinueActivation,
1195+ scopes::OnlineAccountClient::PostLoginAction::DoNothing);
1196 widgets.push_back(buttons);
1197 }
1198
1199
1200=== modified file 'libclickscope/click/preview.h'
1201--- libclickscope/click/preview.h 2014-08-26 14:21:42 +0000
1202+++ libclickscope/click/preview.h 2015-04-14 19:49:39 +0000
1203@@ -38,6 +38,7 @@
1204 #include <click/network_access_manager.h>
1205
1206 #include <unity/scopes/ActionMetadata.h>
1207+#include <unity/scopes/OnlineAccountClient.h>
1208 #include <unity/scopes/PreviewQueryBase.h>
1209 #include <unity/scopes/PreviewWidget.h>
1210 #include <unity/scopes/Result.h>
1211@@ -136,7 +137,7 @@
1212 virtual scopes::PreviewWidgetList progressBarWidget(const std::string& object_path);
1213 virtual scopes::PreviewWidgetList reviewsWidgets(const click::ReviewList &reviewlist);
1214 virtual scopes::PreviewWidgetList downloadErrorWidgets();
1215- virtual scopes::PreviewWidgetList loginErrorWidgets();
1216+ virtual scopes::PreviewWidgetList loginErrorWidgets(const PackageDetails& details);
1217 virtual scopes::PreviewWidgetList errorWidgets(const scopes::Variant& title,
1218 const scopes::Variant& subtitle,
1219 const scopes::Variant& action_id,
1220@@ -145,9 +146,10 @@
1221 virtual void pushPackagePreviewWidgets(const unity::scopes::PreviewReplyProxy &reply,
1222 const PackageDetails& details,
1223 const scopes::PreviewWidgetList& button_area_widgets);
1224- virtual std::string build_other_metadata(const PackageDetails& details);
1225- virtual std::string build_updates_table(const PackageDetails& details);
1226+ virtual scopes::PreviewWidget build_other_metadata(const PackageDetails& details);
1227+ virtual scopes::PreviewWidget build_updates_table(const PackageDetails& details);
1228 virtual std::string build_whats_new(const PackageDetails& details);
1229+ virtual void run_under_qt(const std::function<void ()> &task);
1230
1231 scopes::Result result;
1232 QSharedPointer<click::web::Client> client;
1233@@ -156,6 +158,7 @@
1234 QSharedPointer<click::Reviews> reviews;
1235 click::web::Cancellable reviews_operation;
1236 click::web::Cancellable submit_operation;
1237+ scopes::OnlineAccountClient oa_client;
1238 };
1239
1240 class DownloadErrorPreview : public PreviewStrategy
1241
1242=== modified file 'libclickscope/click/reviews.h'
1243--- libclickscope/click/reviews.h 2014-05-26 14:02:45 +0000
1244+++ libclickscope/click/reviews.h 2015-04-14 19:49:39 +0000
1245@@ -72,10 +72,11 @@
1246 QSharedPointer<web::Client> client;
1247 public:
1248 enum class Error {NoError, CredentialsError, NetworkError};
1249+ Reviews() {}
1250 Reviews(const QSharedPointer<click::web::Client>& client);
1251 virtual ~Reviews();
1252
1253- click::web::Cancellable fetch_reviews (const std::string& package_name,
1254+ virtual click::web::Cancellable fetch_reviews (const std::string& package_name,
1255 std::function<void(ReviewList, Error)> callback);
1256 click::web::Cancellable submit_review (const Review& review,
1257 std::function<void(Error)> callback);
1258
1259=== modified file 'libclickscope/click/webclient.cpp'
1260--- libclickscope/click/webclient.cpp 2014-08-26 15:16:25 +0000
1261+++ libclickscope/click/webclient.cpp 2015-04-14 19:49:39 +0000
1262@@ -131,9 +131,10 @@
1263 doConnect();
1264 });
1265 sc.connect(impl->sso.data(), &click::CredentialsService::credentialsNotFound,
1266- [this]() {
1267- // TODO: Need to handle and propagate error conditons.
1268+ [=]() {
1269 impl->sso.clear();
1270+ qWarning() << "Signing reuested but no credentials found. Using unsigned URL.";
1271+ doConnect();
1272 });
1273 // TODO: Need to handle error signal once in CredentialsService.
1274 impl->sso->getCredentials();
1275@@ -169,6 +170,16 @@
1276 [this](QNetworkReply::NetworkError err){errorHandler(err);});
1277 }
1278
1279+bool click::web::Response::has_header(const std::string& header) const
1280+{
1281+ return reply->hasRawHeader(header.c_str());
1282+}
1283+
1284+std::string click::web::Response::get_header(const std::string& header) const
1285+{
1286+ return reply->rawHeader(header.c_str()).toUtf8().data();
1287+}
1288+
1289 void click::web::Response::replyFinished()
1290 {
1291 auto response = reply->readAll();
1292
1293=== modified file 'libclickscope/click/webclient.h'
1294--- libclickscope/click/webclient.h 2014-06-17 13:56:26 +0000
1295+++ libclickscope/click/webclient.h 2015-04-14 19:49:39 +0000
1296@@ -78,6 +78,9 @@
1297 virtual void abort();
1298 virtual ~Response();
1299
1300+ virtual bool has_header(const std::string& header) const;
1301+ virtual std::string get_header(const std::string& header) const;
1302+
1303 public slots:
1304 void replyFinished();
1305 void errorHandler(QNetworkReply::NetworkError network_error);
1306
1307=== modified file 'libclickscope/tests/fake_json.cpp'
1308--- libclickscope/tests/fake_json.cpp 2014-08-20 20:45:48 +0000
1309+++ libclickscope/tests/fake_json.cpp 2015-04-14 19:49:39 +0000
1310@@ -56,9 +56,15 @@
1311 {
1312 "name": "org.example.awesomelauncher",
1313 "title": "Awesome Launcher",
1314+ "version": "0.83b",
1315 "description": "This is an awesome launcher.",
1316 "price": 1.99,
1317 "icon_url": "http://software-center.ubuntu.com/site_media/appmedia/2012/09/SPAZ.png",
1318+ "prices": {
1319+ "USD": 1.99,
1320+ "EUR": 1.69,
1321+ "GBP": 1.29
1322+ },
1323 "_links": {
1324 "self": {
1325 "href": "http://search.apps.ubuntu.com/api/v1/package/org.example.awesomelauncher"
1326@@ -216,6 +222,11 @@
1327
1328 "framework": "None",
1329 "price": 1.99,
1330+ "prices": {
1331+ "USD": 1.99,
1332+ "EUR": 1.69,
1333+ "GBP": 1.29
1334+ },
1335 "license_key_path": "",
1336 "click_version": "0.1",
1337 "icon_urls": {
1338@@ -721,6 +732,150 @@
1339 }
1340 },
1341 "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
1342+ }
1343+ ]
1344+ },
1345+ "_links": {
1346+ "self": {
1347+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/new-releases"
1348+ }
1349+ },
1350+ "name": "Editor's Pick",
1351+ "slug": "editors-pick"
1352+ }
1353+ ]
1354+ }, "has_children": true,
1355+ "_links": {
1356+ "curies": [
1357+ {
1358+ "href": "https://search.apps.staging.ubuntu.com/docs/v1/relations.html{#rel}",
1359+ "name": "clickindex", "templated": true
1360+ }
1361+ ],
1362+ "self": {
1363+ "href": "https://search.apps.staging.ubuntu.com/api/v1/departments/fake-department-with-subdepartments"
1364+ },
1365+ "collection": {
1366+ "href": "https://search.apps.staging.ubuntu.com/api/v1/departments"
1367+ }
1368+ },
1369+ "name": "Fake Department With Subdepartments",
1370+ "slug": "fake-department-with-subdepartments"
1371+ })";
1372+
1373+const std::string FAKE_JSON_STORE_HOME = R"(
1374+ {
1375+ "_embedded": {
1376+ "clickindex:package": [
1377+ {
1378+ "publisher": "Awesome Widget Company",
1379+ "name": "org.example.awesomelauncher",
1380+ "title": "Awesome Launcher",
1381+ "price": 1.99,
1382+ "_links": {
1383+ "self": {
1384+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
1385+ }
1386+ },
1387+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
1388+ },
1389+ {
1390+ "publisher": "Awesome Widget Company",
1391+ "name": "org.example.awesomewidget",
1392+ "title": "Awesome Widget",
1393+ "price": 1.99,
1394+ "_links": {
1395+ "self": {
1396+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
1397+ }
1398+ },
1399+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
1400+ },
1401+ {
1402+ "publisher": "Awesome Widget Company",
1403+ "name": "org.example.awesomescope",
1404+ "title": "Awesome Scope",
1405+ "price": 1.99,
1406+ "content": "scope",
1407+ "_links": {
1408+ "self": {
1409+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomescope"
1410+ }
1411+ },
1412+ "icon": "http://example.org/media/org.example.awesomescope/icons/icon16.png"
1413+ },
1414+ {
1415+ "publisher": "Awesome Widget Company",
1416+ "name": "org.example.awesomescope2",
1417+ "title": "Awesome Scope 2.0",
1418+ "price": 1.99,
1419+ "content": "scope",
1420+ "_links": {
1421+ "self": {
1422+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomescope2"
1423+ }
1424+ },
1425+ "icon": "http://example.org/media/org.example.awesomescope2/icons/icon16.png"
1426+ }
1427+ ],
1428+
1429+ "clickindex:department": [
1430+ {
1431+ "has_children": false,
1432+ "_links": {
1433+ "self": {
1434+ "href": "https://search.apps.staging.ubuntu.com/api/v1/departments/fake-subdepartment"}
1435+ },
1436+ "name": "Fake Subdepartment", "slug": "fake-subdepartment"}
1437+ ],
1438+ "clickindex:highlight": [
1439+ {
1440+ "_embedded": {
1441+ "clickindex:package": [
1442+ {
1443+ "publisher": "Awesome Widget Company",
1444+ "name": "org.example.awesomelauncher",
1445+ "title": "Awesome Launcher",
1446+ "price": 1.99,
1447+ "_links": {
1448+ "self": {
1449+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"}
1450+ },
1451+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
1452+ },
1453+ {
1454+ "publisher": "Awesome Widget Company",
1455+ "name": "org.example.awesomewidget",
1456+ "title": "Awesome Widget", "price": 1.99,
1457+ "_links": {
1458+ "self": {
1459+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomewidget"
1460+ }
1461+ },
1462+ "icon": "http://example.org/media/org.example.awesomewidget/icons/icon16.png"}
1463+ ]
1464+ },
1465+ "_links": {
1466+ "self": {
1467+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/top-apps"
1468+ }
1469+ },
1470+ "name": "Top Apps", "slug": "top-apps"
1471+ },
1472+ {
1473+ "_embedded": {
1474+ "clickindex:package": [
1475+ {
1476+ "publisher": "Awesome Widget Company",
1477+ "name": "org.example.awesomelauncher",
1478+ "title": "Awesome Launcher",
1479+ "price": 1.99,
1480+ "_links": {
1481+ "self": {
1482+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
1483+ }
1484+ },
1485+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
1486 },
1487 {
1488 "publisher": "Awesome Widget Company",
1489@@ -738,11 +893,36 @@
1490 },
1491 "_links": {
1492 "self": {
1493+ "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/most-purchased"
1494+ }
1495+ },
1496+ "name": "Most Purchased",
1497+ "slug": "most-purchased"
1498+ },
1499+ {
1500+ "_embedded": {
1501+ "clickindex:package": [
1502+ {
1503+ "publisher": "Awesome Widget Company",
1504+ "name": "org.example.awesomelauncher",
1505+ "title": "Awesome Launcher",
1506+ "price": 1.99,
1507+ "_links": {
1508+ "self": {
1509+ "href": "https://search.apps.staging.ubuntu.com/api/v1/package/org.example.awesomelauncher"
1510+ }
1511+ },
1512+ "icon": "http://example.org/media/org.example.awesomelauncher/icons/icon16.png"
1513+ }
1514+ ]
1515+ },
1516+ "_links": {
1517+ "self": {
1518 "href": "https://search.apps.staging.ubuntu.com/api/v1/highlights/new-releases"
1519 }
1520 },
1521- "name": "New Releases",
1522- "slug": "new-releases"
1523+ "name": "App of the Week",
1524+ "slug": "app-of-the-week"
1525 }
1526 ]
1527 }, "has_children": true,
1528
1529=== modified file 'libclickscope/tests/fake_json.h'
1530--- libclickscope/tests/fake_json.h 2014-08-20 20:45:48 +0000
1531+++ libclickscope/tests/fake_json.h 2015-04-14 19:49:39 +0000
1532@@ -44,6 +44,7 @@
1533 extern const std::string FAKE_JSON_BROKEN_BOOTSTRAP;
1534 extern const std::string FAKE_JSON_DEPARTMENTS_ONLY;
1535 extern const std::string FAKE_JSON_DEPARTMENT_WITH_APPS;
1536+extern const std::string FAKE_JSON_STORE_HOME;
1537 extern const std::string FAKE_JSON_BROKEN_DEPARTMENTS;
1538 extern const std::string FAKE_JSON_MANIFEST_REMOVABLE;
1539 extern const std::string FAKE_JSON_MANIFEST_NONREMOVABLE;
1540
1541=== modified file 'libclickscope/tests/mock_ubuntu_download_manager.h'
1542--- libclickscope/tests/mock_ubuntu_download_manager.h 2014-07-07 15:09:18 +0000
1543+++ libclickscope/tests/mock_ubuntu_download_manager.h 2015-04-14 19:49:39 +0000
1544@@ -60,9 +60,14 @@
1545
1546 MOCK_METHOD1(setDestinationDir, void(const QString& path));
1547 MOCK_METHOD0(metadata, QVariantMap());
1548+ MOCK_METHOD1(setMetadata, void(QVariantMap));
1549 MOCK_METHOD0(progress, qulonglong());
1550 MOCK_METHOD0(totalSize, qulonglong());
1551
1552+ MOCK_CONST_METHOD0(clickPackage, QString());
1553+ MOCK_CONST_METHOD0(title, QString());
1554+ MOCK_CONST_METHOD0(showInIndicator, bool());
1555+
1556 MOCK_CONST_METHOD0(isError, bool());
1557 MOCK_CONST_METHOD0(error, Error*());
1558 MOCK_METHOD0(headers, QMap<QString, QString>());
1559
1560=== modified file 'libclickscope/tests/mock_webclient.h'
1561--- libclickscope/tests/mock_webclient.h 2014-06-03 13:14:02 +0000
1562+++ libclickscope/tests/mock_webclient.h 2015-04-14 19:49:39 +0000
1563@@ -99,6 +99,9 @@
1564 const click::web::CallParams& params=click::web::CallParams()) override {
1565 return callImpl(iri, method, sign, headers, data, params);
1566 }
1567+
1568+ MOCK_METHOD1(has_header, bool(const std::string& header));
1569+ MOCK_METHOD1(get_header, std::string(const std::string&header));
1570 };
1571
1572 }
1573
1574=== modified file 'libclickscope/tests/test_bootstrap.cpp'
1575--- libclickscope/tests/test_bootstrap.cpp 2014-08-21 21:15:13 +0000
1576+++ libclickscope/tests/test_bootstrap.cpp 2015-04-14 19:49:39 +0000
1577@@ -182,25 +182,59 @@
1578 auto highlights = click::Highlight::from_json_root_node(root);
1579 EXPECT_EQ(5u, highlights.size());
1580 auto it = highlights.begin();
1581- EXPECT_EQ("Top Apps", it->name());
1582- EXPECT_EQ(2u, it->packages().size());
1583- EXPECT_EQ(false, it->contains_scopes());
1584- ++it;
1585- EXPECT_EQ("Most Purchased", it->name());
1586- EXPECT_EQ(2u, it->packages().size());
1587- EXPECT_EQ(false, it->contains_scopes());
1588- ++it;
1589- EXPECT_EQ("New Releases", it->name());
1590- EXPECT_EQ(2u, it->packages().size());
1591- EXPECT_EQ(false, it->contains_scopes());
1592- ++it;
1593- EXPECT_EQ("Apps", it->name());
1594- EXPECT_EQ(2u, it->packages().size());
1595- EXPECT_EQ(false, it->contains_scopes());
1596- ++it;
1597- EXPECT_EQ("Scopes", it->name());
1598- EXPECT_EQ(2u, it->packages().size());
1599- EXPECT_EQ(true, it->contains_scopes());
1600+ EXPECT_EQ("Editor's Pick", it->name());
1601+ EXPECT_EQ(1u, it->packages().size());
1602+ EXPECT_EQ(false, it->contains_scopes());
1603+ ++it;
1604+ EXPECT_EQ("Top Apps", it->name());
1605+ EXPECT_EQ(2u, it->packages().size());
1606+ EXPECT_EQ(false, it->contains_scopes());
1607+ ++it;
1608+ EXPECT_EQ("Most Purchased", it->name());
1609+ EXPECT_EQ(2u, it->packages().size());
1610+ EXPECT_EQ(false, it->contains_scopes());
1611+ ++it;
1612+ EXPECT_EQ("Scopes", it->name());
1613+ EXPECT_EQ(2u, it->packages().size());
1614+ EXPECT_EQ(true, it->contains_scopes());
1615+ ++it;
1616+ EXPECT_EQ("Apps", it->name());
1617+ EXPECT_EQ(2u, it->packages().size());
1618+ EXPECT_EQ(false, it->contains_scopes());
1619+ }
1620+
1621+}
1622+
1623+TEST_F(BootstrapTest, testStoreHomeAppOfTheWeek)
1624+{
1625+ Json::Reader reader;
1626+ Json::Value root;
1627+
1628+ EXPECT_TRUE(reader.parse(FAKE_JSON_STORE_HOME, root));
1629+
1630+ {
1631+ auto highlights = click::Highlight::from_json_root_node(root);
1632+ EXPECT_EQ(5u, highlights.size());
1633+ auto it = highlights.begin();
1634+ EXPECT_EQ("App of the Week", it->name());
1635+ EXPECT_EQ(1u, it->packages().size());
1636+ EXPECT_EQ(false, it->contains_scopes());
1637+ ++it;
1638+ EXPECT_EQ("Top Apps", it->name());
1639+ EXPECT_EQ(2u, it->packages().size());
1640+ EXPECT_EQ(false, it->contains_scopes());
1641+ ++it;
1642+ EXPECT_EQ("Most Purchased", it->name());
1643+ EXPECT_EQ(2u, it->packages().size());
1644+ EXPECT_EQ(false, it->contains_scopes());
1645+ ++it;
1646+ EXPECT_EQ("Scopes", it->name());
1647+ EXPECT_EQ(2u, it->packages().size());
1648+ EXPECT_EQ(true, it->contains_scopes());
1649+ ++it;
1650+ EXPECT_EQ("Apps", it->name());
1651+ EXPECT_EQ(2u, it->packages().size());
1652+ EXPECT_EQ(false, it->contains_scopes());
1653 }
1654
1655 }
1656
1657=== modified file 'libclickscope/tests/test_configuration.cpp'
1658--- libclickscope/tests/test_configuration.cpp 2014-07-31 19:15:59 +0000
1659+++ libclickscope/tests/test_configuration.cpp 2015-04-14 19:49:39 +0000
1660@@ -242,5 +242,37 @@
1661 TEST(Configuration, getPurchasesEnabledDefault)
1662 {
1663 ASSERT_EQ(unsetenv(Configuration::PURCHASES_ENVVAR), 0);
1664- ASSERT_EQ(false, Configuration().get_purchases_enabled());
1665+ ASSERT_EQ(true, Configuration().get_purchases_enabled());
1666+}
1667+
1668+TEST(Configuration, getCurrencyDefault)
1669+{
1670+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
1671+ EXPECT_EQ("USD", Configuration().get_currency());
1672+}
1673+
1674+TEST(Configuration, getCurrencyFallback)
1675+{
1676+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
1677+ EXPECT_EQ("HKD", Configuration().get_currency("HKD"));
1678+}
1679+
1680+TEST(Configuration, getCurrencyFallbackUnknown)
1681+{
1682+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
1683+ EXPECT_EQ("USD", Configuration().get_currency("not_valid"));
1684+}
1685+
1686+TEST(Configuration, getCurrencyOverride)
1687+{
1688+ ASSERT_EQ(setenv(Configuration::CURRENCY_ENVVAR, "TWD", 1), 0);
1689+ EXPECT_EQ("TWD", Configuration().get_currency());
1690+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
1691+}
1692+
1693+TEST(Configuration, getCurrencyOverrideUnknown)
1694+{
1695+ ASSERT_EQ(setenv(Configuration::CURRENCY_ENVVAR, "not_valid", 1), 0);
1696+ EXPECT_EQ("USD", Configuration().get_currency());
1697+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
1698 }
1699
1700=== modified file 'libclickscope/tests/test_departments-db.cpp'
1701--- libclickscope/tests/test_departments-db.cpp 2014-08-07 15:34:47 +0000
1702+++ libclickscope/tests/test_departments-db.cpp 2015-04-14 19:49:39 +0000
1703@@ -188,10 +188,10 @@
1704 }
1705 {
1706 auto depts = db->get_children_departments("games");
1707- EXPECT_EQ(2u, depts.size());
1708+ EXPECT_EQ(3u, depts.size());
1709 EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("rpg", false)) != depts.end());
1710 EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("fps", false)) != depts.end());
1711- EXPECT_FALSE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("card", false)) != depts.end());
1712+ EXPECT_TRUE(std::find(depts.begin(), depts.end(), DepartmentsDb::DepartmentInfo("card", false)) != depts.end());
1713 }
1714 }
1715
1716
1717=== modified file 'libclickscope/tests/test_interface.cpp'
1718--- libclickscope/tests/test_interface.cpp 2014-08-18 21:36:01 +0000
1719+++ libclickscope/tests/test_interface.cpp 2015-04-14 19:49:39 +0000
1720@@ -55,11 +55,11 @@
1721 static const std::vector<click::Application> non_desktop_applications =
1722 {
1723 {"com.ubuntu.stock-ticker-mobile", "Stock Ticker", 0.0,
1724- "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.stock-ticker-mobile/icons/stock_icon_48.png", "application:///com.ubuntu.stock-ticker-mobile_stock-ticker-mobile_0.3.7.66.desktop", "", "", ""},
1725+ "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.stock-ticker-mobile/icons/stock_icon_48.png", "application:///com.ubuntu.stock-ticker-mobile_stock-ticker-mobile_0.3.7.66.desktop", "An awesome Stock Ticker application with all the features you could imagine", "", ""},
1726 {"", "Weather", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.weather/./weather64.png", "application:///com.ubuntu.weather_weather_1.0.168.desktop", "", "", ""},
1727 {"com.ubuntu.developer.webapps.webapp-twitter", "Twitter", 0.0,
1728 "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.developer.webapps.webapp-twitter/./twitter.png", "application:///com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter_1.0.5.desktop", "", "", ""},
1729- {"com.ubuntu.music", "Music", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.music/images/music.png", "application:///com.ubuntu.music_music_1.1.329.desktop", "", "", ""},
1730+ {"com.ubuntu.music", "Music", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.music/images/music.png", "application:///com.ubuntu.music_music_1.1.329.desktop", "Ubuntu Touch Music Player", "", ""},
1731 {"com.ubuntu.clock", "Clock", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.clock/./clock64.png", "application:///com.ubuntu.clock_clock_1.0.300.desktop", "", "", ""},
1732 {"com.ubuntu.dropping-letters", "Dropping Letters", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.dropping-letters/dropping-letters.png", "application:///com.ubuntu.dropping-letters_dropping-letters_0.1.2.2.43.desktop", "", "", ""},
1733 {"com.ubuntu.developer.webapps.webapp-gmail", "Gmail", 0.0,
1734@@ -72,7 +72,7 @@
1735 {"com.ubuntu.shorts", "Shorts", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.shorts/./rssreader64.png", "application:///com.ubuntu.shorts_shorts_0.2.162.desktop", "", "", ""},
1736 {"com.ubuntu.filemanager", "File Manager", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.filemanager/./filemanager64.png", "application:///com.ubuntu.filemanager_filemanager_0.1.1.97.desktop", "", "", ""},
1737 {"com.ubuntu.calculator", "Calculator", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.calculator/./calculator64.png", "application:///com.ubuntu.calculator_calculator_0.1.3.206.desktop", "", "", ""},
1738- {"com.ubuntu.sudoku", "Sudoku", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.sudoku/SudokuGameIcon.png", "application:///com.ubuntu.sudoku_sudoku_1.0.142.desktop", "", "", ""},
1739+ {"com.ubuntu.sudoku", "Sudoku", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.sudoku/SudokuGameIcon.png", "application:///com.ubuntu.sudoku_sudoku_1.0.142.desktop", "Sudoku Game for Ubuntu Touch", "", ""},
1740 {"com.ubuntu.developer.webapps.webapp-ebay", "eBay", 0.0,
1741 "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.developer.webapps.webapp-ebay/./ebay.png", "application:///com.ubuntu.developer.webapps.webapp-ebay_webapp-ebay_1.0.8.desktop", "", "", ""},
1742 {"com.ubuntu.developer.webapps.webapp-facebook", "Facebook", 0.0,
1743@@ -250,7 +250,7 @@
1744 const std::vector<click::Application> expected_results = {
1745 {"com.ubuntu.clock", "Clock", 0.0, "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.clock/./clock64.png", "application:///com.ubuntu.clock_clock_1.0.300.desktop", "", "", ""},
1746 {"com.ubuntu.stock-ticker-mobile", "Stock Ticker", 0.0,
1747- "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.stock-ticker-mobile/icons/stock_icon_48.png", "application:///com.ubuntu.stock-ticker-mobile_stock-ticker-mobile_0.3.7.66.desktop", "", "", ""},
1748+ "/usr/share/click/preinstalled/.click/users/@all/com.ubuntu.stock-ticker-mobile/icons/stock_icon_48.png", "application:///com.ubuntu.stock-ticker-mobile_stock-ticker-mobile_0.3.7.66.desktop", "An awesome Stock Ticker application with all the features you could imagine", "", ""},
1749 };
1750 EXPECT_EQ(expected_results, results);
1751 }
1752
1753=== modified file 'libclickscope/tests/test_package.cpp'
1754--- libclickscope/tests/test_package.cpp 2014-08-05 19:00:03 +0000
1755+++ libclickscope/tests/test_package.cpp 2015-04-14 19:49:39 +0000
1756@@ -84,3 +84,30 @@
1757 ASSERT_EQ(1, pl.size());
1758 }
1759
1760+TEST_F(PackageTest, testPackageParsesMultiplePrices)
1761+{
1762+ Json::Value root;
1763+ Json::Reader().parse(FAKE_JSON_SEARCH_RESULT_ONE, root);
1764+ auto const embedded = root[Package::JsonKeys::embedded];
1765+ auto const ci_package = embedded[Package::JsonKeys::ci_package];
1766+
1767+ Packages pl = package_list_from_json_node(ci_package);
1768+ ASSERT_EQ(3, pl[0].prices.size());
1769+}
1770+
1771+TEST_F(PackageTest, testPackageParsesVersion)
1772+{
1773+ Json::Value root;
1774+ Json::Reader().parse(FAKE_JSON_SEARCH_RESULT_ONE, root);
1775+ auto const embedded = root[Package::JsonKeys::embedded];
1776+ auto const ci_package = embedded[Package::JsonKeys::ci_package];
1777+
1778+ Packages pl = package_list_from_json_node(ci_package);
1779+ ASSERT_EQ("0.83b", pl[0].version);
1780+}
1781+
1782+TEST_F(PackageTest, testPackageDetailsParsesMultiplePrices)
1783+{
1784+ auto details = PackageDetails::from_json(FAKE_JSON_PACKAGE_DETAILS);
1785+ ASSERT_EQ(3, details.package.prices.size());
1786+}
1787
1788=== modified file 'libclickscope/tests/test_preview.cpp'
1789--- libclickscope/tests/test_preview.cpp 2014-08-25 19:21:32 +0000
1790+++ libclickscope/tests/test_preview.cpp 2015-04-14 19:49:39 +0000
1791@@ -33,6 +33,9 @@
1792 #include <gtest/gtest.h>
1793 #include <click/preview.h>
1794 #include <fake_json.h>
1795+#include <click/index.h>
1796+#include <click/reviews.h>
1797+#include <boost/locale/time_zone.hpp>
1798
1799 using namespace ::testing;
1800 using namespace unity::scopes;
1801@@ -46,23 +49,57 @@
1802
1803 };
1804
1805+class FakeIndex : public click::Index
1806+{
1807+public:
1808+ FakeIndex() {
1809+
1810+ }
1811+ click::web::Cancellable get_details(const std::string& /*package_name*/, std::function<void(click::PackageDetails, Error)> callback) override {
1812+ callback(click::PackageDetails(), Error::NetworkError);
1813+ return click::web::Cancellable();
1814+ }
1815+
1816+};
1817+
1818+class FakeReviews : public click::Reviews
1819+{
1820+public:
1821+ FakeReviews() {
1822+
1823+ }
1824+
1825+ click::web::Cancellable fetch_reviews(const std::string &/*package_name*/, std::function<void (click::ReviewList, Error)> callback) override {
1826+ callback(click::ReviewList(), Error::NoError);
1827+ return click::web::Cancellable();
1828+ }
1829+};
1830+
1831 class FakePreview : public click::PreviewStrategy
1832 {
1833 public:
1834 FakePreview(Result& result) : click::PreviewStrategy::PreviewStrategy(result)
1835 {
1836-
1837+ index.reset(new FakeIndex());
1838+ reviews.reset(new FakeReviews());
1839 }
1840
1841 void run(const PreviewReplyProxy &/*reply*/)
1842 {
1843
1844 }
1845+
1846+ void run_under_qt(const std::function<void()> &task) {
1847+ // when testing, do not actually run under qt
1848+ task();
1849+ }
1850+
1851 using click::PreviewStrategy::screenshotsWidgets;
1852 using click::PreviewStrategy::descriptionWidgets;
1853 using click::PreviewStrategy::build_other_metadata;
1854 using click::PreviewStrategy::build_updates_table;
1855 using click::PreviewStrategy::build_whats_new;
1856+ using click::PreviewStrategy::populateDetails;
1857 };
1858
1859 class PreviewsBaseTest : public Test
1860@@ -103,6 +140,19 @@
1861 ASSERT_EQ(sources[2].get_string(), "sshot3");
1862 }
1863
1864+TEST_F(PreviewStrategyTest, testEmptyResults)
1865+{
1866+ FakeResult result{vm};
1867+ result["name"] = "fake name";
1868+ FakePreview preview{result};
1869+ preview.populateDetails(
1870+ [](const click::PackageDetails &){
1871+ },
1872+ [](const click::ReviewList&, click::Reviews::Error){
1873+ });
1874+
1875+}
1876+
1877 class PreviewStrategyDescriptionTest : public PreviewStrategyTest
1878 {
1879 public:
1880@@ -121,20 +171,38 @@
1881 auto attributes = widget->attribute_values();
1882 ASSERT_EQ(expected_value, attributes[attribute_name].get_string());
1883 }
1884+ void assertWidgetNestedVariantArray(int n, int x, std::string expected_title, std::string expected_value)
1885+ {
1886+ auto widget = std::next(widgets.begin(), n);
1887+ auto attributes = widget->attribute_values();
1888+ auto found_title = attributes["values"].get_array()[x].get_array()[0].get_string();
1889+ ASSERT_EQ(found_title, expected_title);
1890+ auto found_value = attributes["values"].get_array()[x].get_array()[1].get_string();
1891+ ASSERT_EQ(found_value, expected_value);
1892+ }
1893 };
1894
1895 TEST_F(PreviewStrategyDescriptionTest, testDescriptionWidgetsFull)
1896 {
1897+ boost::locale::time_zone::global("UTC");
1898 details = click::PackageDetails::from_json(FAKE_JSON_PACKAGE_DETAILS);
1899 widgets = preview.descriptionWidgets(details);
1900
1901 assertWidgetAttribute(0, "title", "Info");
1902 assertWidgetAttribute(0, "text", details.description);
1903
1904- assertWidgetAttribute(1, "text", preview.build_other_metadata(details));
1905+ assertWidgetNestedVariantArray(1, 0, "Publisher/Creator", "Fake Publisher");
1906+ assertWidgetNestedVariantArray(1, 1, "Seller", "Fake Company");
1907+ assertWidgetNestedVariantArray(1, 2, "Website", "http://example.com");
1908+ assertWidgetNestedVariantArray(1, 3, "Contact", "http://example.com/support");
1909+ assertWidgetNestedVariantArray(1, 4, "License", "Proprietary");
1910
1911 assertWidgetAttribute(2, "title", "Updates");
1912- assertWidgetAttribute(2, "text", preview.build_updates_table(details));
1913+ assertWidgetNestedVariantArray(2, 0, "Version number", "0.2");
1914+
1915+ assertWidgetNestedVariantArray(2, 1, "Last updated", "Jul 3, 2014");
1916+ assertWidgetNestedVariantArray(2, 2, "First released", "Nov 4, 2013");
1917+ assertWidgetNestedVariantArray(2, 3, "Size", "173.4 KiB");
1918
1919 assertWidgetAttribute(3, "title", "What's new");
1920 assertWidgetAttribute(3, "text", preview.build_whats_new(details));
1921@@ -244,6 +312,7 @@
1922
1923 class FakeDownloader : public click::Downloader {
1924 std::string object_path;
1925+ std::function<void (std::string)> callback;
1926 public:
1927 FakeDownloader(const std::string& object_path, const QSharedPointer<click::network::AccessManager>& networkAccessManager)
1928 : click::Downloader(networkAccessManager), object_path(object_path)
1929@@ -252,6 +321,11 @@
1930 }
1931 void get_download_progress(std::string /*package_name*/, const std::function<void (std::string)> &callback)
1932 {
1933+ this->callback = callback;
1934+ }
1935+
1936+ void activate_callback()
1937+ {
1938 callback(object_path);
1939 }
1940 };
1941@@ -259,19 +333,21 @@
1942 class FakeUninstalledPreview : public click::UninstalledPreview {
1943 std::string object_path;
1944 public:
1945+ std::unique_ptr<FakeDownloader> fake_downloader;
1946 FakeUninstalledPreview(const std::string& object_path,
1947 const unity::scopes::Result& result,
1948 const QSharedPointer<click::web::Client>& client,
1949 const std::shared_ptr<click::DepartmentsDb>& depts,
1950 const QSharedPointer<click::network::AccessManager>& nam)
1951- : click::UninstalledPreview(result, client, depts, nam), object_path(object_path)
1952+ : click::UninstalledPreview(result, client, depts, nam), object_path(object_path),
1953+ fake_downloader(new FakeDownloader(object_path, nam))
1954 {
1955
1956 }
1957
1958- virtual click::Downloader* get_downloader(const QSharedPointer<click::network::AccessManager> &nam)
1959+ virtual click::Downloader* get_downloader(const QSharedPointer<click::network::AccessManager> &/*nam*/)
1960 {
1961- return new FakeDownloader(object_path, nam);
1962+ return fake_downloader.get();
1963 }
1964
1965 void populateDetails(std::function<void (const click::PackageDetails &)> details_callback,
1966@@ -294,6 +370,7 @@
1967 .Times(1)
1968 .WillOnce(Return(response));
1969 preview.run(replyptr);
1970+ preview.fake_downloader->activate_callback();
1971 }
1972
1973 TEST_F(UninstalledPreviewTest, testNoDownloadProgress) {
1974@@ -306,4 +383,5 @@
1975 .Times(1)
1976 .WillOnce(Return(response));
1977 preview.run(replyptr);
1978+ preview.fake_downloader->activate_callback();
1979 }
1980
1981=== modified file 'libclickscope/tests/test_webclient.cpp'
1982--- libclickscope/tests/test_webclient.cpp 2014-08-11 15:38:12 +0000
1983+++ libclickscope/tests/test_webclient.cpp 2015-04-14 19:49:39 +0000
1984@@ -45,10 +45,10 @@
1985 return arg.url().toString() == refUrl;
1986 }
1987
1988-MATCHER_P(IsValidOAuthHeader, refOAuth, "")
1989+MATCHER_P(IsValidOAuthHeader, valid, "")
1990 {
1991- return arg.hasRawHeader(click::web::AUTHORIZATION_HEADER.c_str()) && arg.rawHeader(click::web::AUTHORIZATION_HEADER.c_str())
1992- .startsWith("OAuth ");
1993+ return (arg.hasRawHeader(click::web::AUTHORIZATION_HEADER.c_str()) &&
1994+ arg.rawHeader(click::web::AUTHORIZATION_HEADER.c_str()).startsWith("OAuth ")) == valid;
1995 }
1996
1997 MATCHER_P(IsCorrectAcceptLanguageHeader, refAcceptLanguages, "")
1998@@ -226,7 +226,7 @@
1999 "consumer_key", "consumer_secret");
2000 sso.credentialsFound(token);
2001 }));
2002- EXPECT_CALL(nam, sendCustomRequest(IsValidOAuthHeader(""), _, _))
2003+ EXPECT_CALL(nam, sendCustomRequest(IsValidOAuthHeader(true), _, _))
2004 .Times(1)
2005 .WillOnce(Return(replyPtr));
2006
2007@@ -264,13 +264,19 @@
2008 {
2009 using namespace ::testing;
2010
2011+ auto reply = new NiceMock<MockNetworkReply>();
2012+ ON_CALL(*reply, readAll()).WillByDefault(Return("HOLA"));
2013+ QSharedPointer<click::network::Reply> replyPtr(reply);
2014+
2015 click::web::Client wc(namPtr);
2016 wc.setCredentialsService(ssoPtr);
2017
2018 EXPECT_CALL(sso, getCredentials()).WillOnce(Invoke([&]() {
2019 sso.credentialsNotFound();
2020 }));
2021- EXPECT_CALL(nam, sendCustomRequest(_, _, _)).Times(0);
2022+ EXPECT_CALL(nam, sendCustomRequest(IsValidOAuthHeader(false), _, _))
2023+ .Times(1)
2024+ .WillOnce(Return(replyPtr));
2025
2026 auto wr = wc.call(FAKE_SERVER + FAKE_PATH,
2027 "HEAD", true);
2028
2029=== modified file 'po/unity-scope-click.pot'
2030--- po/unity-scope-click.pot 2014-08-19 21:20:18 +0000
2031+++ po/unity-scope-click.pot 2015-04-14 19:49:39 +0000
2032@@ -8,7 +8,7 @@
2033 msgstr ""
2034 "Project-Id-Version: PACKAGE VERSION\n"
2035 "Report-Msgid-Bugs-To: \n"
2036-"POT-Creation-Date: 2014-08-19 17:19-0400\n"
2037+"POT-Creation-Date: 2014-09-25 12:59-0400\n"
2038 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
2039 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
2040 "Language-Team: LANGUAGE <LL@li.org>\n"
2041@@ -31,7 +31,7 @@
2042 msgid "Search store"
2043 msgstr ""
2044
2045-#: ../data/clickscope.ini.in.in.h:1 ../libclickscope/click/highlights.cpp:116
2046+#: ../data/clickscope.ini.in.in.h:1 ../libclickscope/click/highlights.cpp:122
2047 #: ../scope/clickapps/apps-query.cpp:159
2048 msgid "Apps"
2049 msgstr ""
2050@@ -44,120 +44,123 @@
2051 msgid "Search apps"
2052 msgstr ""
2053
2054-#: ../libclickscope/click/highlights.cpp:119
2055+#: ../libclickscope/click/highlights.cpp:125
2056 msgid "Scopes"
2057 msgstr ""
2058
2059-#: ../libclickscope/click/preview.cpp:58
2060-msgid "Info"
2061-msgstr ""
2062-
2063-#: ../libclickscope/click/preview.cpp:59
2064-msgid "Updates"
2065-msgstr ""
2066-
2067-#: ../libclickscope/click/preview.cpp:60
2068-msgid "What's new"
2069-msgstr ""
2070-
2071-#: ../libclickscope/click/preview.cpp:229
2072+#: ../libclickscope/click/preview.cpp:225
2073 msgid "Publisher/Creator"
2074 msgstr ""
2075
2076-#: ../libclickscope/click/preview.cpp:230
2077+#: ../libclickscope/click/preview.cpp:226
2078 msgid "Seller"
2079 msgstr ""
2080
2081-#: ../libclickscope/click/preview.cpp:231
2082+#: ../libclickscope/click/preview.cpp:227
2083 msgid "Website"
2084 msgstr ""
2085
2086-#: ../libclickscope/click/preview.cpp:232
2087+#: ../libclickscope/click/preview.cpp:228
2088 msgid "Contact"
2089 msgstr ""
2090
2091-#: ../libclickscope/click/preview.cpp:233
2092+#: ../libclickscope/click/preview.cpp:229
2093 msgid "License"
2094 msgstr ""
2095
2096-#: ../libclickscope/click/preview.cpp:241
2097+#: ../libclickscope/click/preview.cpp:237
2098 msgid "Version number"
2099 msgstr ""
2100
2101-#: ../libclickscope/click/preview.cpp:242
2102+#: ../libclickscope/click/preview.cpp:238
2103 msgid "Last updated"
2104 msgstr ""
2105
2106-#: ../libclickscope/click/preview.cpp:243
2107+#: ../libclickscope/click/preview.cpp:239
2108 msgid "First released"
2109 msgstr ""
2110
2111-#: ../libclickscope/click/preview.cpp:244
2112+#: ../libclickscope/click/preview.cpp:240
2113 msgid "Size"
2114 msgstr ""
2115
2116-#: ../libclickscope/click/preview.cpp:252
2117+#: ../libclickscope/click/preview.cpp:248
2118 msgid "Version"
2119 msgstr ""
2120
2121-#: ../libclickscope/click/preview.cpp:383
2122+#: ../libclickscope/click/preview.cpp:349
2123+msgid "Info"
2124+msgstr ""
2125+
2126+#: ../libclickscope/click/preview.cpp:361
2127+msgid "Updates"
2128+msgstr ""
2129+
2130+#: ../libclickscope/click/preview.cpp:366
2131+msgid "What's new"
2132+msgstr ""
2133+
2134+#: ../libclickscope/click/preview.cpp:382
2135 msgid "Reviews"
2136 msgstr ""
2137
2138+#: ../libclickscope/click/preview.cpp:401
2139+msgid "Download Error"
2140+msgstr ""
2141+
2142 #: ../libclickscope/click/preview.cpp:402
2143-msgid "Download Error"
2144-msgstr ""
2145-
2146-#: ../libclickscope/click/preview.cpp:403
2147 msgid "Download or install failed. Please try again."
2148 msgstr ""
2149
2150 #. TODO see bug LP: #1289434
2151-#: ../libclickscope/click/preview.cpp:405
2152+#: ../libclickscope/click/preview.cpp:404
2153 msgid "Close"
2154 msgstr ""
2155
2156+#: ../libclickscope/click/preview.cpp:409
2157+msgid "Login Error"
2158+msgstr ""
2159+
2160 #: ../libclickscope/click/preview.cpp:410
2161-msgid "Login Error"
2162-msgstr ""
2163-
2164-#: ../libclickscope/click/preview.cpp:411
2165 msgid "Please log in to your Ubuntu One account."
2166 msgstr ""
2167
2168-#: ../libclickscope/click/preview.cpp:413
2169+#: ../libclickscope/click/preview.cpp:412
2170 msgid "Go to Accounts"
2171 msgstr ""
2172
2173-#: ../libclickscope/click/preview.cpp:646
2174+#: ../libclickscope/click/preview.cpp:645
2175 msgid "Open"
2176 msgstr ""
2177
2178-#: ../libclickscope/click/preview.cpp:649
2179-#: ../libclickscope/click/preview.cpp:733
2180+#: ../libclickscope/click/preview.cpp:648
2181+#: ../libclickscope/click/preview.cpp:732
2182 msgid "Search"
2183 msgstr ""
2184
2185-#: ../libclickscope/click/preview.cpp:667
2186-#: ../libclickscope/click/preview.cpp:804
2187+#: ../libclickscope/click/preview.cpp:666
2188 msgid "Uninstall"
2189 msgstr ""
2190
2191-#: ../libclickscope/click/preview.cpp:788
2192+#: ../libclickscope/click/preview.cpp:787
2193 msgid "Confirmation"
2194 msgstr ""
2195
2196 #. TRANSLATORS: Do NOT translate ${title} here.
2197-#: ../libclickscope/click/preview.cpp:791
2198+#: ../libclickscope/click/preview.cpp:790
2199 msgid "Uninstall ${title}?"
2200 msgstr ""
2201
2202 #. TODO: see bug LP: #1289434
2203-#: ../libclickscope/click/preview.cpp:800
2204+#: ../libclickscope/click/preview.cpp:799
2205 msgid "Cancel"
2206 msgstr ""
2207
2208-#: ../libclickscope/click/preview.cpp:866
2209+#: ../libclickscope/click/preview.cpp:803
2210+msgid "Confirm"
2211+msgstr ""
2212+
2213+#: ../libclickscope/click/preview.cpp:885
2214 msgid "Install"
2215 msgstr ""
2216
2217@@ -181,35 +184,35 @@
2218 msgstr ""
2219
2220 #: ../scope/clickapps/apps-query.cpp:315
2221-#: ../scope/clickstore/store-query.cpp:244
2222-#: ../scope/clickstore/store-query.cpp:255
2223-#: ../scope/clickstore/store-query.cpp:467
2224+#: ../scope/clickstore/store-query.cpp:269
2225+#: ../scope/clickstore/store-query.cpp:280
2226+#: ../scope/clickstore/store-query.cpp:517
2227 msgid "All"
2228 msgstr ""
2229
2230-#: ../scope/clickstore/store-query.cpp:297
2231+#: ../scope/clickstore/store-query.cpp:311
2232+msgid "FREE"
2233+msgstr ""
2234+
2235+#: ../scope/clickstore/store-query.cpp:329
2236 msgid "✔ INSTALLED"
2237 msgstr ""
2238
2239-#: ../scope/clickstore/store-query.cpp:302
2240+#: ../scope/clickstore/store-query.cpp:334
2241 msgid "✔ PURCHASED"
2242 msgstr ""
2243
2244-#: ../scope/clickstore/store-query.cpp:310
2245-msgid "FREE"
2246-msgstr ""
2247-
2248-#: ../scope/clickstore/store-query.cpp:427
2249+#: ../scope/clickstore/store-query.cpp:477
2250 msgid "Available"
2251 msgstr ""
2252
2253-#: ../scope/clickstore/store-query.cpp:433
2254+#: ../scope/clickstore/store-query.cpp:483
2255 #, c-format
2256 msgid "%u result in Ubuntu Store"
2257 msgid_plural "%u results in Ubuntu Store"
2258 msgstr[0] ""
2259 msgstr[1] ""
2260
2261-#: ../scope/clickstore/store-query.cpp:443
2262+#: ../scope/clickstore/store-query.cpp:493
2263 msgid "Recommended"
2264 msgstr ""
2265
2266=== modified file 'scope/clickstore/pay.cpp'
2267--- scope/clickstore/pay.cpp 2014-07-17 15:35:17 +0000
2268+++ scope/clickstore/pay.cpp 2015-04-14 19:49:39 +0000
2269@@ -31,11 +31,16 @@
2270
2271 #include <future>
2272
2273+#include <json/reader.h>
2274+#include <json/value.h>
2275+
2276 #include <glib.h>
2277 #include <libpay/pay-package.h>
2278
2279 #include <QDebug>
2280
2281+namespace json = Json;
2282+
2283 struct pay::Package::Private
2284 {
2285 Private()
2286@@ -76,8 +81,13 @@
2287
2288 namespace pay {
2289
2290-Package::Package() :
2291- impl(new Private())
2292+bool operator==(const Purchase& lhs, const Purchase& rhs) {
2293+ return lhs.name == rhs.name && lhs.refundable_until == rhs.refundable_until;
2294+}
2295+
2296+Package::Package(const QSharedPointer<click::web::Client>& client) :
2297+ impl(new Private()),
2298+ client(client)
2299 {
2300 }
2301
2302@@ -125,6 +135,64 @@
2303 return false;
2304 }
2305
2306+time_t parse_timestamp(json::Value v)
2307+{
2308+ if (v.isNull()) {
2309+ return 0;
2310+ }
2311+
2312+ QDateTime when = QDateTime::fromString(QString::fromStdString(v.asString()), Qt::ISODate);
2313+ when.setTimeSpec(Qt::OffsetFromUTC);
2314+
2315+ return when.toTime_t();
2316+}
2317+
2318+
2319+click::web::Cancellable Package::get_purchases(std::function<void(const PurchaseSet&)> callback)
2320+{
2321+ QSharedPointer<click::CredentialsService> sso(new click::CredentialsService());
2322+ client->setCredentialsService(sso);
2323+
2324+ QSharedPointer<click::web::Response> response = client->call
2325+ (get_base_url() + pay::API_ROOT + pay::PURCHASES_API_PATH, "GET", true);
2326+
2327+ QObject::connect(response.data(), &click::web::Response::finished,
2328+ [=](QString reply) {
2329+ PurchaseSet purchases;
2330+ json::Reader reader;
2331+ json::Value root;
2332+
2333+ if (reader.parse(reply.toUtf8().constData(), root)) {
2334+ for (uint i = 0; i < root.size(); i++) {
2335+ const json::Value item = root[i];
2336+ if (item[JsonKeys::state].asString() == PURCHASE_STATE_COMPLETE) {
2337+ auto package_name = item[JsonKeys::package_name].asString();
2338+ auto refundable_until_value = item[JsonKeys::refundable_until];
2339+ Purchase p(package_name, parse_timestamp(refundable_until_value));
2340+ purchases.insert(p);
2341+ }
2342+ }
2343+ }
2344+ callback(purchases);
2345+ });
2346+ QObject::connect(response.data(), &click::web::Response::error,
2347+ [=](QString) {
2348+ qWarning() << "Network error getting purchases.";
2349+ callback(PurchaseSet());
2350+ });
2351+
2352+ return click::web::Cancellable(response);
2353+}
2354+
2355+std::string Package::get_base_url()
2356+{
2357+ const char *env_url = getenv(pay::BASE_URL_ENVVAR);
2358+ if (env_url != NULL) {
2359+ return env_url;
2360+ }
2361+ return pay::BASE_URL;
2362+}
2363+
2364 void Package::setup_pay_service()
2365 {
2366 impl->pay_package = pay_package_new(Package::NAME);
2367
2368=== modified file 'scope/clickstore/pay.h'
2369--- scope/clickstore/pay.h 2014-07-17 15:35:17 +0000
2370+++ scope/clickstore/pay.h 2015-04-14 19:49:39 +0000
2371@@ -30,34 +30,86 @@
2372 #ifndef _PAY_H_
2373 #define _PAY_H_
2374
2375+#include <click/webclient.h>
2376+
2377+#include <ctime>
2378 #include <map>
2379 #include <memory>
2380+#include <unordered_set>
2381
2382
2383 namespace pay
2384 {
2385+ struct Purchase
2386+ {
2387+ std::string name;
2388+ time_t refundable_until;
2389+
2390+ Purchase() = default;
2391+ Purchase(const std::string &name) : name(name), refundable_until(0)
2392+ {
2393+ }
2394+
2395+ Purchase(const std::string& name, time_t refundable_until) :
2396+ name(name), refundable_until(refundable_until)
2397+ {
2398+ }
2399+
2400+ struct hash_name {
2401+ public :
2402+ size_t operator()(const Purchase &purchase ) const
2403+ {
2404+ return std::hash<std::string>()(purchase.name);
2405+ }
2406+ };
2407+
2408+ };
2409+
2410+ bool operator==(const Purchase& lhs, const Purchase& rhs);
2411+
2412+ typedef std::unordered_set<Purchase, Purchase::hash_name> PurchaseSet;
2413+
2414+ typedef std::function<void(const std::string& item_id,
2415+ bool status)> StatusFunction;
2416+
2417+ constexpr static const char* BASE_URL_ENVVAR{"PAY_BASE_URL"};
2418+ constexpr static const char* BASE_URL{"https://software-center.ubuntu.com"};
2419+ constexpr static const char* API_ROOT{"/api/2.0/click/"};
2420+ constexpr static const char* PURCHASES_API_PATH{"purchases/"};
2421+ constexpr static const char* PURCHASE_STATE_COMPLETE{"Complete"};
2422+
2423+ struct JsonKeys
2424+ {
2425+ JsonKeys() = delete;
2426+
2427+ constexpr static const char* package_name{"package_name"};
2428+ constexpr static const char* refundable_until{"refundable_until"};
2429+ constexpr static const char* state{"state"};
2430+ };
2431+
2432 class Package
2433 {
2434 public:
2435- typedef std::function<void(const std::string& item_id,
2436- bool status)> StatusFunction;
2437-
2438 constexpr static const char* NAME{"click-scope"};
2439
2440- Package();
2441+ Package() = default;
2442+ Package(const QSharedPointer<click::web::Client>& client);
2443 virtual ~Package();
2444
2445 virtual bool verify(const std::string& pkg_name);
2446+ virtual click::web::Cancellable get_purchases(std::function<void(const PurchaseSet& purchased_apps)> callback);
2447+
2448+ static std::string get_base_url();
2449
2450 protected:
2451 virtual void setup_pay_service();
2452 virtual void pay_package_verify(const std::string& pkg_name);
2453
2454- private:
2455 struct Private;
2456 std::shared_ptr<pay::Package::Private> impl;
2457
2458 bool running = false;
2459+ QSharedPointer<click::web::Client> client;
2460 public:
2461 std::map<std::string, StatusFunction> callbacks;
2462 };
2463
2464=== modified file 'scope/clickstore/store-query.cpp'
2465--- scope/clickstore/store-query.cpp 2014-08-28 19:35:41 +0000
2466+++ scope/clickstore/store-query.cpp 2015-04-14 19:49:39 +0000
2467@@ -163,6 +163,7 @@
2468 click::HighlightList& highlights;
2469 scopes::SearchMetadata meta;
2470 click::web::Cancellable search_operation;
2471+ click::web::Cancellable purchases_operation;
2472 pay::Package& pay_package;
2473 };
2474
2475@@ -223,62 +224,40 @@
2476 });
2477 }
2478
2479-//
2480-// creates department menu with narrowed-down list of subdepartments of current department, as
2481-// returned by server call
2482-void click::Query::populate_departments(const click::DepartmentList& subdepts, const std::string& current_dep_id, unity::scopes::Department::SPtr &root)
2483+unity::scopes::Department::SPtr click::Query::fromClickDepartment(const click::Department::SCPtr click_dept, const std::string& current_dept_id, const click::DepartmentList& subdepts)
2484 {
2485+ const std::locale loc("");
2486+ unity::scopes::Department::SPtr dep = unity::scopes::Department::create(click_dept->id(), query(), click_dept->name());
2487+ if (click_dept->has_children_flag())
2488+ {
2489+ dep->set_has_subdepartments();
2490+ }
2491 unity::scopes::DepartmentList departments;
2492
2493- // create a list of subdepartments of current department
2494- foreach (auto d, subdepts)
2495+ // subdepts is a list of subdepartments of current department fetched from the Store for current search; if we encountered current department in the tree,
2496+ // insert the list from the server rather than the one from internal cache.
2497+ for (auto click_subdep: (click_dept->id() == current_dept_id ? subdepts : click_dept->sub_departments()))
2498 {
2499- unity::scopes::Department::SPtr department = unity::scopes::Department::create(d->id(), query(), d->name());
2500- if (d->has_children_flag())
2501- {
2502- department->set_has_subdepartments();
2503- }
2504- departments.push_back(department);
2505+ departments.push_back(fromClickDepartment(click_subdep, current_dept_id, subdepts));
2506 }
2507-
2508- const std::locale loc("");
2509 departments.sort([&loc](const unity::scopes::Department::SCPtr &d1, const unity::scopes::Department::SCPtr &d2) -> bool {
2510 return loc(d1->label(), d2->label()) > 0;
2511- });
2512-
2513- if (current_dep_id != "")
2514- {
2515- auto curr_dpt = impl->department_lookup.get_department_info(current_dep_id);
2516- if (curr_dpt != nullptr)
2517- {
2518- unity::scopes::Department::SPtr current = unity::scopes::Department::create(current_dep_id, query(), curr_dpt->name());
2519- if (departments.size() > 0) // this may be a leaf department
2520- {
2521- current->set_subdepartments(departments);
2522- }
2523-
2524- auto parent_info = impl->department_lookup.get_parent(current_dep_id);
2525- if (parent_info != nullptr)
2526- {
2527- root = unity::scopes::Department::create(parent_info->id(), query(), parent_info->name());
2528- root->set_subdepartments({current});
2529- return;
2530- }
2531- else
2532- {
2533- root = unity::scopes::Department::create("", query(), _("All"));
2534- root->set_subdepartments({current});
2535- return;
2536- }
2537- }
2538- else
2539- {
2540- qWarning() << "Unknown department:" << QString::fromStdString(current_dep_id);
2541- }
2542- }
2543-
2544- root = unity::scopes::Department::create("", query(), _("All"));
2545- root->set_subdepartments(departments);
2546+ });
2547+ dep->set_subdepartments(departments);
2548+
2549+ return dep;
2550+}
2551+
2552+//
2553+// creates department menu with narrowed-down list of subdepartments of current department, as
2554+// returned by server call
2555+unity::scopes::Department::SPtr click::Query::populate_departments(const click::DepartmentList& subdepts, const std::string& current_dep_id)
2556+{
2557+ // Return complete departments tree (starting from 'All') every time, rather than only parent - current - children; this is the only
2558+ // way we can display siblings corrctly when navigating from Apps scope straight into a subdepartment of Store - see LP #1343242.
2559+ // For currently visible department include its subdepartments as returned for current search by the server (subdepts) -
2560+ // all others are constructed from the department lookup.
2561+ return fromClickDepartment(impl->department_lookup.get_department_info(""), current_dep_id, subdepts);
2562 }
2563
2564 // recursively store all departments in the departments database
2565@@ -315,13 +294,26 @@
2566 std::string rating{ss.str()};
2567
2568 bool purchased = false;
2569- if (pkg.price > 0.00f) {
2570+ double cur_price{0.00};
2571+ auto suggested = impl->index.get_suggested_currency();
2572+ std::string currency = Configuration::get_currency(suggested);
2573+ if (pkg.prices.count(currency) == 1) {
2574+ cur_price = pkg.prices.at(currency);
2575+ } else {
2576+ // NOTE: This is decprecated. Here for compatibility.
2577+ currency = Configuration::CURRENCY_DEFAULT;
2578+ cur_price = pkg.price;
2579+ }
2580+ res["price"] = scopes::Variant(cur_price);
2581+ res[click::Query::ResultKeys::VERSION] = pkg.version;
2582+
2583+ if (cur_price > 0.00f) {
2584 if (!Configuration::get_purchases_enabled()) {
2585 // Don't show priced apps if flag not set
2586 return;
2587 }
2588 // Check if the priced app was already purchased.
2589- purchased = impl->pay_package.verify(pkg.name);
2590+ purchased = purchased_apps.count({pkg.name}) != 0;
2591 }
2592 if (installed != installedPackages.end()) {
2593 res[click::Query::ResultKeys::INSTALLED] = true;
2594@@ -335,12 +327,18 @@
2595 } else {
2596 res[click::Query::ResultKeys::INSTALLED] = false;
2597 res[click::Query::ResultKeys::PURCHASED] = false;
2598- if (pkg.price > 0.00f) {
2599+ if (cur_price > 0.00f) {
2600 QLocale locale;
2601- price = locale.toCurrencyString(pkg.price, "$").toUtf8().data();
2602+ auto symbol = Configuration::CURRENCY_MAP.at(currency);
2603+ price = locale.toCurrencyString(cur_price,
2604+ symbol.c_str()).toUtf8().data();
2605+ res["currency_symbol"] = symbol;
2606 }
2607 }
2608
2609+ res["price_area"] = price;
2610+ res["rating"] = rating;
2611+
2612 // Add the price and rating as attributes.
2613 scopes::VariantBuilder builder;
2614 builder.add_tuple({
2615@@ -359,7 +357,7 @@
2616
2617 this->push_result(searchReply, res);
2618 } catch(const std::exception& e){
2619- qDebug() << "PackageDetails::loadJson: Exception thrown while decoding JSON: " << e.what() ;
2620+ qCritical() << "push_package: Exception: " << e.what() ;
2621 } catch(...){
2622 qDebug() << "no reason to catch";
2623 }
2624@@ -427,9 +425,8 @@
2625
2626 if (query().department_id() == "") // top-level departments
2627 {
2628- unity::scopes::Department::SPtr root;
2629 auto subdepts = curdep->sub_departments();
2630- populate_departments(subdepts, query().department_id(), root);
2631+ auto root = populate_departments(subdepts, query().department_id());
2632 push_departments(searchReply, root);
2633
2634 qDebug() << "pushing cached highlights";
2635@@ -445,8 +442,7 @@
2636 if (error == click::Index::Error::NoError)
2637 {
2638 qDebug() << "departments call completed";
2639- unity::scopes::Department::SPtr root;
2640- populate_departments(depts, query().department_id(), root);
2641+ auto root = populate_departments(depts, query().department_id());
2642 push_departments(searchReply, root);
2643 push_highlights(searchReply, highlights, locallyInstalledApps);
2644 }
2645@@ -589,6 +585,17 @@
2646 if (q.empty()) {
2647 categoryTemplate = CATEGORY_APPS_DISPLAY;
2648 }
2649+ if (Configuration::get_purchases_enabled()) {
2650+ std::promise<pay::PurchaseSet> purchased_promise;
2651+ std::future<pay::PurchaseSet> purchased_future = purchased_promise.get_future();
2652+ qDebug() << "Getting list of purchased apps.";
2653+ run_under_qt([this, &purchased_promise]() {
2654+ impl->purchases_operation = impl->pay_package.get_purchases([&purchased_promise](const pay::PurchaseSet& purchases) {
2655+ purchased_promise.set_value(purchases);
2656+ });
2657+ });
2658+ purchased_apps = purchased_future.get();
2659+ }
2660
2661 add_available_apps(searchReply, get_installed_packages(), categoryTemplate);
2662 }
2663
2664=== modified file 'scope/clickstore/store-query.h'
2665--- scope/clickstore/store-query.h 2014-08-11 18:30:00 +0000
2666+++ scope/clickstore/store-query.h 2015-04-14 19:49:39 +0000
2667@@ -81,8 +81,11 @@
2668
2669 virtual void run(scopes::SearchReplyProxy const& reply) override;
2670
2671+ pay::PurchaseSet purchased_apps;
2672+
2673 protected:
2674- virtual void populate_departments(const click::DepartmentList& depts, const std::string& current_department_id, unity::scopes::Department::SPtr &root);
2675+ virtual unity::scopes::Department::SPtr fromClickDepartment(const click::Department::SCPtr click_dept, const std::string& current_dept_id, const click::DepartmentList& subdepts);
2676+ virtual unity::scopes::Department::SPtr populate_departments(const click::DepartmentList& depts, const std::string& current_department_id);
2677 virtual void store_departments(const click::DepartmentList& depts);
2678 virtual void push_departments(const scopes::SearchReplyProxy& searchReply, const scopes::Department::SCPtr& root);
2679 virtual void add_highlights(scopes::SearchReplyProxy const& searchReply, const PackageSet& installedPackages);
2680
2681=== modified file 'scope/clickstore/store-scope.cpp'
2682--- scope/clickstore/store-scope.cpp 2014-08-13 14:43:32 +0000
2683+++ scope/clickstore/store-scope.cpp 2015-04-14 19:49:39 +0000
2684@@ -52,7 +52,7 @@
2685 index.reset(new click::Index(client));
2686 depts.reset(new click::DepartmentLookup());
2687 highlights.reset(new click::HighlightList());
2688- pay_package.reset(new pay::Package());
2689+ pay_package.reset(new pay::Package(client));
2690
2691 try
2692 {
2693
2694=== modified file 'scope/tests/CMakeLists.txt'
2695--- scope/tests/CMakeLists.txt 2014-08-13 14:43:32 +0000
2696+++ scope/tests/CMakeLists.txt 2015-04-14 19:49:39 +0000
2697@@ -21,6 +21,8 @@
2698 test_pay.cpp
2699 test_query.cpp
2700 test_store_scope.cpp
2701+
2702+ ${CMAKE_SOURCE_DIR}/libclickscope/tests/fake_json.cpp
2703 )
2704
2705 add_executable (${APPS_SCOPE_TESTS_TARGET}
2706
2707=== modified file 'scope/tests/mock_pay.h'
2708--- scope/tests/mock_pay.h 2014-07-17 16:20:24 +0000
2709+++ scope/tests/mock_pay.h 2015-04-14 19:49:39 +0000
2710@@ -29,6 +29,8 @@
2711
2712 #include "clickstore/pay.h"
2713
2714+#include <click/webclient.h>
2715+
2716 #include <gtest/gtest.h>
2717 #include <gmock/gmock.h>
2718
2719@@ -36,9 +38,40 @@
2720 namespace
2721 {
2722
2723+ constexpr static const char* FAKE_PURCHASES_LIST_JSON{R"foo(
2724+ [
2725+ {
2726+ "state": "Complete",
2727+ "package_name": "com.example.fake",
2728+ "refundable_until": "1970-01-01T00:01:23Z",
2729+ "open_id": "https:\/\/login.ubuntu.com/+openid/fakeuser"
2730+ }
2731+ ]
2732+ )foo"};
2733+
2734+
2735+ constexpr static const char* FAKE_PURCHASES_LIST_JSON_NULL_TIMESTAMP{R"foo(
2736+ [
2737+ {
2738+ "state": "Complete",
2739+ "package_name": "com.example.fake",
2740+ "refundable_until": null,
2741+ "open_id": "https:\/\/login.ubuntu.com/+openid/fakeuser"
2742+ }
2743+ ]
2744+ )foo"};
2745+
2746+
2747+
2748 class MockPayPackage : public pay::Package {
2749 public:
2750 MockPayPackage()
2751+ : Package(QSharedPointer<click::web::Client>())
2752+ {
2753+ }
2754+
2755+ MockPayPackage(const QSharedPointer<click::web::Client>& client)
2756+ : Package(client)
2757 {
2758 }
2759
2760@@ -52,6 +85,7 @@
2761 MOCK_METHOD1(do_pay_package_verify, void(const std::string&));
2762
2763 bool purchased = false;
2764+ pay::PurchaseSet purchases;
2765 };
2766
2767 } // namespace
2768
2769=== modified file 'scope/tests/test_pay.cpp'
2770--- scope/tests/test_pay.cpp 2014-07-17 16:20:24 +0000
2771+++ scope/tests/test_pay.cpp 2015-04-14 19:49:39 +0000
2772@@ -29,28 +29,208 @@
2773
2774 #include "mock_pay.h"
2775
2776+#include <click/webclient.h>
2777+
2778+#include <tests/mock_network_access_manager.h>
2779+#include <tests/mock_ubuntuone_credentials.h>
2780+#include <tests/mock_webclient.h>
2781+
2782 #include <gtest/gtest.h>
2783 #include <gmock/gmock.h>
2784
2785-
2786-TEST(PayTest, testPayPackageVerifyCalled)
2787-{
2788- MockPayPackage package;
2789- EXPECT_CALL(package, do_pay_package_verify("foo")).Times(1);
2790- EXPECT_EQ(false, package.verify("foo"));
2791-}
2792-
2793-TEST(PayTest, testPayPackageVerifyNotCalledIfCallbackExists)
2794-{
2795- MockPayPackage package;
2796- package.callbacks["foo"] = [](const std::string&, bool) {};
2797- EXPECT_CALL(package, do_pay_package_verify("foo")).Times(0);
2798- EXPECT_EQ(false, package.verify("foo"));
2799-}
2800-
2801-TEST(PayTest, testVerifyReturnsTrueForPurchasedItem)
2802-{
2803- MockPayPackage package;
2804- package.purchased = true;
2805- EXPECT_EQ(true, package.verify("foo"));
2806+#include <memory>
2807+
2808+using namespace ::testing;
2809+
2810+
2811+namespace
2812+{
2813+
2814+class PayTest : public ::testing::Test
2815+{
2816+protected:
2817+ QSharedPointer<MockNetworkAccessManager> namPtr;
2818+ QSharedPointer<MockClient> clientPtr;
2819+ std::shared_ptr<MockPayPackage> package;
2820+
2821+ virtual void SetUp()
2822+ {
2823+ namPtr.reset(new MockNetworkAccessManager());
2824+ clientPtr.reset(new NiceMock<MockClient>(namPtr));
2825+ package.reset(new MockPayPackage(clientPtr));
2826+ }
2827+
2828+public:
2829+ MOCK_METHOD1(purchases_callback, void(pay::PurchaseSet));
2830+};
2831+
2832+}
2833+
2834+TEST_F(PayTest, testPayPackageVerifyCalled)
2835+{
2836+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2837+ auto response = responseForReply(reply.asSharedPtr());
2838+
2839+ EXPECT_CALL(*package, do_pay_package_verify("foo")).Times(1);
2840+ EXPECT_EQ(false, package->verify("foo"));
2841+}
2842+
2843+TEST_F(PayTest, testPayPackageVerifyNotCalledIfCallbackExists)
2844+{
2845+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2846+ auto response = responseForReply(reply.asSharedPtr());
2847+
2848+ package->callbacks["foo"] = [](const std::string&, bool) {};
2849+ EXPECT_CALL(*package, do_pay_package_verify("foo")).Times(0);
2850+ EXPECT_EQ(false, package->verify("foo"));
2851+}
2852+
2853+TEST_F(PayTest, testVerifyReturnsTrueForPurchasedItem)
2854+{
2855+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2856+ auto response = responseForReply(reply.asSharedPtr());
2857+
2858+ package->purchased = true;
2859+ EXPECT_CALL(*package, do_pay_package_verify("foo")).Times(1);
2860+ EXPECT_EQ(true, package->verify("foo"));
2861+}
2862+
2863+TEST_F(PayTest, testGetPurchasesCallsWebservice)
2864+{
2865+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2866+ auto response = responseForReply(reply.asSharedPtr());
2867+
2868+ EXPECT_CALL(*clientPtr, callImpl(_, _, _, _, _, _))
2869+ .Times(1)
2870+ .WillOnce(Return(response));
2871+
2872+ package->get_purchases([](pay::PurchaseSet) {});
2873+}
2874+
2875+TEST_F(PayTest, testGetPurchasesSendsCorrectPath)
2876+{
2877+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2878+ auto response = responseForReply(reply.asSharedPtr());
2879+
2880+ EXPECT_CALL(*clientPtr, callImpl(EndsWith(pay::PURCHASES_API_PATH),
2881+ _, _, _, _, _))
2882+ .Times(1)
2883+ .WillOnce(Return(response));
2884+
2885+ package->get_purchases([](pay::PurchaseSet) {});
2886+}
2887+
2888+TEST_F(PayTest, testGetPurchasesCallbackCalled)
2889+{
2890+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2891+ auto response = responseForReply(reply.asSharedPtr());
2892+
2893+ QByteArray fake_json("[]");
2894+ EXPECT_CALL(reply.instance, readAll())
2895+ .Times(1)
2896+ .WillOnce(Return(fake_json));
2897+ EXPECT_CALL(*clientPtr, callImpl(_, _, _, _, _, _))
2898+ .Times(1)
2899+ .WillOnce(Return(response));
2900+ EXPECT_CALL(*this, purchases_callback(_)).Times(1);
2901+
2902+ package->get_purchases([this](pay::PurchaseSet purchases) {
2903+ purchases_callback(purchases);
2904+ });
2905+ response->replyFinished();
2906+}
2907+
2908+TEST_F(PayTest, testGetPurchasesEmptyJsonIsParsed)
2909+{
2910+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2911+ auto response = responseForReply(reply.asSharedPtr());
2912+
2913+ QByteArray fake_json("[]");
2914+ EXPECT_CALL(reply.instance, readAll())
2915+ .Times(1)
2916+ .WillOnce(Return(fake_json));
2917+ EXPECT_CALL(*clientPtr, callImpl(_, _, _, _, _, _))
2918+ .Times(1)
2919+ .WillOnce(Return(response));
2920+ pay::PurchaseSet empty_purchases_list;
2921+ EXPECT_CALL(*this, purchases_callback(empty_purchases_list)).Times(1);
2922+
2923+ package->get_purchases([this](pay::PurchaseSet purchases) {
2924+ purchases_callback(purchases);
2925+ });
2926+ response->replyFinished();
2927+}
2928+
2929+TEST_F(PayTest, testGetPurchasesSingleJsonIsParsedNullTimestamp)
2930+{
2931+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2932+ auto response = responseForReply(reply.asSharedPtr());
2933+
2934+ QByteArray fake_json(FAKE_PURCHASES_LIST_JSON_NULL_TIMESTAMP);
2935+ EXPECT_CALL(reply.instance, readAll())
2936+ .Times(1)
2937+ .WillOnce(Return(fake_json));
2938+ EXPECT_CALL(*clientPtr, callImpl(_, _, _, _, _, _))
2939+ .Times(1)
2940+ .WillOnce(Return(response));
2941+ pay::PurchaseSet single_purchase_list{{"com.example.fake", 0}};
2942+ EXPECT_CALL(*this, purchases_callback(single_purchase_list)).Times(1);
2943+
2944+ package->get_purchases([this](pay::PurchaseSet purchases) {
2945+ purchases_callback(purchases);
2946+ });
2947+ response->replyFinished();
2948+}
2949+
2950+TEST_F(PayTest, testGetPurchasesTimestampIsParsed)
2951+{
2952+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2953+ auto response = responseForReply(reply.asSharedPtr());
2954+
2955+ QByteArray fake_json(FAKE_PURCHASES_LIST_JSON);
2956+ EXPECT_CALL(reply.instance, readAll())
2957+ .Times(1)
2958+ .WillOnce(Return(fake_json));
2959+ EXPECT_CALL(*clientPtr, callImpl(_, _, _, _, _, _))
2960+ .Times(1)
2961+ .WillOnce(Return(response));
2962+ const time_t EIGHTYTHREE_SECONDS_INTO_THE_SEVENTIES=83;
2963+ pay::PurchaseSet single_purchase_list{{"com.example.fake", EIGHTYTHREE_SECONDS_INTO_THE_SEVENTIES}};
2964+ EXPECT_CALL(*this, purchases_callback(single_purchase_list)).Times(1);
2965+
2966+ package->get_purchases([this](pay::PurchaseSet purchases) {
2967+ purchases_callback(purchases);
2968+ });
2969+ response->replyFinished();
2970+}
2971+
2972+TEST_F(PayTest, testGetPurchasesIsCancellable)
2973+{
2974+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
2975+ auto response = responseForReply(reply.asSharedPtr());
2976+
2977+ EXPECT_CALL(*clientPtr, callImpl(_, _, _, _, _, _))
2978+ .Times(1)
2979+ .WillOnce(Return(response));
2980+
2981+ auto get_purchases_op = package->get_purchases([](pay::PurchaseSet) {});
2982+ EXPECT_CALL(reply.instance, abort()).Times(1);
2983+ get_purchases_op.cancel();
2984+}
2985+
2986+TEST_F(PayTest, testGetBaseUrl)
2987+{
2988+ const char *value = getenv(pay::BASE_URL_ENVVAR);
2989+ if (value != NULL) {
2990+ ASSERT_TRUE(unsetenv(pay::BASE_URL_ENVVAR) == 0);
2991+ }
2992+ ASSERT_TRUE(pay::Package::get_base_url() == pay::BASE_URL);
2993+
2994+}
2995+
2996+TEST_F(PayTest, testGetBaseUrlFromEnv)
2997+{
2998+ ASSERT_TRUE(setenv(pay::BASE_URL_ENVVAR, FAKE_SERVER.c_str(), 1) == 0);
2999+ ASSERT_TRUE(pay::Package::get_base_url() == FAKE_SERVER);
3000+ ASSERT_TRUE(unsetenv(pay::BASE_URL_ENVVAR) == 0);
3001 }
3002
3003=== modified file 'scope/tests/test_query.cpp'
3004--- scope/tests/test_query.cpp 2014-08-21 20:05:19 +0000
3005+++ scope/tests/test_query.cpp 2015-04-14 19:49:39 +0000
3006@@ -43,7 +43,9 @@
3007 #include "click/application.h"
3008 #include "click/departments-db.h"
3009 #include "test_helpers.h"
3010+#include "click/package.h"
3011
3012+#include <tests/fake_json.h>
3013 #include <tests/mock_network_access_manager.h>
3014
3015 #include <unity/scopes/CategoryRenderer.h>
3016@@ -147,6 +149,7 @@
3017 scopes::CategoryRenderer renderer("{}");
3018 auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
3019 EXPECT_CALL(q, register_category(_, _, _, _, _)).Times(2).WillRepeatedly(Return(ptrCat));
3020+ EXPECT_CALL(q, finished(_)).Times(1);
3021 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3022 }
3023
3024@@ -179,6 +182,7 @@
3025
3026 auto expected_title = packages.front().title;
3027 EXPECT_CALL(q, push_result(_, Property(&scopes::CategorisedResult::title, expected_title)));
3028+ EXPECT_CALL(q, finished(_)).Times(1);
3029 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3030 }
3031
3032@@ -203,7 +207,8 @@
3033
3034 scopes::testing::MockSearchReply mock_reply;
3035 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3036- EXPECT_CALL(q, finished(_));
3037+ EXPECT_CALL(q, push_result(_, _));
3038+ EXPECT_CALL(q, finished(_)).Times(1);
3039 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3040 }
3041
3042@@ -224,6 +229,9 @@
3043 EXPECT_CALL(q, get_installed_packages()).WillOnce(Return(no_installed_packages));
3044 EXPECT_CALL(q, add_available_apps(reply, no_installed_packages, _));
3045
3046+ // No need to test purchases in this testcase
3047+ ASSERT_EQ(setenv(Configuration::PURCHASES_ENVVAR, "0", 1), 0);
3048+
3049 q.run(reply);
3050 }
3051
3052@@ -258,6 +266,7 @@
3053 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name1)));
3054 auto expected_name2 = packages.back().name;
3055 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name2)));
3056+ EXPECT_CALL(q, finished(_)).Times(1);
3057 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);
3058 }
3059
3060@@ -287,6 +296,7 @@
3061 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3062 EXPECT_CALL(q, push_result(_, IsInstalled(true)));
3063 EXPECT_CALL(q, push_result(_, IsInstalled(false)));
3064+ EXPECT_CALL(q, finished(_)).Times(1);
3065 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);
3066 }
3067
3068@@ -323,6 +333,7 @@
3069
3070 scopes::testing::MockSearchReply mock_reply;
3071 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3072+ EXPECT_CALL(q, finished(_)).Times(1);
3073 q.wrap_add_available_apps(reply, one_installed_package, FAKE_CATEGORY_TEMPLATE);
3074 }
3075
3076@@ -359,40 +370,6 @@
3077 typedef std::pair<bool, bool> _PurchasedValues;
3078 MATCHER_P(PurchasedProperties, b, "") { return arg[click::Query::ResultKeys::PURCHASED].get_bool() == b.first && arg[click::Query::ResultKeys::INSTALLED].get_bool() == b.second; }
3079
3080-TEST(QueryTest, testQueryRunCallsPayPackageVerify)
3081-{
3082- ASSERT_EQ(setenv(Configuration::PURCHASES_ENVVAR, "1", 1), 0);
3083- click::Packages packages {
3084- {"name", "title", 0.99, "icon", "uri"}
3085- };
3086- MockIndex mock_index(packages);
3087- click::DepartmentLookup dept_lookup;
3088- click::HighlightList highlights;
3089- scopes::SearchMetadata metadata("en_EN", "phone");
3090- MockPayPackage pay_pkg;
3091- PackageSet no_installed_packages;
3092- const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
3093- MockQuery q(query, mock_index, dept_lookup, nullptr, highlights, metadata, pay_pkg);
3094- EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
3095-
3096- scopes::CategoryRenderer renderer("{}");
3097- auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
3098-
3099- ON_CALL(q, register_category(_, _, _, _, _)).WillByDefault(Return(ptrCat));
3100- EXPECT_CALL(q, register_category(_, "appstore", CategoryHasNumberOfResults(1), _, _));
3101- EXPECT_CALL(q, register_category(_, "recommends", _, _, _));
3102-
3103- scopes::testing::MockSearchReply mock_reply;
3104- scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3105-
3106- EXPECT_CALL(pay_pkg, do_pay_package_verify(_)).Times(1);
3107- EXPECT_CALL(q, push_result(_, PurchasedProperties(_PurchasedValues{false, false}))).Times(1);
3108- EXPECT_CALL(q, finished(_));
3109-
3110- q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3111- ASSERT_EQ(unsetenv(Configuration::PURCHASES_ENVVAR), 0);
3112-}
3113-
3114 TEST(QueryTest, testQueryRunPurchased)
3115 {
3116 ASSERT_EQ(setenv(Configuration::PURCHASES_ENVVAR, "1", 1), 0);
3117@@ -404,10 +381,10 @@
3118 click::HighlightList highlights;
3119 scopes::SearchMetadata metadata("en_EN", "phone");
3120 MockPayPackage pay_pkg;
3121- pay_pkg.purchased = true;
3122 PackageSet no_installed_packages;
3123 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
3124 MockQuery q(query, mock_index, dept_lookup, nullptr, highlights, metadata, pay_pkg);
3125+ q.purchased_apps.insert({"name"});
3126 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
3127
3128 scopes::CategoryRenderer renderer("{}");
3129@@ -420,7 +397,6 @@
3130 scopes::testing::MockSearchReply mock_reply;
3131 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3132
3133- EXPECT_CALL(pay_pkg, do_pay_package_verify(_)).Times(1);
3134 EXPECT_CALL(q, push_result(_, PurchasedProperties(_PurchasedValues{true, false}))).Times(1);
3135 EXPECT_CALL(q, finished(_));
3136
3137@@ -442,9 +418,9 @@
3138 click::HighlightList highlights;
3139 scopes::SearchMetadata metadata("en_EN", "phone");
3140 MockPayPackage pay_pkg;
3141- pay_pkg.purchased = true;
3142 const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
3143 MockQuery q(query, mock_index, dept_lookup, nullptr, highlights, metadata, pay_pkg);
3144+ q.purchased_apps.insert({"name"});
3145 EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
3146
3147 scopes::CategoryRenderer renderer("{}");
3148@@ -457,7 +433,6 @@
3149 scopes::testing::MockSearchReply mock_reply;
3150 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3151
3152- EXPECT_CALL(pay_pkg, do_pay_package_verify(_)).Times(1);
3153 EXPECT_CALL(q, push_result(_, PurchasedProperties(_PurchasedValues{true, true}))).Times(1);
3154 EXPECT_CALL(q, finished(_));
3155
3156@@ -491,6 +466,7 @@
3157 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3158 auto expected_name2 = packages.back().name;
3159 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name2)));
3160+ EXPECT_CALL(q, finished(_)).Times(1);
3161 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3162
3163 ASSERT_EQ(unsetenv(Configuration::PURCHASES_ENVVAR), 0);
3164@@ -524,6 +500,7 @@
3165 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name1)));
3166 auto expected_name2 = packages.back().name;
3167 EXPECT_CALL(q, push_result(_, HasPackageName(expected_name2)));
3168+ EXPECT_CALL(q, finished(_)).Times(1);
3169 q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3170
3171 ASSERT_EQ(unsetenv(Configuration::PURCHASES_ENVVAR), 0);
3172@@ -553,5 +530,103 @@
3173 scopes::testing::MockSearchReply mock_reply;
3174 scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3175 EXPECT_CALL(q, push_result(_, HasAttributes(true)));
3176- q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3177-}
3178+ EXPECT_CALL(q, finished(_)).Times(1);
3179+ q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3180+}
3181+
3182+MATCHER_P(HasPrice, price, "") { return arg["price"].get_double() == price; }
3183+
3184+TEST(QueryTest, testPushPackagePushesPriceUSD)
3185+{
3186+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
3187+ ASSERT_EQ(setenv(Configuration::PURCHASES_ENVVAR, "1", 1), 0);
3188+
3189+ Json::Value root;
3190+ Json::Reader().parse(FAKE_JSON_SEARCH_RESULT_ONE, root);
3191+ auto const embedded = root[Package::JsonKeys::embedded];
3192+ auto const ci_package = embedded[Package::JsonKeys::ci_package];
3193+
3194+ Packages packages = package_list_from_json_node(ci_package);
3195+
3196+ MockIndex mock_index(packages);
3197+ scopes::SearchMetadata metadata("en_EN", "phone");
3198+ PackageSet no_installed_packages;
3199+ DepartmentLookup dept_lookup;
3200+ HighlightList highlights;
3201+ MockPayPackage pay_pkg;
3202+ const unity::scopes::CannedQuery query("foo.scope", "", "");
3203+ MockQuery q(query, mock_index, dept_lookup, nullptr, highlights, metadata, pay_pkg);
3204+
3205+ scopes::CategoryRenderer renderer("{}");
3206+ auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
3207+
3208+ scopes::testing::MockSearchReply mock_reply;
3209+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3210+ EXPECT_CALL(q, push_result(reply, HasPrice(1.99)));
3211+ q.push_package(reply, ptrCat, no_installed_packages, packages[0]);
3212+
3213+ ASSERT_EQ(unsetenv(Configuration::PURCHASES_ENVVAR), 0);
3214+}
3215+
3216+TEST(QueryTest, testPushPackagePushesPriceEUR)
3217+{
3218+ ASSERT_EQ(setenv(Configuration::CURRENCY_ENVVAR, "EUR", 1), 0);
3219+ ASSERT_EQ(setenv(Configuration::PURCHASES_ENVVAR, "1", 1), 0);
3220+
3221+ Json::Value root;
3222+ Json::Reader().parse(FAKE_JSON_SEARCH_RESULT_ONE, root);
3223+ auto const embedded = root[Package::JsonKeys::embedded];
3224+ auto const ci_package = embedded[Package::JsonKeys::ci_package];
3225+
3226+ Packages packages = package_list_from_json_node(ci_package);
3227+
3228+ MockIndex mock_index(packages);
3229+ scopes::SearchMetadata metadata("en_EN", "phone");
3230+ PackageSet no_installed_packages;
3231+ DepartmentLookup dept_lookup;
3232+ HighlightList highlights;
3233+ MockPayPackage pay_pkg;
3234+ const unity::scopes::CannedQuery query("foo.scope", "", "");
3235+ MockQuery q(query, mock_index, dept_lookup, nullptr, highlights, metadata, pay_pkg);
3236+
3237+ scopes::CategoryRenderer renderer("{}");
3238+ auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
3239+
3240+ scopes::testing::MockSearchReply mock_reply;
3241+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3242+ EXPECT_CALL(q, push_result(reply, HasPrice(1.69)));
3243+ q.push_package(reply, ptrCat, no_installed_packages, packages[0]);
3244+
3245+ ASSERT_EQ(unsetenv(Configuration::CURRENCY_ENVVAR), 0);
3246+ ASSERT_EQ(unsetenv(Configuration::PURCHASES_ENVVAR), 0);
3247+}
3248+
3249+MATCHER_P(HasVersion, v, "") { return arg[click::Query::ResultKeys::VERSION].get_string() == v; }
3250+
3251+TEST(QueryTest, testPushPackagePushesVersion)
3252+{
3253+ auto const fake_version = "0.83b";
3254+ click::Packages packages {
3255+ {"org.example.app1", "app title1", 0.0, "icon", "uri", fake_version, "scope"},
3256+ };
3257+ MockIndex mock_index(packages);
3258+ scopes::SearchMetadata metadata("en_EN", "phone");
3259+ PackageSet no_installed_packages;
3260+ click::DepartmentLookup dept_lookup;
3261+ click::HighlightList highlights;
3262+ MockPayPackage pay_pkg;
3263+ const unity::scopes::CannedQuery query("foo.scope", FAKE_QUERY, "");
3264+ MockQuery q(query, mock_index, dept_lookup, nullptr, highlights, metadata, pay_pkg);
3265+ EXPECT_CALL(mock_index, do_search(FAKE_QUERY, _));
3266+
3267+ scopes::CategoryRenderer renderer("{}");
3268+ auto ptrCat = std::make_shared<FakeCategory>("id", "", "", renderer);
3269+ EXPECT_CALL(q, register_category(_, _, _, _, _)).Times(2).WillRepeatedly(Return(ptrCat));
3270+
3271+ scopes::testing::MockSearchReply mock_reply;
3272+ scopes::SearchReplyProxy reply(&mock_reply, [](unity::scopes::SearchReply*){});
3273+ EXPECT_CALL(q, push_result(_, HasVersion(fake_version)));
3274+ EXPECT_CALL(q, finished(_)).Times(1);
3275+ q.wrap_add_available_apps(reply, no_installed_packages, FAKE_CATEGORY_TEMPLATE);
3276+}
3277+
3278
3279=== added directory 'tests'
3280=== renamed file 'autopilot/CMakeLists.txt' => 'tests/CMakeLists.txt'
3281--- autopilot/CMakeLists.txt 2014-05-26 15:37:02 +0000
3282+++ tests/CMakeLists.txt 2015-04-14 19:49:39 +0000
3283@@ -1,6 +1,7 @@
3284 find_package (PythonInterp)
3285
3286 set (AUTOPILOT_DIR unityclickscope)
3287+set (COMMON_PYTHONPATH common)
3288
3289 execute_process (
3290 COMMAND python3 -c "from distutils import sysconfig; print(sysconfig.get_python_lib())"
3291@@ -8,11 +9,11 @@
3292 OUTPUT_VARIABLE PYTHON_PACKAGE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
3293
3294 install (
3295- DIRECTORY ${AUTOPILOT_DIR}
3296+ DIRECTORY autopilot/${AUTOPILOT_DIR}
3297 DESTINATION ${PYTHON_PACKAGE_DIR}
3298 )
3299
3300 add_custom_target(test-click-scope-autopilot-fake-servers
3301- COMMAND U1_SEARCH_BASE_URL=fake DOWNLOAD_BASE_URL=fake BUILD_DIR=${CMAKE_BINARY_DIR} autopilot3 run -v ${AUTOPILOT_DIR}
3302- WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
3303+ COMMAND PYTHONPATH=$ENV{PYTHONPATH}:../${COMMON_PYTHONPATH} U1_SEARCH_BASE_URL=fake DOWNLOAD_BASE_URL=fake BUILD_DIR=${CMAKE_BINARY_DIR} autopilot3 run -v ${AUTOPILOT_DIR}
3304+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/autopilot
3305 )
3306
3307=== renamed directory 'autopilot' => 'tests/autopilot'
3308=== modified file 'tests/autopilot/unityclickscope/__init__.py'
3309--- autopilot/unityclickscope/__init__.py 2014-07-07 21:14:09 +0000
3310+++ tests/autopilot/unityclickscope/__init__.py 2015-04-14 19:49:39 +0000
3311@@ -17,85 +17,95 @@
3312 import logging
3313
3314 import autopilot.logging
3315-import ubuntuuitoolkit
3316-from autopilot.introspection import dbus as autopilot_dbus
3317-from testtools.matchers import Equals, MatchesAny
3318+from autopilot import exceptions, introspection
3319 from unity8.shell.emulators import dash
3320
3321
3322 logger = logging.getLogger(__name__)
3323
3324
3325-class GenericScopeView(dash.DashApps):
3326+class GenericScopeView(dash.GenericScopeView):
3327+
3328+ # XXX workaround for bug http://pad.lv/1350532, which causes the unity8
3329+ # GenericScopeView class to match with the ClickScope and StoreScope.
3330+ # --elopio - 2014-11-28
3331+
3332+ @classmethod
3333+ def validate_dbus_object(cls, path, state):
3334+ return False
3335+
3336+
3337+class ClickScope(GenericScopeView):
3338+
3339+ """Autopilot helper for the click scope."""
3340+
3341+ @classmethod
3342+ def validate_dbus_object(cls, path, state):
3343+ name = introspection.get_classname_from_path(path)
3344+ if name == b'GenericScopeView':
3345+ if state['objectName'][1] == 'clickscope':
3346+ return True
3347+ return False
3348+
3349+ @autopilot.logging.log_action(logger.info)
3350+ def go_to_store(self):
3351+ """Open the applicatmions store.
3352+
3353+ :return: The store Scope View.
3354+
3355+ """
3356+ # XXX The click_scope_item in unity should take care of swiping if the
3357+ # item is not visible.
3358+ # TODO file a bug. --elopio - 2014-11-28
3359+ self._swipe_to_bottom()
3360+ self.click_scope_item('store', 'Ubuntu Store')
3361+ store_scope = self.get_root_instance().select_single(
3362+ 'GenericScopeView', objectName='dashTempScopeItem')
3363+ store_scope.isCurrent.wait_for(True)
3364+ return store_scope
3365+
3366+ def _swipe_to_bottom(self):
3367+ list_view = self.select_single(
3368+ 'ListViewWithPageHeader', objectName='categoryListView')
3369+ list_view.swipe_to_bottom()
3370+
3371+
3372+class StoreScope(GenericScopeView):
3373
3374 """Autopilot helper for the generic scope view of the store."""
3375
3376- # XXX We need to set an objectName to this scope, so we can put a different
3377- # name to this custom proxy object class. --elopio - 2014-06-28
3378+ @classmethod
3379+ def validate_dbus_object(cls, path, state):
3380+ name = introspection.get_classname_from_path(path)
3381+ if name == b'GenericScopeView':
3382+ # This is probably not a good objectName. It can cause more than
3383+ # one custom proxy object to match. --elopio - 2014-11-28
3384+ if state['objectName'][1] == 'dashTempScopeItem':
3385+ return True
3386+ return False
3387
3388+ @autopilot.logging.log_action(logger.info)
3389 def enter_search_query(self, query):
3390- # TODO once http://pad.lv/1335551 is fixed, we can use the search
3391- # helpers from unity. --elopio - 2014-06-28
3392- search_text_field = self._get_search_text_field()
3393+ # XXX the enter_search_query of the dash provided by unity doesn't
3394+ # work for the temp store scope.
3395+ # TODO file a bug. --elopio - 2014-11-28
3396+ search_button = self.select_single(objectName='search_header_button')
3397+ self.pointing_device.click_object(search_button)
3398+ headerContainer = self.select_single(objectName='headerContainer')
3399+ headerContainer.contentY.wait_for(0)
3400+ search_text_field = self.select_single(objectName='searchTextField')
3401 search_text_field.write(query)
3402- search_text_field.state.wait_for('idle')
3403-
3404- def _get_search_text_field(self):
3405- page_header = self._get_scope_item_header()
3406- search_container = page_header.select_single(
3407- 'QQuickItem', objectName='searchContainer')
3408- search_container.state.wait_for(
3409- MatchesAny(Equals('narrowActive'), Equals('active')))
3410- return search_container.select_single(ubuntuuitoolkit.TextField)
3411-
3412- def _get_scope_item_header(self):
3413- return self._get_scope_item().select_single(
3414- 'PageHeader', objectName='')
3415-
3416- def _get_scope_item(self):
3417- return self.get_root_instance().select_single('ScopeItem')
3418-
3419- @autopilot.logging.log_action(logger.info)
3420+ self.get_root_instance().select_single(
3421+ objectName='processingIndicator').visible.wait_for(False)
3422+
3423 def open_preview(self, category, app_name):
3424- """Open the preview of an application.
3425-
3426- :parameter category: The name of the category where the application is.
3427- :parameter app_name: The name of the application.
3428- :return: The opened preview.
3429-
3430- """
3431- category_element = self._get_category_element(category)
3432- icon = category_element.select_single('AbstractButton', title=app_name)
3433- self.pointing_device.click_object(icon)
3434- # TODO assign an object name to this preview list view.
3435- # --elopio - 2014-06-29
3436- preview_list = self._get_scope_item().wait_select_single(
3437- 'PreviewListView', objectName='')
3438- preview_list.x.wait_for(0)
3439- return preview_list.select_single(
3440- Preview, objectName='preview{}'.format(preview_list.currentIndex))
3441-
3442-
3443-class DashApps(dash.DashApps):
3444-
3445- """Autopilot helper for the applicatios scope."""
3446-
3447- @autopilot.logging.log_action(logger.info)
3448- def go_to_store(self):
3449- """Open the applications store.
3450-
3451- :return: The store Scope View.
3452-
3453- """
3454- # TODO call click_scope_item once the fix for bug http://pad.lv/1335548
3455- # lands. --elopio - 2014-06-28
3456- category_element = self._get_category_element('store')
3457- icon = category_element.select_single(
3458- 'AbstractButton', title='Ubuntu Store')
3459- self.pointing_device.click_object(icon)
3460- scope_item = self.get_root_instance().select_single('ScopeItem')
3461- scope_item.x.wait_for(0)
3462- return scope_item.select_single(GenericScopeView)
3463+ # XXX the open preview method provided by unity doesn't wait for the
3464+ # preview to be ready.
3465+ # TODO file a bug. --elopio - 2014-11-28
3466+ preview = super(StoreScope, self).open_preview(category, app_name)
3467+ self.get_root_instance().select_single(
3468+ objectName='processingIndicator').visible.wait_for(False)
3469+ return preview
3470
3471
3472 class Preview(dash.Preview):
3473@@ -113,16 +123,13 @@
3474 title=title_label.text, subtitle=subtitle_label.text)
3475
3476 def install(self):
3477- parent = self.get_parent()
3478 install_button = self.select_single(
3479 'PreviewActionButton', objectName='buttoninstall_click')
3480 self.pointing_device.click_object(install_button)
3481- self.implicitHeight.wait_for(0)
3482- parent.ready.wait_for(True)
3483
3484 def is_progress_bar_visible(self):
3485 try:
3486 self.select_single('ProgressBar', objectName='progressBar')
3487 return True
3488- except autopilot_dbus.StateNotFoundError:
3489+ except exceptions.StateNotFoundError:
3490 return False
3491
3492=== modified file 'tests/autopilot/unityclickscope/fixture_setup.py'
3493--- autopilot/unityclickscope/fixture_setup.py 2014-02-01 12:37:12 +0000
3494+++ tests/autopilot/unityclickscope/fixture_setup.py 2015-04-14 19:49:39 +0000
3495@@ -16,52 +16,19 @@
3496
3497 """Set up and clean up fixtures for the Unity Click Scope acceptance tests."""
3498
3499-
3500-import logging
3501-import threading
3502-
3503-import fixtures
3504+import fake_server_fixture
3505
3506 from unityclickscope import fake_servers
3507
3508
3509-logger = logging.getLogger(__name__)
3510-
3511-
3512-class FakeServerRunning(fixtures.Fixture):
3513-
3514- def __init__(self, server_class):
3515- super(FakeServerRunning, self).__init__()
3516- self.server_class = server_class
3517-
3518- def setUp(self):
3519- super(FakeServerRunning, self).setUp()
3520- self._start_fake_server()
3521-
3522- def _start_fake_server(self):
3523- logger.info('Starting fake server: {}.'.format(self.server_class))
3524- server_address = ('', 0)
3525- fake_server = self.server_class(server_address)
3526- server_thread = threading.Thread(target=fake_server.serve_forever)
3527- server_thread.start()
3528- logger.info('Serving at port {}.'.format(fake_server.server_port))
3529- self.addCleanup(self._stop_fake_server, server_thread, fake_server)
3530- self.url = 'http://localhost:{}/'.format(fake_server.server_port)
3531-
3532- def _stop_fake_server(self, thread, server):
3533- logger.info('Stopping fake server: {}.'.format(self.server_class))
3534- server.shutdown()
3535- thread.join()
3536-
3537-
3538-class FakeSearchServerRunning(FakeServerRunning):
3539+class FakeSearchServerRunning(fake_server_fixture.FakeServerFixture):
3540
3541 def __init__(self):
3542 super(FakeSearchServerRunning, self).__init__(
3543 fake_servers.FakeSearchServer)
3544
3545
3546-class FakeDownloadServerRunning(FakeServerRunning):
3547+class FakeDownloadServerRunning(fake_server_fixture.FakeServerFixture):
3548
3549 def __init__(self):
3550 super(FakeDownloadServerRunning, self).__init__(
3551
3552=== modified file 'tests/autopilot/unityclickscope/test_click_scope.py'
3553--- autopilot/unityclickscope/test_click_scope.py 2014-07-07 18:25:46 +0000
3554+++ tests/autopilot/unityclickscope/test_click_scope.py 2015-04-14 19:49:39 +0000
3555@@ -18,15 +18,14 @@
3556 import os
3557 import shutil
3558 import subprocess
3559+import time
3560
3561 import dbusmock
3562 import fixtures
3563 from autopilot.matchers import Eventually
3564 from testtools.matchers import Equals
3565-from unity8 import process_helpers
3566 from unity8.shell import tests as unity_tests
3567
3568-import unityclickscope
3569 from unityclickscope import credentials, fake_services, fixture_setup
3570
3571
3572@@ -37,7 +36,8 @@
3573 """Exception raised when there's a problem with the scope."""
3574
3575
3576-class BaseClickScopeTestCase(dbusmock.DBusTestCase, unity_tests.UnityTestCase):
3577+class BaseClickScopeTestCase(
3578+ dbusmock.DBusTestCase, unity_tests.DashBaseTestCase):
3579
3580 scenarios = [
3581 ('Desktop Nexus 4', dict(
3582@@ -47,8 +47,6 @@
3583 ]
3584
3585 def setUp(self):
3586- super(BaseClickScopeTestCase, self).setUp()
3587-
3588 # We use fake servers by default because the current Jenkins
3589 # configuration does not let us override the variables.
3590 if os.environ.get('DOWNLOAD_BASE_URL', 'fake') == 'fake':
3591@@ -59,10 +57,7 @@
3592
3593 self.useFixture(fixtures.EnvironmentVariable('U1_DEBUG', newvalue='1'))
3594 self._restart_scopes()
3595-
3596- unity_proxy = self.launch_unity()
3597- process_helpers.unlock_unity(unity_proxy)
3598- self.dash = self.main_window.get_dash()
3599+ super(BaseClickScopeTestCase, self).setUp()
3600 self.scope = self.dash.get_scope('clickscope')
3601
3602 def _use_fake_server(self):
3603@@ -187,18 +182,6 @@
3604 scope.isCurrent.wait_for(True)
3605 return scope
3606
3607- def search(self, query):
3608- search_indicator = self._proxy.select_single(
3609- 'SearchIndicator', objectName='search')
3610- search_indicator.pointing_device.click_object(search_indicator)
3611- self.scope.enter_search_query(query)
3612-
3613- def open_app_preview(self, category, name):
3614- self.search(name)
3615- preview = self.scope.open_preview(category, name)
3616- preview.get_parent().ready.wait_for(True)
3617- return preview
3618-
3619
3620 class TestCaseWithHomeScopeOpen(BaseClickScopeTestCase):
3621
3622@@ -218,24 +201,29 @@
3623 class TestCaseWithStoreScopeOpen(BaseTestCaseWithStoreScopeOpen):
3624
3625 def test_search_available_app(self):
3626- self.search('Delta')
3627+ self.scope.enter_search_query('Delta')
3628 applications = self.scope.get_applications('appstore')
3629 self.assertThat(applications[0], Equals('Delta'))
3630
3631 def test_open_app_preview(self):
3632 expected_details = dict(
3633 title='Delta', subtitle='Rodney Dawes')
3634- preview = self.open_app_preview('appstore', 'Delta')
3635+
3636+ self.scope.enter_search_query('Delta')
3637+ preview = self.scope.open_preview('appstore', 'Delta')
3638 details = preview.get_details()
3639 self.assertEqual(details, expected_details)
3640
3641 def test_install_without_credentials(self):
3642- preview = self.open_app_preview('appstore', 'Delta')
3643+ self.scope.enter_search_query('Delta')
3644+ preview = self.scope.open_preview('appstore', 'Delta')
3645 preview.install()
3646- error = self.dash.wait_select_single(unityclickscope.Preview)
3647-
3648- details = error.get_details()
3649- self.assertEqual('Login Error', details.get('title'))
3650+ # XXX hacky way to check if the online accounts ui was opened.
3651+ time.sleep(3)
3652+ online_accounts_pid = subprocess.check_output(
3653+ ['pgrep', '-f', 'online-accounts-ui'],
3654+ universal_newlines=True).strip()
3655+ subprocess.check_output(['kill', '-9', online_accounts_pid])
3656
3657
3658 class ClickScopeTestCaseWithCredentials(BaseTestCaseWithStoreScopeOpen):
3659@@ -246,7 +234,8 @@
3660 'opened prompting for a password. http://pad.lv/1338714')
3661 self.add_u1_credentials()
3662 super(ClickScopeTestCaseWithCredentials, self).setUp()
3663- self.preview = self.open_app_preview('appstore', 'Delta')
3664+ self.scope.enter_search_query('Delta')
3665+ self.preview = self.scope.open_preview('appstore', 'Delta')
3666
3667 def add_u1_credentials(self):
3668 account_manager = credentials.AccountManager()
3669
3670=== added directory 'tests/common'
3671=== added file 'tests/common/__init__.py'
3672--- tests/common/__init__.py 1970-01-01 00:00:00 +0000
3673+++ tests/common/__init__.py 2015-04-14 19:49:39 +0000
3674@@ -0,0 +1,15 @@
3675+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3676+#
3677+# Copyright (C) 2015 Canonical Ltd.
3678+#
3679+# This program is free software; you can redistribute it and/or modify
3680+# it under the terms of the GNU General Public License version 3, as published
3681+# by the Free Software Foundation.
3682+#
3683+# This program is distributed in the hope that it will be useful,
3684+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3685+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3686+# GNU General Public License for more details.
3687+#
3688+# You should have received a copy of the GNU General Public License
3689+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3690
3691=== added file 'tests/common/fake_server_fixture.py'
3692--- tests/common/fake_server_fixture.py 1970-01-01 00:00:00 +0000
3693+++ tests/common/fake_server_fixture.py 2015-04-14 19:49:39 +0000
3694@@ -0,0 +1,60 @@
3695+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
3696+#
3697+# Copyright (C) 2013, 2014, 2015 Canonical Ltd.
3698+#
3699+# This program is free software; you can redistribute it and/or modify
3700+# it under the terms of the GNU General Public License version 3, as published
3701+# by the Free Software Foundation.
3702+#
3703+# This program is distributed in the hope that it will be useful,
3704+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3705+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3706+# GNU General Public License for more details.
3707+#
3708+# You should have received a copy of the GNU General Public License
3709+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3710+
3711+"""Base class for fake server fixtures."""
3712+
3713+
3714+import logging
3715+import multiprocessing as mp
3716+
3717+import fixtures
3718+
3719+logger = logging.getLogger(__name__)
3720+logger.setLevel(logging.INFO)
3721+
3722+
3723+class FakeServerFixture(fixtures.Fixture):
3724+
3725+ def __init__(self, server_class, *server_args):
3726+ super().__init__()
3727+ self.server_class = server_class
3728+ self.server_args = server_args
3729+
3730+ def setUp(self):
3731+ super().setUp()
3732+ self._start_fake_server()
3733+
3734+ def _server_process(self, queue, server_class):
3735+ logger.info('Starting fake server: {}.'.format(server_class))
3736+ server_address = ('localhost', 0)
3737+ fake_server = server_class(server_address, *self.server_args)
3738+ fake_server.url = 'http://localhost:{}/'.format(fake_server.server_port)
3739+ logger.info('Serving at port {}.'.format(fake_server.server_port))
3740+ queue.put(fake_server.url)
3741+ fake_server.serve_forever()
3742+
3743+ def _start_fake_server(self):
3744+ queue = mp.Queue()
3745+ server_process = mp.Process(target=self._server_process,
3746+ args=(queue, self.server_class))
3747+ server_process.start()
3748+ self.addCleanup(self._stop_fake_server, server_process)
3749+ self.url = queue.get()
3750+
3751+ def _stop_fake_server(self, process):
3752+ logger.info('Stopping fake server: {}.'.format(self.server_class))
3753+ process.terminate()
3754+ process.join()
3755
3756=== added directory 'tests/scope-harness'
3757=== added file 'tests/scope-harness/apps-scope-harness.py'
3758--- tests/scope-harness/apps-scope-harness.py 1970-01-01 00:00:00 +0000
3759+++ tests/scope-harness/apps-scope-harness.py 2015-04-14 19:49:39 +0000
3760@@ -0,0 +1,200 @@
3761+#!/usr/bin/env python3
3762+
3763+from scope_harness import *
3764+from scope_harness.testing import *
3765+import unittest, sys
3766+
3767+class AppsTest (ScopeHarnessTestCase):
3768+ @classmethod
3769+ def setUpClass(cls):
3770+ cls.harness = ScopeHarness.new_from_scope_list(Parameters([
3771+ "/usr/lib/arm-linux-gnueabihf/unity-scopes/clickapps/clickscope.ini"
3772+ ]))
3773+
3774+ def setUp(self):
3775+ self.view = self.harness.results_view
3776+ self.view.active_scope = 'clickscope'
3777+
3778+ def test_surfacing_results(self):
3779+ self.view.browse_department('')
3780+ self.view.search_query = ''
3781+
3782+ # Check first apps of every category
3783+ match = CategoryListMatcher() \
3784+ .has_exactly(3) \
3785+ .mode(CategoryListMatcherMode.BY_ID) \
3786+ .category(CategoryMatcher("predefined") \
3787+ .has_at_least(1) \
3788+ .mode(CategoryMatcherMode.BY_URI) \
3789+ .result(ResultMatcher("application:///dialer-app.desktop") \
3790+ .title('Phone') \
3791+ .property('installed', True) \
3792+ )) \
3793+ .category(CategoryMatcher("local") \
3794+ .has_at_least(1) \
3795+ .mode(CategoryMatcherMode.STARTS_WITH) \
3796+ .result(ResultMatcher("application:///com.ubuntu.developer.webapps.webapp-amazon_webapp-amazon.*") \
3797+ .properties({'installed': True, 'version': '1.0.10'}) \
3798+ .title('Amazon') \
3799+ )) \
3800+ .category(CategoryMatcher("store") \
3801+ .has_at_least(1) \
3802+ .mode(CategoryMatcherMode.BY_URI) \
3803+ .result(ResultMatcher("scope://com.canonical.scopes.clickstore.*") \
3804+ )) \
3805+ .match(self.view.categories)
3806+ self.assertMatchResult(match)
3807+
3808+ def test_surfacing_departments(self):
3809+ self.view.search_query = ''
3810+
3811+ departments = self.view.browse_department('')
3812+
3813+ self.assertTrue(self.view.has_departments)
3814+ self.assertFalse(self.view.has_alt_departments)
3815+
3816+ # TODO: list all expected departments (depending on installed apps)
3817+ match = DepartmentMatcher() \
3818+ .mode(DepartmentMatcherMode.STARTS_WITH) \
3819+ .id('') \
3820+ .label('All') \
3821+ .all_label('') \
3822+ .parent_id('') \
3823+ .parent_label('') \
3824+ .is_root(True) \
3825+ .is_hidden(False) \
3826+ .child(ChildDepartmentMatcher('communication')) \
3827+ .child(ChildDepartmentMatcher('games')) \
3828+ .match(departments)
3829+ self.assertMatchResult(match)
3830+
3831+ def test_department_browsing(self):
3832+ self.view.search_query = ''
3833+
3834+ departments = self.view.browse_department('games')
3835+
3836+ match = DepartmentMatcher() \
3837+ .has_exactly(0) \
3838+ .mode(DepartmentMatcherMode.STARTS_WITH) \
3839+ .label('Games') \
3840+ .all_label('') \
3841+ .parent_id('') \
3842+ .parent_label('All') \
3843+ .is_root(False) \
3844+ .is_hidden(False) \
3845+ .match(departments)
3846+ self.assertMatchResult(match)
3847+
3848+ # FIXME: scope harness shouldn't report empty categories, so should be exactly 2
3849+ res_match = CategoryListMatcher() \
3850+ .has_at_least(2) \
3851+ .mode(CategoryListMatcherMode.BY_ID) \
3852+ .category(CategoryMatcher("local") \
3853+ .has_at_least(1) \
3854+ .mode(CategoryMatcherMode.STARTS_WITH) \
3855+ .result(ResultMatcher("application:///com.ubuntu.dropping-letters_dropping-letters.*") \
3856+ .art('/custom/click/.click/users/@all/com.ubuntu.dropping-letters/./dropping-letters.png') \
3857+ .properties({'installed': True, 'version': '0.1.2.2.67'})
3858+ .title('Dropping Letters') \
3859+ )) \
3860+ .category(CategoryMatcher("store") \
3861+ .has_at_least(1) \
3862+ .mode(CategoryMatcherMode.BY_URI) \
3863+ .result(ResultMatcher("scope://com.canonical.scopes.clickstore.*dep=games") \
3864+ )) \
3865+ .match(self.view.categories)
3866+ self.assertMatchResult(res_match)
3867+
3868+ # browse different department
3869+
3870+ departments = self.view.browse_department('communication')
3871+ self.view.search_query = ''
3872+
3873+ match = DepartmentMatcher() \
3874+ .has_exactly(0) \
3875+ .mode(DepartmentMatcherMode.STARTS_WITH) \
3876+ .label('Communication') \
3877+ .all_label('') \
3878+ .parent_id('') \
3879+ .parent_label('All') \
3880+ .is_root(False) \
3881+ .is_hidden(False) \
3882+ .match(departments)
3883+ self.assertMatchResult(match)
3884+
3885+ # FIXME: scope harness shouldn't report empty categories, so should be exactly 2
3886+ res_match = CategoryListMatcher() \
3887+ .has_at_least(2) \
3888+ .mode(CategoryListMatcherMode.BY_ID) \
3889+ .category(CategoryMatcher("local") \
3890+ .has_at_least(1) \
3891+ .mode(CategoryMatcherMode.STARTS_WITH) \
3892+ .result(ResultMatcher('application:///com.ubuntu.developer.webapps.webapp-amazon_webapp-amazon_1.0.10.desktop')) \
3893+ .result(ResultMatcher('application:///webbrowser-app.desktop') \
3894+ .title('Browser') \
3895+ )) \
3896+ .category(CategoryMatcher("store") \
3897+ .has_at_least(1) \
3898+ .mode(CategoryMatcherMode.BY_URI) \
3899+ .result(ResultMatcher("scope://com.canonical.scopes.clickstore.*dep=communication") \
3900+ )) \
3901+ .match(self.view.categories)
3902+ self.assertMatchResult(res_match)
3903+
3904+ def test_nonremovable_app_preview(self):
3905+ self.view.browse_department('')
3906+ self.view.search_query = 'Brow'
3907+
3908+ pview = self.view.categories[0].results[0].long_press()
3909+ self.assertIsInstance(pview, PreviewView)
3910+
3911+ match = PreviewColumnMatcher().column(\
3912+ PreviewMatcher() \
3913+ .widget(PreviewWidgetMatcher("hdr")) \
3914+ .widget(PreviewWidgetMatcher("buttons") \
3915+ .type("actions") \
3916+ .data({'actions':[{'id':'open_click', 'label':'Open', 'uri':'application:///webbrowser-app.desktop'}]}) \
3917+ ) \
3918+ .widget(PreviewWidgetMatcher("screenshots")) \
3919+ .widget(PreviewWidgetMatcher("summary")) \
3920+ ).match(pview.widgets)
3921+ self.assertMatchResult(match)
3922+
3923+ def test_removable_app_preview(self):
3924+ self.view.browse_department('')
3925+ self.view.search_query = 'Amazon'
3926+
3927+ pview = self.view.categories[0].results[0].long_press()
3928+ self.assertIsInstance(pview, PreviewView)
3929+
3930+ match = PreviewColumnMatcher().column(\
3931+ PreviewMatcher() \
3932+ .widget(PreviewWidgetMatcher("hdr")) \
3933+ .widget(PreviewWidgetMatcher("buttons") \
3934+ .type("actions") \
3935+ .data({'actions':[
3936+ {'id':'open_click',
3937+ 'label':'Open',
3938+ 'uri':'application:///com.ubuntu.developer.webapps.webapp-amazon_webapp-amazon_1.0.10.desktop'},
3939+ {'id':'uninstall_click',
3940+ 'label':'Uninstall'}]}) \
3941+ ) \
3942+ .widget(PreviewWidgetMatcher("summary") \
3943+ .type('text')) \
3944+ .widget(PreviewWidgetMatcher("other_metadata") \
3945+ .type('text')) \
3946+ .widget(PreviewWidgetMatcher("updates") \
3947+ .type('text')) \
3948+ .widget(PreviewWidgetMatcher("whats_new") \
3949+ .type('text')) \
3950+ .widget(PreviewWidgetMatcher("rating") \
3951+ .type('rating-input')) \
3952+ .widget(PreviewWidgetMatcher("reviews_title") \
3953+ .type('text')) \
3954+ .widget(PreviewWidgetMatcher("summary") \
3955+ .type('reviews')) \
3956+ ).match(pview.widgets)
3957+ self.assertMatchResult(match)
3958+
3959+if __name__ == '__main__':
3960+ unittest.main(argv = sys.argv[:1])
3961
3962=== added directory 'tests/scope-harness/fake_responses'
3963=== added directory 'tests/scope-harness/fake_responses/click-package-index'
3964=== added directory 'tests/scope-harness/fake_responses/click-package-index/api'
3965=== added directory 'tests/scope-harness/fake_responses/click-package-index/api/v1'
3966=== added directory 'tests/scope-harness/fake_responses/click-package-index/api/v1/departments'
3967=== added directory 'tests/scope-harness/fake_responses/click-package-index/api/v1/departments/games'
3968=== added file 'tests/scope-harness/fake_responses/click-package-index/api/v1/departments/games/index.json'
3969--- tests/scope-harness/fake_responses/click-package-index/api/v1/departments/games/index.json 1970-01-01 00:00:00 +0000
3970+++ tests/scope-harness/fake_responses/click-package-index/api/v1/departments/games/index.json 2015-04-14 19:49:39 +0000
3971@@ -0,0 +1,174 @@
3972+{
3973+ "_embedded": {
3974+ "clickindex:package": [
3975+ {
3976+ "publisher": "Robert Ancell",
3977+ "name": "com.ubuntu.developer.robert-ancell.yatzy",
3978+ "title": "Yatzy",
3979+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/02/yatzy_rqoPQxE.png",
3980+ "price": 0,
3981+ "content": "application",
3982+ "ratings_average": 5,
3983+ "version": "8",
3984+ "_links": {
3985+ "self": {
3986+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.robert-ancell.yatzy"
3987+ }
3988+ },
3989+ "architecture": [
3990+ "all"
3991+ ],
3992+ "prices": {}
3993+ },
3994+ {
3995+ "publisher": "Victor Tuson Palau",
3996+ "name": "com.ubuntu.developer.vtuson.lego",
3997+ "title": "uBrick scope",
3998+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/01/256.png",
3999+ "price": 0,
4000+ "content": "scope",
4001+ "ratings_average": 5,
4002+ "version": "0.3",
4003+ "_links": {
4004+ "self": {
4005+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.vtuson.lego"
4006+ }
4007+ },
4008+ "architecture": [
4009+ "armhf"
4010+ ],
4011+ "prices": {}
4012+ }
4013+ ],
4014+ "clickindex:highlight": [
4015+ {
4016+ "_embedded": {
4017+ "clickindex:package": [
4018+ {
4019+ "publisher": "Daniel Beck",
4020+ "name": "com.ubuntu.developer.danielbeck.greenmahjong",
4021+ "title": "Green Mahjong",
4022+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/08/greenmahjong.png",
4023+ "price": 0,
4024+ "content": "application",
4025+ "ratings_average": 3.5,
4026+ "version": "2.2.1",
4027+ "_links": {
4028+ "self": {
4029+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.danielbeck.greenmahjong"
4030+ }
4031+ },
4032+ "architecture": [
4033+ "all"
4034+ ],
4035+ "prices": {}
4036+ },
4037+ {
4038+ "publisher": "Riccardo Padovani",
4039+ "name": "com.ubuntu.developer.rpadovani.100balls",
4040+ "title": "100balls",
4041+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/02/100balls_256.png",
4042+ "price": 0,
4043+ "content": "application",
4044+ "ratings_average": 4.57,
4045+ "version": "0.4.1",
4046+ "_links": {
4047+ "self": {
4048+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.rpadovani.100balls"
4049+ }
4050+ },
4051+ "architecture": [
4052+ "armhf"
4053+ ],
4054+ "prices": {}
4055+ },
4056+ {
4057+ "publisher": "Robert Ancell",
4058+ "name": "com.ubuntu.developer.robert-ancell.dotty",
4059+ "title": "Dotty",
4060+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2013/12/dotty.png",
4061+ "price": 0,
4062+ "content": "application",
4063+ "ratings_average": 4.2,
4064+ "version": "8",
4065+ "_links": {
4066+ "self": {
4067+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.robert-ancell.dotty"
4068+ }
4069+ },
4070+ "architecture": [
4071+ "all"
4072+ ],
4073+ "prices": {}
4074+ },
4075+ {
4076+ "publisher": "Filippo Scognamiglio",
4077+ "name": "com.ubuntu.developer.flscogna.ubuntu-netwalk",
4078+ "title": "Ubuntu Netwalk",
4079+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/03/ubuntuNetwalkicon256.png",
4080+ "price": 0,
4081+ "content": "application",
4082+ "ratings_average": 4.92,
4083+ "version": "0.9.2",
4084+ "_links": {
4085+ "self": {
4086+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.flscogna.ubuntu-netwalk"
4087+ }
4088+ },
4089+ "architecture": [
4090+ "armhf"
4091+ ],
4092+ "prices": {}
4093+ },
4094+ {
4095+ "publisher": "Ken VanDine",
4096+ "name": "com.ubuntu.developer.ken-vandine.pathwind",
4097+ "title": "PathWind",
4098+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/05/pathwind.png",
4099+ "price": 0,
4100+ "content": "application",
4101+ "ratings_average": 4.38,
4102+ "version": "0.2.9",
4103+ "_links": {
4104+ "self": {
4105+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.ken-vandine.pathwind"
4106+ }
4107+ },
4108+ "architecture": [
4109+ "armhf"
4110+ ],
4111+ "prices": {}
4112+ }
4113+ ]
4114+ },
4115+ "slug": "top-games",
4116+ "_links": {
4117+ "self": {
4118+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights/top-games"
4119+ }
4120+ },
4121+ "description": "",
4122+ "name": "Top Games"
4123+ }
4124+ ]
4125+ },
4126+ "has_children": false,
4127+ "_links": {
4128+ "curies": [
4129+ {
4130+ "href": "https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#reltype_{rel}",
4131+ "name": "clickindex",
4132+ "templated": true
4133+ }
4134+ ],
4135+ "self": {
4136+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/games"
4137+ },
4138+ "collection": {
4139+ "href": "[FAKE_SERVER_BASE]/api/v1/departments"
4140+ }
4141+ },
4142+ "name": "Games",
4143+ "slug": "games"
4144+}
4145+
4146
4147=== added file 'tests/scope-harness/fake_responses/click-package-index/api/v1/index.json'
4148--- tests/scope-harness/fake_responses/click-package-index/api/v1/index.json 1970-01-01 00:00:00 +0000
4149+++ tests/scope-harness/fake_responses/click-package-index/api/v1/index.json 2015-04-14 19:49:39 +0000
4150@@ -0,0 +1,642 @@
4151+{
4152+ "_embedded": {
4153+ "clickindex:department": [
4154+ {
4155+ "has_children": false,
4156+ "_links": {
4157+ "self": {
4158+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/social-networking"
4159+ }
4160+ },
4161+ "name": "Social Networking",
4162+ "slug": "social-networking"
4163+ },
4164+ {
4165+ "has_children": false,
4166+ "_links": {
4167+ "self": {
4168+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/travel-local"
4169+ }
4170+ },
4171+ "name": "Travel & Local",
4172+ "slug": "travel-local"
4173+ },
4174+ {
4175+ "has_children": false,
4176+ "_links": {
4177+ "self": {
4178+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/reference"
4179+ }
4180+ },
4181+ "name": "Reference",
4182+ "slug": "reference"
4183+ },
4184+ {
4185+ "has_children": false,
4186+ "_links": {
4187+ "self": {
4188+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/food-drink"
4189+ }
4190+ },
4191+ "name": "Food & Drink",
4192+ "slug": "food-drink"
4193+ },
4194+ {
4195+ "has_children": false,
4196+ "_links": {
4197+ "self": {
4198+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/communication"
4199+ }
4200+ },
4201+ "name": "Communication",
4202+ "slug": "communication"
4203+ },
4204+ {
4205+ "has_children": false,
4206+ "_links": {
4207+ "self": {
4208+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/accessories"
4209+ }
4210+ },
4211+ "name": "Utilities",
4212+ "slug": "accessories"
4213+ },
4214+ {
4215+ "has_children": false,
4216+ "_links": {
4217+ "self": {
4218+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/science-engineering"
4219+ }
4220+ },
4221+ "name": "Science & Engineering",
4222+ "slug": "science-engineering"
4223+ },
4224+ {
4225+ "has_children": false,
4226+ "_links": {
4227+ "self": {
4228+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/personalisation"
4229+ }
4230+ },
4231+ "name": "Personalisation",
4232+ "slug": "personalisation"
4233+ },
4234+ {
4235+ "has_children": false,
4236+ "_links": {
4237+ "self": {
4238+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/education"
4239+ }
4240+ },
4241+ "name": "Education",
4242+ "slug": "education"
4243+ },
4244+ {
4245+ "has_children": false,
4246+ "_links": {
4247+ "self": {
4248+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/productivity"
4249+ }
4250+ },
4251+ "name": "Productivity",
4252+ "slug": "productivity"
4253+ },
4254+ {
4255+ "has_children": false,
4256+ "_links": {
4257+ "self": {
4258+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/entertainment"
4259+ }
4260+ },
4261+ "name": "Entertainment",
4262+ "slug": "entertainment"
4263+ },
4264+ {
4265+ "has_children": false,
4266+ "_links": {
4267+ "self": {
4268+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/sports"
4269+ }
4270+ },
4271+ "name": "Sports",
4272+ "slug": "sports"
4273+ },
4274+ {
4275+ "has_children": false,
4276+ "_links": {
4277+ "self": {
4278+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/health-fitness"
4279+ }
4280+ },
4281+ "name": "Health & Fitness",
4282+ "slug": "health-fitness"
4283+ },
4284+ {
4285+ "has_children": false,
4286+ "_links": {
4287+ "self": {
4288+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/music-audio"
4289+ }
4290+ },
4291+ "name": "Music & Audio",
4292+ "slug": "music-audio"
4293+ },
4294+ {
4295+ "has_children": false,
4296+ "_links": {
4297+ "self": {
4298+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/weather"
4299+ }
4300+ },
4301+ "name": "Weather",
4302+ "slug": "weather"
4303+ },
4304+ {
4305+ "has_children": false,
4306+ "_links": {
4307+ "self": {
4308+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/shopping"
4309+ }
4310+ },
4311+ "name": "Shopping",
4312+ "slug": "shopping"
4313+ },
4314+ {
4315+ "has_children": false,
4316+ "_links": {
4317+ "self": {
4318+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/finance"
4319+ }
4320+ },
4321+ "name": "Finance",
4322+ "slug": "finance"
4323+ },
4324+ {
4325+ "has_children": false,
4326+ "_links": {
4327+ "self": {
4328+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/business"
4329+ }
4330+ },
4331+ "name": "Business",
4332+ "slug": "business"
4333+ },
4334+ {
4335+ "has_children": false,
4336+ "_links": {
4337+ "self": {
4338+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/media-video"
4339+ }
4340+ },
4341+ "name": "Media & Video",
4342+ "slug": "media-video"
4343+ },
4344+ {
4345+ "has_children": false,
4346+ "_links": {
4347+ "self": {
4348+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/universal-accessaccessibility"
4349+ }
4350+ },
4351+ "name": "Universal Access/Accessibility",
4352+ "slug": "universal-accessaccessibility"
4353+ },
4354+ {
4355+ "has_children": false,
4356+ "_links": {
4357+ "self": {
4358+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/news-magazines"
4359+ }
4360+ },
4361+ "name": "News & Magazines",
4362+ "slug": "news-magazines"
4363+ },
4364+ {
4365+ "has_children": false,
4366+ "_links": {
4367+ "self": {
4368+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/graphics"
4369+ }
4370+ },
4371+ "name": "Graphics",
4372+ "slug": "graphics"
4373+ },
4374+ {
4375+ "has_children": false,
4376+ "_links": {
4377+ "self": {
4378+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/lifestyle"
4379+ }
4380+ },
4381+ "name": "Lifestyle",
4382+ "slug": "lifestyle"
4383+ },
4384+ {
4385+ "has_children": false,
4386+ "_links": {
4387+ "self": {
4388+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/medical"
4389+ }
4390+ },
4391+ "name": "Medical",
4392+ "slug": "medical"
4393+ },
4394+ {
4395+ "has_children": false,
4396+ "_links": {
4397+ "self": {
4398+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/developer-tools"
4399+ }
4400+ },
4401+ "name": "Developer Tools",
4402+ "slug": "developer-tools"
4403+ },
4404+ {
4405+ "has_children": false,
4406+ "_links": {
4407+ "self": {
4408+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/games"
4409+ }
4410+ },
4411+ "name": "Games",
4412+ "slug": "games"
4413+ },
4414+ {
4415+ "has_children": false,
4416+ "_links": {
4417+ "self": {
4418+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/books-comics"
4419+ }
4420+ },
4421+ "name": "Books & Comics",
4422+ "slug": "books-comics"
4423+ }
4424+ ],
4425+ "clickindex:highlight": [
4426+ {
4427+ "_embedded": {
4428+ "clickindex:package": [
4429+ {
4430+ "publisher": "Zhang Boren",
4431+ "name": "com.ubuntu.developer.bobo1993324.udropcabin",
4432+ "title": "uDropCabin",
4433+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/06/logo.png",
4434+ "price": 0,
4435+ "content": "application",
4436+ "ratings_average": 4.17,
4437+ "version": "0.2.1",
4438+ "_links": {
4439+ "self": {
4440+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.bobo1993324.udropcabin"
4441+ }
4442+ },
4443+ "architecture": [
4444+ "armhf"
4445+ ],
4446+ "prices": {}
4447+ },
4448+ {
4449+ "publisher": "Ubuntu Core App Developers",
4450+ "name": "com.ubuntu.docviewer",
4451+ "title": "Document Viewer",
4452+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/11/docviewer_32.png",
4453+ "price": 0,
4454+ "content": "application",
4455+ "ratings_average": 4,
4456+ "version": "0.3.92",
4457+ "_links": {
4458+ "self": {
4459+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.docviewer"
4460+ }
4461+ },
4462+ "architecture": [
4463+ "armhf"
4464+ ],
4465+ "prices": {}
4466+ },
4467+ {
4468+ "publisher": "Michael Zanetti",
4469+ "name": "com.ubuntu.developer.mzanetti.wheretheissat",
4470+ "title": "WhereTheIssAt",
4471+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/08/wheretheissat.png",
4472+ "price": 0,
4473+ "content": "application",
4474+ "ratings_average": 5,
4475+ "version": "0.4.0",
4476+ "_links": {
4477+ "self": {
4478+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.mzanetti.wheretheissat"
4479+ }
4480+ },
4481+ "architecture": [
4482+ "armhf"
4483+ ],
4484+ "prices": {}
4485+ },
4486+ {
4487+ "publisher": "Michael Zanetti",
4488+ "name": "com.ubuntu.developer.mzanetti.ubuntu-authenticator",
4489+ "title": "Authenticator",
4490+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2013/10/ubuntu-authenticator256.png",
4491+ "price": 0,
4492+ "content": "application",
4493+ "ratings_average": 4.72,
4494+ "version": "0.10.0",
4495+ "_links": {
4496+ "self": {
4497+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.mzanetti.ubuntu-authenticator"
4498+ }
4499+ },
4500+ "architecture": [
4501+ "amd64",
4502+ "armhf"
4503+ ],
4504+ "prices": {}
4505+ },
4506+ {
4507+ "publisher": "Michael Zanetti",
4508+ "name": "com.ubuntu.developer.mzanetti.tagger",
4509+ "title": "Tagger",
4510+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2013/11/tagger256.png",
4511+ "price": 0,
4512+ "content": "application",
4513+ "ratings_average": 3.64,
4514+ "version": "0.9.0.0",
4515+ "_links": {
4516+ "self": {
4517+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.mzanetti.tagger"
4518+ }
4519+ },
4520+ "architecture": [
4521+ "armhf"
4522+ ],
4523+ "prices": {}
4524+ },
4525+ {
4526+ "publisher": "Dennis O'Flaherty",
4527+ "name": "com.ubuntu.developer.doflah.realtai",
4528+ "title": "Réaltaí",
4529+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/04/realtai256.png",
4530+ "price": 0,
4531+ "content": "application",
4532+ "ratings_average": 4.6,
4533+ "version": "0.6.3",
4534+ "_links": {
4535+ "self": {
4536+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.doflah.realtai"
4537+ }
4538+ },
4539+ "architecture": [
4540+ "armhf"
4541+ ],
4542+ "prices": {}
4543+ }
4544+ ]
4545+ },
4546+ "slug": "top-apps",
4547+ "_links": {
4548+ "self": {
4549+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights/top-apps"
4550+ }
4551+ },
4552+ "description": "",
4553+ "name": "Top apps"
4554+ },
4555+ {
4556+ "_embedded": {
4557+ "clickindex:package": [
4558+ {
4559+ "publisher": "Michael Zanetti",
4560+ "name": "com.ubuntu.developer.mzanetti.machines-vs-machines",
4561+ "title": "Machines vs. Machines",
4562+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/01/mvm-game-icon256.png",
4563+ "price": 0,
4564+ "content": "application",
4565+ "ratings_average": 4.97,
4566+ "version": "1.2.0",
4567+ "_links": {
4568+ "self": {
4569+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.mzanetti.machines-vs-machines"
4570+ }
4571+ },
4572+ "architecture": [
4573+ "armhf"
4574+ ],
4575+ "prices": {}
4576+ },
4577+ {
4578+ "publisher": "Riccardo Padovani",
4579+ "name": "com.ubuntu.developer.rpadovani.100balls",
4580+ "title": "100balls",
4581+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/02/100balls_256.png",
4582+ "price": 0,
4583+ "content": "application",
4584+ "ratings_average": 4.57,
4585+ "version": "0.4.1",
4586+ "_links": {
4587+ "self": {
4588+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.rpadovani.100balls"
4589+ }
4590+ },
4591+ "architecture": [
4592+ "armhf"
4593+ ],
4594+ "prices": {}
4595+ },
4596+ {
4597+ "publisher": "Robert Ancell",
4598+ "name": "com.ubuntu.developer.robert-ancell.dotty",
4599+ "title": "Dotty",
4600+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2013/12/dotty.png",
4601+ "price": 0,
4602+ "content": "application",
4603+ "ratings_average": 4.2,
4604+ "version": "8",
4605+ "_links": {
4606+ "self": {
4607+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.andrew-hayzen.volleyball2d"
4608+ }
4609+ },
4610+ "architecture": [
4611+ "all"
4612+ ],
4613+ "prices": {}
4614+ },
4615+ {
4616+ "publisher": "Filippo Scognamiglio",
4617+ "name": "com.ubuntu.developer.flscogna.ubuntu-netwalk",
4618+ "title": "Ubuntu Netwalk",
4619+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/03/ubuntuNetwalkicon256.png",
4620+ "price": 0,
4621+ "content": "application",
4622+ "ratings_average": 4.92,
4623+ "version": "0.9.2",
4624+ "_links": {
4625+ "self": {
4626+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.flscogna.ubuntu-netwalk"
4627+ }
4628+ },
4629+ "architecture": [
4630+ "armhf"
4631+ ],
4632+ "prices": {}
4633+ },
4634+ {
4635+ "publisher": "Ken VanDine",
4636+ "name": "com.ubuntu.developer.ken-vandine.pathwind",
4637+ "title": "PathWind",
4638+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/05/pathwind.png",
4639+ "price": 0,
4640+ "content": "application",
4641+ "ratings_average": 4.38,
4642+ "version": "0.2.9",
4643+ "_links": {
4644+ "self": {
4645+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.ken-vandine.pathwind"
4646+ }
4647+ },
4648+ "architecture": [
4649+ "armhf"
4650+ ],
4651+ "prices": {}
4652+ },
4653+ {
4654+ "publisher": "Oliver Grawert",
4655+ "name": "com.ubuntu.developer.ogra.speed-pool-king",
4656+ "title": "Speed Billiards",
4657+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/11/icon_e2OQD6l.png",
4658+ "price": 0,
4659+ "content": "application",
4660+ "ratings_average": 5,
4661+ "version": "0.1",
4662+ "_links": {
4663+ "self": {
4664+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.ogra.speed-pool-king"
4665+ }
4666+ },
4667+ "architecture": [
4668+ "all"
4669+ ],
4670+ "prices": {}
4671+ }
4672+ ]
4673+ },
4674+ "slug": "our-favorite-games",
4675+ "_links": {
4676+ "self": {
4677+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights/our-favorite-games"
4678+ }
4679+ },
4680+ "description": "",
4681+ "name": "Our favorite games"
4682+ },
4683+ {
4684+ "_embedded": {
4685+ "clickindex:package": [
4686+ {
4687+ "publisher": "Canonical",
4688+ "name": "com.canonical.scopes.wikinear",
4689+ "title": "Nearby Articles Scope",
4690+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/11/store-icon_cDUSB9o.png",
4691+ "price": 0,
4692+ "content": "scope",
4693+ "ratings_average": 5,
4694+ "version": "1.0.9",
4695+ "_links": {
4696+ "self": {
4697+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.canonical.scopes.wikinear"
4698+ }
4699+ },
4700+ "architecture": [
4701+ "armhf"
4702+ ],
4703+ "prices": {}
4704+ }
4705+ ]
4706+ },
4707+ "slug": "travel-apps",
4708+ "_links": {
4709+ "self": {
4710+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights/travel-apps"
4711+ }
4712+ },
4713+ "description": "",
4714+ "name": "Travel apps"
4715+ },
4716+ {
4717+ "_embedded": {
4718+ "clickindex:package": [
4719+ {
4720+ "publisher": "Ubuntu Core App Developers",
4721+ "name": "com.ubuntu.telegram",
4722+ "title": "Telegram",
4723+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2015/02/icon_Gah5OG4.png",
4724+ "price": 0,
4725+ "content": "application",
4726+ "ratings_average": 4.52,
4727+ "version": "1.0.6.90",
4728+ "_links": {
4729+ "self": {
4730+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.telegram"
4731+ }
4732+ },
4733+ "architecture": [
4734+ "armhf"
4735+ ],
4736+ "prices": {}
4737+ }
4738+ ]
4739+ },
4740+ "slug": "app-of-the-week",
4741+ "_links": {
4742+ "self": {
4743+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights/app-of-the-week"
4744+ }
4745+ },
4746+ "description": "",
4747+ "name": "App of the week"
4748+ }
4749+ ]
4750+ },
4751+ "_links": {
4752+ "search": {
4753+ "title": "Search",
4754+ "href": "[FAKE_SERVER_BASE]/api/v1/search{?q}",
4755+ "templated": true
4756+ },
4757+ "clickindex:departments": {
4758+ "href": "[FAKE_SERVER_BASE]/api/v1/departments",
4759+ "title": "Departments"
4760+ },
4761+ "clickindex:highlight": {
4762+ "title": "Highlight",
4763+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights/{slug}",
4764+ "templated": true
4765+ },
4766+ "curies": [
4767+ {
4768+ "href": "https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#reltype_{rel}",
4769+ "name": "clickindex",
4770+ "templated": true
4771+ }
4772+ ],
4773+ "self": {
4774+ "href": "[FAKE_SERVER_BASE]/api/v1"
4775+ },
4776+ "clickindex:highlights": {
4777+ "href": "[FAKE_SERVER_BASE]/api/v1/highlights",
4778+ "title": "Highlights"
4779+ },
4780+ "clickindex:package": {
4781+ "title": "Package",
4782+ "href": "[FAKE_SERVER_BASE]/api/v1/package/{name}",
4783+ "templated": true
4784+ },
4785+ "clickindex:department": {
4786+ "title": "Department",
4787+ "href": "[FAKE_SERVER_BASE]/api/v1/departments/{slug}",
4788+ "templated": true
4789+ }
4790+ }
4791+}
4792+
4793
4794=== added directory 'tests/scope-harness/fake_responses/click-package-index/api/v1/package'
4795=== added file 'tests/scope-harness/fake_responses/click-package-index/api/v1/package/com.ubuntu.calendar'
4796--- tests/scope-harness/fake_responses/click-package-index/api/v1/package/com.ubuntu.calendar 1970-01-01 00:00:00 +0000
4797+++ tests/scope-harness/fake_responses/click-package-index/api/v1/package/com.ubuntu.calendar 2015-04-14 19:49:39 +0000
4798@@ -0,0 +1,86 @@
4799+{
4800+ "whitelist_country_codes": [],
4801+ "status": "Published",
4802+ "last_updated": "2015-03-05T14:25:13.226958Z",
4803+ "video_embedded_html_urls": [],
4804+ "screenshot_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/10/device-2014-10-02-155500.png",
4805+ "video_urls": [],
4806+ "framework": [
4807+ "ubuntu-sdk-14.10-qml"
4808+ ],
4809+ "terms_of_service": "",
4810+ "keywords": [
4811+ "calendar",
4812+ "time",
4813+ "task",
4814+ "plan",
4815+ "journal",
4816+ "diary"
4817+ ],
4818+ "stores": {
4819+ "china-mobile": {
4820+ "status": "Published"
4821+ },
4822+ "ninjablocks": {
4823+ "status": "Published"
4824+ },
4825+ "ubuntu": {
4826+ "status": "Published"
4827+ }
4828+ },
4829+ "id": 156,
4830+ "title": "Calendar",
4831+ "support_url": "mailto:ubuntu-touch-coreapps@lists.launchpad.net",
4832+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/02/calendar-app_32.png",
4833+ "binary_filesize": 149943,
4834+ "download_url": "https://public.apps.ubuntu.com/download/com.ubuntu/calendar/com.ubuntu.calendar_0.4.572_all.click",
4835+ "allow_unauthenticated": false,
4836+ "content": "application",
4837+ "developer_name": "Ubuntu Core App Developers",
4838+ "version": "0.4.600",
4839+ "_links": {
4840+ "curies": [
4841+ {
4842+ "href": "https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#reltype_{rel}",
4843+ "name": "clickindex",
4844+ "templated": true
4845+ }
4846+ ],
4847+ "self": {
4848+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.calendar"
4849+ }
4850+ },
4851+ "company_name": "",
4852+ "department": [
4853+ "accessories"
4854+ ],
4855+ "screenshot_urls": [
4856+ "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/10/device-2014-10-02-155500.png",
4857+ "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/10/device-2014-10-02-155757.png",
4858+ "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/10/device-2014-10-02-155855.png"
4859+ ],
4860+ "website": "https://launchpad.net/ubuntu-calendar-app",
4861+ "description": "The Calendar application for Ubuntu devices lets you organise your life your way by month, week or daily diary\nIt’s about the task and the context; use the calendar app as a todo list, a diary, a planner, a journal, a life log; and the calendar will behave how you need it to.",
4862+ "click_framework": [
4863+ "ubuntu-sdk-14.10-qml"
4864+ ],
4865+ "price": 0,
4866+ "translations": {},
4867+ "blacklist_country_codes": [],
4868+ "date_published": "2013-10-16T19:19:29.080512Z",
4869+ "alias": null,
4870+ "prices": {},
4871+ "icon_urls": {
4872+ "256": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/02/calendar-app_32.png"
4873+ },
4874+ "download_sha512": "2fa658804e63da1869037cd9bc74b792875404f03b6c6449271ae5244688ff42a4524712ccb748ab9004344cccddd59063f3d3a4af899a3cc6f64ddc1a27072b",
4875+ "publisher": "Ubuntu Core App Developers",
4876+ "name": "com.ubuntu.calendar",
4877+ "license": "GNU GPL v3",
4878+ "changelog": "* Improved week view\r\n* Events appear faster\r\n* Events can be created by longpress on timeline\r\n* Events can be moved via drag/drop",
4879+ "click_version": "0.1",
4880+ "ratings_average": 4,
4881+ "architecture": [
4882+ "all"
4883+ ]
4884+}
4885
4886=== added file 'tests/scope-harness/fake_responses/click-package-index/api/v1/search'
4887--- tests/scope-harness/fake_responses/click-package-index/api/v1/search 1970-01-01 00:00:00 +0000
4888+++ tests/scope-harness/fake_responses/click-package-index/api/v1/search 2015-04-14 19:49:39 +0000
4889@@ -0,0 +1,76 @@
4890+{
4891+ "_embedded": {
4892+ "clickindex:package": [
4893+ {
4894+ "publisher": "Ubuntu Core App Developers",
4895+ "name": "com.ubuntu.calendar",
4896+ "title": "Calendar",
4897+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/02/calendar-app_32.png",
4898+ "price": 0,
4899+ "content": "application",
4900+ "ratings_average": 4,
4901+ "version": "0.4.600",
4902+ "_links": {
4903+ "self": {
4904+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.calendar"
4905+ }
4906+ },
4907+ "architecture": [
4908+ "all"
4909+ ],
4910+ "prices": {}
4911+ },
4912+ {
4913+ "publisher": "Sonrise Software",
4914+ "name": "com.ubuntu.developer.mdspencer.project-dashboard",
4915+ "title": "Project Dashboard",
4916+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/03/project-dashboard-256.png",
4917+ "price": 0,
4918+ "content": "application",
4919+ "ratings_average": 3.71,
4920+ "version": "0.5.3",
4921+ "_links": {
4922+ "self": {
4923+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.mdspencer.project-dashboard"
4924+ }
4925+ },
4926+ "architecture": [
4927+ "all"
4928+ ],
4929+ "prices": {}
4930+ },
4931+ {
4932+ "publisher": "Canonical Group Limited",
4933+ "name": "com.ubuntu.developer.webapps.webapp-googlecalendar",
4934+ "title": "Google Calendar",
4935+ "icon_url": "https://myapps.developer.ubuntu.com/site_media/appmedia/2014/04/icon256_1.png",
4936+ "price": 0,
4937+ "content": "application",
4938+ "ratings_average": 3.5,
4939+ "version": "1.0.12",
4940+ "_links": {
4941+ "self": {
4942+ "href": "[FAKE_SERVER_BASE]/api/v1/package/com.ubuntu.developer.webapps.webapp-googlecalendar"
4943+ }
4944+ },
4945+ "architecture": [
4946+ "all"
4947+ ],
4948+ "prices": {}
4949+ }
4950+ ]
4951+ },
4952+ "_links": {
4953+ "curies": [
4954+ {
4955+ "href": "https://wiki.ubuntu.com/AppStore/Interfaces/ClickPackageIndex#reltype_{rel}",
4956+ "name": "clickindex",
4957+ "templated": true
4958+ }
4959+ ],
4960+ "self": {
4961+ "href": "[FAKE_SERVER_BASE]/api/v1/search?q=Calendar"
4962+ }
4963+ }
4964+}
4965+
4966
4967=== added directory 'tests/scope-harness/fake_responses/ratings-and-reviews'
4968=== added directory 'tests/scope-harness/fake_responses/ratings-and-reviews/click'
4969=== added directory 'tests/scope-harness/fake_responses/ratings-and-reviews/click/api'
4970=== added directory 'tests/scope-harness/fake_responses/ratings-and-reviews/click/api/1.0'
4971=== added directory 'tests/scope-harness/fake_responses/ratings-and-reviews/click/api/1.0/reviews'
4972=== added file 'tests/scope-harness/fake_responses/ratings-and-reviews/click/api/1.0/reviews/index.json'
4973--- tests/scope-harness/fake_responses/ratings-and-reviews/click/api/1.0/reviews/index.json 1970-01-01 00:00:00 +0000
4974+++ tests/scope-harness/fake_responses/ratings-and-reviews/click/api/1.0/reviews/index.json 2015-04-14 19:49:39 +0000
4975@@ -0,0 +1,210 @@
4976+[
4977+ {
4978+ "rating": 2,
4979+ "hide": false,
4980+ "package_name": "com.ubuntu.calendar",
4981+ "language": "es",
4982+ "reviewer_username": "hnXrswe",
4983+ "usefulness_total": 0,
4984+ "usefulness_favorable": 0,
4985+ "review_text": "El diseño es simple pero aceptable, pero no sincroniza con google. Nexus 4.",
4986+ "date_deleted": null,
4987+ "summary": "Review",
4988+ "version": "0.4.585",
4989+ "date_created": "2015-02-23T16:46:16.314Z",
4990+ "reviewer_displayname": "javipt",
4991+ "id": 1201
4992+ },
4993+ {
4994+ "rating": 2,
4995+ "hide": false,
4996+ "package_name": "com.ubuntu.calendar",
4997+ "language": "es",
4998+ "reviewer_username": "oskitar1981",
4999+ "usefulness_total": 0,
5000+ "usefulness_favorable": 0,
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: