| /* |
| Copyright (c) 2018-2019 Nokia. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include <gtest/gtest.h> |
| #include <arpa/inet.h> |
| #include <sdl/asyncstorage.hpp> |
| #include "private/createlogger.hpp" |
| #include "private/hostandport.hpp" |
| #include "private/timer.hpp" |
| #include "private/redis/asyncsentineldatabasediscovery.hpp" |
| #include "private/tst/asynccommanddispatchermock.hpp" |
| #include "private/tst/contentsbuildermock.hpp" |
| #include "private/tst/enginemock.hpp" |
| #include "private/tst/replymock.hpp" |
| #include "private/tst/wellknownerrorcode.hpp" |
| |
| using namespace shareddatalayer; |
| using namespace shareddatalayer::redis; |
| using namespace shareddatalayer::tst; |
| using namespace testing; |
| |
| namespace |
| { |
| class AsyncSentinelDatabaseDiscoveryBaseTest: public testing::Test |
| { |
| public: |
| std::unique_ptr<AsyncSentinelDatabaseDiscovery> asyncSentinelDatabaseDiscovery; |
| std::shared_ptr<StrictMock<EngineMock>> engineMock; |
| std::shared_ptr<StrictMock<AsyncCommandDispatcherMock>> dispatcherMock; |
| std::shared_ptr<StrictMock<ContentsBuilderMock>> contentsBuilderMock; |
| std::shared_ptr<Logger> logger; |
| Contents contents; |
| AsyncCommandDispatcher::ConnectAck dispatcherConnectAck; |
| AsyncCommandDispatcher::CommandCb savedCommandCb; |
| ReplyMock replyMock; |
| std::string someHost; |
| uint16_t somePort; |
| Reply::DataItem hostDataItem; |
| Reply::DataItem portDataItem; |
| std::shared_ptr<ReplyMock> masterInquiryReplyHost; |
| std::shared_ptr<ReplyMock> masterInquiryReplyPort; |
| Reply::ReplyVector masterInquiryReply; |
| Timer::Duration expectedMasterInquiryRetryTimerDuration; |
| Timer::Callback savedConnectionRetryTimerCallback; |
| |
| AsyncSentinelDatabaseDiscoveryBaseTest(): |
| engineMock(std::make_shared<StrictMock<EngineMock>>()), |
| dispatcherMock(std::make_shared<StrictMock<AsyncCommandDispatcherMock>>()), |
| contentsBuilderMock(std::make_shared<StrictMock<ContentsBuilderMock>>(AsyncStorage::SEPARATOR)), |
| logger(createLogger(SDL_LOG_PREFIX)), |
| contents({{"aaa","bbb"},{3,3}}), |
| someHost("somehost"), |
| somePort(1234), |
| hostDataItem({someHost,ReplyStringLength(someHost.length())}), |
| portDataItem({std::to_string(somePort),ReplyStringLength(std::to_string(somePort).length())}), |
| masterInquiryReplyHost(std::make_shared<ReplyMock>()), |
| masterInquiryReplyPort(std::make_shared<ReplyMock>()), |
| expectedMasterInquiryRetryTimerDuration(std::chrono::seconds(1)) |
| { |
| masterInquiryReply.push_back(masterInquiryReplyHost); |
| masterInquiryReply.push_back(masterInquiryReplyPort); |
| } |
| |
| virtual ~AsyncSentinelDatabaseDiscoveryBaseTest() |
| { |
| } |
| |
| std::shared_ptr<AsyncCommandDispatcher> asyncCommandDispatcherCreator(Engine&, |
| const DatabaseInfo&, |
| std::shared_ptr<ContentsBuilder>) |
| { |
| // @TODO Add database info checking when configuration support for sentinel is added. |
| newDispatcherCreated(); |
| return dispatcherMock; |
| } |
| |
| MOCK_METHOD0(newDispatcherCreated, void()); |
| |
| void expectNewDispatcherCreated() |
| { |
| EXPECT_CALL(*this, newDispatcherCreated()) |
| .Times(1); |
| } |
| |
| void expectDispatcherWaitConnectedAsync() |
| { |
| EXPECT_CALL(*dispatcherMock, waitConnectedAsync(_)) |
| .Times(1) |
| .WillOnce(Invoke([this](const AsyncCommandDispatcher::ConnectAck& connectAck) |
| { |
| dispatcherConnectAck = connectAck; |
| })); |
| } |
| |
| void expectContentsBuild(const std::string& string, |
| const std::string& string2, |
| const std::string& string3) |
| { |
| EXPECT_CALL(*contentsBuilderMock, build(string, string2, string3)) |
| .Times(1) |
| .WillOnce(Return(contents)); |
| } |
| |
| void expectDispatchAsync() |
| { |
| EXPECT_CALL(*dispatcherMock, dispatchAsync(_, _, contents)) |
| .Times(1) |
| .WillOnce(SaveArg<0>(&savedCommandCb)); |
| } |
| |
| void expectMasterInquiry() |
| { |
| expectContentsBuild("SENTINEL", "get-master-addr-by-name", "mymaster"); |
| expectDispatchAsync(); |
| } |
| |
| MOCK_METHOD1(stateChangedCb, void(const DatabaseInfo&)); |
| |
| void expectStateChangedCb() |
| { |
| EXPECT_CALL(*this, stateChangedCb(_)) |
| .Times(1) |
| .WillOnce(Invoke([this](const DatabaseInfo& databaseInfo) |
| { |
| EXPECT_THAT(DatabaseConfiguration::Addresses({ HostAndPort(someHost, htons(somePort)) }), |
| ContainerEq(databaseInfo.hosts)); |
| EXPECT_EQ(DatabaseInfo::Type::SINGLE, databaseInfo.type); |
| EXPECT_EQ(boost::none, databaseInfo.ns); |
| EXPECT_EQ(DatabaseInfo::Discovery::SENTINEL, databaseInfo.discovery); |
| })); |
| } |
| |
| void expectGetReplyType(ReplyMock& mock, const Reply::Type& type) |
| { |
| EXPECT_CALL(mock, getType()) |
| .Times(1) |
| .WillOnce(Return(type)); |
| } |
| |
| void expectGetReplyArray_ReturnMasterInquiryReply() |
| { |
| EXPECT_CALL(replyMock, getArray()) |
| .Times(1) |
| .WillOnce(Return(&masterInquiryReply)); |
| } |
| |
| void expectGetReplyString(ReplyMock& mock, const Reply::DataItem& item) |
| { |
| EXPECT_CALL(mock, getString()) |
| .Times(1) |
| .WillOnce(Return(&item)); |
| } |
| |
| void expectMasterIquiryReply() |
| { |
| expectGetReplyType(replyMock, Reply::Type::ARRAY); |
| expectGetReplyArray_ReturnMasterInquiryReply(); |
| expectGetReplyType(*masterInquiryReplyHost, Reply::Type::STRING); |
| expectGetReplyString(*masterInquiryReplyHost, hostDataItem); |
| expectGetReplyType(*masterInquiryReplyPort, Reply::Type::STRING); |
| expectGetReplyString(*masterInquiryReplyPort, portDataItem); |
| } |
| |
| void expectMasterInquiryRetryTimer() |
| { |
| EXPECT_CALL(*engineMock, armTimer(_, expectedMasterInquiryRetryTimerDuration, _)) |
| .Times(1) |
| .WillOnce(SaveArg<2>(&savedConnectionRetryTimerCallback)); |
| } |
| |
| void setDefaultResponsesForMasterInquiryReplyParsing() |
| { |
| ON_CALL(replyMock, getType()) |
| .WillByDefault(Return(Reply::Type::ARRAY)); |
| ON_CALL(replyMock, getArray()) |
| .WillByDefault(Return(&masterInquiryReply)); |
| ON_CALL(*masterInquiryReplyHost, getType()) |
| .WillByDefault(Return(Reply::Type::STRING)); |
| ON_CALL(*masterInquiryReplyHost, getString()) |
| .WillByDefault(Return(&hostDataItem)); |
| ON_CALL(*masterInquiryReplyPort, getType()) |
| .WillByDefault(Return(Reply::Type::STRING)); |
| ON_CALL(*masterInquiryReplyHost, getString()) |
| .WillByDefault(Return(&portDataItem)); |
| } |
| }; |
| |
| class AsyncSentinelDatabaseDiscoveryTest: public AsyncSentinelDatabaseDiscoveryBaseTest |
| { |
| public: |
| AsyncSentinelDatabaseDiscoveryTest() |
| { |
| expectNewDispatcherCreated(); |
| asyncSentinelDatabaseDiscovery.reset( |
| new AsyncSentinelDatabaseDiscovery( |
| engineMock, |
| logger, |
| std::bind(&AsyncSentinelDatabaseDiscoveryBaseTest::asyncCommandDispatcherCreator, |
| this, |
| std::placeholders::_1, |
| std::placeholders::_2, |
| std::placeholders::_3), |
| contentsBuilderMock)); |
| } |
| }; |
| |
| using AsyncSentinelDatabaseDiscoveryDeathTest = AsyncSentinelDatabaseDiscoveryTest; |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryBaseTest, IsNotCopyable) |
| { |
| InSequence dummy; |
| EXPECT_FALSE(std::is_copy_constructible<AsyncSentinelDatabaseDiscovery>::value); |
| EXPECT_FALSE(std::is_copy_assignable<AsyncSentinelDatabaseDiscovery>::value); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryBaseTest, ImplementsAsyncDatabaseDiscovery) |
| { |
| InSequence dummy; |
| EXPECT_TRUE((std::is_base_of<AsyncDatabaseDiscovery, AsyncSentinelDatabaseDiscovery>::value)); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryTest, RedisMasterIsInquiredFromSentinel) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| expectMasterIquiryReply(); |
| expectStateChangedCb(); |
| savedCommandCb(std::error_code(), replyMock); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryTest, RedisMasterInquiryErrorTriggersRetry) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| expectMasterInquiryRetryTimer(); |
| savedCommandCb(getWellKnownErrorCode(), replyMock); |
| expectMasterInquiry(); |
| savedConnectionRetryTimerCallback(); |
| expectMasterIquiryReply(); |
| expectStateChangedCb(); |
| savedCommandCb(std::error_code(), replyMock); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidReplyType) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| ON_CALL(replyMock, getType()) |
| .WillByDefault(Return(Reply::Type::NIL)); |
| EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error"); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidHostElementType) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| setDefaultResponsesForMasterInquiryReplyParsing(); |
| ON_CALL(*masterInquiryReplyHost, getType()) |
| .WillByDefault(Return(Reply::Type::NIL)); |
| EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error"); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidPortElementType) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| setDefaultResponsesForMasterInquiryReplyParsing(); |
| ON_CALL(*masterInquiryReplyPort, getType()) |
| .WillByDefault(Return(Reply::Type::NIL)); |
| EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error"); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_PortCantBeCastedToInt) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| setDefaultResponsesForMasterInquiryReplyParsing(); |
| std::string invalidPort("invalidPort"); |
| Reply::DataItem invalidPortDataItem({invalidPort,ReplyStringLength(invalidPort.length())}); |
| ON_CALL(*masterInquiryReplyPort, getString()) |
| .WillByDefault(Return(&invalidPortDataItem)); |
| EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error"); |
| } |
| |
| TEST_F(AsyncSentinelDatabaseDiscoveryTest, CallbackIsNotCalledAfterCleared) |
| { |
| InSequence dummy; |
| expectDispatcherWaitConnectedAsync(); |
| asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb, |
| this, |
| std::placeholders::_1)); |
| expectMasterInquiry(); |
| dispatcherConnectAck(); |
| expectMasterInquiryRetryTimer(); |
| savedCommandCb(getWellKnownErrorCode(), replyMock); |
| expectMasterInquiry(); |
| savedConnectionRetryTimerCallback(); |
| expectMasterIquiryReply(); |
| asyncSentinelDatabaseDiscovery->clearStateChangedCb(); |
| EXPECT_CALL(*this, stateChangedCb(_)) |
| .Times(0); |
| savedCommandCb(std::error_code(), replyMock); |
| } |