932 lines
34 KiB
C++
932 lines
34 KiB
C++
/*
|
|
* Copyright 2018 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 <chrono>
|
|
#include <queue>
|
|
#include <sstream>
|
|
#include <iostream>
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <AVSCommon/AVS/Attachment/MockAttachmentManager.h>
|
|
#include <AVSCommon/AVS/IndicatorState.h>
|
|
#include <AVSCommon/AVS/Initialization/AlexaClientSDKInit.h>
|
|
#include <AVSCommon/SDKInterfaces/Audio/NotificationsAudioFactoryInterface.h>
|
|
#include <AVSCommon/SDKInterfaces/MockExceptionEncounteredSender.h>
|
|
#include <AVSCommon/SDKInterfaces/MockContextManager.h>
|
|
#include <AVSCommon/SDKInterfaces/MockDirectiveSequencer.h>
|
|
#include <AVSCommon/SDKInterfaces/MockDirectiveHandlerResult.h>
|
|
#include <AVSCommon/Utils/JSON/JSONUtils.h>
|
|
#include <AVSCommon/Utils/Logger/ConsoleLogger.h>
|
|
#include <RegistrationManager/CustomerDataManager.h>
|
|
|
|
#include "Notifications/NotificationsCapabilityAgent.h"
|
|
#include "Notifications/NotificationIndicator.h"
|
|
#include "Notifications/NotificationRendererInterface.h"
|
|
#include "Notifications/NotificationRendererObserverInterface.h"
|
|
|
|
namespace alexaClientSDK {
|
|
namespace capabilityAgents {
|
|
namespace notifications {
|
|
namespace test {
|
|
|
|
using namespace avsCommon::avs;
|
|
using namespace avsCommon::avs::initialization;
|
|
using namespace avsCommon::avs::attachment::test;
|
|
using namespace avsCommon::sdkInterfaces;
|
|
using namespace avsCommon::sdkInterfaces::audio;
|
|
using namespace avsCommon::sdkInterfaces::test;
|
|
using namespace ::testing;
|
|
|
|
/// Plenty of time for a test to complete.
|
|
static std::chrono::milliseconds WAIT_TIMEOUT(1000);
|
|
|
|
/// Time to simulate a notification rendering.
|
|
static std::chrono::milliseconds RENDER_TIME(900);
|
|
|
|
/// Notifications namespace
|
|
static const std::string NAMESPACE_NOTIFICATIONS("Notifications");
|
|
|
|
/// Name for Notifications SetIndicator directive
|
|
static const std::string NAME_SET_INDICATOR("SetIndicator");
|
|
|
|
/// Name for Notifications ClearIndicator directive
|
|
static const std::string NAME_CLEAR_INDICATOR("ClearIndicator");
|
|
|
|
/// The @c NamespaceAndName to send to the @c ContextManager
|
|
static const NamespaceAndName NAMESPACE_AND_NAME_INDICATOR_STATE{NAMESPACE_NOTIFICATIONS, "IndicatorState"};
|
|
|
|
/// Message Id for testing.
|
|
static const std::string MESSAGE_ID_TEST("MessageId_Test");
|
|
static const std::string MESSAGE_ID_TEST2("MessageId_Test2");
|
|
|
|
/// Context ID for testing
|
|
static const std::string CONTEXT_ID_TEST("ContextId_Test");
|
|
|
|
/// Test fields for payloads
|
|
static const std::string ASSET_ID1("assetId1");
|
|
static const std::string ASSET_ID2("assetId2");
|
|
static const std::string ASSET_URL1("assetUrl1");
|
|
static const std::string ASSET_URL2("assetUrl2");
|
|
|
|
/// Default "audio" for testing
|
|
static const std::string DEFAULT_NOTIFICATION_AUDIO{"default notification audio"};
|
|
|
|
/// Mocking the json config file
|
|
// clang-format off
|
|
static const std::string NOTIFICATIONS_CONFIG_JSON =
|
|
"{"
|
|
"\"notifications\":{"
|
|
"\"databaseFilePath\":\"notificationsUnitTest.db\""
|
|
"}"
|
|
"}";
|
|
// clang-format on
|
|
|
|
/// String to identify log entries originating from this file.
|
|
static const std::string TAG("NotificationsCapabilityAgentTest");
|
|
|
|
/**
|
|
* Create a LogEntry using this file's TAG and the specified event string.
|
|
*
|
|
* @param The event string for this @c LogEntry.
|
|
*/
|
|
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
|
|
|
|
/**
|
|
* A test class that acts as a NotificationsAudioFactory.
|
|
*/
|
|
class TestNotificationsAudioFactory : public NotificationsAudioFactoryInterface {
|
|
public:
|
|
std::function<std::unique_ptr<std::istream>()> notificationDefault() const override;
|
|
|
|
private:
|
|
static std::unique_ptr<std::istream> defaultNotification() {
|
|
return std::unique_ptr<std::stringstream>(new std::stringstream(DEFAULT_NOTIFICATION_AUDIO));
|
|
}
|
|
};
|
|
|
|
std::function<std::unique_ptr<std::istream>()> TestNotificationsAudioFactory::notificationDefault() const {
|
|
return defaultNotification;
|
|
}
|
|
|
|
/**
|
|
* A test class that acts as a NotificationsObserver.
|
|
*/
|
|
class TestNotificationsObserver : public NotificationsObserverInterface {
|
|
public:
|
|
TestNotificationsObserver();
|
|
|
|
/**
|
|
* Waits for an IndicatorState change.
|
|
*
|
|
* @param state The IndicatorState to wait for.
|
|
* @param timeout The amount of time to wait for the state change.
|
|
*/
|
|
bool waitFor(IndicatorState state, std::chrono::milliseconds timeout);
|
|
|
|
void onSetIndicator(IndicatorState state) override;
|
|
|
|
private:
|
|
/// The most recently observed IndicatorState.
|
|
IndicatorState m_indicatorState;
|
|
|
|
/// Serializes access to m_conditionVariable.
|
|
std::mutex m_mutex;
|
|
|
|
/// Used to wait for a particular IndicatorState.
|
|
std::condition_variable m_conditionVariable;
|
|
};
|
|
|
|
TestNotificationsObserver::TestNotificationsObserver() : m_indicatorState{IndicatorState::OFF} {
|
|
}
|
|
|
|
bool TestNotificationsObserver::waitFor(IndicatorState state, std::chrono::milliseconds timeout) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
return m_conditionVariable.wait_for(lock, timeout, [this, state] { return m_indicatorState == state; });
|
|
}
|
|
|
|
void TestNotificationsObserver::onSetIndicator(IndicatorState state) {
|
|
ACSDK_ERROR(LX("onSetIndicator").d("indicatorState", indicatorStateToInt(state)));
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_indicatorState = state;
|
|
m_conditionVariable.notify_all();
|
|
}
|
|
|
|
/**
|
|
* A test class that acts as NotificationsStorage. This storage is implemented using a std::queue.
|
|
*/
|
|
class TestNotificationsStorage : public NotificationsStorageInterface {
|
|
public:
|
|
bool createDatabase() override;
|
|
|
|
bool open() override;
|
|
|
|
void close() override;
|
|
|
|
bool enqueue(const NotificationIndicator& notificationIndicator) override;
|
|
|
|
bool dequeue() override;
|
|
|
|
bool peek(NotificationIndicator* notificationIndicator) override;
|
|
|
|
bool setIndicatorState(IndicatorState state) override;
|
|
|
|
bool getIndicatorState(IndicatorState* state) override;
|
|
|
|
bool checkForEmptyQueue(bool* empty) override;
|
|
|
|
bool clearNotificationIndicators() override;
|
|
|
|
bool getQueueSize(int* size) override;
|
|
|
|
/**
|
|
* Waits until the queue is a particular size.
|
|
*
|
|
* @param size The size to wait for.
|
|
* @param timeout How much time to wait before failing.
|
|
*/
|
|
bool waitForQueueSizeToBe(size_t size, std::chrono::milliseconds timeout = WAIT_TIMEOUT);
|
|
|
|
private:
|
|
/// The underlying NotificationIndicator queue.
|
|
std::queue<NotificationIndicator> m_notificationQueue;
|
|
|
|
/// The currently stored IndicatorState.
|
|
IndicatorState m_indicatorState;
|
|
|
|
/// For waiting on a particular queue size.
|
|
std::mutex m_mutex;
|
|
std::condition_variable m_conditionVariable;
|
|
};
|
|
|
|
bool TestNotificationsStorage::createDatabase() {
|
|
if (!setIndicatorState(IndicatorState::OFF)) {
|
|
ACSDK_ERROR(LX("createTestDatabaseFailed").d("reason", "failed to set default indicator state"));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::open() {
|
|
return true;
|
|
}
|
|
|
|
void TestNotificationsStorage::close() {
|
|
// no-op
|
|
}
|
|
|
|
bool TestNotificationsStorage::enqueue(const NotificationIndicator& notificationIndicator) {
|
|
m_notificationQueue.push(notificationIndicator);
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::dequeue() {
|
|
if (m_notificationQueue.empty()) {
|
|
return false;
|
|
}
|
|
m_notificationQueue.pop();
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::peek(NotificationIndicator* notificationIndicator) {
|
|
if (m_notificationQueue.empty() || !notificationIndicator) {
|
|
return false;
|
|
}
|
|
*notificationIndicator = m_notificationQueue.front();
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::setIndicatorState(IndicatorState state) {
|
|
m_indicatorState = state;
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::getIndicatorState(IndicatorState* state) {
|
|
if (!state) {
|
|
return false;
|
|
}
|
|
*state = m_indicatorState;
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::checkForEmptyQueue(bool* empty) {
|
|
*empty = m_notificationQueue.empty();
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::clearNotificationIndicators() {
|
|
m_notificationQueue = std::queue<NotificationIndicator>();
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::getQueueSize(int* size) {
|
|
if (!size) {
|
|
return false;
|
|
}
|
|
*size = m_notificationQueue.size();
|
|
return true;
|
|
}
|
|
|
|
bool TestNotificationsStorage::waitForQueueSizeToBe(size_t size, std::chrono::milliseconds timeout) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
return m_conditionVariable.wait_for(lock, timeout, [this, size] { return m_notificationQueue.size() == size; });
|
|
}
|
|
|
|
class MockNotificationRenderer : public NotificationRendererInterface {
|
|
public:
|
|
~MockNotificationRenderer();
|
|
|
|
MockNotificationRenderer();
|
|
|
|
static std::shared_ptr<NiceMock<MockNotificationRenderer>> create() {
|
|
auto renderer = std::make_shared<NiceMock<MockNotificationRenderer>>();
|
|
|
|
// tie the mock methods to mock implementations
|
|
ON_CALL(*renderer.get(), renderNotificationShim(_, _))
|
|
.WillByDefault(Invoke(renderer.get(), &MockNotificationRenderer::mockRender));
|
|
ON_CALL(*renderer.get(), cancelNotificationRenderingShim())
|
|
.WillByDefault(InvokeWithoutArgs(renderer.get(), &MockNotificationRenderer::mockCancel));
|
|
|
|
return renderer;
|
|
}
|
|
|
|
void addObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) override;
|
|
|
|
void removeObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) override;
|
|
|
|
bool renderNotification(std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url)
|
|
override;
|
|
|
|
bool cancelNotificationRendering() override;
|
|
|
|
// create shim methods to prevent compiler from complaining
|
|
MOCK_METHOD2(
|
|
renderNotificationShim,
|
|
bool(std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url));
|
|
MOCK_METHOD0(cancelNotificationRenderingShim, bool());
|
|
|
|
/**
|
|
* A method mocking renderNotification().
|
|
* This method kicks off two threads (for waitForRenderCall() and waitForRenderCallDone()) and then
|
|
* notifies those threads. This method may be interrupted by mockCancel().
|
|
*
|
|
* Both params are ignored in this mock implementation.
|
|
*/
|
|
bool mockRender(std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url);
|
|
|
|
/**
|
|
* A method mocking cancelRenderingNotification().
|
|
* This method attempts to sneak in between waitForRenderCall() and waitForRenderCallDone() by
|
|
* triggering m_renderTrigger before m_finishedRendering has been set to true;
|
|
*/
|
|
bool mockCancel();
|
|
|
|
/**
|
|
* Waits for mockRender() to set m_startedRendering to true.
|
|
*/
|
|
bool waitForRenderCall();
|
|
|
|
/**
|
|
* Waits for mockRender() to set m_finishedRendering to true.
|
|
*/
|
|
bool waitForRenderCallDone();
|
|
|
|
/**
|
|
* Waits for the fulfillment of m_renderStartedPromise, then resets any needed variables.
|
|
*/
|
|
bool waitUntilRenderingStarted(std::chrono::milliseconds timeout = WAIT_TIMEOUT);
|
|
|
|
/**
|
|
* Waits for the fulfillment of m_renderFinishedPromise, then resets any needed variables.
|
|
*/
|
|
bool waitUntilRenderingFinished(std::chrono::milliseconds timeout = WAIT_TIMEOUT);
|
|
|
|
private:
|
|
/// The current renderer observer.
|
|
std::shared_ptr<NotificationRendererObserverInterface> m_observer;
|
|
|
|
/// Threads that are kicked off by mockRender() to wait for the rendering to begin and end, respectively.
|
|
std::thread m_renderStartedThread;
|
|
std::thread m_renderFinishedThread;
|
|
|
|
/// Used to notify the above threads.
|
|
std::condition_variable m_renderTrigger;
|
|
|
|
/// Flags for the state of the rendering operation.
|
|
bool m_startedRendering;
|
|
bool m_finishedRendering;
|
|
bool m_cancelling;
|
|
|
|
/// Promise to be fulfilled when @c renderNotification is called.
|
|
std::promise<void> m_renderStartedPromise;
|
|
|
|
/// Future to notify when @c renderNotification is called.
|
|
std::future<void> m_renderStartedFuture;
|
|
|
|
/// Promise to be fulfilled when @c renderNotification is done.
|
|
std::promise<void> m_renderFinishedPromise;
|
|
|
|
/// Future to notify when @c renderNotification is done.
|
|
std::future<void> m_renderFinishedFuture;
|
|
|
|
/// Serializes access to m_renderTrigger;
|
|
std::mutex m_mutex;
|
|
};
|
|
|
|
MockNotificationRenderer::~MockNotificationRenderer() {
|
|
if (m_renderStartedThread.joinable()) {
|
|
m_renderStartedThread.join();
|
|
}
|
|
if (m_renderFinishedThread.joinable()) {
|
|
m_renderFinishedThread.join();
|
|
}
|
|
}
|
|
|
|
MockNotificationRenderer::MockNotificationRenderer() :
|
|
m_observer{nullptr},
|
|
m_startedRendering{false},
|
|
m_finishedRendering{false},
|
|
m_cancelling{false},
|
|
m_renderStartedPromise{},
|
|
m_renderStartedFuture{m_renderStartedPromise.get_future()},
|
|
m_renderFinishedPromise{},
|
|
m_renderFinishedFuture{m_renderFinishedPromise.get_future()} {
|
|
}
|
|
|
|
void MockNotificationRenderer::addObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) {
|
|
if (observer) {
|
|
m_observer = observer;
|
|
}
|
|
}
|
|
|
|
void MockNotificationRenderer::removeObserver(std::shared_ptr<NotificationRendererObserverInterface> observer) {
|
|
m_observer = nullptr;
|
|
}
|
|
|
|
bool MockNotificationRenderer::renderNotification(
|
|
std::function<std::unique_ptr<std::istream>()> audioFactory,
|
|
const std::string& url) {
|
|
return renderNotificationShim(audioFactory, url);
|
|
}
|
|
|
|
bool MockNotificationRenderer::cancelNotificationRendering() {
|
|
return cancelNotificationRenderingShim();
|
|
}
|
|
|
|
bool MockNotificationRenderer::mockRender(
|
|
std::function<std::unique_ptr<std::istream>()> audioFactory,
|
|
const std::string& url) {
|
|
std::lock_guard<std::mutex> lock(m_mutex);
|
|
|
|
if (m_renderStartedThread.joinable() && m_renderFinishedThread.joinable()) {
|
|
m_renderStartedThread.join();
|
|
m_renderFinishedThread.join();
|
|
}
|
|
|
|
m_renderStartedThread = std::thread(&MockNotificationRenderer::waitForRenderCall, this);
|
|
m_renderFinishedThread = std::thread(&MockNotificationRenderer::waitForRenderCallDone, this);
|
|
|
|
m_startedRendering = true;
|
|
m_renderTrigger.notify_all();
|
|
|
|
std::this_thread::sleep_for(RENDER_TIME);
|
|
|
|
m_finishedRendering = true;
|
|
m_renderTrigger.notify_all();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MockNotificationRenderer::mockCancel() {
|
|
m_cancelling = true;
|
|
m_renderTrigger.notify_all();
|
|
return true;
|
|
}
|
|
|
|
bool MockNotificationRenderer::waitForRenderCall() {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
m_renderTrigger.wait_for(lock, WAIT_TIMEOUT, [this]() { return m_startedRendering; });
|
|
m_renderStartedPromise.set_value();
|
|
return true;
|
|
}
|
|
|
|
bool MockNotificationRenderer::waitForRenderCallDone() {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
m_renderTrigger.wait_for(lock, WAIT_TIMEOUT, [this]() { return m_cancelling || m_finishedRendering; });
|
|
m_renderFinishedPromise.set_value();
|
|
return true;
|
|
}
|
|
|
|
bool MockNotificationRenderer::waitUntilRenderingStarted(std::chrono::milliseconds timeout) {
|
|
if (m_renderStartedFuture.wait_for(timeout) == std::future_status::ready) {
|
|
m_startedRendering = false;
|
|
m_renderStartedPromise = std::promise<void>();
|
|
m_renderStartedFuture = m_renderStartedPromise.get_future();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool MockNotificationRenderer::waitUntilRenderingFinished(std::chrono::milliseconds timeout) {
|
|
if (m_renderFinishedFuture.wait_for(timeout) == std::future_status::ready) {
|
|
m_finishedRendering = false;
|
|
m_renderFinishedPromise = std::promise<void>();
|
|
m_renderFinishedFuture = m_renderFinishedPromise.get_future();
|
|
m_observer->onNotificationRenderingFinished();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class NotificationsCapabilityAgentTest : public ::testing::Test {
|
|
public:
|
|
void SetUp() override;
|
|
void TearDown() override;
|
|
|
|
/**
|
|
* Utility function to create the capability agent. This allows modifying of subcomponents before the CA is created.
|
|
*/
|
|
void initializeCapabilityAgent();
|
|
|
|
/**
|
|
* Utility function to send SetIndicatorDirective
|
|
*/
|
|
void sendSetIndicatorDirective(const std::string& payload, const std::string& messageId);
|
|
|
|
/**
|
|
* Utility function to send ClearIndicatorDirective
|
|
*/
|
|
void sendClearIndicatorDirective(const std::string& messageId);
|
|
|
|
/**
|
|
* Utility function to generate direcive payloads
|
|
*/
|
|
const std::string generatePayload(
|
|
bool persistVisualIndicator,
|
|
bool playAudioIndicator,
|
|
const std::string& assetId = ASSET_ID1,
|
|
const std::string& assetUrl = ASSET_URL1);
|
|
|
|
/// @c A test observer to wait for @c AudioPlayer state changes
|
|
std::shared_ptr<TestNotificationsObserver> m_testNotificationsObserver;
|
|
|
|
/// @c NotificationsCapabilityAgent to test
|
|
std::shared_ptr<NotificationsCapabilityAgent> m_notificationsCapabilityAgent;
|
|
|
|
/// @c Storage for notifications
|
|
std::shared_ptr<TestNotificationsStorage> m_notificationsStorage;
|
|
|
|
/// Player to play notification audio assets
|
|
std::shared_ptr<MockNotificationRenderer> m_renderer;
|
|
|
|
/// @c ContextManager to provide state and update state.
|
|
std::shared_ptr<MockContextManager> m_mockContextManager;
|
|
|
|
/// A directive handler result to send the result to.
|
|
std::unique_ptr<MockDirectiveHandlerResult> m_mockDirectiveHandlerResult;
|
|
|
|
/// An exception sender used to send exception encountered events to AVS.
|
|
std::shared_ptr<MockExceptionEncounteredSender> m_mockExceptionSender;
|
|
|
|
/// An audio factory for testing.
|
|
std::shared_ptr<TestNotificationsAudioFactory> m_testNotificationsAudioFactory;
|
|
|
|
/// Serializes access to m_setIndicatorTrigger.
|
|
std::mutex m_mutex;
|
|
|
|
/// Triggers threads waiting on a SetIndicator directive to be processed.
|
|
std::condition_variable m_setIndicatorTrigger;
|
|
|
|
/// Shared pointer to @c CustomerDataManager
|
|
std::shared_ptr<registrationManager::CustomerDataManager> m_dataManager;
|
|
|
|
/// A count of how many SetIndicator directives have been processed.
|
|
unsigned int m_numSetIndicatorsProcessed;
|
|
};
|
|
|
|
void NotificationsCapabilityAgentTest::initializeCapabilityAgent() {
|
|
m_notificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
m_notificationsStorage,
|
|
m_renderer,
|
|
m_mockContextManager,
|
|
m_mockExceptionSender,
|
|
m_testNotificationsAudioFactory,
|
|
m_dataManager);
|
|
ASSERT_TRUE(m_notificationsCapabilityAgent);
|
|
m_notificationsCapabilityAgent->addObserver(m_testNotificationsObserver);
|
|
m_renderer->addObserver(m_notificationsCapabilityAgent);
|
|
}
|
|
|
|
void NotificationsCapabilityAgentTest::SetUp() {
|
|
auto inString = std::shared_ptr<std::istringstream>(new std::istringstream(NOTIFICATIONS_CONFIG_JSON));
|
|
ASSERT_TRUE(AlexaClientSDKInit::initialize({inString}));
|
|
|
|
m_notificationsStorage = std::make_shared<TestNotificationsStorage>();
|
|
m_renderer = MockNotificationRenderer::create();
|
|
m_mockContextManager = std::make_shared<NiceMock<MockContextManager>>();
|
|
m_mockExceptionSender = std::make_shared<NiceMock<MockExceptionEncounteredSender>>();
|
|
m_testNotificationsAudioFactory = std::make_shared<TestNotificationsAudioFactory>();
|
|
|
|
m_testNotificationsObserver = std::make_shared<TestNotificationsObserver>();
|
|
|
|
m_mockDirectiveHandlerResult = std::unique_ptr<MockDirectiveHandlerResult>(new MockDirectiveHandlerResult);
|
|
|
|
m_numSetIndicatorsProcessed = 0;
|
|
m_dataManager = std::make_shared<registrationManager::CustomerDataManager>();
|
|
}
|
|
|
|
void NotificationsCapabilityAgentTest::TearDown() {
|
|
if (m_notificationsCapabilityAgent) {
|
|
m_notificationsCapabilityAgent->shutdown();
|
|
}
|
|
AlexaClientSDKInit::uninitialize();
|
|
}
|
|
|
|
void NotificationsCapabilityAgentTest::sendSetIndicatorDirective(
|
|
const std::string& payload,
|
|
const std::string& messageId) {
|
|
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(NAMESPACE_NOTIFICATIONS, NAME_SET_INDICATOR, messageId);
|
|
|
|
auto mockAttachmentManager = std::make_shared<MockAttachmentManager>();
|
|
|
|
std::shared_ptr<AVSDirective> setIndicatorDirective =
|
|
AVSDirective::create("", avsMessageHeader, payload, mockAttachmentManager, CONTEXT_ID_TEST);
|
|
|
|
// cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature
|
|
std::shared_ptr<DirectiveHandlerInterface> agentAsDirectiveHandler = m_notificationsCapabilityAgent;
|
|
|
|
agentAsDirectiveHandler->preHandleDirective(setIndicatorDirective, std::move(m_mockDirectiveHandlerResult));
|
|
agentAsDirectiveHandler->handleDirective(messageId);
|
|
|
|
m_numSetIndicatorsProcessed++;
|
|
m_setIndicatorTrigger.notify_all();
|
|
}
|
|
|
|
void NotificationsCapabilityAgentTest::sendClearIndicatorDirective(const std::string& messageId) {
|
|
auto avsMessageHeader =
|
|
std::make_shared<AVSMessageHeader>(NAMESPACE_NOTIFICATIONS, NAME_CLEAR_INDICATOR, messageId);
|
|
|
|
auto mockAttachmentManager = std::make_shared<MockAttachmentManager>();
|
|
|
|
std::shared_ptr<AVSDirective> clearIndicatorDirective =
|
|
AVSDirective::create("", avsMessageHeader, "", mockAttachmentManager, CONTEXT_ID_TEST);
|
|
|
|
// cast to DirectiveHandlerInterface so the compiler can find the correct preHandleDirective signature
|
|
std::shared_ptr<DirectiveHandlerInterface> agentAsDirectiveHandler = m_notificationsCapabilityAgent;
|
|
|
|
agentAsDirectiveHandler->preHandleDirective(clearIndicatorDirective, std::move(m_mockDirectiveHandlerResult));
|
|
agentAsDirectiveHandler->handleDirective(messageId);
|
|
}
|
|
|
|
const std::string NotificationsCapabilityAgentTest::generatePayload(
|
|
bool persistVisualIndicator,
|
|
bool playAudioIndicator,
|
|
const std::string& assetId,
|
|
const std::string& assetUrl) {
|
|
std::string stringPersistVisualIndicator(persistVisualIndicator ? "true" : "false");
|
|
std::string stringPlayAudioIndicator(playAudioIndicator ? "true" : "false");
|
|
|
|
// clang-format off
|
|
const std::string payload =
|
|
"{"
|
|
"\"persistVisualIndicator\":" + stringPersistVisualIndicator + ","
|
|
"\"playAudioIndicator\":" + stringPlayAudioIndicator + ","
|
|
"\"asset\": {"
|
|
"\"assetId\":\"" + assetId + "\","
|
|
"\"url\":\"" + assetUrl + "\""
|
|
|
|
"}"
|
|
"}";
|
|
// clang-format on
|
|
return payload;
|
|
}
|
|
|
|
/**
|
|
* Test create() with nullptrs
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testCreate) {
|
|
std::shared_ptr<NotificationsCapabilityAgent> testNotificationsCapabilityAgent;
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
nullptr,
|
|
m_renderer,
|
|
m_mockContextManager,
|
|
m_mockExceptionSender,
|
|
m_testNotificationsAudioFactory,
|
|
m_dataManager);
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
m_notificationsStorage,
|
|
nullptr,
|
|
m_mockContextManager,
|
|
m_mockExceptionSender,
|
|
m_testNotificationsAudioFactory,
|
|
m_dataManager);
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
m_notificationsStorage,
|
|
m_renderer,
|
|
nullptr,
|
|
m_mockExceptionSender,
|
|
m_testNotificationsAudioFactory,
|
|
m_dataManager);
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
m_notificationsStorage,
|
|
m_renderer,
|
|
m_mockContextManager,
|
|
nullptr,
|
|
m_testNotificationsAudioFactory,
|
|
m_dataManager);
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
|
|
testNotificationsCapabilityAgent = NotificationsCapabilityAgent::create(
|
|
m_notificationsStorage, m_renderer, m_mockContextManager, m_mockExceptionSender, nullptr, m_dataManager);
|
|
EXPECT_EQ(testNotificationsCapabilityAgent, nullptr);
|
|
}
|
|
|
|
/**
|
|
* Test starting up the capability agent with a non-empty queue.
|
|
* Expect that the next item in the queue will be played.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testNonEmptyStartupQueue) {
|
|
NotificationIndicator ni(true, true, ASSET_ID1, ASSET_URL1);
|
|
ASSERT_TRUE(m_notificationsStorage->enqueue(ni));
|
|
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(1);
|
|
initializeCapabilityAgent();
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
}
|
|
|
|
/**
|
|
* Test a single SetIndicator directive with persistVisualIndicator and playAudioIndicator set to false.
|
|
* Expect that the NotificationsObserver is notified of the indicator's state remaining OFF.
|
|
* Expect no calls to render notifications since playAudioIndicator is false.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testSendSetIndicator) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, _)).Times(0);
|
|
initializeCapabilityAgent();
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST);
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
// check that the NotificationIndicator was dequeued as expected
|
|
ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0));
|
|
}
|
|
|
|
/**
|
|
* Test a single SetIndicator directive with with playAudioIndicator set to true.
|
|
* Expect the renderer to start playback of the Notification.
|
|
* Expect that the NotificationsObserver is notified of the indicator's state being OFF.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testSendSetIndicatorWithAudio) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1));
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(false, true, ASSET_ID1, ASSET_URL1), MESSAGE_ID_TEST);
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
}
|
|
|
|
/**
|
|
* Test a single SetIndicator directive with with persistVisualIndicator set to true.
|
|
* Expect that the NotificationsObserver is notified of the indicator's state being ON.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testSendSetIndicatorWithVisualIndicator) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, _)).Times(0);
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, false), MESSAGE_ID_TEST);
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
}
|
|
|
|
/**
|
|
* Test sending two SetIndicator directives where the second has the same assetId as the first.
|
|
* Expect that the renderer only gets one call to renderNotification().
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testSameAssetId) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1))
|
|
.Times(1)
|
|
.WillOnce(Invoke([this](std::function<std::unique_ptr<std::istream>()> audioFactory, const std::string& url) {
|
|
unsigned int expectedNumSetIndicators = 2;
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
if (!m_setIndicatorTrigger.wait_for(lock, WAIT_TIMEOUT, [this, expectedNumSetIndicators]() {
|
|
return m_numSetIndicatorsProcessed == expectedNumSetIndicators;
|
|
})) {
|
|
return false;
|
|
}
|
|
m_renderer->mockRender(audioFactory, url);
|
|
EXPECT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
return true;
|
|
}));
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, ASSET_ID1), MESSAGE_ID_TEST);
|
|
|
|
// send a second SetIndicator with the same assetId but persistVisualIndicator set to false.
|
|
sendSetIndicatorDirective(generatePayload(false, true, ASSET_ID1), MESSAGE_ID_TEST2);
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
// the IndicatorState should not have changed since the second directive should have been ignored.
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
}
|
|
|
|
/**
|
|
* Test that the persistVisualIndicator setting is preserved and used across shutdown.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testPersistVisualIndicatorPreserved) {
|
|
initializeCapabilityAgent();
|
|
|
|
// set IndicatorState to ON
|
|
sendSetIndicatorDirective(generatePayload(true, false, ASSET_ID1), MESSAGE_ID_TEST);
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
m_notificationsCapabilityAgent->shutdown();
|
|
|
|
// reboot and check that the persistVisualIndicator value has been preserved
|
|
initializeCapabilityAgent();
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
// same test but with IndicatorState set to OFF
|
|
sendSetIndicatorDirective(generatePayload(false, false, ASSET_ID1), MESSAGE_ID_TEST);
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
|
|
m_notificationsCapabilityAgent->shutdown();
|
|
|
|
initializeCapabilityAgent();
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
}
|
|
|
|
/**
|
|
* Test sending a ClearIndicator directive with an empty queue, expecting nothing to happen.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearIndicatorWithEmptyQueue) {
|
|
initializeCapabilityAgent();
|
|
sendClearIndicatorDirective(MESSAGE_ID_TEST);
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
}
|
|
|
|
/**
|
|
* Test sending a ClearIndicator directive with an empty queue and the indicator state set to ON.
|
|
* Expect that the indicator is set to OFF.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearIndicatorWithEmptyQueueAndIndicatorOn) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(1);
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, ASSET_ID1), MESSAGE_ID_TEST);
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::ON, WAIT_TIMEOUT));
|
|
|
|
sendClearIndicatorDirective(MESSAGE_ID_TEST2);
|
|
|
|
ASSERT_TRUE(m_testNotificationsObserver->waitFor(IndicatorState::OFF, WAIT_TIMEOUT));
|
|
}
|
|
|
|
/**
|
|
* Test sending a ClearIndicator directive after multiple SetIndicator directives.
|
|
* Expect that the indicator is set to OFF.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearIndicatorAfterMultipleSetIndicators) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(1);
|
|
EXPECT_CALL(*(m_renderer.get()), cancelNotificationRenderingShim()).Times(1);
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId1"), "firstIndicatorMessageId");
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId2"), "secondIndicatorMessageId");
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId3"), "thirdIndicatorMessageId");
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
sendClearIndicatorDirective(MESSAGE_ID_TEST);
|
|
|
|
// the renderer still calls onNotificationRenderingFinished() when a notification has been cancelled
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0));
|
|
}
|
|
|
|
/**
|
|
* Test sending multiple SetIndicators and letting them all render.
|
|
* Expect multiple calls to renderNotification().
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testMultipleSetIndicators) {
|
|
EXPECT_CALL(*(m_renderer.get()), renderNotificationShim(_, ASSET_URL1)).Times(3);
|
|
initializeCapabilityAgent();
|
|
|
|
sendSetIndicatorDirective(generatePayload(true, true, "id1"), "firstIndicatorMessageId");
|
|
sendSetIndicatorDirective(generatePayload(true, true, "id2"), "secondIndicatorMessageId");
|
|
sendSetIndicatorDirective(generatePayload(true, true, "id3"), "thirdIndicatorMessageId");
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
}
|
|
|
|
/**
|
|
* Test that @c clearData() removes all notifications and sets the indicator to OFF.
|
|
*/
|
|
TEST_F(NotificationsCapabilityAgentTest, testClearData) {
|
|
initializeCapabilityAgent();
|
|
sendSetIndicatorDirective(generatePayload(true, true, "assetId1"), "firstIndicatorMessageId");
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingStarted());
|
|
ASSERT_TRUE(m_renderer->waitUntilRenderingFinished());
|
|
|
|
// Check that indicator is ON
|
|
IndicatorState state = IndicatorState::UNDEFINED;
|
|
m_notificationsStorage->getIndicatorState(&state);
|
|
ASSERT_EQ(state, IndicatorState::ON);
|
|
|
|
// Check that the notification queue is not empty
|
|
int queueSize;
|
|
m_notificationsStorage->getQueueSize(&queueSize);
|
|
ASSERT_GT(queueSize, 0);
|
|
|
|
m_notificationsCapabilityAgent->clearData();
|
|
ASSERT_TRUE(m_notificationsStorage->waitForQueueSizeToBe(0));
|
|
|
|
m_notificationsStorage->getIndicatorState(&state);
|
|
ASSERT_EQ(state, IndicatorState::OFF);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace notifications
|
|
} // namespace capabilityAgents
|
|
} // namespace alexaClientSDK
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
|
|
// ACSDK-1367 - Some tests fail on Windows
|
|
#if defined(_WIN32) && !defined(RESOLVED_ACSDK_1367)
|
|
::testing::GTEST_FLAG(filter) = "-NotificationsCapabilityAgentTest.testSameAssetId";
|
|
#endif
|
|
return RUN_ALL_TESTS();
|
|
} |