Merge lp:~dobey/unity-scope-click/submit-reviews-backend into lp:unity-scope-click/devel

Proposed by dobey
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 220
Merged at revision: 221
Proposed branch: lp:~dobey/unity-scope-click/submit-reviews-backend
Merge into: lp:unity-scope-click/devel
Diff against target: 199 lines (+125/-3)
6 files modified
scope/click/reviews.cpp (+39/-0)
scope/click/reviews.h (+3/-0)
scope/click/webclient.cpp (+1/-1)
scope/click/webclient.h (+4/-1)
scope/tests/test_reviews.cpp (+77/-0)
scope/tests/test_webclient.cpp (+1/-1)
To merge this branch: bzr merge lp:~dobey/unity-scope-click/submit-reviews-backend
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Paweł Stołowski (community) Approve
Alejandro J. Cura (community) Approve
Review via email: mp+217122@code.launchpad.net

This proposal supersedes a proposal from 2014-04-21.

Commit message

Add a backend method to submit a review to the server.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote : Posted in a previous version of this proposal

26 + data << "{"
27 + << " 'package_name': '" << package.package.name << "',"
28 + << " 'version': '" << package.package.version << "',"
29 + << " 'rating': " << rating << ","
30 + << " 'review_text': '" << review_text << "',"
31 + << " 'summary': ''"
32 + << " }";

IMHO constructing json like that is a bad idea... What about proper escaping so that the resulting json is stil valid if review text contains an apostrophe? I think we should be using Json::FastWriter from jsoncpp instead.

Also, question - what is the purpose of the empty 'summary' attribute?

review: Needs Fixing
Revision history for this message
dobey (dobey) wrote : Posted in a previous version of this proposal

> 26 + data << "{"
> 27 + << " 'package_name': '" << package.package.name << "',"
> 28 + << " 'version': '" << package.package.version << "',"
> 29 + << " 'rating': " << rating << ","
> 30 + << " 'review_text': '" << review_text << "',"
> 31 + << " 'summary': ''"
> 32 + << " }";
>
> IMHO constructing json like that is a bad idea... What about proper escaping
> so that the resulting json is stil valid if review text contains an
> apostrophe? I think we should be using Json::FastWriter from jsoncpp instead.

Is there any good example of how to use that API? I spent some time trying to find how to construct a JSON string in C++, and best I found was to use a string stream.

> Also, question - what is the purpose of the empty 'summary' attribute?

The 'summary' is required by the server, but we have no UI to enter a summary (and summaries don't really make sense), so we just send an empty string for it.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
dobey (dobey) wrote :

> IMHO constructing json like that is a bad idea... What about proper escaping
> so that the resulting json is stil valid if review text contains an
> apostrophe? I think we should be using Json::FastWriter from jsoncpp instead.

Discovered Json::Value to use here. No need for FastWriter AFAICT, just creating a root Value, adding the members, and converting it with .toStyledString() appears to be all we need.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Branch looks good, but please add some test that checks that the json is being built right, and that both unicode and json special characters are escaped properly.

For this, it might make sense to refactor into a new function the bits that create the json, including the root.toStyledString(), then add a few tests for this function, plus one test that checks that Reviews::submit_review calls it right.

review: Needs Fixing
Revision history for this message
Paweł Stołowski (stolowski) wrote :

> > IMHO constructing json like that is a bad idea... What about proper escaping
> > so that the resulting json is stil valid if review text contains an
> > apostrophe? I think we should be using Json::FastWriter from jsoncpp
> instead.
>
> Discovered Json::Value to use here. No need for FastWriter AFAICT, just
> creating a root Value, adding the members, and converting it with
> .toStyledString() appears to be all we need.

Right, but if you take a look at the implementation of .toStyledString(), it just creates StyledWriter that outputs human-friendly JSON (i.e. with line breaks and indentation), whereas FastWriter outputs JSON without any formatting, which saves a little bit of bandwidth ;). Creating a FastWriter and passing it the root value yourself is just one extra line of code - see http://jsoncpp.sourceforge.net/json__value_8cpp_source.html#l01471 - and I think it's worth it; we don't need the data to be pretty-printed.

review: Needs Fixing
218. By dobey

Connect to the finished/error signals on the request, to log the result.

219. By dobey

Add a test that ensures we're passing UTf8 properly to the server in JSON.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Looks good, +1.

ps: small typo here:
+ // NOTE: gmock replaes the \" above as \\\" for HasSubstr(), so we have

review: Approve
220. By dobey

Fix small typo.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

LGTM, +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 'scope/click/reviews.cpp'
2--- scope/click/reviews.cpp 2014-04-09 18:49:03 +0000
3+++ scope/click/reviews.cpp 2014-04-25 18:38:08 +0000
4@@ -33,6 +33,9 @@
5 #include <boost/property_tree/json_parser.hpp>
6 #include <boost/foreach.hpp>
7
8+#include <json/value.h>
9+#include <json/writer.h>
10+
11 #include "reviews.h"
12
13 namespace click
14@@ -123,6 +126,42 @@
15 return click::web::Cancellable(response);
16 }
17
18+click::web::Cancellable Reviews::submit_review (const PackageDetails& package,
19+ int rating,
20+ const std::string& review_text)
21+{
22+ std::map<std::string, std::string> headers({
23+ {click::web::CONTENT_TYPE_HEADER, click::web::CONTENT_TYPE_JSON},
24+ });
25+ // TODO: Need to get language for the package/review.
26+ Json::Value root(Json::ValueType::objectValue);
27+ root["package_name"] = package.package.name;
28+ root["version"] = package.package.version;
29+ root["rating"] = rating;
30+ root["review_text"] = review_text;
31+ root["arch_tag"] = click::Configuration().get_architecture();
32+
33+ // NOTE: "summary" is a required field, but we don't have one. Use "".
34+ root["summary"] = "";
35+
36+ qDebug() << "Rating" << package.package.name.c_str() << rating;
37+
38+ QSharedPointer<click::web::Response> response = client->call
39+ (get_base_url() + click::REVIEWS_API_PATH, "POST", true,
40+ headers, Json::FastWriter().write(root), click::web::CallParams());
41+
42+ QObject::connect(response.data(), &click::web::Response::finished,
43+ [=](QString) {
44+ qDebug() << "Review submitted for:" << package.package.name.c_str();
45+ });
46+ QObject::connect(response.data(), &click::web::Response::error,
47+ [=](QString) {
48+ qCritical() << "Network error submitting a reviews for:" << package.package.name.c_str();
49+ });
50+
51+ return click::web::Cancellable(response);
52+}
53+
54 std::string Reviews::get_base_url ()
55 {
56 const char *env_url = getenv(REVIEWS_BASE_URL_ENVVAR.c_str());
57
58=== modified file 'scope/click/reviews.h'
59--- scope/click/reviews.h 2014-04-09 18:08:56 +0000
60+++ scope/click/reviews.h 2014-04-25 18:38:08 +0000
61@@ -78,6 +78,9 @@
62
63 click::web::Cancellable fetch_reviews (const std::string& package_name,
64 std::function<void(ReviewList, Error)> callback);
65+ click::web::Cancellable submit_review (const PackageDetails& package,
66+ int rating,
67+ const std::string& review_text);
68
69 static std::string get_base_url ();
70 };
71
72=== modified file 'scope/click/webclient.cpp'
73--- scope/click/webclient.cpp 2014-04-01 13:27:09 +0000
74+++ scope/click/webclient.cpp 2014-04-25 18:38:08 +0000
75@@ -117,7 +117,7 @@
76 [=, &request](const UbuntuOne::Token& token) {
77 QString auth_header = token.signUrl(url.toString(),
78 method.c_str());
79- request.setRawHeader(AUTHORIZATION.c_str(), auth_header.toUtf8());
80+ request.setRawHeader(AUTHORIZATION_HEADER.c_str(), auth_header.toUtf8());
81 doConnect();
82 });
83 sc.connect(impl->sso.data(), &click::CredentialsService::credentialsNotFound,
84
85=== modified file 'scope/click/webclient.h'
86--- scope/click/webclient.h 2014-04-01 13:27:09 +0000
87+++ scope/click/webclient.h 2014-04-25 18:38:08 +0000
88@@ -49,7 +49,10 @@
89 namespace web
90 {
91
92-const std::string AUTHORIZATION = "Authorization";
93+const std::string AUTHORIZATION_HEADER = "Authorization";
94+const std::string CONTENT_TYPE_HEADER = "Content-Type";
95+
96+const std::string CONTENT_TYPE_JSON = "application/json";
97
98 class Client;
99
100
101=== modified file 'scope/tests/test_reviews.cpp'
102--- scope/tests/test_reviews.cpp 2014-03-25 18:49:16 +0000
103+++ scope/tests/test_reviews.cpp 2014-04-25 18:38:08 +0000
104@@ -219,6 +219,83 @@
105 fetch_reviews_op.cancel();
106 }
107
108+TEST_F(ReviewsTest, testSubmitReviewIsCancellable)
109+{
110+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
111+ auto response = responseForReply(reply.asSharedPtr());
112+
113+ click::PackageDetails fake_details {
114+ {
115+ "ar.com.beuno.wheather-touch",
116+ "\u1F4A9 Weather",
117+ 1.99,
118+ "http://developer.staging.ubuntu.com/site_media/appmedia/2013/07/weather-icone-6797-64.png",
119+ "https://public.apps.staging.ubuntu.com/download/ar.com.beuno/wheather-touch/ar.com.beuno.wheather-touch-0.2",
120+ "0.2",
121+ },
122+ "\u1F4A9 Weather\nA weather application.",
123+ "https://public.apps.staging.ubuntu.com/download/ar.com.beuno/wheather-touch/ar.com.beuno.wheather-touch-0.2",
124+ 3.5,
125+ "these, are, key, words",
126+ "tos",
127+ "Proprietary",
128+ "Beuno",
129+ "sshot0",
130+ {"sshot1", "sshot2"},
131+ 177582,
132+ "0.2",
133+ "None"
134+ };
135+ EXPECT_CALL(*clientPtr, callImpl(_, "POST", true, _, _, _))
136+ .Times(1)
137+ .WillOnce(Return(response));
138+
139+ auto submit_op = reviewsPtr->submit_review(fake_details, 3, "blah");
140+ EXPECT_CALL(reply.instance, abort()).Times(1);
141+ submit_op.cancel();
142+}
143+
144+TEST_F(ReviewsTest, testSubmitReviewUtf8)
145+{
146+ LifetimeHelper<click::network::Reply, MockNetworkReply> reply;
147+ auto response = responseForReply(reply.asSharedPtr());
148+
149+ click::PackageDetails fake_details {
150+ {
151+ "com.example.weather",
152+ "Weather",
153+ 1.99,
154+ "http://developer.staging.ubuntu.com/site_media/appmedia/2013/07/weather-icone-6797-64.png",
155+ "https://public.apps.staging.ubuntu.com/download/com.example/weather/com.example.weather-0.2",
156+ "0.2",
157+ },
158+ "Weather application.",
159+ "https://public.apps.staging.ubuntu.com/download/com.example/weather/com.example.weather-0.2",
160+ 3.5,
161+ "these, are, key, words",
162+ "tos",
163+ "Proprietary",
164+ "Beuno",
165+ "sshot0",
166+ {"sshot1", "sshot2"},
167+ 177582,
168+ "0.2",
169+ "None"
170+ };
171+ std::string review_utf8 = "'\"小海嚴選";
172+
173+ // NOTE: gmock replaces the \" above as \\\" for HasSubstr(), so we have
174+ // to manually copy the string into the expected result.
175+ std::string expected_review_text = "\"review_text\":\"'\\\"小海嚴選\"";
176+
177+ EXPECT_CALL(*clientPtr, callImpl(_, "POST", true, _,
178+ HasSubstr(expected_review_text), _))
179+ .Times(1)
180+ .WillOnce(Return(response));
181+
182+ auto submit_op = reviewsPtr->submit_review(fake_details, 3, review_utf8);
183+}
184+
185 TEST_F(ReviewsTest, testGetBaseUrl)
186 {
187 const char *value = getenv(click::REVIEWS_BASE_URL_ENVVAR.c_str());
188
189=== modified file 'scope/tests/test_webclient.cpp'
190--- scope/tests/test_webclient.cpp 2014-04-01 13:27:09 +0000
191+++ scope/tests/test_webclient.cpp 2014-04-25 18:38:08 +0000
192@@ -46,7 +46,7 @@
193
194 MATCHER_P(IsValidOAuthHeader, refOAuth, "")
195 {
196- return arg.hasRawHeader("Authorization") && arg.rawHeader(click::web::AUTHORIZATION.c_str())
197+ return arg.hasRawHeader(click::web::AUTHORIZATION_HEADER.c_str()) && arg.rawHeader(click::web::AUTHORIZATION_HEADER.c_str())
198 .startsWith("OAuth ");
199 }
200

Subscribers

People subscribed via source and target branches

to all changes: