Merge lp:~alecu/unity-scope-click/refunds-previews into lp:unity-scope-click

Proposed by Alejandro J. Cura
Status: Merged
Approved by: dobey
Approved revision: 328
Merged at revision: 327
Proposed branch: lp:~alecu/unity-scope-click/refunds-previews
Merge into: lp:unity-scope-click
Prerequisite: lp:~alecu/unity-scope-click/split-close-preview
Diff against target: 742 lines (+438/-23)
9 files modified
libclickscope/click/pay.cpp (+33/-3)
libclickscope/click/pay.h (+4/-3)
libclickscope/click/preview.cpp (+138/-4)
libclickscope/click/preview.h (+35/-3)
libclickscope/tests/test_preview.cpp (+199/-6)
scope/clickapps/apps-scope.cpp (+6/-0)
scope/clickstore/store-query.cpp (+13/-4)
scope/clickstore/store-query.h (+1/-0)
scope/clickstore/store-scope.cpp (+9/-0)
To merge this branch: bzr merge lp:~alecu/unity-scope-click/refunds-previews
Reviewer Review Type Date Requested Status
dobey (community) Approve
Charles Kerr (community) Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+257444@code.launchpad.net

Commit message

Show the "Refund" button and call the pay-service when clicked

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
324. By Alejandro J. Cura

Do not use the refundable time for equality comparison

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Charles Kerr (charlesk) wrote :

I think the branch needs to be resynced with trunk before silo.

Individual comments inline. Overall, looks good. I added a few optional suggestions but didn't see any showstoppers.

Revision history for this message
Charles Kerr (charlesk) :
review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
325. By Alejandro J. Cura

Merged trunk in

326. By Alejandro J. Cura

Fixes suggested by charles' review

327. By Alejandro J. Cura

Fix broken tests

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Thanks for the thorough review!

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

Some commentary in line.

review: Needs Information
Revision history for this message
Charles Kerr (charlesk) wrote :

Thanks for making the minor tweaks I suggested :)

Still LGTM overall, I agree with dobey's suggestion and it got me thinking about another issue, comments inline.

review: Approve
328. By Alejandro J. Cura

Don't call parse_timestamp twice in a row.

Revision history for this message
Alejandro J. Cura (alecu) wrote :

Thanks for the reviews, fixed as suggested.

Revision history for this message
dobey (dobey) :
review: Approve
329. By Alejandro J. Cura

Fixed the type of the timestamp stored in result variant

330. By Alejandro J. Cura

Call libpay.refund() from the qt thread

331. By Alejandro J. Cura

Add more logging before and after calling libpay

332. By Alejandro J. Cura

Initialize pay package before starting a refund

333. By Alejandro J. Cura

Now correctly setting up the pay package

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'libclickscope/click/pay.cpp'
--- libclickscope/click/pay.cpp 2015-04-10 13:13:46 +0000
+++ libclickscope/click/pay.cpp 2015-05-29 21:20:25 +0000
@@ -82,7 +82,16 @@
82namespace pay {82namespace pay {
8383
84bool operator==(const Purchase& lhs, const Purchase& rhs) {84bool operator==(const Purchase& lhs, const Purchase& rhs) {
85 return lhs.name == rhs.name && lhs.refundable_until == rhs.refundable_until;85 return lhs.name == rhs.name;
86}
87
88Package& Package::instance() {
89 static Package the_instance;
90 return the_instance;
91}
92
93Package::Package() : impl(new Private())
94{
86}95}
8796
88Package::Package(const QSharedPointer<click::web::Client>& client) :97Package::Package(const QSharedPointer<click::web::Client>& client) :
@@ -101,6 +110,16 @@
101 }110 }
102}111}
103112
113bool Package::refund(const std::string& pkg_name)
114{
115 if (!running) {
116 qDebug() << "pay service starting";
117 setup_pay_service();
118 }
119 qDebug() << "actually calling refund";
120 return pay_package_item_start_refund(impl->pay_package, pkg_name.c_str());
121}
122
104bool Package::verify(const std::string& pkg_name)123bool Package::verify(const std::string& pkg_name)
105{124{
106 typedef std::pair<std::string, bool> _PurchasedTuple;125 typedef std::pair<std::string, bool> _PurchasedTuple;
@@ -167,8 +186,12 @@
167 const json::Value item = root[i];186 const json::Value item = root[i];
168 if (item[JsonKeys::state].asString() == PURCHASE_STATE_COMPLETE) {187 if (item[JsonKeys::state].asString() == PURCHASE_STATE_COMPLETE) {
169 auto package_name = item[JsonKeys::package_name].asString();188 auto package_name = item[JsonKeys::package_name].asString();
189 qDebug() << "parsing:" << package_name.c_str();
170 auto refundable_until_value = item[JsonKeys::refundable_until];190 auto refundable_until_value = item[JsonKeys::refundable_until];
171 Purchase p(package_name, parse_timestamp(refundable_until_value));191 qDebug() << "refundable until:" << refundable_until_value.asString().c_str();
192 auto refundable_parsed = parse_timestamp(refundable_until_value);
193 qDebug() << "parsed:" << refundable_parsed;
194 Purchase p(package_name, refundable_parsed);
172 purchases.insert(p);195 purchases.insert(p);
173 }196 }
174 }197 }
@@ -195,10 +218,17 @@
195218
196void Package::setup_pay_service()219void Package::setup_pay_service()
197{220{
198 impl->pay_package = pay_package_new(Package::NAME);221 qDebug() << "new package";
222 PayPackage* newpkg = pay_package_new(Package::NAME);
223 qDebug() << "got package:" << newpkg;
224 qDebug() << "about to set it on impl:" << impl.isNull();
225 fprintf(stderr, "and the package is at: %p\n", impl->pay_package);
226 impl->pay_package = newpkg;
227 qDebug() << "installing observer";
199 pay_package_item_observer_install(impl->pay_package,228 pay_package_item_observer_install(impl->pay_package,
200 pay_verification_observer,229 pay_verification_observer,
201 this);230 this);
231 qDebug() << "Flag we are running";
202 running = true;232 running = true;
203}233}
204234
205235
=== modified file 'libclickscope/click/pay.h'
--- libclickscope/click/pay.h 2015-04-10 13:13:46 +0000
+++ libclickscope/click/pay.h 2015-05-29 21:20:25 +0000
@@ -92,21 +92,22 @@
92 public:92 public:
93 constexpr static const char* NAME{"click-scope"};93 constexpr static const char* NAME{"click-scope"};
9494
95 Package() = default;95 Package();
96 Package(const QSharedPointer<click::web::Client>& client);96 Package(const QSharedPointer<click::web::Client>& client);
97 virtual ~Package();97 virtual ~Package();
9898
99 virtual bool verify(const std::string& pkg_name);99 virtual bool verify(const std::string& pkg_name);
100 virtual click::web::Cancellable get_purchases(std::function<void(const PurchaseSet& purchased_apps)> callback);100 virtual click::web::Cancellable get_purchases(std::function<void(const PurchaseSet& purchased_apps)> callback);
101101 virtual bool refund(const std::string& pkg_name);
102 static std::string get_base_url();102 static std::string get_base_url();
103 static Package& instance();
103104
104 protected:105 protected:
105 virtual void setup_pay_service();106 virtual void setup_pay_service();
106 virtual void pay_package_verify(const std::string& pkg_name);107 virtual void pay_package_verify(const std::string& pkg_name);
107108
108 struct Private;109 struct Private;
109 std::shared_ptr<pay::Package::Private> impl;110 QScopedPointer<pay::Package::Private> impl;
110111
111 bool running = false;112 bool running = false;
112 QSharedPointer<click::web::Client> client;113 QSharedPointer<click::web::Client> client;
113114
=== modified file 'libclickscope/click/preview.cpp'
--- libclickscope/click/preview.cpp 2015-04-10 14:24:10 +0000
+++ libclickscope/click/preview.cpp 2015-05-29 21:20:25 +0000
@@ -36,6 +36,7 @@
36#include <click/dbus_constants.h>36#include <click/dbus_constants.h>
37#include <click/departments-db.h>37#include <click/departments-db.h>
38#include <click/utils.h>38#include <click/utils.h>
39#include <click/pay.h>
3940
40#include <boost/algorithm/string/replace.hpp>41#include <boost/algorithm/string/replace.hpp>
4142
@@ -148,10 +149,16 @@
148 << " given with download_url" << QString::fromStdString(download_url);149 << " given with download_url" << QString::fromStdString(download_url);
149 return new UninstalledPreview(result, client, depts, nam);150 return new UninstalledPreview(result, client, depts, nam);
150 }151 }
152 } else if (metadict.count(click::Preview::Actions::CANCEL_PURCHASE_UNINSTALLED) != 0) {
153 return new CancelPurchasePreview(result, false);
154 } else if (metadict.count(click::Preview::Actions::CANCEL_PURCHASE_INSTALLED) != 0) {
155 return new CancelPurchasePreview(result, true);
151 } else if (metadict.count(click::Preview::Actions::UNINSTALL_CLICK) != 0) {156 } else if (metadict.count(click::Preview::Actions::UNINSTALL_CLICK) != 0) {
152 return new UninstallConfirmationPreview(result);157 return new UninstallConfirmationPreview(result);
153 } else if (metadict.count(click::Preview::Actions::CONFIRM_UNINSTALL) != 0) {158 } else if (metadict.count(click::Preview::Actions::CONFIRM_UNINSTALL) != 0) {
154 return new UninstallingPreview(result, client, nam);159 return new UninstallingPreview(result, client, nam);
160 } else if (metadict.count(click::Preview::Actions::CONFIRM_CANCEL_PURCHASE) != 0) {
161 return new CancellingPurchasePreview(result, client, nam);
155 } else if (metadict.count(click::Preview::Actions::RATED) != 0) {162 } else if (metadict.count(click::Preview::Actions::RATED) != 0) {
156 return new InstalledPreview(result, metadata, client, depts);163 return new InstalledPreview(result, metadata, client, depts);
157 } else if (metadict.count(click::Preview::Actions::SHOW_UNINSTALLED) != 0) {164 } else if (metadict.count(click::Preview::Actions::SHOW_UNINSTALLED) != 0) {
@@ -522,6 +529,16 @@
522 return widgets;529 return widgets;
523}530}
524531
532bool PreviewStrategy::isRefundable() const
533{
534 time_t refundable_until = 0;
535 if (result.contains("refundable_until")) {
536 refundable_until = result["refundable_until"].get_int64_t();
537 }
538 time_t now = time(NULL);
539 // refund button is not shown if less than ten seconds left
540 return refundable_until >= (now + 10);
541}
525542
526// class DownloadErrorPreview543// class DownloadErrorPreview
527544
@@ -747,10 +764,17 @@
747 }764 }
748 if (manifest.removable)765 if (manifest.removable)
749 {766 {
750 builder.add_tuple({767 if (!isRefundable()) {
751 {"id", scopes::Variant(click::Preview::Actions::UNINSTALL_CLICK)},768 builder.add_tuple({
752 {"label", scopes::Variant(_("Uninstall"))}769 {"id", scopes::Variant(click::Preview::Actions::UNINSTALL_CLICK)},
753 });770 {"label", scopes::Variant(_("Uninstall"))}
771 });
772 } else {
773 builder.add_tuple({
774 {"id", scopes::Variant(click::Preview::Actions::CANCEL_PURCHASE_INSTALLED)},
775 {"label", scopes::Variant(_("Cancel Purchase"))}
776 });
777 }
754 }778 }
755 if (!uri.empty() || manifest.removable) {779 if (!uri.empty() || manifest.removable) {
756 buttons.add_attribute_value("actions", builder.end());780 buttons.add_attribute_value("actions", builder.end());
@@ -852,6 +876,69 @@
852 return widgets;876 return widgets;
853}877}
854878
879// class CancelPurchasePreview
880
881CancelPurchasePreview::CancelPurchasePreview(const unity::scopes::Result& result, bool installed)
882 : PreviewStrategy(result), installed(installed)
883{
884}
885
886CancelPurchasePreview::~CancelPurchasePreview()
887{
888}
889
890scopes::PreviewWidgetList CancelPurchasePreview::build_widgets()
891{
892 scopes::PreviewWidgetList widgets;
893
894 scopes::PreviewWidget confirmation("confirmation", "text");
895
896 std::string title = result["title"].get_string();
897 // TRANSLATORS: Do NOT translate ${title} here.
898 std::string message =
899 _("Are you sure you want to cancel the purchase of '${title}'? The app will be uninstalled.");
900
901 boost::replace_first(message, "${title}", title);
902 confirmation.add_attribute_value("text", scopes::Variant(message));
903 widgets.push_back(confirmation);
904
905 scopes::PreviewWidget buttons("buttons", "actions");
906 scopes::VariantBuilder builder;
907
908 auto action_no = installed ? click::Preview::Actions::SHOW_INSTALLED
909 : click::Preview::Actions::SHOW_UNINSTALLED;
910
911 builder.add_tuple({
912 {"id", scopes::Variant(action_no)},
913 {"label", scopes::Variant(_("No"))}
914 });
915 builder.add_tuple({
916 {"id", scopes::Variant(click::Preview::Actions::CONFIRM_CANCEL_PURCHASE)},
917 {"label", scopes::Variant(_("Yes, cancel purchase"))}
918 });
919
920 buttons.add_attribute_value("actions", builder.end());
921 widgets.push_back(buttons);
922
923 scopes::PreviewWidget policy("policy", "text");
924 policy.add_attribute_value("title", scopes::Variant{_("Returns and cancellation policy")});
925 policy.add_attribute_value("text", scopes::Variant{
926 _("When purchasing an app in the Ubuntu Store, you can cancel the charge within 15 minutes "
927 "after installation. If the cancel period has passed, we recommend contacting the app "
928 "developer directly for a refund.\n"
929 "You can find the developer’s contact information listed on the app’s preview page in the "
930 "Ubuntu Store.\n"
931 "Keep in mind that you cannot cancel the purchasing process of an app more than once.")});
932 widgets.push_back(policy);
933
934 return widgets;
935}
936
937void CancelPurchasePreview::run(unity::scopes::PreviewReplyProxy const& reply)
938{
939 // NOTE: no need to populateDetails() here.
940 reply->push(build_widgets());
941}
855942
856// class UninstallConfirmationPreview943// class UninstallConfirmationPreview
857944
@@ -975,6 +1062,13 @@
975 {"download_url", scopes::Variant(details.download_url)},1062 {"download_url", scopes::Variant(details.download_url)},
976 {"download_sha512", scopes::Variant(details.download_sha512)},1063 {"download_sha512", scopes::Variant(details.download_sha512)},
977 });1064 });
1065 if (isRefundable()) {
1066 builder.add_tuple(
1067 {
1068 {"id", scopes::Variant(click::Preview::Actions::CANCEL_PURCHASE_UNINSTALLED)},
1069 {"label", scopes::Variant(_("Cancel Purchase"))},
1070 });
1071 }
978 buttons.add_attribute_value("actions", builder.end());1072 buttons.add_attribute_value("actions", builder.end());
979 oa_client.register_account_login_item(buttons,1073 oa_client.register_account_login_item(buttons,
980 scopes::OnlineAccountClient::PostLoginAction::ContinueActivation,1074 scopes::OnlineAccountClient::PostLoginAction::ContinueActivation,
@@ -1028,4 +1122,44 @@
1028}1122}
10291123
10301124
1125// class CancellingPurchasePreview : public UninstallingPreview
1126
1127CancellingPurchasePreview::CancellingPurchasePreview(const unity::scopes::Result& result,
1128 const QSharedPointer<click::web::Client>& client,
1129 const QSharedPointer<click::network::AccessManager>& nam)
1130 : UninstallingPreview(result, client, nam)
1131{
1132}
1133
1134CancellingPurchasePreview::~CancellingPurchasePreview()
1135{
1136}
1137
1138void CancellingPurchasePreview::run(unity::scopes::PreviewReplyProxy const& reply)
1139{
1140 qDebug() << "in CancellingPurchasePreview::run, calling cancel_purchase";
1141 cancel_purchase();
1142 qDebug() << "in CancellingPurchasePreview::run, calling UninstallingPreview::run()";
1143 UninstallingPreview::run(reply);
1144}
1145
1146void CancellingPurchasePreview::cancel_purchase()
1147{
1148 auto package_name = result["name"].get_string();
1149 qDebug() << "Will cancel the purchase of:" << package_name.c_str();
1150
1151 std::promise<bool> refund_promise;
1152 std::future<bool> refund_future = refund_promise.get_future();
1153
1154 run_under_qt([&refund_promise, package_name]() {
1155 qDebug() << "Calling refund for:" << package_name.c_str();
1156 auto ret = pay::Package::instance().refund(package_name);
1157 qDebug() << "Refund returned:" << ret;
1158 refund_promise.set_value(ret);
1159 });
1160 bool finished = refund_future.get();
1161 qDebug() << "Finished refund:" << finished;
1162}
1163
1164
1031} // namespace click1165} // namespace click
10321166
=== modified file 'libclickscope/click/preview.h'
--- libclickscope/click/preview.h 2015-04-10 14:24:10 +0000
+++ libclickscope/click/preview.h 2015-05-29 21:20:25 +0000
@@ -98,8 +98,11 @@
98 constexpr static const char* PIN_TO_LAUNCHER{"pin_to_launcher"};98 constexpr static const char* PIN_TO_LAUNCHER{"pin_to_launcher"};
99 constexpr static const char* UNINSTALL_CLICK{"uninstall_click"};99 constexpr static const char* UNINSTALL_CLICK{"uninstall_click"};
100 constexpr static const char* CONFIRM_UNINSTALL{"confirm_uninstall"};100 constexpr static const char* CONFIRM_UNINSTALL{"confirm_uninstall"};
101 constexpr static const char* CANCEL_PURCHASE_UNINSTALLED{"cancel_purchase_uninstalled"};
102 constexpr static const char* CANCEL_PURCHASE_INSTALLED{"cancel_purchase_installed"};
101 constexpr static const char* SHOW_UNINSTALLED{"show_uninstalled"};103 constexpr static const char* SHOW_UNINSTALLED{"show_uninstalled"};
102 constexpr static const char* SHOW_INSTALLED{"show_installed"};104 constexpr static const char* SHOW_INSTALLED{"show_installed"};
105 constexpr static const char* CONFIRM_CANCEL_PURCHASE{"confirm_cancel_purchase"};
103 constexpr static const char* OPEN_ACCOUNTS{"open_accounts"};106 constexpr static const char* OPEN_ACCOUNTS{"open_accounts"};
104 constexpr static const char* RATED{"rated"};107 constexpr static const char* RATED{"rated"};
105 };108 };
@@ -151,6 +154,7 @@
151 virtual scopes::PreviewWidget build_updates_table(const PackageDetails& details);154 virtual scopes::PreviewWidget build_updates_table(const PackageDetails& details);
152 virtual std::string build_whats_new(const PackageDetails& details);155 virtual std::string build_whats_new(const PackageDetails& details);
153 virtual void run_under_qt(const std::function<void ()> &task);156 virtual void run_under_qt(const std::function<void ()> &task);
157 virtual bool isRefundable() const;
154158
155 scopes::Result result;159 scopes::Result result;
156 QSharedPointer<click::web::Client> client;160 QSharedPointer<click::web::Client> client;
@@ -209,10 +213,9 @@
209213
210protected:214protected:
211 void getApplicationUri(const Manifest& manifest, std::function<void(const std::string&)> callback);215 void getApplicationUri(const Manifest& manifest, std::function<void(const std::string&)> callback);
212216 scopes::PreviewWidgetList createButtons(const std::string& uri,
217 const click::Manifest& manifest);
213private:218private:
214 static scopes::PreviewWidgetList createButtons(const std::string& uri,
215 const click::Manifest& manifest);
216 scopes::ActionMetadata metadata;219 scopes::ActionMetadata metadata;
217};220};
218221
@@ -236,6 +239,20 @@
236 virtual scopes::PreviewWidgetList purchasingWidgets(const PackageDetails &);239 virtual scopes::PreviewWidgetList purchasingWidgets(const PackageDetails &);
237};240};
238241
242class CancelPurchasePreview : public PreviewStrategy
243{
244public:
245 CancelPurchasePreview(const unity::scopes::Result& result, bool installed);
246
247 virtual ~CancelPurchasePreview();
248
249 void run(unity::scopes::PreviewReplyProxy const& reply) override;
250
251protected:
252 scopes::PreviewWidgetList build_widgets();
253 bool installed;
254};
255
239class UninstallConfirmationPreview : public PreviewStrategy256class UninstallConfirmationPreview : public PreviewStrategy
240{257{
241public:258public:
@@ -284,6 +301,21 @@
284301
285};302};
286303
304class CancellingPurchasePreview : public UninstallingPreview
305{
306public:
307 CancellingPurchasePreview(const unity::scopes::Result& result,
308 const QSharedPointer<click::web::Client>& client,
309 const QSharedPointer<click::network::AccessManager>& nam);
310
311 virtual ~CancellingPurchasePreview();
312
313 void run(unity::scopes::PreviewReplyProxy const& reply) override;
314
315protected:
316 void cancel_purchase();
317};
318
287} // namespace click319} // namespace click
288320
289#endif321#endif
290322
=== modified file 'libclickscope/tests/test_preview.cpp'
--- libclickscope/tests/test_preview.cpp 2015-03-26 18:49:42 +0000
+++ libclickscope/tests/test_preview.cpp 2015-05-29 21:20:25 +0000
@@ -27,6 +27,8 @@
27 * files in the program, then also delete it here.27 * files in the program, then also delete it here.
28 */28 */
2929
30#include <time.h>
31
30#include <unity/scopes/testing/MockPreviewReply.h>32#include <unity/scopes/testing/MockPreviewReply.h>
31#include <unity/scopes/testing/Result.h>33#include <unity/scopes/testing/Result.h>
3234
@@ -34,6 +36,7 @@
34#include <click/preview.h>36#include <click/preview.h>
35#include <fake_json.h>37#include <fake_json.h>
36#include <click/index.h>38#include <click/index.h>
39#include <click/interface.h>
37#include <click/reviews.h>40#include <click/reviews.h>
38#include <boost/locale/time_zone.hpp>41#include <boost/locale/time_zone.hpp>
3942
@@ -100,6 +103,7 @@
100 using click::PreviewStrategy::build_updates_table;103 using click::PreviewStrategy::build_updates_table;
101 using click::PreviewStrategy::build_whats_new;104 using click::PreviewStrategy::build_whats_new;
102 using click::PreviewStrategy::populateDetails;105 using click::PreviewStrategy::populateDetails;
106 using click::PreviewStrategy::isRefundable;
103};107};
104108
105class PreviewsBaseTest : public Test109class PreviewsBaseTest : public Test
@@ -330,15 +334,15 @@
330 }334 }
331};335};
332336
333class FakeUninstalledPreview : public click::UninstalledPreview {337class FakeBaseUninstalledPreview : public click::UninstalledPreview {
334 std::string object_path;338 std::string object_path;
335public:339public:
336 std::unique_ptr<FakeDownloader> fake_downloader;340 std::unique_ptr<FakeDownloader> fake_downloader;
337 FakeUninstalledPreview(const std::string& object_path,341 FakeBaseUninstalledPreview(const std::string& object_path,
338 const unity::scopes::Result& result,342 const unity::scopes::Result& result,
339 const QSharedPointer<click::web::Client>& client,343 const QSharedPointer<click::web::Client>& client,
340 const std::shared_ptr<click::DepartmentsDb>& depts,344 const std::shared_ptr<click::DepartmentsDb>& depts,
341 const QSharedPointer<click::network::AccessManager>& nam)345 const QSharedPointer<click::network::AccessManager>& nam)
342 : click::UninstalledPreview(result, client, depts, nam), object_path(object_path),346 : click::UninstalledPreview(result, client, depts, nam), object_path(object_path),
343 fake_downloader(new FakeDownloader(object_path, nam))347 fake_downloader(new FakeDownloader(object_path, nam))
344 {348 {
@@ -356,10 +360,22 @@
356 details_callback(details);360 details_callback(details);
357 reviews_callback({}, click::Reviews::Error::NoError);361 reviews_callback({}, click::Reviews::Error::NoError);
358 }362 }
363};
364
365class FakeUninstalledPreview : public FakeBaseUninstalledPreview {
366public:
359 MOCK_METHOD1(uninstalledActionButtonWidgets, scopes::PreviewWidgetList (const click::PackageDetails &details));367 MOCK_METHOD1(uninstalledActionButtonWidgets, scopes::PreviewWidgetList (const click::PackageDetails &details));
360 MOCK_METHOD1(progressBarWidget, scopes::PreviewWidgetList(const std::string& object_path));368 MOCK_METHOD1(progressBarWidget, scopes::PreviewWidgetList(const std::string& object_path));
369 FakeUninstalledPreview(const std::string& object_path,
370 const unity::scopes::Result& result,
371 const QSharedPointer<click::web::Client>& client,
372 const std::shared_ptr<click::DepartmentsDb>& depts,
373 const QSharedPointer<click::network::AccessManager>& nam)
374 : FakeBaseUninstalledPreview(object_path, result, client, depts, nam) {
375 }
361};376};
362377
378
363TEST_F(UninstalledPreviewTest, testDownloadInProgress) {379TEST_F(UninstalledPreviewTest, testDownloadInProgress) {
364 std::string fake_object_path = "/fake/object/path";380 std::string fake_object_path = "/fake/object/path";
365381
@@ -385,3 +401,180 @@
385 preview.run(replyptr);401 preview.run(replyptr);
386 preview.fake_downloader->activate_callback();402 preview.fake_downloader->activate_callback();
387}403}
404
405class FakeUninstalledRefundablePreview : FakeBaseUninstalledPreview {
406public:
407 FakeUninstalledRefundablePreview(const unity::scopes::Result& result,
408 const QSharedPointer<click::web::Client>& client,
409 const std::shared_ptr<click::DepartmentsDb>& depts,
410 const QSharedPointer<click::network::AccessManager>& nam)
411 : FakeBaseUninstalledPreview(std::string{""}, result, client, depts, nam){
412 }
413 using click::UninstalledPreview::uninstalledActionButtonWidgets;
414 MOCK_CONST_METHOD0(isRefundable, bool());
415};
416
417unity::scopes::VariantArray get_actions_from_widgets(const unity::scopes::PreviewWidgetList& widgets, int widget_number) {
418 auto widget = *std::next(widgets.begin(), widget_number);
419 return widget.attribute_values()["actions"].get_array();
420}
421
422std::string get_action_from_widgets(const unity::scopes::PreviewWidgetList& widgets, int widget_number, int action_number) {
423 auto actions = get_actions_from_widgets(widgets, widget_number);
424 auto selected_action = actions.at(action_number).get_dict();
425 return selected_action["id"].get_string();
426}
427
428TEST_F(UninstalledPreviewTest, testIsRefundableButtonShown) {
429 result["name"] = "fake_app_name";
430 result["price"] = 2.99;
431 result["purchased"] = true;
432 FakeUninstalledRefundablePreview preview(result, client, depts, nam);
433
434 click::PackageDetails pkgdetails;
435 EXPECT_CALL(preview, isRefundable()).Times(1)
436 .WillOnce(Return(true));
437 auto widgets = preview.uninstalledActionButtonWidgets(pkgdetails);
438 ASSERT_EQ(get_action_from_widgets(widgets, 0, 1), "cancel_purchase_uninstalled");
439}
440
441TEST_F(UninstalledPreviewTest, testIsRefundableButtonNotShown) {
442 result["name"] = "fake_app_name";
443 result["price"] = 2.99;
444 result["purchased"] = true;
445 FakeUninstalledRefundablePreview preview(result, client, depts, nam);
446
447 click::PackageDetails pkgdetails;
448 EXPECT_CALL(preview, isRefundable()).Times(1)
449 .WillOnce(Return(false));
450 auto widgets = preview.uninstalledActionButtonWidgets(pkgdetails);
451 ASSERT_EQ(get_actions_from_widgets(widgets, 0).size(), 1);
452}
453
454class InstalledPreviewTest : public Test {
455protected:
456 unity::scopes::testing::Result result;
457 unity::scopes::ActionMetadata metadata;
458 unity::scopes::VariantMap metadict;
459 QSharedPointer<click::web::Client> client;
460 QSharedPointer<click::network::AccessManager> nam;
461 std::shared_ptr<click::DepartmentsDb> depts;
462
463public:
464 InstalledPreviewTest() : metadata("en_EN", "phone") {
465 }
466};
467
468class FakeInstalledRefundablePreview : public click::InstalledPreview {
469public:
470 FakeInstalledRefundablePreview(const unity::scopes::Result& result,
471 const unity::scopes::ActionMetadata& metadata,
472 const QSharedPointer<click::web::Client> client,
473 const std::shared_ptr<click::DepartmentsDb> depts)
474 : click::InstalledPreview(result, metadata, client, depts) {
475
476 }
477 using click::InstalledPreview::createButtons;
478 MOCK_CONST_METHOD0(isRefundable, bool());
479};
480
481TEST_F(InstalledPreviewTest, testIsRefundableButtonShown) {
482 FakeInstalledRefundablePreview preview(result, metadata, client, depts);
483 EXPECT_CALL(preview, isRefundable()).Times(1)
484 .WillOnce(Return(true));
485 click::Manifest manifest;
486 manifest.removable = true;
487 auto widgets = preview.createButtons("fake uri", manifest);
488 ASSERT_EQ(get_actions_from_widgets(widgets, 0).size(), 2);
489 ASSERT_EQ(get_action_from_widgets(widgets, 0, 1), "cancel_purchase_installed");
490}
491
492TEST_F(InstalledPreviewTest, testIsRefundableButtonNotShown) {
493 FakeInstalledRefundablePreview preview(result, metadata, client, depts);
494 EXPECT_CALL(preview, isRefundable()).Times(1)
495 .WillOnce(Return(false));
496 click::Manifest manifest;
497 manifest.removable = true;
498 auto widgets = preview.createButtons("fake uri", manifest);
499 ASSERT_EQ(get_actions_from_widgets(widgets, 0).size(), 2);
500 ASSERT_EQ(get_action_from_widgets(widgets, 0, 1), "uninstall_click");
501}
502
503
504class RefundableTest : public PreviewStrategyTest {
505
506};
507
508TEST_F(RefundableTest, testIsNotRefundableWhenFieldMissing) {
509 FakeResult result{vm};
510 FakePreview preview{result};
511 ASSERT_FALSE(preview.isRefundable());
512}
513
514TEST_F(RefundableTest, testIsNotRefundableWhenExpired) {
515 FakeResult result{vm};
516 time_t now = time(NULL);
517 result["refundable_until"] = (int64_t) (now - 300);
518 FakePreview preview{result};
519 ASSERT_FALSE(preview.isRefundable());
520}
521
522TEST_F(RefundableTest, testIsRefundable) {
523 FakeResult result{vm};
524 time_t now = time(NULL);
525 result["refundable_until"] = (int64_t) (now + 300);
526 FakePreview preview{result};
527 ASSERT_TRUE(preview.isRefundable());
528}
529
530TEST_F(RefundableTest, testIsNotRefundableWhenExpiringRealSoon) {
531 FakeResult result{vm};
532 time_t now = time(NULL);
533 result["refundable_until"] = (int64_t) (now + 8);
534 FakePreview preview{result};
535 ASSERT_FALSE(preview.isRefundable());
536}
537
538
539class FakeCancelPurchasePreview : public click::CancelPurchasePreview {
540public:
541 FakeCancelPurchasePreview(const unity::scopes::Result& result, bool installed)
542 : click::CancelPurchasePreview(result, installed) {
543
544 }
545 using click::CancelPurchasePreview::build_widgets;
546};
547
548class CancelPurchasePreviewTest : public PreviewsBaseTest {
549
550};
551
552TEST_F(CancelPurchasePreviewTest, testNoShowsInstalled)
553{
554 FakeResult result{vm};
555 result["title"] = "fake app";
556 FakeCancelPurchasePreview preview(result, true);
557 auto widgets = preview.build_widgets();
558 auto action = get_action_from_widgets(widgets, 1, 0);
559 ASSERT_EQ(action, "show_installed");
560}
561
562TEST_F(CancelPurchasePreviewTest, testNoShowsUninstalled)
563{
564 FakeResult result{vm};
565 result["title"] = "fake app";
566 FakeCancelPurchasePreview preview(result, false);
567 auto widgets = preview.build_widgets();
568 auto action = get_action_from_widgets(widgets, 1, 0);
569 ASSERT_EQ(action, "show_uninstalled");
570}
571
572TEST_F(CancelPurchasePreviewTest, testYesCancelsPurchase)
573{
574 FakeResult result{vm};
575 result["title"] = "fake app";
576 FakeCancelPurchasePreview preview(result, false);
577 auto widgets = preview.build_widgets();
578 auto action = get_action_from_widgets(widgets, 1, 1);
579 ASSERT_EQ(action, "confirm_cancel_purchase");
580}
388581
=== modified file 'scope/clickapps/apps-scope.cpp'
--- scope/clickapps/apps-scope.cpp 2015-04-10 14:24:10 +0000
+++ scope/clickapps/apps-scope.cpp 2015-05-29 21:20:25 +0000
@@ -118,6 +118,12 @@
118 if (action_id == click::Preview::Actions::UNINSTALL_CLICK) {118 if (action_id == click::Preview::Actions::UNINSTALL_CLICK) {
119 activation->setHint(click::Preview::Actions::UNINSTALL_CLICK, unity::scopes::Variant(true));119 activation->setHint(click::Preview::Actions::UNINSTALL_CLICK, unity::scopes::Variant(true));
120 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);120 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
121 } else if (action_id == click::Preview::Actions::CANCEL_PURCHASE_INSTALLED) {
122 activation->setHint(click::Preview::Actions::CANCEL_PURCHASE_INSTALLED, unity::scopes::Variant(true));
123 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
124 } else if (action_id == click::Preview::Actions::CANCEL_PURCHASE_UNINSTALLED) {
125 activation->setHint(click::Preview::Actions::CANCEL_PURCHASE_UNINSTALLED, unity::scopes::Variant(true));
126 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
121 } else if (action_id == click::Preview::Actions::SHOW_INSTALLED) {127 } else if (action_id == click::Preview::Actions::SHOW_INSTALLED) {
122 activation->setHint(click::Preview::Actions::SHOW_INSTALLED, unity::scopes::Variant(true));128 activation->setHint(click::Preview::Actions::SHOW_INSTALLED, unity::scopes::Variant(true));
123 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);129 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
124130
=== modified file 'scope/clickstore/store-query.cpp'
--- scope/clickstore/store-query.cpp 2015-03-18 14:00:53 +0000
+++ scope/clickstore/store-query.cpp 2015-05-29 21:20:25 +0000
@@ -293,7 +293,8 @@
293 ss << "☆ " << pkg.rating;293 ss << "☆ " << pkg.rating;
294 std::string rating{ss.str()};294 std::string rating{ss.str()};
295295
296 bool purchased = false;296 bool was_purchased = false;
297 time_t refundable_until = 0;
297 double cur_price{0.00};298 double cur_price{0.00};
298 auto suggested = impl->index.get_suggested_currency();299 auto suggested = impl->index.get_suggested_currency();
299 std::string currency = Configuration::get_currency(suggested);300 std::string currency = Configuration::get_currency(suggested);
@@ -307,20 +308,27 @@
307 res["price"] = scopes::Variant(cur_price);308 res["price"] = scopes::Variant(cur_price);
308 res[click::Query::ResultKeys::VERSION] = pkg.version;309 res[click::Query::ResultKeys::VERSION] = pkg.version;
309310
311
312 qDebug() << "App:" << pkg.name.c_str() << ", price:" << cur_price;
310 if (cur_price > 0.00f) {313 if (cur_price > 0.00f) {
311 if (!Configuration::get_purchases_enabled()) {314 if (!Configuration::get_purchases_enabled()) {
312 // Don't show priced apps if flag not set315 // Don't show priced apps if flag not set
313 return;316 return;
314 }317 }
315 // Check if the priced app was already purchased.318 // Check if the priced app was already purchased.
316 purchased = purchased_apps.count({pkg.name}) != 0;319 auto purchased = purchased_apps.find({pkg.name});
320 was_purchased = purchased != purchased_apps.end();
321 if (was_purchased) {
322 refundable_until = purchased->refundable_until;
323 }
324 qDebug() << "was purchased?" << was_purchased << ", refundable_until:" << refundable_until;
317 }325 }
318 if (installed != installedPackages.end()) {326 if (installed != installedPackages.end()) {
319 res[click::Query::ResultKeys::INSTALLED] = true;327 res[click::Query::ResultKeys::INSTALLED] = true;
320 res[click::Query::ResultKeys::PURCHASED] = purchased;328 res[click::Query::ResultKeys::PURCHASED] = was_purchased;
321 price = _("✔ INSTALLED");329 price = _("✔ INSTALLED");
322 res[click::Query::ResultKeys::VERSION] = installed->version;330 res[click::Query::ResultKeys::VERSION] = installed->version;
323 } else if (purchased) {331 } else if (was_purchased) {
324 res[click::Query::ResultKeys::PURCHASED] = true;332 res[click::Query::ResultKeys::PURCHASED] = true;
325 res[click::Query::ResultKeys::INSTALLED] = false;333 res[click::Query::ResultKeys::INSTALLED] = false;
326 price = _("✔ PURCHASED");334 price = _("✔ PURCHASED");
@@ -336,6 +344,7 @@
336 }344 }
337 }345 }
338346
347 res[click::Query::ResultKeys::REFUNDABLE_UNTIL] = unity::scopes::Variant((int64_t)refundable_until);
339 res["price_area"] = price;348 res["price_area"] = price;
340 res["rating"] = rating;349 res["rating"] = rating;
341350
342351
=== modified file 'scope/clickstore/store-query.h'
--- scope/clickstore/store-query.h 2015-04-10 13:13:46 +0000
+++ scope/clickstore/store-query.h 2015-05-29 21:20:25 +0000
@@ -64,6 +64,7 @@
64 constexpr static const char* MAIN_SCREENSHOT{"main_screenshot"};64 constexpr static const char* MAIN_SCREENSHOT{"main_screenshot"};
65 constexpr static const char* INSTALLED{"installed"};65 constexpr static const char* INSTALLED{"installed"};
66 constexpr static const char* PURCHASED{"purchased"};66 constexpr static const char* PURCHASED{"purchased"};
67 constexpr static const char* REFUNDABLE_UNTIL{"refundable_until"};
67 constexpr static const char* VERSION{"version"};68 constexpr static const char* VERSION{"version"};
68 };69 };
6970
7071
=== modified file 'scope/clickstore/store-scope.cpp'
--- scope/clickstore/store-scope.cpp 2015-04-10 14:24:10 +0000
+++ scope/clickstore/store-scope.cpp 2015-05-29 21:20:25 +0000
@@ -145,6 +145,12 @@
145 } else if (action_id == click::Preview::Actions::DOWNLOAD_COMPLETED) {145 } else if (action_id == click::Preview::Actions::DOWNLOAD_COMPLETED) {
146 activation->setHint(click::Preview::Actions::DOWNLOAD_COMPLETED, unity::scopes::Variant(true));146 activation->setHint(click::Preview::Actions::DOWNLOAD_COMPLETED, unity::scopes::Variant(true));
147 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);147 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
148 } else if (action_id == click::Preview::Actions::CANCEL_PURCHASE_INSTALLED) {
149 activation->setHint(click::Preview::Actions::CANCEL_PURCHASE_INSTALLED, unity::scopes::Variant(true));
150 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
151 } else if (action_id == click::Preview::Actions::CANCEL_PURCHASE_UNINSTALLED) {
152 activation->setHint(click::Preview::Actions::CANCEL_PURCHASE_UNINSTALLED, unity::scopes::Variant(true));
153 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
148 } else if (action_id == click::Preview::Actions::UNINSTALL_CLICK) {154 } else if (action_id == click::Preview::Actions::UNINSTALL_CLICK) {
149 activation->setHint(click::Preview::Actions::UNINSTALL_CLICK, unity::scopes::Variant(true));155 activation->setHint(click::Preview::Actions::UNINSTALL_CLICK, unity::scopes::Variant(true));
150 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);156 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
@@ -157,6 +163,9 @@
157 } else if (action_id == click::Preview::Actions::CONFIRM_UNINSTALL) {163 } else if (action_id == click::Preview::Actions::CONFIRM_UNINSTALL) {
158 activation->setHint(click::Preview::Actions::CONFIRM_UNINSTALL, unity::scopes::Variant(true));164 activation->setHint(click::Preview::Actions::CONFIRM_UNINSTALL, unity::scopes::Variant(true));
159 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);165 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
166 } else if (action_id == click::Preview::Actions::CONFIRM_CANCEL_PURCHASE) {
167 activation->setHint(click::Preview::Actions::CONFIRM_CANCEL_PURCHASE, unity::scopes::Variant(true));
168 activation->setStatus(unity::scopes::ActivationResponse::Status::ShowPreview);
160 } else if (action_id == click::Preview::Actions::RATED) {169 } else if (action_id == click::Preview::Actions::RATED) {
161 scopes::VariantMap rating_info = metadata.scope_data().get_dict();170 scopes::VariantMap rating_info = metadata.scope_data().get_dict();
162 // Cast to int because widget gives us double, which is wrong.171 // Cast to int because widget gives us double, which is wrong.

Subscribers

People subscribed via source and target branches