339 lines
14 KiB
C++
339 lines
14 KiB
C++
/*
|
|
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License").
|
|
* You may not use this file except in compliance with the License.
|
|
* A copy of the License is located at
|
|
*
|
|
* http://aws.amazon.com/apache2.0/
|
|
*
|
|
* or in the "license" file accompanying this file. This file 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 <future>
|
|
#include <memory>
|
|
#include <thread>
|
|
|
|
#include <gmock/gmock.h>
|
|
|
|
#include <AVSCommon/SDKInterfaces/MockContextManager.h>
|
|
#include <AVSCommon/SDKInterfaces/MockPostConnectSendMessage.h>
|
|
#include <AVSCommon/Utils/JSON/JSONUtils.h>
|
|
#include <SynchronizeStateSender/PostConnectSynchronizeStateSender.h>
|
|
|
|
namespace alexaClientSDK {
|
|
namespace synchronizeStateSender {
|
|
namespace test {
|
|
|
|
using namespace ::testing;
|
|
using namespace avsCommon::avs;
|
|
using namespace avsCommon::sdkInterfaces;
|
|
using namespace avsCommon::sdkInterfaces::test;
|
|
using namespace avsCommon::utils::json::jsonUtils;
|
|
|
|
/// String indicating the device's context.
|
|
static const std::string TEST_CONTEXT_VALUE = "{}";
|
|
|
|
/// String indicating the SynchronizeState event's expected namespace.
|
|
static const std::string EXPECTED_NAMESPACE = "System";
|
|
|
|
/// String indicating the SynchronizeState event's expected name.
|
|
static const std::string EXPECTED_NAME = "SynchronizeState";
|
|
|
|
/// String indicating the SynchronizeState event's expected payload.
|
|
static const std::string EXPECTED_PAYLOAD = "{}";
|
|
|
|
/// Request token used to mock getContext return value.
|
|
static const ContextRequestToken MOCK_CONTEXT_REQUEST_TOKEN = 1;
|
|
|
|
/// Number of retries used in tests.
|
|
static const int TEST_RETRY_COUNT = 3;
|
|
/**
|
|
* Test harness for @c PostConnectSynchronizeStateSender class.
|
|
*/
|
|
class PostConnectSynchronizeStateSenderTest : public Test {
|
|
public:
|
|
/// Setup the test harness for running the test.
|
|
void SetUp() override;
|
|
|
|
/// Clean up the test harness after running the test.
|
|
void TearDown() override;
|
|
|
|
protected:
|
|
/// The mock @c ContextManager used to test.
|
|
std::shared_ptr<MockContextManager> m_mockContextManager;
|
|
|
|
/// The mock @c PostConnectSendMessage used to test.
|
|
std::shared_ptr<MockPostConnectSendMessage> m_mockPostConnectSendMessage;
|
|
|
|
/// The mock @c ContextManager used to test.
|
|
std::thread m_mockContextManagerThread;
|
|
|
|
/// The executor thread to run @c MockPostConnectSendMessage.
|
|
std::thread m_mockPostConnectSenderThread;
|
|
|
|
/// The executor thread to run @c MockPostConnectSendMessage.
|
|
std::shared_ptr<PostConnectSynchronizeStateSender> m_postConnectSynchronizeStateSender;
|
|
};
|
|
|
|
struct EventData {
|
|
std::string contextString;
|
|
std::string namespaceString;
|
|
std::string nameString;
|
|
std::string payloadString;
|
|
};
|
|
|
|
bool parseEventJson(const std::string& eventJson, EventData* eventData) {
|
|
if (!retrieveValue(eventJson, "context", &eventData->contextString)) {
|
|
return false;
|
|
}
|
|
std::string eventString;
|
|
if (!retrieveValue(eventJson, "event", &eventString)) {
|
|
return false;
|
|
}
|
|
|
|
std::string headerString;
|
|
if (!retrieveValue(eventString, "header", &headerString)) {
|
|
return false;
|
|
}
|
|
|
|
if (!retrieveValue(headerString, "namespace", &eventData->namespaceString)) {
|
|
return false;
|
|
}
|
|
|
|
if (!retrieveValue(headerString, "name", &eventData->nameString)) {
|
|
return false;
|
|
}
|
|
|
|
if (!retrieveValue(eventString, "payload", &eventData->payloadString)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool validateEvent(const std::string& eventJson) {
|
|
EventData eventData;
|
|
if (!parseEventJson(eventJson, &eventData)) {
|
|
return false;
|
|
}
|
|
|
|
if (eventData.contextString != TEST_CONTEXT_VALUE || eventData.nameString != EXPECTED_NAME ||
|
|
eventData.namespaceString != EXPECTED_NAMESPACE || eventData.payloadString != EXPECTED_PAYLOAD) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PostConnectSynchronizeStateSenderTest::SetUp() {
|
|
m_mockContextManager = std::make_shared<NiceMock<MockContextManager>>();
|
|
m_mockPostConnectSendMessage = std::make_shared<NiceMock<MockPostConnectSendMessage>>();
|
|
|
|
m_postConnectSynchronizeStateSender = PostConnectSynchronizeStateSender::create(m_mockContextManager);
|
|
}
|
|
|
|
void PostConnectSynchronizeStateSenderTest::TearDown() {
|
|
if (m_mockContextManagerThread.joinable()) {
|
|
m_mockContextManagerThread.join();
|
|
}
|
|
|
|
if (m_mockPostConnectSenderThread.joinable()) {
|
|
m_mockPostConnectSenderThread.join();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test create with null @c ContextManager.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_createWithNullContextManager) {
|
|
auto instance = PostConnectSynchronizeStateSender::create(nullptr);
|
|
ASSERT_EQ(instance, nullptr);
|
|
}
|
|
|
|
/**
|
|
* Test GetPriority method for @c PostConnectSynchronizeStateSender.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_getOperationPriority) {
|
|
ASSERT_EQ(
|
|
m_postConnectSynchronizeStateSender->getOperationPriority(),
|
|
static_cast<unsigned int>(PostConnectOperationInterface::SYNCHRONIZE_STATE_PRIORITY));
|
|
}
|
|
|
|
/**
|
|
* Test happy case for performOperation method. SynchronizeState event is sent and performOperation() returns true.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_perfromOperationSendsSynchronizeStateEvent) {
|
|
auto getContextLambda = [this](
|
|
std::shared_ptr<ContextRequesterInterface> contextRequester,
|
|
const std::string& endpointId,
|
|
const std::chrono::milliseconds& timeout) {
|
|
if (m_mockContextManagerThread.joinable()) {
|
|
m_mockContextManagerThread.join();
|
|
}
|
|
m_mockContextManagerThread =
|
|
std::thread([contextRequester]() { contextRequester->onContextAvailable(TEST_CONTEXT_VALUE); });
|
|
return MOCK_CONTEXT_REQUEST_TOKEN;
|
|
};
|
|
EXPECT_CALL(*m_mockContextManager, getContext(_, _, _)).WillOnce(Invoke(getContextLambda));
|
|
|
|
auto sendMessageLambda = [this](std::shared_ptr<MessageRequest> request) {
|
|
if (m_mockPostConnectSenderThread.joinable()) {
|
|
m_mockPostConnectSenderThread.join();
|
|
}
|
|
|
|
m_mockPostConnectSenderThread = std::thread([request]() {
|
|
EXPECT_TRUE(validateEvent(request->getJsonContent()));
|
|
request->sendCompleted(MessageRequestObserverInterface::Status::SUCCESS_NO_CONTENT);
|
|
});
|
|
};
|
|
EXPECT_CALL(*m_mockPostConnectSendMessage, sendPostConnectMessage(_)).WillOnce(Invoke(sendMessageLambda));
|
|
|
|
ASSERT_TRUE(m_postConnectSynchronizeStateSender->performOperation(m_mockPostConnectSendMessage));
|
|
}
|
|
|
|
/**
|
|
* Test performOperation() method retries sending SynchronizeState event on context fetch failure.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_performOperationRetriesOnContextFailure) {
|
|
std::promise<int> retryCountPromise;
|
|
auto getContextLambda = [this, &retryCountPromise](
|
|
std::shared_ptr<ContextRequesterInterface> contextRequester,
|
|
const std::string& endpointId,
|
|
const std::chrono::milliseconds& timeout) {
|
|
if (m_mockContextManagerThread.joinable()) {
|
|
m_mockContextManagerThread.join();
|
|
}
|
|
m_mockContextManagerThread = std::thread([this, contextRequester, &retryCountPromise]() {
|
|
contextRequester->onContextFailure(ContextRequestError::STATE_PROVIDER_TIMEDOUT);
|
|
static int count = 0;
|
|
count++;
|
|
/// abort operation after 3 retries.
|
|
if (count == TEST_RETRY_COUNT) {
|
|
retryCountPromise.set_value(count);
|
|
m_postConnectSynchronizeStateSender->abortOperation();
|
|
}
|
|
});
|
|
return MOCK_CONTEXT_REQUEST_TOKEN;
|
|
};
|
|
EXPECT_CALL(*m_mockContextManager, getContext(_, _, _)).WillRepeatedly(Invoke(getContextLambda));
|
|
/// Synchronize State event not sent.
|
|
EXPECT_CALL(*m_mockPostConnectSendMessage, sendPostConnectMessage(_)).Times(0);
|
|
/// Abort after 3 retries.
|
|
EXPECT_FALSE(m_postConnectSynchronizeStateSender->performOperation(m_mockPostConnectSendMessage));
|
|
EXPECT_EQ(retryCountPromise.get_future().get(), TEST_RETRY_COUNT);
|
|
}
|
|
|
|
/**
|
|
* Test performOperation() method retries sending SynchronizeState event on unsuccessful response to SynchronizeState
|
|
* event.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_testPerfromOperationRetriesOnUnsuccessfulResponse) {
|
|
std::promise<int> retryCountPromise;
|
|
auto getContextLambda = [this](
|
|
std::shared_ptr<ContextRequesterInterface> contextRequester,
|
|
const std::string& endpointId,
|
|
const std::chrono::milliseconds& timeout) {
|
|
if (m_mockContextManagerThread.joinable()) {
|
|
m_mockContextManagerThread.join();
|
|
}
|
|
m_mockContextManagerThread =
|
|
std::thread([contextRequester]() { contextRequester->onContextAvailable(TEST_CONTEXT_VALUE); });
|
|
return MOCK_CONTEXT_REQUEST_TOKEN;
|
|
};
|
|
EXPECT_CALL(*m_mockContextManager, getContext(_, _, _)).WillRepeatedly(Invoke(getContextLambda));
|
|
|
|
auto sendMessageLambda = [this, &retryCountPromise](std::shared_ptr<MessageRequest> request) {
|
|
if (m_mockPostConnectSenderThread.joinable()) {
|
|
m_mockPostConnectSenderThread.join();
|
|
}
|
|
m_mockPostConnectSenderThread = std::thread([this, request, &retryCountPromise]() {
|
|
ASSERT_TRUE(validateEvent(request->getJsonContent()));
|
|
request->sendCompleted(MessageRequestObserverInterface::Status::SERVER_INTERNAL_ERROR_V2);
|
|
static int count = 0;
|
|
count++;
|
|
/// abort operation after 3 retries.
|
|
if (count == TEST_RETRY_COUNT) {
|
|
retryCountPromise.set_value(count);
|
|
|
|
std::thread localThread([this]() { m_postConnectSynchronizeStateSender->abortOperation(); });
|
|
if (localThread.joinable()) {
|
|
localThread.join();
|
|
}
|
|
}
|
|
});
|
|
};
|
|
EXPECT_CALL(*m_mockPostConnectSendMessage, sendPostConnectMessage(_)).WillRepeatedly(Invoke(sendMessageLambda));
|
|
/// Abort after 3 retries.
|
|
EXPECT_FALSE(m_postConnectSynchronizeStateSender->performOperation(m_mockPostConnectSendMessage));
|
|
EXPECT_EQ(retryCountPromise.get_future().get(), TEST_RETRY_COUNT);
|
|
}
|
|
|
|
/**
|
|
* Test abortOperation() triggers performOperation() to return when context fetch is in progress.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_abortOperationWhenContextRequestInProgress) {
|
|
auto getContextLambda = [this](
|
|
std::shared_ptr<ContextRequesterInterface> contextRequester,
|
|
const std::string& endpointId,
|
|
const std::chrono::milliseconds& timeout) {
|
|
if (m_mockContextManagerThread.joinable()) {
|
|
m_mockContextManagerThread.join();
|
|
}
|
|
m_mockContextManagerThread = std::thread([this]() {
|
|
/// Wait for 500 ms and spin a thread to abort the post connect operation.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
std::thread localThread([this]() { m_postConnectSynchronizeStateSender->abortOperation(); });
|
|
if (localThread.joinable()) {
|
|
localThread.join();
|
|
}
|
|
});
|
|
return MOCK_CONTEXT_REQUEST_TOKEN;
|
|
};
|
|
EXPECT_CALL(*m_mockContextManager, getContext(_, _, _)).WillRepeatedly(Invoke(getContextLambda));
|
|
EXPECT_CALL(*m_mockPostConnectSendMessage, sendPostConnectMessage(_)).Times(0);
|
|
|
|
EXPECT_FALSE(m_postConnectSynchronizeStateSender->performOperation(m_mockPostConnectSendMessage));
|
|
}
|
|
|
|
/**
|
|
* Test abortOperation() triggers performOperation() to return when SynchronizeState event send is in progress.
|
|
*/
|
|
TEST_F(PostConnectSynchronizeStateSenderTest, test_abortOperationWhenSendMessageInProgress) {
|
|
auto getContextLambda = [this](
|
|
std::shared_ptr<ContextRequesterInterface> contextRequester,
|
|
const std::string& endpointId,
|
|
const std::chrono::milliseconds& timeout) {
|
|
if (m_mockContextManagerThread.joinable()) {
|
|
m_mockContextManagerThread.join();
|
|
}
|
|
m_mockContextManagerThread =
|
|
std::thread([contextRequester]() { contextRequester->onContextAvailable(TEST_CONTEXT_VALUE); });
|
|
return MOCK_CONTEXT_REQUEST_TOKEN;
|
|
};
|
|
EXPECT_CALL(*m_mockContextManager, getContext(_, _, _)).WillRepeatedly(Invoke(getContextLambda));
|
|
|
|
auto sendMessageLambda = [this](std::shared_ptr<MessageRequest> request) {
|
|
if (m_mockPostConnectSenderThread.joinable()) {
|
|
m_mockPostConnectSenderThread.join();
|
|
}
|
|
m_mockPostConnectSenderThread = std::thread([this, request]() {
|
|
ASSERT_TRUE(validateEvent(request->getJsonContent()));
|
|
/// Wait for 500 ms and spin a thread to abort the post connect operation.
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
std::thread localThread([this]() { m_postConnectSynchronizeStateSender->abortOperation(); });
|
|
if (localThread.joinable()) {
|
|
localThread.join();
|
|
}
|
|
});
|
|
};
|
|
EXPECT_CALL(*m_mockPostConnectSendMessage, sendPostConnectMessage(_)).WillOnce(Invoke(sendMessageLambda));
|
|
EXPECT_FALSE(m_postConnectSynchronizeStateSender->performOperation(m_mockPostConnectSendMessage));
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace synchronizeStateSender
|
|
} // namespace alexaClientSDK
|