dcf35c7871d7c91c829fe2c6f799d0f67cc02af5
[ric-plt/sdl.git] / tst / asyncsentineldatabasediscovery_test.cpp
1 /*
2    Copyright (c) 2018-2019 Nokia.
3
4    Licensed under the Apache License, Version 2.0 (the "License");
5    you may not use this file except in compliance with the License.
6    You may obtain a copy of the License at
7
8        http://www.apache.org/licenses/LICENSE-2.0
9
10    Unless required by applicable law or agreed to in writing, software
11    distributed under the License is distributed on an "AS IS" BASIS,
12    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13    See the License for the specific language governing permissions and
14    limitations under the License.
15 */
16
17 #include <gtest/gtest.h>
18 #include <arpa/inet.h>
19 #include <sdl/asyncstorage.hpp>
20 #include "private/createlogger.hpp"
21 #include "private/hostandport.hpp"
22 #include "private/timer.hpp"
23 #include "private/redis/asyncsentineldatabasediscovery.hpp"
24 #include "private/tst/asynccommanddispatchermock.hpp"
25 #include "private/tst/contentsbuildermock.hpp"
26 #include "private/tst/enginemock.hpp"
27 #include "private/tst/replymock.hpp"
28 #include "private/tst/wellknownerrorcode.hpp"
29
30 using namespace shareddatalayer;
31 using namespace shareddatalayer::redis;
32 using namespace shareddatalayer::tst;
33 using namespace testing;
34
35 namespace
36 {
37     class AsyncSentinelDatabaseDiscoveryBaseTest: public testing::Test
38     {
39     public:
40         std::unique_ptr<AsyncSentinelDatabaseDiscovery> asyncSentinelDatabaseDiscovery;
41         std::shared_ptr<StrictMock<EngineMock>> engineMock;
42         std::shared_ptr<StrictMock<AsyncCommandDispatcherMock>> dispatcherMock;
43         std::shared_ptr<StrictMock<ContentsBuilderMock>> contentsBuilderMock;
44         std::shared_ptr<Logger> logger;
45         Contents contents;
46         AsyncCommandDispatcher::ConnectAck dispatcherConnectAck;
47         AsyncCommandDispatcher::CommandCb savedCommandCb;
48         ReplyMock replyMock;
49         std::string someHost;
50         uint16_t somePort;
51         Reply::DataItem hostDataItem;
52         Reply::DataItem portDataItem;
53         std::shared_ptr<ReplyMock> masterInquiryReplyHost;
54         std::shared_ptr<ReplyMock> masterInquiryReplyPort;
55         Reply::ReplyVector masterInquiryReply;
56         Timer::Duration expectedMasterInquiryRetryTimerDuration;
57         Timer::Callback savedConnectionRetryTimerCallback;
58
59         AsyncSentinelDatabaseDiscoveryBaseTest():
60             engineMock(std::make_shared<StrictMock<EngineMock>>()),
61             dispatcherMock(std::make_shared<StrictMock<AsyncCommandDispatcherMock>>()),
62             contentsBuilderMock(std::make_shared<StrictMock<ContentsBuilderMock>>(AsyncStorage::SEPARATOR)),
63             logger(createLogger(SDL_LOG_PREFIX)),
64             contents({{"aaa","bbb"},{3,3}}),
65             someHost("somehost"),
66             somePort(1234),
67             hostDataItem({someHost,ReplyStringLength(someHost.length())}),
68             portDataItem({std::to_string(somePort),ReplyStringLength(std::to_string(somePort).length())}),
69             masterInquiryReplyHost(std::make_shared<ReplyMock>()),
70             masterInquiryReplyPort(std::make_shared<ReplyMock>()),
71             expectedMasterInquiryRetryTimerDuration(std::chrono::seconds(1))
72         {
73             masterInquiryReply.push_back(masterInquiryReplyHost);
74             masterInquiryReply.push_back(masterInquiryReplyPort);
75         }
76
77         virtual ~AsyncSentinelDatabaseDiscoveryBaseTest()
78         {
79         }
80
81         std::shared_ptr<AsyncCommandDispatcher> asyncCommandDispatcherCreator(Engine&,
82                                                                               const DatabaseInfo&,
83                                                                               std::shared_ptr<ContentsBuilder>)
84         {
85             // @TODO Add database info checking when configuration support for sentinel is added.
86             newDispatcherCreated();
87             return dispatcherMock;
88         }
89
90         MOCK_METHOD0(newDispatcherCreated, void());
91
92         void expectNewDispatcherCreated()
93         {
94             EXPECT_CALL(*this, newDispatcherCreated())
95                 .Times(1);
96         }
97
98         void expectDispatcherWaitConnectedAsync()
99         {
100             EXPECT_CALL(*dispatcherMock, waitConnectedAsync(_))
101                 .Times(1)
102                 .WillOnce(Invoke([this](const AsyncCommandDispatcher::ConnectAck& connectAck)
103                         {
104                             dispatcherConnectAck = connectAck;
105                         }));
106         }
107
108         void expectContentsBuild(const std::string& string,
109                                  const std::string& string2,
110                                  const std::string& string3)
111         {
112             EXPECT_CALL(*contentsBuilderMock, build(string, string2, string3))
113                 .Times(1)
114                 .WillOnce(Return(contents));
115         }
116
117         void expectDispatchAsync()
118         {
119             EXPECT_CALL(*dispatcherMock, dispatchAsync(_, _, contents))
120                 .Times(1)
121                 .WillOnce(SaveArg<0>(&savedCommandCb));
122         }
123
124         void expectMasterInquiry()
125         {
126             expectContentsBuild("SENTINEL", "get-master-addr-by-name", "mymaster");
127             expectDispatchAsync();
128         }
129
130         MOCK_METHOD1(stateChangedCb, void(const DatabaseInfo&));
131
132         void expectStateChangedCb()
133         {
134             EXPECT_CALL(*this, stateChangedCb(_))
135                 .Times(1)
136                 .WillOnce(Invoke([this](const DatabaseInfo& databaseInfo)
137                                  {
138                                      EXPECT_THAT(DatabaseConfiguration::Addresses({ HostAndPort(someHost, htons(somePort)) }),
139                                                  ContainerEq(databaseInfo.hosts));
140                                      EXPECT_EQ(DatabaseInfo::Type::SINGLE, databaseInfo.type);
141                                      EXPECT_EQ(boost::none, databaseInfo.ns);
142                                      EXPECT_EQ(DatabaseInfo::Discovery::SENTINEL, databaseInfo.discovery);
143                                  }));
144         }
145
146         void expectGetReplyType(ReplyMock& mock, const Reply::Type& type)
147         {
148             EXPECT_CALL(mock, getType())
149                 .Times(1)
150                 .WillOnce(Return(type));
151         }
152
153         void expectGetReplyArray_ReturnMasterInquiryReply()
154         {
155             EXPECT_CALL(replyMock, getArray())
156                 .Times(1)
157                 .WillOnce(Return(&masterInquiryReply));
158         }
159
160         void expectGetReplyString(ReplyMock& mock, const Reply::DataItem& item)
161         {
162             EXPECT_CALL(mock, getString())
163                 .Times(1)
164                 .WillOnce(Return(&item));
165         }
166
167         void expectMasterIquiryReply()
168         {
169             expectGetReplyType(replyMock, Reply::Type::ARRAY);
170             expectGetReplyArray_ReturnMasterInquiryReply();
171             expectGetReplyType(*masterInquiryReplyHost, Reply::Type::STRING);
172             expectGetReplyString(*masterInquiryReplyHost, hostDataItem);
173             expectGetReplyType(*masterInquiryReplyPort, Reply::Type::STRING);
174             expectGetReplyString(*masterInquiryReplyPort, portDataItem);
175         }
176
177         void expectMasterInquiryRetryTimer()
178         {
179             EXPECT_CALL(*engineMock, armTimer(_, expectedMasterInquiryRetryTimerDuration, _))
180                 .Times(1)
181                 .WillOnce(SaveArg<2>(&savedConnectionRetryTimerCallback));
182         }
183
184         void setDefaultResponsesForMasterInquiryReplyParsing()
185         {
186             ON_CALL(replyMock, getType())
187                 .WillByDefault(Return(Reply::Type::ARRAY));
188             ON_CALL(replyMock, getArray())
189                 .WillByDefault(Return(&masterInquiryReply));
190             ON_CALL(*masterInquiryReplyHost, getType())
191                 .WillByDefault(Return(Reply::Type::STRING));
192             ON_CALL(*masterInquiryReplyHost, getString())
193                 .WillByDefault(Return(&hostDataItem));
194             ON_CALL(*masterInquiryReplyPort, getType())
195                 .WillByDefault(Return(Reply::Type::STRING));
196             ON_CALL(*masterInquiryReplyHost, getString())
197                 .WillByDefault(Return(&portDataItem));
198         }
199     };
200
201     class AsyncSentinelDatabaseDiscoveryTest: public AsyncSentinelDatabaseDiscoveryBaseTest
202     {
203     public:
204         AsyncSentinelDatabaseDiscoveryTest()
205         {
206             expectNewDispatcherCreated();
207             asyncSentinelDatabaseDiscovery.reset(
208                     new AsyncSentinelDatabaseDiscovery(
209                             engineMock,
210                             logger,
211                             std::bind(&AsyncSentinelDatabaseDiscoveryBaseTest::asyncCommandDispatcherCreator,
212                                       this,
213                                       std::placeholders::_1,
214                                       std::placeholders::_2,
215                                       std::placeholders::_3),
216                             contentsBuilderMock));
217         }
218     };
219
220     using AsyncSentinelDatabaseDiscoveryDeathTest = AsyncSentinelDatabaseDiscoveryTest;
221 }
222
223 TEST_F(AsyncSentinelDatabaseDiscoveryBaseTest, IsNotCopyable)
224 {
225     InSequence dummy;
226     EXPECT_FALSE(std::is_copy_constructible<AsyncSentinelDatabaseDiscovery>::value);
227     EXPECT_FALSE(std::is_copy_assignable<AsyncSentinelDatabaseDiscovery>::value);
228 }
229
230 TEST_F(AsyncSentinelDatabaseDiscoveryBaseTest, ImplementsAsyncDatabaseDiscovery)
231 {
232     InSequence dummy;
233     EXPECT_TRUE((std::is_base_of<AsyncDatabaseDiscovery, AsyncSentinelDatabaseDiscovery>::value));
234 }
235
236 TEST_F(AsyncSentinelDatabaseDiscoveryTest, RedisMasterIsInquiredFromSentinel)
237 {
238     InSequence dummy;
239     expectDispatcherWaitConnectedAsync();
240     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
241             this,
242             std::placeholders::_1));
243     expectMasterInquiry();
244     dispatcherConnectAck();
245     expectMasterIquiryReply();
246     expectStateChangedCb();
247     savedCommandCb(std::error_code(), replyMock);
248 }
249
250 TEST_F(AsyncSentinelDatabaseDiscoveryTest, RedisMasterInquiryErrorTriggersRetry)
251 {
252     InSequence dummy;
253     expectDispatcherWaitConnectedAsync();
254     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
255             this,
256             std::placeholders::_1));
257     expectMasterInquiry();
258     dispatcherConnectAck();
259     expectMasterInquiryRetryTimer();
260     savedCommandCb(getWellKnownErrorCode(), replyMock);
261     expectMasterInquiry();
262     savedConnectionRetryTimerCallback();
263     expectMasterIquiryReply();
264     expectStateChangedCb();
265     savedCommandCb(std::error_code(), replyMock);
266 }
267
268 TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidReplyType)
269 {
270     InSequence dummy;
271     expectDispatcherWaitConnectedAsync();
272     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
273             this,
274             std::placeholders::_1));
275     expectMasterInquiry();
276     dispatcherConnectAck();
277     ON_CALL(replyMock, getType())
278         .WillByDefault(Return(Reply::Type::NIL));
279     EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
280 }
281
282 TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidHostElementType)
283 {
284     InSequence dummy;
285     expectDispatcherWaitConnectedAsync();
286     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
287             this,
288             std::placeholders::_1));
289     expectMasterInquiry();
290     dispatcherConnectAck();
291     setDefaultResponsesForMasterInquiryReplyParsing();
292     ON_CALL(*masterInquiryReplyHost, getType())
293         .WillByDefault(Return(Reply::Type::NIL));
294     EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
295 }
296
297 TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidPortElementType)
298 {
299     InSequence dummy;
300     expectDispatcherWaitConnectedAsync();
301     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
302             this,
303             std::placeholders::_1));
304     expectMasterInquiry();
305     dispatcherConnectAck();
306     setDefaultResponsesForMasterInquiryReplyParsing();
307     ON_CALL(*masterInquiryReplyPort, getType())
308         .WillByDefault(Return(Reply::Type::NIL));
309     EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
310 }
311
312 TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_PortCantBeCastedToInt)
313 {
314     InSequence dummy;
315     expectDispatcherWaitConnectedAsync();
316     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
317             this,
318             std::placeholders::_1));
319     expectMasterInquiry();
320     dispatcherConnectAck();
321     setDefaultResponsesForMasterInquiryReplyParsing();
322     std::string invalidPort("invalidPort");
323     Reply::DataItem invalidPortDataItem({invalidPort,ReplyStringLength(invalidPort.length())});
324     ON_CALL(*masterInquiryReplyPort, getString())
325         .WillByDefault(Return(&invalidPortDataItem));
326     EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
327 }
328
329 TEST_F(AsyncSentinelDatabaseDiscoveryTest, CallbackIsNotCalledAfterCleared)
330 {
331     InSequence dummy;
332     expectDispatcherWaitConnectedAsync();
333     asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
334             this,
335             std::placeholders::_1));
336     expectMasterInquiry();
337     dispatcherConnectAck();
338     expectMasterInquiryRetryTimer();
339     savedCommandCb(getWellKnownErrorCode(), replyMock);
340     expectMasterInquiry();
341     savedConnectionRetryTimerCallback();
342     expectMasterIquiryReply();
343     asyncSentinelDatabaseDiscovery->clearStateChangedCb();
344     EXPECT_CALL(*this, stateChangedCb(_))
345         .Times(0);
346     savedCommandCb(std::error_code(), replyMock);
347 }