1
#include <scope/scope.h>
3
#include <core/posix/exec.h>
4
#include <gtest/gtest.h>
5
#include <gmock/gmock.h>
7
#include <unity/scopes/SearchReply.h>
8
#include <unity/scopes/SearchReplyProxyFwd.h>
9
#include <unity/scopes/Variant.h>
10
#include <unity/scopes/testing/Category.h>
11
#include <unity/scopes/testing/MockSearchReply.h>
12
#include <unity/scopes/testing/TypedScopeFixture.h>
15
using namespace testing;
16
using namespace scope;
18
namespace posix = core::posix;
19
namespace sc = unity::scopes;
20
namespace sct = unity::scopes::testing;
23
* Keep the tests in an anonymous namespace
28
* Custom matcher to check the properties of search results
30
MATCHER_P2(ResultProp, prop, value, "") {
31
if (arg.contains(prop)) {
32
*result_listener << "result[" << prop << "] is " << arg[prop].serialize_json();
34
*result_listener << "result[" << prop << "] is not set";
36
return arg.contains(prop) && arg[prop] == sc::Variant(value);
40
* Custom matcher to check the presence of departments
42
MATCHER_P(IsDepartment, department, "") {
43
return arg->serialize() == department->serialize();
46
typedef sct::TypedScopeFixture<Scope> TypedScopeFixtureScope;
48
class TestScope: public TypedScopeFixtureScope {
52
// Start up Python-based fake OpenWeatherMap server
53
fake_server_ = posix::exec("/usr/bin/python3", { FAKE_SERVER }, { },
54
posix::StandardStream::stdout);
57
ASSERT_GT(fake_server_.pid(), 0);
59
// The server will print out the random port it is using
60
fake_server_.cout() >> port;
61
// Check we have a port
62
ASSERT_FALSE(port.empty());
64
// Build up the API root
65
string apiroot = "http://127.0.0.1:" + port;
66
// Override the API root that the scope will use
67
setenv("NETWORK_SCOPE_APIROOT", apiroot.c_str(), true);
69
// Do the parent SetUp
70
TypedScopeFixture::set_scope_directory(TEST_SCOPE_DIRECTORY);
71
TypedScopeFixtureScope::SetUp();
75
* Start by assuming the server is invalid
77
posix::ChildProcess fake_server_ = posix::ChildProcess::invalid();
80
TEST_F(TestScope, empty_search_string) {
81
const sc::CategoryRenderer renderer;
82
NiceMock<sct::MockSearchReply> reply;
84
// Build a query with an empty search string
85
sc::CannedQuery query(SCOPE_NAME, "", "");
87
// Expect the current weather category
88
EXPECT_CALL(reply, register_category("current", "London, GB", "", _)).Times(1)
89
.WillOnce(Return(make_shared<sct::Category>("current", "London, GB", "", renderer)));
92
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
93
ResultProp("title", "21.8°C"),
94
ResultProp("art", "http://openweathermap.org/img/w/02d.png"),
95
ResultProp("subtitle", "few clouds")
99
// Expect the forecast category
100
EXPECT_CALL(reply, register_category("forecast", "7 day forecast", "", _)).Times(1)
101
.WillOnce(Return(make_shared<sct::Category>("forecast", "7 day forecast", "", renderer)));
103
// With seven results
104
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
105
ResultProp("title", "25.1°C to 18.8°C"),
106
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
107
ResultProp("subtitle", "light rain")
108
)))).WillOnce(Return(true));
109
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
110
ResultProp("title", "20.9°C to 15.5°C"),
111
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
112
ResultProp("subtitle", "moderate rain")
113
)))).WillOnce(Return(true));
114
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
115
ResultProp("title", "19.6°C to 13.2°C"),
116
ResultProp("art", "http://openweathermap.org/img/w/03d.png"),
117
ResultProp("subtitle", "scattered clouds")
118
)))).WillOnce(Return(true));
119
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
120
ResultProp("title", "18.1°C to 13.5°C"),
121
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
122
ResultProp("subtitle", "moderate rain")
123
)))).WillOnce(Return(true));
124
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
125
ResultProp("title", "17.4°C to 15.4°C"),
126
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
127
ResultProp("subtitle", "heavy intensity rain")
128
)))).WillOnce(Return(true));
129
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
130
ResultProp("title", "18.4°C to 16.2°C"),
131
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
132
ResultProp("subtitle", "moderate rain")
133
)))).WillOnce(Return(true));
134
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
135
ResultProp("title", "19°C to 16.7°C"),
136
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
137
ResultProp("subtitle", "light rain")
138
)))).WillOnce(Return(true));
140
sc::SearchReplyProxy reply_proxy(&reply, [](sc::SearchReply*) {}); // note: this is a std::shared_ptr with empty deleter
141
sc::SearchMetadata meta_data("en_EN", "phone");
143
// Create a query object
144
auto search_query = scope->search(query, meta_data);
145
ASSERT_NE(nullptr, search_query);
148
search_query->run(reply_proxy);
150
// Google Mock will make assertions when the mocks are destructed.
153
TEST_F(TestScope, search) {
154
const sc::CategoryRenderer renderer;
155
NiceMock<sct::MockSearchReply> reply;
157
// Build a query with a non-empty search string
158
sc::CannedQuery query(SCOPE_NAME, "Manchester,uk", "");
160
// Expect the current weather category
161
EXPECT_CALL(reply, register_category("current", "Manchester, GB", "", _)).Times(1)
162
.WillOnce(Return(make_shared<sct::Category>("current", "Manchester, GB", "", renderer)));
165
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
166
ResultProp("title", "17.4°C"),
167
ResultProp("art", "http://openweathermap.org/img/w/03d.png"),
168
ResultProp("subtitle", "scattered clouds")
172
// Expect the forecast category
173
EXPECT_CALL(reply, register_category("forecast", "7 day forecast", "", _)).Times(1)
174
.WillOnce(Return(make_shared<sct::Category>("forecast", "7 day forecast", "", renderer)));
176
// With seven results
177
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
178
ResultProp("title", "18.8°C to 12°C"),
179
ResultProp("art", "http://openweathermap.org/img/w/01d.png"),
180
ResultProp("subtitle", "sky is clear")
181
)))).WillOnce(Return(true));
182
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
183
ResultProp("title", "18.6°C to 12.3°C"),
184
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
185
ResultProp("subtitle", "moderate rain")
186
)))).WillOnce(Return(true));
187
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
188
ResultProp("title", "17.1°C to 10.8°C"),
189
ResultProp("art", "http://openweathermap.org/img/w/04d.png"),
190
ResultProp("subtitle", "broken clouds")
191
)))).WillOnce(Return(true));
192
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
193
ResultProp("title", "16°C to 12.2°C"),
194
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
195
ResultProp("subtitle", "moderate rain")
196
)))).WillOnce(Return(true));
197
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
198
ResultProp("title", "15.7°C to 13°C"),
199
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
200
ResultProp("subtitle", "light rain")
201
)))).WillOnce(Return(true));
202
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
203
ResultProp("title", "17.6°C to 14.1°C"),
204
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
205
ResultProp("subtitle", "moderate rain")
206
)))).WillOnce(Return(true));
207
EXPECT_CALL(reply, push(Matcher<sc::CategorisedResult const&>(AllOf(
208
ResultProp("title", "15.5°C to 13.4°C"),
209
ResultProp("art", "http://openweathermap.org/img/w/10d.png"),
210
ResultProp("subtitle", "moderate rain")
211
)))).WillOnce(Return(true));
213
sc::SearchReplyProxy reply_proxy(&reply, [](sc::SearchReply*) {}); // note: this is a std::shared_ptr with empty deleter
214
sc::SearchMetadata meta_data("en_EN", "phone");
216
// Create a query object
217
auto search_query = scope->search(query, meta_data);
218
ASSERT_NE(nullptr, search_query);
221
search_query->run(reply_proxy);
223
// Google Mock will make assertions when the mocks are destructed.