diff options
author | Naveen Bobbili <nbobbili@amazon.com> | 2018-11-12 16:12:38 -0800 |
---|---|---|
committer | Naveen Bobbili <nbobbili@amazon.com> | 2018-11-13 15:05:41 -0800 |
commit | b6abca2edcb36c0c0848d1cd8dc291f23293aa80 (patch) | |
tree | a838812e0b66f0695cb6cf0f8bebfa38315ce8b8 /src/plugins/voiceagents | |
parent | be70712f89eacd20dca413bcce46e4aa26b5709e (diff) |
SPEC-1924: AGL Speech Framework's Voice Service High Level 1.0 Release.
Details:
1) Control plugin implementation for VSHL 1.0
2) Exposed APIs that are documented in the confluence page
https://confluence.automotivelinux.org/display/SPE/Speech+EG+Architecture
3) Implemented 39 unit tests based on GTest framework to test all the low
level components of VSHL binding.
4) Implemented a HTML5 based VSHL API tester application to test VSHL APIs.
API specification:
https://confluence.automotivelinux.org/display/SPE/Speech+EG+Architecture#SpeechEGArchitecture-HighLevelVoiceService
Test performed:
1) Tested AGL service running Alexa Auto SDK https://github.com/alexa/aac-sdk on Ubuntu 16.04 and Renesas R-Car M3 board.
License:
Apache 2.0
Developers/Owners:
Naveen Bobbili (nbobbili@amazon.com)
Prakash Buddhiraja (buddhip@amazon.com)
Shotaro Uchida (shotaru@amazon.co.jp)
Change-Id: I3370f4ad65aff030f24f4ad571fb02d525bbfbca
Signed-off-by: Naveen Bobbili <nbobbili@amazon.com>
Diffstat (limited to 'src/plugins/voiceagents')
-rw-r--r-- | src/plugins/voiceagents/VoiceAgentEventNames.h | 38 | ||||
-rw-r--r-- | src/plugins/voiceagents/VoiceAgentsDataManager.h | 135 | ||||
-rw-r--r-- | src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp | 272 | ||||
-rw-r--r-- | src/plugins/voiceagents/include/VoiceAgent.h | 95 | ||||
-rw-r--r-- | src/plugins/voiceagents/include/VoiceAgentEventsHandler.h | 95 | ||||
-rw-r--r-- | src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp | 139 | ||||
-rw-r--r-- | src/plugins/voiceagents/src/VoiceAgentImpl.cpp | 126 | ||||
-rw-r--r-- | src/plugins/voiceagents/test/VoiceAgentTest.cpp | 94 | ||||
-rw-r--r-- | src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp | 294 | ||||
-rw-r--r-- | src/plugins/voiceagents/test/VoiceAgentsTestData.h | 67 |
10 files changed, 1355 insertions, 0 deletions
diff --git a/src/plugins/voiceagents/VoiceAgentEventNames.h b/src/plugins/voiceagents/VoiceAgentEventNames.h new file mode 100644 index 0000000..4575528 --- /dev/null +++ b/src/plugins/voiceagents/VoiceAgentEventNames.h @@ -0,0 +1,38 @@ +/* + * Copyright 2018-2019 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_ + +#include <list> +#include <string> + +using namespace std; + +namespace vshl { +namespace voiceagents { + +static string VSHL_EVENT_AUTH_STATE_EVENT = "voice_authstate_event"; +static string VSHL_EVENT_CONNECTION_STATE_EVENT = "voice_connectionstate_event"; +static string VSHL_EVENT_DIALOG_STATE_EVENT = "voice_dialogstate_event"; + +static list<string> VSHL_EVENTS = { + VSHL_EVENT_AUTH_STATE_EVENT, VSHL_EVENT_CONNECTION_STATE_EVENT, + VSHL_EVENT_DIALOG_STATE_EVENT, +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTEVENTNAMES_H_ diff --git a/src/plugins/voiceagents/VoiceAgentsDataManager.h b/src/plugins/voiceagents/VoiceAgentsDataManager.h new file mode 100644 index 0000000..a4c9143 --- /dev/null +++ b/src/plugins/voiceagents/VoiceAgentsDataManager.h @@ -0,0 +1,135 @@ +/* + * Copyright 2018-2019 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_ + +#include <memory> +#include <set> +#include <unordered_map> +#include <unordered_set> + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/events/IEventFilter.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgent.h" +#include "interfaces/voiceagents/IVoiceAgentsChangeObserver.h" +#include "voiceagents/include/VoiceAgent.h" +#include "voiceagents/include/VoiceAgentEventsHandler.h" + +namespace vshl { +namespace voiceagents { +/* + * This class implements the data model for voiceagents. + * Supports add, remove and query operations on voiceagent data. + * Notifies the observers of the changes in the voiceagents data model. + */ +class VoiceAgentsDataManager { +public: + // Create a VoiceAgentsDataManager. + static std::unique_ptr<VoiceAgentsDataManager> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + /** + * Activates the list of voiceagents. + * + * @return Number of activated agents + */ + uint32_t activateVoiceAgents(const unordered_set<string>& activeVoiceAgentIds); + + /** + * Deactivates the list of voiceagents. + * + * @return Number of de-activated agents + */ + uint32_t deactivateVoiceAgents(const unordered_set<string>& inactiveVoiceAgentIds); + + // Sets the default voiceagent. + bool setDefaultVoiceAgent(const string& voiceAgentId); + + // Sets the default voiceagent. + std::string getDefaultVoiceAgent(); + + // Sets the active wakeword for the voiceagent. + bool setActiveWakeWord(const string& voiceAgentId, const string& wakeword); + + // Adds a new voiceagent to the cache and also persists the information in a + // database. + // This call would notify all the observers about the new voiceagent addition. + bool addNewVoiceAgent( + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr<unordered_set<string>> wakewords); + + // Removes the voiceagent from thecache and also from persistent database. + // This call would notify all the observers about the removal of the + // voiceagent. + bool removeVoiceAgent(const string& voiceAgentId); + + // Returns the set of all voice agents in @c VoiceAgentsDataManger cache + std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> getAllVoiceAgents(); + + // Returns the event filter that belongs to the core module. + shared_ptr<vshl::common::interfaces::IEventFilter> getEventFilter() const; + + // Subscribe to an event coming from the voiceagent. + bool subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const string voiceagentId); + + // Adds a new voiceagent change observer. + bool addVoiceAgentsChangeObserver(shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer); + + // Removes the voiceagent change observer from the list. + bool removeVoiceAgentsChangeObserver(shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer); + + // Destructor + ~VoiceAgentsDataManager(); + +private: + // Constructor + VoiceAgentsDataManager( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi; + + // A list of all the voiceagent change observers + unordered_set<shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver>> mVoiceAgentChangeObservers; + + // A map of voiceagents grouped by ID + unordered_map<string, shared_ptr<VoiceAgent>> mVoiceAgents; + + // Voiceagent event handler. + shared_ptr<VoiceAgentEventsHandler> mVoiceAgentEventsHandler; + + // Default voiceagent + string mDefaultVoiceAgentId; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> mLogger; +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTS_H_ diff --git a/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp b/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp new file mode 100644 index 0000000..626a7fc --- /dev/null +++ b/src/plugins/voiceagents/VoiceAgentsDataManagerImpl.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2018-2019 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 "voiceagents/VoiceAgentsDataManager.h" + +#include "voiceagents/include/VoiceAgentEventsHandler.h" + +static string TAG = "vshl::voiceagents::VoiceAgentsDataManager"; + +/** + * Specifies the severity level of a log message + */ +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace voiceagents { + +std::unique_ptr<VoiceAgentsDataManager> VoiceAgentsDataManager::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) { + return std::unique_ptr<VoiceAgentsDataManager>(new VoiceAgentsDataManager(logger, afbApi)); +} + +// Constructor +VoiceAgentsDataManager::VoiceAgentsDataManager( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) : + mLogger(logger), + mAfbApi(afbApi) { + mVoiceAgentEventsHandler = VoiceAgentEventsHandler::create(mLogger, mAfbApi); +} + +// Destructor +VoiceAgentsDataManager::~VoiceAgentsDataManager() { + // Clear the observers + mVoiceAgentChangeObservers.clear(); + // Clear the voiceagents + mVoiceAgents.clear(); +} + +uint32_t VoiceAgentsDataManager::activateVoiceAgents(const unordered_set<string>& activeVoiceAgentIds) { + if (activeVoiceAgentIds.empty() || mVoiceAgents.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to activate voiceagents"); + return 0; + } + + uint32_t agentsActivated = 0; + for (auto voiceAgentId : activeVoiceAgentIds) { + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt != mVoiceAgents.end()) { + // activate the voiceagent + ++agentsActivated; + if (!voiceAgentIt->second->isActive()) { + voiceAgentIt->second->setIsActive(true); + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentActivated(voiceAgentIt->second); + } + } + } + } + return agentsActivated; +} + +uint32_t VoiceAgentsDataManager::deactivateVoiceAgents(const unordered_set<string>& inactiveVoiceAgentIds) { + if (inactiveVoiceAgentIds.empty() || mVoiceAgents.empty()) { + mLogger->log(Level::ERROR, TAG, "Failed to deactivate voiceagents"); + return 0; + } + + uint32_t agentsDeactivated = 0; + for (auto voiceAgentId : inactiveVoiceAgentIds) { + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt != mVoiceAgents.end()) { + ++agentsDeactivated; + if (voiceAgentIt->second->isActive()) { + // deactivate the voiceagent + voiceAgentIt->second->setIsActive(false); + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentDeactivated(voiceAgentIt->second); + } + } + } + } + + return agentsDeactivated; +} + +bool VoiceAgentsDataManager::setDefaultVoiceAgent(const string& voiceAgentId) { + if (mVoiceAgents.empty() || voiceAgentId.empty()) { + string message = string("Failed to set default voiceagent id: ") + voiceAgentId; + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto defaultVoiceAgentIt = mVoiceAgents.find(voiceAgentId); + + if (defaultVoiceAgentIt != mVoiceAgents.end()) { + if (mDefaultVoiceAgentId != voiceAgentId) { + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnDefaultVoiceAgentChanged(defaultVoiceAgentIt->second); + } + } + mDefaultVoiceAgentId = voiceAgentId; + } else { + mLogger->log(Level::ERROR, TAG, "Can't set default agent. Invalid voice agent id:" + voiceAgentId); + return false; + } + + return true; +} + +std::string VoiceAgentsDataManager::getDefaultVoiceAgent() { + return mDefaultVoiceAgentId; +} + +bool VoiceAgentsDataManager::setActiveWakeWord(const string& voiceAgentId, const string& wakeword) { + if (mVoiceAgents.empty() || wakeword.empty()) { + string message = + string("Failed to set active wakeword: ") + wakeword + string(" for voiceagent id: ") + voiceAgentId; + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt == mVoiceAgents.end()) { + return false; + } + + string oldWakeWord = voiceAgentIt->second->getActiveWakeword(); + if (oldWakeWord != wakeword) { + voiceAgentIt->second->setActiveWakeWord(wakeword); + // Notify observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentActiveWakeWordChanged(voiceAgentIt->second); + } + } + + return true; +} + +bool VoiceAgentsDataManager::addNewVoiceAgent( + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr<unordered_set<string>> wakewords) { + shared_ptr<VoiceAgent> voiceAgent = + VoiceAgent::create(mLogger, id, name, description, api, vendor, activeWakeword, isActive, wakewords); + + if (voiceAgent.get() == nullptr || voiceAgent->getId().empty()) { + string message = string("Invalid Arguments: Failed to add new voiceagent"); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + if (!mVoiceAgents.empty() && mVoiceAgents.find(voiceAgent->getId()) != mVoiceAgents.end()) { + string message = + string("Failed to add new voiceagent. Voiceagent: ") + voiceAgent->getId() + string(" already exists."); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + mVoiceAgents.insert(make_pair(voiceAgent->getId(), voiceAgent)); + + // Notify the observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentAdded(voiceAgent); + } + + // Create all vshl events for the voiceagent. + mVoiceAgentEventsHandler->createVshlEventsForVoiceAgent(voiceAgent->getId()); + + return true; +} + +bool VoiceAgentsDataManager::removeVoiceAgent(const string& voiceAgentId) { + if (mVoiceAgents.empty()) { + string message = string("Failed to remove voiceagent: ") + voiceAgentId + string(". Voiceagents data empty."); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt == mVoiceAgents.end()) { + string message = string("Failed to remove voiceagent: ") + voiceAgentId + string(". Doesn't exist."); + mLogger->log(Level::ERROR, TAG, message); + return false; + } + + auto voiceAgent = voiceAgentIt->second; + // Remove from the map + mVoiceAgents.erase(voiceAgentId); + // Notify the observers + for (auto observer : mVoiceAgentChangeObservers) { + observer->OnVoiceAgentRemoved(voiceAgent); + } + + // Remove all vshl events for the voiceagent. + mVoiceAgentEventsHandler->removeVshlEventsForVoiceAgent(voiceAgent->getId()); + + return true; +} + +std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> VoiceAgentsDataManager::getAllVoiceAgents() { + std::set<std::shared_ptr<vshl::common::interfaces::IVoiceAgent>> voiceAgentsSet; + for (auto element : mVoiceAgents) { + voiceAgentsSet.insert(element.second); + } + + return voiceAgentsSet; +} + +// Returns the event filter that belongs to the core module. +shared_ptr<vshl::common::interfaces::IEventFilter> VoiceAgentsDataManager::getEventFilter() const { + return mVoiceAgentEventsHandler; +} + +bool VoiceAgentsDataManager::subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const string voiceAgentId) { + auto voiceAgentIt = mVoiceAgents.find(voiceAgentId); + if (voiceAgentIt == mVoiceAgents.end()) { + mLogger->log( + Level::ERROR, + TAG, + "\ + Failed to subscribe to VSHL events from voiceagent. VoiceAgent: " + + voiceAgentId + " doesn't exist."); + return false; + } + return mVoiceAgentEventsHandler->subscribeToVshlEventFromVoiceAgent(request, eventName, voiceAgentIt->second); +} + +bool VoiceAgentsDataManager::addVoiceAgentsChangeObserver( + shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer) { + if (!observer) { + return false; + } + + mVoiceAgentChangeObservers.insert(observer); + return true; +} + +bool VoiceAgentsDataManager::removeVoiceAgentsChangeObserver( + shared_ptr<vshl::common::interfaces::IVoiceAgentsChangeObserver> observer) { + if (!observer) { + return false; + } + + mVoiceAgentChangeObservers.erase(observer); + return true; +} +} // namespace voiceagents +} // namespace vshl diff --git a/src/plugins/voiceagents/include/VoiceAgent.h b/src/plugins/voiceagents/include/VoiceAgent.h new file mode 100644 index 0000000..4dd55d4 --- /dev/null +++ b/src/plugins/voiceagents/include/VoiceAgent.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018-2019 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_ + +#include <memory> +#include <unordered_set> + +#include "interfaces/utilities/logging/ILogger.h" +#include "interfaces/voiceagents/IVoiceAgent.h" + +using namespace std; + +namespace vshl { +namespace voiceagents { +/* + * Default implementation of IVoiceAgent interface. + */ +class VoiceAgent : public vshl::common::interfaces::IVoiceAgent { +public: + // Creates @c VoiceAgent instance + static shared_ptr<VoiceAgent> + create(shared_ptr<vshl::common::interfaces::ILogger> logger, const string &id, + const string &name, const string &description, const string &api, + const string &vendor, const string &activeWakeword, + const bool isActive, + const shared_ptr<unordered_set<string>> wakewords); + + // Destructor + ~VoiceAgent(); + + // IVoiceAgent overriden methods + bool setActiveWakeWord(const string &wakeword) override; + void setIsActive(bool active) override; + string getId() const override; + string getName() const override; + string getDescription() const override; + string getApi() const override; + string getVendor() const override; + shared_ptr<unordered_set<string>> getWakeWords() const override; + bool isActive() const override; + string getActiveWakeword() const override; + +private: + // Constructor + VoiceAgent(shared_ptr<vshl::common::interfaces::ILogger> logger, + const string &id, const string &name, const string &description, + const string &api, const string &vendor, + const string &activeWakeword, const bool isActive, + const shared_ptr<unordered_set<string>> wakewords); + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> mLogger; + + // Id + string mId; + + // Name + string mName; + + // Description + string mDescription; + + // API + string mApi; + + // Vendor + string mVendor; + + // Active wakeword + string mActiveWakeword; + + // Active ?? + bool mIsActive; + + // Wakewords + shared_ptr<unordered_set<string>> mWakewords; +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENT_H_ diff --git a/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h b/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h new file mode 100644 index 0000000..3c1ca6c --- /dev/null +++ b/src/plugins/voiceagents/include/VoiceAgentEventsHandler.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018-2019 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. + */ +#ifndef VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_ +#define VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_ + +#include <algorithm> +#include <memory> +#include <unordered_map> + +#include "interfaces/afb/IAFBApi.h" +#include "interfaces/utilities/events/IEventFilter.h" +#include "interfaces/utilities/logging/ILogger.h" +#include "voiceagents/VoiceAgentEventNames.h" +#include "voiceagents/include/VoiceAgent.h" + +using namespace std; + +namespace vshl { +namespace voiceagents { +/* + * This class is reponsible for handling agent specific events + * subscription and delivery on behalf of the high level voice service. + * This class also listen to the incoming events from voice agents + * and implements propagation to application layer. + */ +class VoiceAgentEventsHandler : public vshl::common::interfaces::IEventFilter { +public: + // Create a VREventFilter. + static shared_ptr<VoiceAgentEventsHandler> create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Creates all the vshl events for a specific voiceagent id. + // For e.g if voiceagent is VA-001 then a new vshl event + // voice_authstate_event#VA-001 for auth state will be created. + // Please see VoiceAgentEventNames.h for all the event names. + void createVshlEventsForVoiceAgent(const string voiceAgentId); + + // Removes the events from its bookkeeping. + void removeVshlEventsForVoiceAgent(const string voiceAgentId); + + // Subscribe to a vshl event corresponding to a voiceagent. + bool subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const shared_ptr<VoiceAgent> voiceAgent); + + ~VoiceAgentEventsHandler(); + +protected: + string getName() override; + + // IEventFilter override + bool onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) override; + +private: + // Constructor + VoiceAgentEventsHandler( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi); + + // Helper method to generate the event name with voiceagent Id + // concatenated. + string createEventNameWithVAId(string eventName, string voiceAgentId); + + // call subscribe verb on the voiceagent. True if subscription successful. + // False otherwise. + bool callSubscribeVerb(const shared_ptr<VoiceAgent> voiceAgent); + + // Binding API reference + shared_ptr<vshl::common::interfaces::IAFBApi> mAfbApi; + + // A map of VSHL event ID to its Event object + unordered_map<string, shared_ptr<common::interfaces::IAFBApi::IAFBEvent>> mEventsMap; + + // Logger + shared_ptr<vshl::common::interfaces::ILogger> mLogger; +}; + +} // namespace voiceagents +} // namespace vshl + +#endif // VSHL_VOICEAGENTS_INCLUDE_VOICEAGENTSTATE_EVENT_HANDLER_H_ diff --git a/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp new file mode 100644 index 0000000..4952721 --- /dev/null +++ b/src/plugins/voiceagents/src/VoiceAgentEventsHandler.cpp @@ -0,0 +1,139 @@ +/* + * Copyright 2018-2019 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 "voiceagents/include/VoiceAgentEventsHandler.h" + +static string TAG = "vshl::voiceagents::VoiceAgentEventsHandler"; +static string VA_VERB_SUBSCRIBE = "subscribe"; + +using Level = vshl::common::interfaces::ILogger::Level; +using namespace vshl::common::interfaces; + +namespace vshl { +namespace voiceagents { + +shared_ptr<VoiceAgentEventsHandler> VoiceAgentEventsHandler::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) { + auto eventFilter = std::shared_ptr<VoiceAgentEventsHandler>(new VoiceAgentEventsHandler(logger, afbApi)); + return eventFilter; +} + +VoiceAgentEventsHandler::VoiceAgentEventsHandler( + shared_ptr<vshl::common::interfaces::ILogger> logger, + shared_ptr<vshl::common::interfaces::IAFBApi> afbApi) : + mAfbApi(afbApi), + mLogger(logger) { +} + +VoiceAgentEventsHandler::~VoiceAgentEventsHandler() { + mEventsMap.clear(); +} + +string VoiceAgentEventsHandler::getName() { + return TAG; +} + +void VoiceAgentEventsHandler::createVshlEventsForVoiceAgent(const string voiceAgentId) { + // Update the events map with all the VSHL Events. + for (auto eventName : VSHL_EVENTS) { + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId); + auto it = mEventsMap.find(eventNameWithVAId); + if (it == mEventsMap.end() && mAfbApi) { + // create a new event and add it to the map. + shared_ptr<IAFBApi::IAFBEvent> event = mAfbApi->createEvent(eventNameWithVAId); + mEventsMap.insert(make_pair(eventNameWithVAId, event)); + } + } +} + +void VoiceAgentEventsHandler::removeVshlEventsForVoiceAgent(const string voiceAgentId) { + // Update the events map with all the VSHL Events. + for (auto eventName : VSHL_EVENTS) { + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId); + auto it = mEventsMap.find(eventNameWithVAId); + if (it != mEventsMap.end()) { + mEventsMap.erase(it); + } + } +} + +bool VoiceAgentEventsHandler::subscribeToVshlEventFromVoiceAgent( + vshl::common::interfaces::IAFBRequest& request, + const string eventName, + const shared_ptr<VoiceAgent> voiceAgent) { + auto supportedEventsIt = find(VSHL_EVENTS.begin(), VSHL_EVENTS.end(), eventName); + if (supportedEventsIt == VSHL_EVENTS.end()) { + mLogger->log(Level::ERROR, TAG, "Event: " + eventName + " not a known event."); + return false; + } + + // Check if the entry for the voiceagent is present in the + // events map. If not then return false because the responsibility + // of adding to the map lies in the hands of AddVoiceAgent method. + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgent->getId()); + auto createdEventsIt = mEventsMap.find(eventNameWithVAId); + if (createdEventsIt == mEventsMap.end()) { + mLogger->log(Level::ERROR, TAG, "Not able to subscribe. Event doesn't exist, " + eventNameWithVAId); + return false; + } + createdEventsIt->second->subscribe(request); + + if (!callSubscribeVerb(voiceAgent)) { + mLogger->log(Level::WARNING, TAG, "Failed to subscribe to voiceagent: " + voiceAgent->getId()); + } + + return true; +} + +// IEventFilter override. +bool VoiceAgentEventsHandler::onIncomingEvent(const string eventName, const string voiceAgentId, const string payload) { + string eventNameWithVAId = createEventNameWithVAId(eventName, voiceAgentId); + auto it = mEventsMap.find(eventNameWithVAId); + if (it != mEventsMap.end()) { + return it->second->publishEvent(json_object_new_string(payload.c_str())); + } + + return true; +} + +string VoiceAgentEventsHandler::createEventNameWithVAId(string eventName, string voiceAgentId) { + return eventName + "#" + voiceAgentId; +} + +bool VoiceAgentEventsHandler::callSubscribeVerb(const shared_ptr<VoiceAgent> voiceAgent) { + if (!voiceAgent) { + mLogger->log(Level::ERROR, TAG, "Failed to callSubscribeVerb. Invalid input parameter."); + return false; + } + + if (!mAfbApi) { + mLogger->log( + Level::ERROR, TAG, "Failed to callSubscribeVerb on voicegent: " + voiceAgent->getId() + ", No API."); + return false; + } + + // TODO(Naveen): Move to utilities. + json_object* object = NULL; + std::string error, info; + int rc = mAfbApi->callSync(voiceAgent->getApi(), VA_VERB_SUBSCRIBE, NULL, &object, error, info); + + if (object) { + free(object); + } + + return true; +} +} // namespace voiceagents +} // namespace vshl diff --git a/src/plugins/voiceagents/src/VoiceAgentImpl.cpp b/src/plugins/voiceagents/src/VoiceAgentImpl.cpp new file mode 100644 index 0000000..f2ef8a1 --- /dev/null +++ b/src/plugins/voiceagents/src/VoiceAgentImpl.cpp @@ -0,0 +1,126 @@ +/* + * Copyright 2018-2019 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 <sstream> + +#include "voiceagents/include/VoiceAgent.h" + +static string TAG = "vshl::voiceagents::VoiceAgent"; + +/** + * Specifies the severity level of a log message + */ +using Level = vshl::common::interfaces::ILogger::Level; + +namespace vshl { +namespace voiceagents { +// Creates @c VoiceAgent instance +shared_ptr<VoiceAgent> VoiceAgent::create( + shared_ptr<vshl::common::interfaces::ILogger> logger, + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr<unordered_set<string>> wakewords) { + if (wakewords == nullptr) { + logger->log(Level::ERROR, TAG, "Wakeword list null"); + return nullptr; + } + + auto voiceAgent = std::unique_ptr<VoiceAgent>( + new VoiceAgent(logger, id, name, description, api, vendor, activeWakeword, isActive, wakewords)); + if (!voiceAgent->setActiveWakeWord(activeWakeword)) { + return nullptr; + } + + return voiceAgent; +} + +VoiceAgent::VoiceAgent( + shared_ptr<vshl::common::interfaces::ILogger> logger, + const string& id, + const string& name, + const string& description, + const string& api, + const string& vendor, + const string& activeWakeword, + const bool isActive, + const shared_ptr<unordered_set<string>> wakewords) : + mLogger(logger), + mId(id), + mName(name), + mDescription(description), + mApi(api), + mVendor(vendor), + mActiveWakeword(activeWakeword), + mIsActive(isActive), + mWakewords(wakewords) { +} + +// Destructor +VoiceAgent::~VoiceAgent() { +} + +// Set the active wakeword for this voiceagent +bool VoiceAgent::setActiveWakeWord(const string& wakeword) { + if (mWakewords->find(wakeword) != mWakewords->end()) { + mActiveWakeword = wakeword; + return true; + } + + mLogger->log(Level::ERROR, TAG, "Wakeword: " + wakeword + " doesn't exist in wakeword list"); + return false; +} + +// Sets the activation state of this voiceagent +void VoiceAgent::setIsActive(bool active) { + mIsActive = active; +} + +string VoiceAgent::getId() const { + return mId; +} + +string VoiceAgent::getName() const { + return mName; +} + +string VoiceAgent::getDescription() const { + return mDescription; +} + +string VoiceAgent::getApi() const { + return mApi; +} + +string VoiceAgent::getVendor() const { + return mVendor; +} + +shared_ptr<unordered_set<string>> VoiceAgent::getWakeWords() const { + return mWakewords; +} + +bool VoiceAgent::isActive() const { + return mIsActive; +} + +string VoiceAgent::getActiveWakeword() const { + return mActiveWakeword; +} +} // namespace voiceagents +} // namespace vshl diff --git a/src/plugins/voiceagents/test/VoiceAgentTest.cpp b/src/plugins/voiceagents/test/VoiceAgentTest.cpp new file mode 100644 index 0000000..e5ad15e --- /dev/null +++ b/src/plugins/voiceagents/test/VoiceAgentTest.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2018-2019 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 <gtest/gtest.h> + +#include "voiceagents/include/VoiceAgent.h" + +#include "voiceagents/test/VoiceAgentsTestData.h" +#include "test/common/ConsoleLogger.h" + +using namespace vshl::voiceagents; +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class VoiceAgentTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared<ConsoleLogger>(); + + mVoiceAgentData = *(getVoiceAgentsTestData().begin()); + mVoiceAgent = VoiceAgent::create( + mConsoleLogger, + mVoiceAgentData.id, + mVoiceAgentData.name, + mVoiceAgentData.description, + mVoiceAgentData.api, + mVoiceAgentData.vendor, + mVoiceAgentData.activeWakeword, + mVoiceAgentData.isActive, + mVoiceAgentData.wakewords); + } + + std::shared_ptr<ConsoleLogger> mConsoleLogger; + std::shared_ptr<VoiceAgent> mVoiceAgent; + VoiceAgentTestData mVoiceAgentData; +}; + +TEST_F(VoiceAgentTest, InitializesCorrectly) { + ASSERT_NE(mVoiceAgent, nullptr); + ASSERT_EQ(mVoiceAgent->getId(), mVoiceAgentData.id); + ASSERT_EQ(mVoiceAgent->getName(), mVoiceAgentData.name); + ASSERT_EQ(mVoiceAgent->getDescription(), mVoiceAgentData.description); + ASSERT_EQ(mVoiceAgent->getApi(), mVoiceAgentData.api); + ASSERT_EQ(mVoiceAgent->getVendor(), mVoiceAgentData.vendor); + ASSERT_EQ(mVoiceAgent->getActiveWakeword(), mVoiceAgentData.activeWakeword); + ASSERT_EQ(mVoiceAgent->isActive(), mVoiceAgentData.isActive); + + std::unordered_set<std::string> wakeWords = *mVoiceAgentData.wakewords; + ASSERT_EQ(*(mVoiceAgent->getWakeWords()), wakeWords); +} + +TEST_F(VoiceAgentTest, FailsCreationOnNonExistentWakeword) { + std::string nonExistentWW = "non-existent"; + auto voiceAgent = VoiceAgent::create( + mConsoleLogger, + mVoiceAgentData.id, + mVoiceAgentData.name, + mVoiceAgentData.description, + mVoiceAgentData.api, + mVoiceAgentData.vendor, + nonExistentWW, + mVoiceAgentData.isActive, + mVoiceAgentData.wakewords); + ASSERT_EQ(voiceAgent, nullptr); +} + +TEST_F(VoiceAgentTest, SetsWakewordCorrectly) { + std::string wakeword = *(mVoiceAgentData.wakewords->begin()); + ASSERT_TRUE(mVoiceAgent->setActiveWakeWord(wakeword)); + ASSERT_EQ(mVoiceAgent->getActiveWakeword(), wakeword); +} + +TEST_F(VoiceAgentTest, FailsToSetNonExistentWakeword) { + std::string nonExistentWW = "non-existent"; + ASSERT_FALSE(mVoiceAgent->setActiveWakeWord(nonExistentWW)); + ASSERT_EQ(mVoiceAgent->getActiveWakeword(), mVoiceAgentData.activeWakeword); +} + +} // namespace test +} // namespace vshl
\ No newline at end of file diff --git a/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp b/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp new file mode 100644 index 0000000..58c62ed --- /dev/null +++ b/src/plugins/voiceagents/test/VoiceAgentsDataManagerTest.cpp @@ -0,0 +1,294 @@ +/* + * Copyright 2018-2019 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 <gtest/gtest.h> + +#include "voiceagents/VoiceAgentsDataManager.h" + +#include "test/common/ConsoleLogger.h" +#include "test/mocks/AFBApiMock.h" +#include "test/mocks/VoiceAgentsChangeObserverMock.h" +#include "voiceagents/test/VoiceAgentsTestData.h" + +using namespace vshl::common::interfaces; +using namespace vshl::voiceagents; + +using namespace vshl::test::common; + +namespace vshl { +namespace test { + +class VoiceAgentDataManagerTest : public ::testing::Test { +protected: + void SetUp() override { + mConsoleLogger = std::make_shared<ConsoleLogger>(); + mAfbApi = std::make_shared<::testing::NiceMock<AFBApiMock>>(); + mVADataManager = VoiceAgentsDataManager::create(mConsoleLogger, mAfbApi); + + mAgentsChangeObserver = std::make_shared< + ::testing::StrictMock<VoiceAgentsChangeObserverMock>>(); + mVADataManager->addVoiceAgentsChangeObserver(mAgentsChangeObserver); + + mVoiceAgentsData = getVoiceAgentsTestData(); + } + + void TearDown() override { + mVADataManager->removeVoiceAgentsChangeObserver(mAgentsChangeObserver); + } + + static bool addVoiceAgent(VoiceAgentsDataManager &mgr, + VoiceAgentTestData &data) { + return mgr.addNewVoiceAgent(data.id, data.name, data.description, data.api, + data.vendor, data.activeWakeword, data.isActive, + data.wakewords); + } + + static bool isEqual(const VoiceAgentTestData &lhs, const IVoiceAgent &rhs) { + return lhs.id == rhs.getId() && lhs.name == rhs.getName() && + lhs.description == rhs.getDescription() && lhs.api == rhs.getApi() && + lhs.vendor == rhs.getVendor() && + lhs.activeWakeword == rhs.getActiveWakeword() && + lhs.isActive == rhs.isActive() && + *lhs.wakewords == *rhs.getWakeWords(); + } + + static std::shared_ptr<IVoiceAgent> + findVoiceAgent(std::set<std::shared_ptr<IVoiceAgent>> &voiceAgents, + std::string &vaId) { + for (auto va : voiceAgents) { + if (va->getId() == vaId) + return va; + } + + return nullptr; + } + + std::shared_ptr<ConsoleLogger> mConsoleLogger; + + // It is a NiceMock because we don't want gtest to produce warnings about non + // interesting calls. + // The non interesting calls like createEvent is internal implementation + // detail for many of the + // tests in this class, and hence suppression of these warnings with NiceMock. + std::shared_ptr<::testing::NiceMock<AFBApiMock>> mAfbApi; + + // It is a StrictMock because we want to fail the test for all non interesting + // calls. + std::shared_ptr<::testing::StrictMock<VoiceAgentsChangeObserverMock>> + mAgentsChangeObserver; + + std::vector<VoiceAgentTestData> mVoiceAgentsData; + std::unique_ptr<VoiceAgentsDataManager> mVADataManager; +}; + +TEST_F(VoiceAgentDataManagerTest, InitializesCorrectly) { + ASSERT_NE(mVADataManager, nullptr); + ASSERT_EQ(mVADataManager->getAllVoiceAgents().size(), 0); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), std::string()); +} + +TEST_F(VoiceAgentDataManagerTest, addingVoiceAgentWithSameIdFails) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(1); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_FALSE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 1); +} + +TEST_F(VoiceAgentDataManagerTest, + addingVoiceAgentWithNonExistentActiveWakewordFails) { + auto voiceAgetData = mVoiceAgentsData[0]; + voiceAgetData.activeWakeword = "non-existent"; + ASSERT_FALSE(addVoiceAgent(*mVADataManager, voiceAgetData)); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 0); +} + +TEST_F(VoiceAgentDataManagerTest, canAddNewVoiceAgents) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 1); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 2); + + for (auto va : allVoiceAgents) { + bool voiceAgentFound = false; + for (auto vaData : mVoiceAgentsData) { + if (isEqual(vaData, *va)) { + voiceAgentFound = true; + break; + } + } + ASSERT_TRUE(voiceAgentFound); + } +} + +TEST_F(VoiceAgentDataManagerTest, removingUnknonwVoiceAgentFails) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(1); + + ASSERT_FALSE(mVADataManager->removeVoiceAgent("non-existent-vaid")); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_FALSE(mVADataManager->removeVoiceAgent("non-existent-vaid")); +} + +TEST_F(VoiceAgentDataManagerTest, canRemoveVoiceAgents) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentRemoved(::testing::_)) + .Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + auto allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 2); + + ASSERT_TRUE(mVADataManager->removeVoiceAgent(mVoiceAgentsData[0].id)); + + allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 1); + + ASSERT_TRUE(mVADataManager->removeVoiceAgent(mVoiceAgentsData[1].id)); + + allVoiceAgents = mVADataManager->getAllVoiceAgents(); + ASSERT_EQ(allVoiceAgents.size(), 0); +} + +TEST_F(VoiceAgentDataManagerTest, activatingNonExistentVoiceAgentsFails) { + uint32_t result = mVADataManager->activateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); + + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + result = mVADataManager->activateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); +} + +TEST_F(VoiceAgentDataManagerTest, deactivatingNonExistentVoiceAgentsFails) { + uint32_t result = mVADataManager->deactivateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); + + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + result = mVADataManager->deactivateVoiceAgents({"non", "existent"}); + ASSERT_EQ(result, 0); +} + +TEST_F(VoiceAgentDataManagerTest, canActivateDeactivateVoiceAgents) { + { + ::testing::InSequence dummy; + + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)) + .Times(2); + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentDeactivated(::testing::_)) + .Times(1); + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentActivated(::testing::_)) + .Times(1); + } + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + std::string vaId = mVoiceAgentsData[0].id; + + auto allVA = mVADataManager->getAllVoiceAgents(); + auto voiceAgent = findVoiceAgent(allVA, vaId); + ASSERT_NE(voiceAgent, nullptr); + ASSERT_TRUE(voiceAgent->isActive()); + + uint32_t result = + mVADataManager->deactivateVoiceAgents({"non-existent", vaId}); + ASSERT_EQ(result, 1); + ASSERT_FALSE(voiceAgent->isActive()); + + // Try de-activating already de-activated agent + result = mVADataManager->deactivateVoiceAgents({vaId}); + ASSERT_EQ(result, 1); + ASSERT_FALSE(voiceAgent->isActive()); + + result = mVADataManager->activateVoiceAgents({"non-existent", vaId}); + ASSERT_EQ(result, 1); + ASSERT_TRUE(voiceAgent->isActive()); + + // Try activating already activated agent + result = mVADataManager->activateVoiceAgents({vaId}); + ASSERT_EQ(result, 1); + ASSERT_TRUE(voiceAgent->isActive()); +} + +TEST_F(VoiceAgentDataManagerTest, + NoDefaultAgentIsReturnedWhenNoDefaultAgentIsSet) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + std::string defaultAgentId = mVADataManager->getDefaultVoiceAgent(); + ASSERT_EQ(defaultAgentId, ""); +} + +TEST_F(VoiceAgentDataManagerTest, DefaultAgentCanBeSet) { + EXPECT_CALL(*mAgentsChangeObserver, OnVoiceAgentAdded(::testing::_)).Times(2); + + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[0])); + ASSERT_TRUE(addVoiceAgent(*mVADataManager, mVoiceAgentsData[1])); + + auto allAgents = mVADataManager->getAllVoiceAgents(); + std::string vaId1 = mVoiceAgentsData[1].id; + std::string vaId2 = mVoiceAgentsData[0].id; + auto va1 = findVoiceAgent(allAgents, vaId1); + auto va2 = findVoiceAgent(allAgents, vaId2); + + { + ::testing::InSequence dummy; + + EXPECT_CALL(*mAgentsChangeObserver, OnDefaultVoiceAgentChanged(va1)) + .Times(1); + EXPECT_CALL(*mAgentsChangeObserver, OnDefaultVoiceAgentChanged(va2)) + .Times(1); + } + + ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId1)); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId1); + + ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId2)); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2); + + ASSERT_FALSE(mVADataManager->setDefaultVoiceAgent("non-existent")); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2); + + // Setting default agent to already default agent shouldn't result in extra + // callback to OnDefaultVoiceAgentChanged + ASSERT_TRUE(mVADataManager->setDefaultVoiceAgent(vaId2)); + ASSERT_EQ(mVADataManager->getDefaultVoiceAgent(), vaId2); +} + +} // namespace test +} // namespace vshl
\ No newline at end of file diff --git a/src/plugins/voiceagents/test/VoiceAgentsTestData.h b/src/plugins/voiceagents/test/VoiceAgentsTestData.h new file mode 100644 index 0000000..ced068f --- /dev/null +++ b/src/plugins/voiceagents/test/VoiceAgentsTestData.h @@ -0,0 +1,67 @@ +/* + * Copyright 2018-2019 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 <memory> +#include <string> +#include <unordered_set> +#include <vector> + +namespace vshl { +namespace test { + +typedef std::shared_ptr<std::unordered_set<std::string>> WakeWords; + +struct VoiceAgentTestData { + std::string id; + std::string name; + std::string description; + std::string api; + std::string vendor; + std::string activeWakeword; + bool isActive; + WakeWords wakewords; +}; + +static std::vector<VoiceAgentTestData> getVoiceAgentsTestData() { + std::vector<VoiceAgentTestData> voiceAgentsTestData{ + { + "VA-001", // Id + "Foundation", // Name + "Voice Agent For Galactic Empire", // Description + "api-1", // API + "Asimov", // Vendor + "Hari Seldon", // Active Wakeword + true, // Is Active + std::shared_ptr<std::unordered_set<std::string>>( + new std::unordered_set<std::string>{"Hari Seldon", "Cleon I ", "Eto Demerzel"}) // Wake Words + }, + { + "VA-002", // Id + "Betelgeuse", // Name + "Voice Agent For Galaxy hopper", // Description + "api-2", // API + "Douglas Adams", // Vendor + "Ford Prefect", // Active Wakeword + true, // Is Active + std::shared_ptr<std::unordered_set<std::string>>( + new std::unordered_set<std::string>{"Ford Prefect", "Zaphod Beeblebrox"}) // Wake Words + }, + }; + + return voiceAgentsTestData; +} + +} // namespace test +} // namespace vshl |