/* * 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 #include #include #include "Alerts/AlertsCapabilityAgent.h" #include #include #include #include #include #include #include #include #include #include #include namespace alexaClientSDK { namespace capabilityAgents { namespace alerts { namespace test { using namespace avsCommon::avs; using namespace avsCommon::avs::speakerConstants; using namespace avsCommon::avs::attachment::test; using namespace avsCommon::sdkInterfaces; using namespace avsCommon::sdkInterfaces::audio::test; using namespace avsCommon::sdkInterfaces::test; using namespace avsCommon::utils::memory; using namespace certifiedSender; using namespace registrationManager; using namespace renderer; using namespace storage; using namespace testing; /// Maximum time to wait for operaiton result. constexpr int MAX_WAIT_TIME_MS = 200; /// Alerts.SetVolume Directive name constexpr char SET_VOLUME_DIRECTIVE_NAME[] = "SetVolume"; /// Alerts.SetVolume Namespace name constexpr char SET_VOLUME_NAMESPACE_NAME[] = "Alerts"; /// Crafted message ID constexpr char MESSAGE_ID[] = "1"; /// General test value for alerts volume constexpr int TEST_VOLUME_VALUE = 33; /// Higher test volume value constexpr int HIGHER_VOLUME_VALUE = 100; /// Lower test volume value constexpr int LOWER_VOLUME_VALUE = 50; // clang-format off /// General test directive payload static const std::string VOLUME_PAYLOAD = "{" R"("volume":)" + std::to_string(TEST_VOLUME_VALUE) + "" "}"; /// Test directive payload with volume too high static const std::string VOLUME_PAYLOAD_ABOVE_MAX = "{" R"("volume":)" + std::to_string(AVS_SET_VOLUME_MAX + 1) + "" "}"; /// Test directive payload with volume too low static const std::string VOLUME_PAYLOAD_BELOW_MIN = "{" R"("volume":)" + std::to_string(AVS_SET_VOLUME_MIN - 1) + "" "}"; // clang-format on /** * Test @c AlertStorageInterface implementation to provide a valid instance for the initialization of other components. */ class StubAlertStorage : public AlertStorageInterface { public: bool createDatabase() override { return true; } bool open() override { return true; } void close() override { } bool store(std::shared_ptr alert) override { return true; } bool load(std::vector>* alertContainer) override { return true; } bool modify(std::shared_ptr alert) override { return true; } bool erase(std::shared_ptr alert) override { return true; } bool clearDatabase() override { return true; } bool bulkErase(const std::list>& alertList) override { return true; } }; /** * Test @c RendererInterface implementation to provide a valid instance for the initialization of other components. */ class StubRenderer : public RendererInterface { void setObserver(std::shared_ptr observer) override { } void start( std::function()> audioFactory, const std::vector& urls, int loopCount, std::chrono::milliseconds loopPause) override { } void stop() override { } }; /** * Test @c MessageStorageInterface implementation to provide a valid instance for the initialization of other * components. */ class StubMessageStorage : public MessageStorageInterface { public: bool createDatabase() override { return true; } bool open() override { return true; } void close() override { } bool store(const std::string& message, int* id) override { return true; } bool load(std::queue* messageContainer) override { return true; } bool erase(int messageId) override { return true; } bool clearDatabase() override { return true; } }; /** * Test @c MessageSenderInterface implementation that allows tracking of messages sent. */ class TestMessageSender : public MessageSenderInterface { public: void sendMessage(std::shared_ptr request) override { if (m_nextMessagePromise) { m_nextMessagePromise->set_value(request); m_nextMessagePromise.reset(); } request->sendCompleted(avsCommon::sdkInterfaces::MessageRequestObserverInterface::Status::SUCCESS); } /** * Wait for next message to be sent using this object. The message sent is then returned to the caller. * @return The last message sent using this object. */ std::future> getNextMessage() { m_nextMessagePromise = std::make_shared>>(); return m_nextMessagePromise->get_future(); } private: std::shared_ptr>> m_nextMessagePromise; }; class AlertsCapabilityAgentTest : public ::testing::Test { protected: void testStartAlertWithContentVolume( int8_t speakerVolume, int8_t alertsVolume, const std::string& otherChannel, bool shouldResultInSetVolume); void SetUp() override; void TearDown() override; std::shared_ptr m_alertsCA; std::shared_ptr m_certifiedSender; std::shared_ptr m_mockMessageSender; std::shared_ptr m_messageStorage; std::shared_ptr m_mockAVSConnectionManager; std::shared_ptr m_mockFocusManager; std::shared_ptr m_speakerManager; std::shared_ptr m_exceptionSender; std::shared_ptr m_contextManager; std::shared_ptr m_alertStorage; std::shared_ptr m_alertsAudioFactory; std::shared_ptr m_renderer; std::shared_ptr m_customerDataManager; std::unique_ptr> m_mockDirectiveHandlerResult; std::mutex m_mutex; }; void AlertsCapabilityAgentTest::SetUp() { m_mockMessageSender = std::make_shared(); m_mockAVSConnectionManager = std::make_shared>(); m_mockFocusManager = std::make_shared>(); m_speakerManager = std::make_shared>(); m_exceptionSender = std::make_shared>(); m_contextManager = std::make_shared>(); m_alertStorage = std::make_shared(); m_alertsAudioFactory = std::make_shared>(); m_renderer = std::make_shared(); m_customerDataManager = std::make_shared(); m_messageStorage = std::make_shared(); m_mockDirectiveHandlerResult = make_unique>(); ON_CALL(*(m_speakerManager.get()), getSpeakerSettings(_, _)) .WillByDefault(Invoke([](avsCommon::sdkInterfaces::SpeakerInterface::Type, avsCommon::sdkInterfaces::SpeakerInterface::SpeakerSettings*) { std::promise promise; promise.set_value(true); return promise.get_future(); })); ON_CALL(*(m_speakerManager.get()), setVolume(_, _, _)) .WillByDefault(Invoke([](avsCommon::sdkInterfaces::SpeakerInterface::Type, int8_t, bool) { std::promise promise; promise.set_value(true); return promise.get_future(); })); m_certifiedSender = CertifiedSender::create( m_mockMessageSender, m_mockAVSConnectionManager, m_messageStorage, m_customerDataManager); m_alertsCA = AlertsCapabilityAgent::create( m_mockMessageSender, m_mockAVSConnectionManager, m_certifiedSender, m_mockFocusManager, m_speakerManager, m_contextManager, m_exceptionSender, m_alertStorage, m_alertsAudioFactory, m_renderer, m_customerDataManager); std::static_pointer_cast(m_certifiedSender) ->onConnectionStatusChanged( ConnectionStatusObserverInterface::Status::CONNECTED, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST); } void AlertsCapabilityAgentTest::TearDown() { m_certifiedSender->shutdown(); m_certifiedSender.reset(); m_alertsCA->shutdown(); m_alertsCA.reset(); m_mockMessageSender.reset(); m_mockAVSConnectionManager.reset(); m_mockFocusManager.reset(); m_speakerManager.reset(); m_exceptionSender.reset(); m_contextManager.reset(); m_alertStorage.reset(); m_alertsAudioFactory.reset(); m_renderer.reset(); m_customerDataManager.reset(); m_messageStorage.reset(); } void AlertsCapabilityAgentTest::testStartAlertWithContentVolume( int8_t speakerVolume, int8_t alertsVolume, const std::string& otherChannel, bool shouldResultInSetVolume) { ON_CALL(*(m_speakerManager.get()), getSpeakerSettings(_, _)) .WillByDefault(Invoke( [speakerVolume, alertsVolume](SpeakerInterface::Type type, SpeakerInterface::SpeakerSettings* settings) { if (type == SpeakerInterface::Type::AVS_SPEAKER_VOLUME) { settings->volume = speakerVolume; } else { settings->volume = alertsVolume; } settings->mute = false; std::promise promise; promise.set_value(true); return promise.get_future(); })); std::condition_variable waitCV; ON_CALL(*(m_speakerManager.get()), setVolume(_, _, _)) .WillByDefault(Invoke([&waitCV](SpeakerInterface::Type, int8_t, bool) { waitCV.notify_all(); std::promise promise; promise.set_value(true); return promise.get_future(); })); EXPECT_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, _, _)) .Times(shouldResultInSetVolume ? 1 : 0); SpeakerInterface::SpeakerSettings speakerSettings{speakerVolume, false}; speakerSettings.mute = false; m_alertsCA->onSpeakerSettingsChanged( SpeakerManagerObserverInterface::Source::LOCAL_API, SpeakerInterface::Type::AVS_SPEAKER_VOLUME, speakerSettings); speakerSettings.volume = alertsVolume; m_alertsCA->onSpeakerSettingsChanged( SpeakerManagerObserverInterface::Source::LOCAL_API, SpeakerInterface::Type::AVS_ALERTS_VOLUME, speakerSettings); // "Start" content m_alertsCA->onFocusChanged(otherChannel, avsCommon::avs::FocusState::BACKGROUND); // "Start" alert m_alertsCA->onAlertStateChange("", AlertObserverInterface::State::STARTED, ""); std::unique_lock ulock(m_mutex); waitCV.wait_for(ulock, std::chrono::milliseconds(MAX_WAIT_TIME_MS)); } /** * Test local alert volume changes. Without alert sounding. Must send event. */ TEST_F(AlertsCapabilityAgentTest, localAlertVolumeChangeNoAlert) { SpeakerInterface::SpeakerSettings speakerSettings; speakerSettings.volume = TEST_VOLUME_VALUE; speakerSettings.mute = false; m_alertsCA->onSpeakerSettingsChanged( SpeakerManagerObserverInterface::Source::LOCAL_API, SpeakerInterface::Type::AVS_ALERTS_VOLUME, speakerSettings); auto future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); std::string content = future.get()->getJsonContent(); ASSERT_TRUE(content.find("\"name\":\"VolumeChanged\"") != std::string::npos); } /** * Test local alert volume changes. With alert sounding. Must not send event, volume is treated as local. */ TEST_F(AlertsCapabilityAgentTest, localAlertVolumeChangeAlertPlaying) { m_alertsCA->onAlertStateChange("", AlertObserverInterface::State::STARTED, ""); // We have to wait for the alert state to be processed before updating speaker settings. auto future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); std::string content = future.get()->getJsonContent(); ASSERT_TRUE(content.find("\"name\":\"AlertStarted\"") != std::string::npos); SpeakerInterface::SpeakerSettings speakerSettings; speakerSettings.volume = TEST_VOLUME_VALUE; m_alertsCA->onSpeakerSettingsChanged( SpeakerManagerObserverInterface::Source::LOCAL_API, SpeakerInterface::Type::AVS_ALERTS_VOLUME, speakerSettings); ASSERT_EQ( m_mockMessageSender->getNextMessage().wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::timeout); } /** * Test volume changes originated from AVS */ TEST_F(AlertsCapabilityAgentTest, avsAlertVolumeChangeNoAlert) { // Create Directive. auto attachmentManager = std::make_shared>(); auto avsMessageHeader = std::make_shared(SET_VOLUME_NAMESPACE_NAME, SET_VOLUME_DIRECTIVE_NAME, MESSAGE_ID); std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, VOLUME_PAYLOAD, attachmentManager, ""); EXPECT_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, TEST_VOLUME_VALUE, _)) .Times(1); std::static_pointer_cast(m_alertsCA) ->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); std::static_pointer_cast(m_alertsCA)->handleDirective(MESSAGE_ID); auto future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); std::string content = future.get()->getJsonContent(); ASSERT_TRUE(content.find("\"name\":\"VolumeChanged\"") != std::string::npos); } /** * Test if AVS alerts volume directive results in a proper event when alert is already playing. */ TEST_F(AlertsCapabilityAgentTest, avsAlertVolumeChangeAlertPlaying) { // Create Directive. auto attachmentManager = std::make_shared>(); auto avsMessageHeader = std::make_shared(SET_VOLUME_NAMESPACE_NAME, SET_VOLUME_DIRECTIVE_NAME, MESSAGE_ID); std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, VOLUME_PAYLOAD, attachmentManager, ""); EXPECT_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, TEST_VOLUME_VALUE, _)) .Times(0); m_alertsCA->onAlertStateChange("", AlertObserverInterface::State::STARTED, ""); auto future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); std::string content = future.get()->getJsonContent(); ASSERT_TRUE(content.find("\"name\":\"AlertStarted\"") != std::string::npos); std::static_pointer_cast(m_alertsCA) ->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); std::static_pointer_cast(m_alertsCA)->handleDirective(MESSAGE_ID); future = m_mockMessageSender->getNextMessage(); ASSERT_EQ(future.wait_for(std::chrono::milliseconds(MAX_WAIT_TIME_MS)), std::future_status::ready); content = future.get()->getJsonContent(); ASSERT_TRUE(content.find("\"name\":\"VolumeChanged\"") != std::string::npos); } /** * Test use cases when alert is going to start when content is being played on Content channel with lower volume. */ TEST_F(AlertsCapabilityAgentTest, startAlertWithContentChannelLowerVolume) { testStartAlertWithContentVolume( LOWER_VOLUME_VALUE, HIGHER_VOLUME_VALUE, FocusManagerInterface::CONTENT_CHANNEL_NAME, false); } /** * Test use cases when alert is going to start when content is being played on Content channel with higher volume. */ TEST_F(AlertsCapabilityAgentTest, startAlertWithContentChannelHigherVolume) { testStartAlertWithContentVolume( HIGHER_VOLUME_VALUE, LOWER_VOLUME_VALUE, FocusManagerInterface::CONTENT_CHANNEL_NAME, true); } /** * Test use cases when alert is going to start when content is being played on Comms channel with lower volume. */ TEST_F(AlertsCapabilityAgentTest, startAlertWithCommsChannelLowerVolume) { testStartAlertWithContentVolume( LOWER_VOLUME_VALUE, HIGHER_VOLUME_VALUE, FocusManagerInterface::COMMUNICATIONS_CHANNEL_NAME, false); } /** * Test use cases when alert is going to start when content is being played on Comms channel with higher volume. */ TEST_F(AlertsCapabilityAgentTest, startAlertWithCommsChannelHigherVolume) { testStartAlertWithContentVolume( HIGHER_VOLUME_VALUE, LOWER_VOLUME_VALUE, FocusManagerInterface::COMMUNICATIONS_CHANNEL_NAME, true); } /** * Test use cases when alert is going to start when content is being played on Dialog channel with lower volume. */ TEST_F(AlertsCapabilityAgentTest, startAlertWithDialogChannelLowerVolume) { testStartAlertWithContentVolume( LOWER_VOLUME_VALUE, HIGHER_VOLUME_VALUE, FocusManagerInterface::DIALOG_CHANNEL_NAME, false); } /** * Test use cases when alert is going to start when content is being played on Dialog channel with higher volume. */ TEST_F(AlertsCapabilityAgentTest, startAlertWithDialogChannelHigherVolume) { testStartAlertWithContentVolume( HIGHER_VOLUME_VALUE, LOWER_VOLUME_VALUE, FocusManagerInterface::DIALOG_CHANNEL_NAME, false); } /** * Test invalid volume value handling. */ TEST_F(AlertsCapabilityAgentTest, invalidVolumeValuesMax) { EXPECT_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, AVS_SET_VOLUME_MAX, _)) .Times(1); std::condition_variable waitCV; ON_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, AVS_SET_VOLUME_MAX, _)) .WillByDefault(Invoke([&waitCV](SpeakerInterface::Type, int8_t, bool) { waitCV.notify_all(); std::promise promise; promise.set_value(true); return promise.get_future(); })); // Create Directive. auto attachmentManager = std::make_shared>(); auto avsMessageHeader = std::make_shared(SET_VOLUME_NAMESPACE_NAME, SET_VOLUME_DIRECTIVE_NAME, MESSAGE_ID); std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, VOLUME_PAYLOAD_ABOVE_MAX, attachmentManager, ""); std::static_pointer_cast(m_alertsCA) ->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); std::static_pointer_cast(m_alertsCA)->handleDirective(MESSAGE_ID); std::unique_lock ulock(m_mutex); waitCV.wait_for(ulock, std::chrono::milliseconds(MAX_WAIT_TIME_MS)); } /** * Test invalid volume value handling. */ TEST_F(AlertsCapabilityAgentTest, invalidVolumeValuesMin) { EXPECT_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, AVS_SET_VOLUME_MIN, _)) .Times(1); std::condition_variable waitCV; ON_CALL(*(m_speakerManager.get()), setVolume(SpeakerInterface::Type::AVS_ALERTS_VOLUME, AVS_SET_VOLUME_MIN, _)) .WillByDefault(Invoke([&waitCV](SpeakerInterface::Type, int8_t, bool) { waitCV.notify_all(); std::promise promise; promise.set_value(true); return promise.get_future(); })); // Create Directive. auto attachmentManager = std::make_shared>(); auto avsMessageHeader = std::make_shared(SET_VOLUME_NAMESPACE_NAME, SET_VOLUME_DIRECTIVE_NAME, MESSAGE_ID); std::shared_ptr directive = AVSDirective::create("", avsMessageHeader, VOLUME_PAYLOAD_BELOW_MIN, attachmentManager, ""); std::static_pointer_cast(m_alertsCA) ->preHandleDirective(directive, std::move(m_mockDirectiveHandlerResult)); std::static_pointer_cast(m_alertsCA)->handleDirective(MESSAGE_ID); std::unique_lock ulock(m_mutex); waitCV.wait_for(ulock, std::chrono::milliseconds(MAX_WAIT_TIME_MS)); } } // namespace test } // namespace alerts } // namespace capabilityAgents } // namespace alexaClientSDK