/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ #include <AVSCommon/AVS/CapabilitySemantics/CapabilitySemantics.h> #include <gtest/gtest.h> #include <rapidjson/document.h> namespace alexaClientSDK { namespace avsCommon { namespace avs { namespace test { using namespace ::testing; using namespace avsCommon::avs::capabilitySemantics; /// Accepted action IDs. /// @{ static std::string ACTION_OPEN = "Alexa.Actions.Open"; static std::string ACTION_CLOSE = "Alexa.Actions.Close"; static std::string ACTION_RAISE = "Alexa.Actions.Raise"; static std::string ACTION_LOWER = "Alexa.Actions.Lower"; /// @} /// Accepted state IDs. /// @{ static std::string STATE_OPEN = "Alexa.States.Open"; static std::string STATE_CLOSED = "Alexa.States.Closed"; /// @} /// Sample directive names. /// @{ static std::string DIRECTIVE_TURNOFF = "TurnOff"; static std::string DIRECTIVE_SETMODE = "SetMode"; static std::string DIRECTIVE_SETRANGE = "SetRangeValue"; static std::string DIRECTIVE_ADJUSTRANGE = "AdjustRangeValue"; /// @} // clang-format off /** * Sample 'semantics' object with multiple ActionsToDirectiveMappings with multiple actions. */ static std::string JSON_SEMANTICS_MULTIPLE_ACTIONS = R"({ "actionMappings": [ { "@type": "ActionsToDirective", "actions": ["Alexa.Actions.Close", "Alexa.Actions.Lower"], "directive": { "name": "SetMode", "payload": { "mode": "Position.Down" } } }, { "@type": "ActionsToDirective", "actions": ["Alexa.Actions.Open", "Alexa.Actions.Raise"], "directive": { "name": "SetMode", "payload": { "mode": "Position.Up" } } } ] })"; /** * Sample 'semantics' object with 'actionMappings' and 'stateMappings'. */ static std::string JSON_SEMANTICS_COMPLETE = R"({ "actionMappings": [ { "@type": "ActionsToDirective", "actions": [ "Alexa.Actions.Close" ], "directive": { "name": "SetRangeValue", "payload": { "rangeValue": 0 } } }, { "@type": "ActionsToDirective", "actions": [ "Alexa.Actions.Open" ], "directive": { "name": "SetRangeValue", "payload": { "rangeValue": 100 } } }, { "@type": "ActionsToDirective", "actions": [ "Alexa.Actions.Lower" ], "directive": { "name": "AdjustRangeValue", "payload": { "rangeValueDelta": -10 } } }, { "@type": "ActionsToDirective", "actions": [ "Alexa.Actions.Raise" ], "directive": { "name": "AdjustRangeValue", "payload": { "rangeValueDelta": 10 } } } ], "stateMappings": [ { "@type": "StatesToValue", "states": [ "Alexa.States.Closed" ], "value": 0 }, { "@type": "StatesToRange", "states": [ "Alexa.States.Open" ], "range": { "minimumValue": 1, "maximumValue": 100 } } ] })"; /// Empty JSON object. static std::string JSON_EMPTY_OBJECT = "{}"; // clang-format on /** * Expects the provided JSON strings to be equal. */ void validateJson(const std::string& providedJson, const std::string& expectedJson) { rapidjson::Document providedStateParsed; providedStateParsed.Parse(providedJson); rapidjson::Document expectedStateParsed; expectedStateParsed.Parse(expectedJson); EXPECT_EQ(providedStateParsed, expectedStateParsed); } /** * The test harness for @c CapabilitySemantics. */ class CapabilitySemanticsTest : public Test {}; /** * Test that ActionsToDirectiveMapping::addAction() checks for an empty action. */ TEST_F(CapabilitySemanticsTest, test_actions_emptyAction) { ActionsToDirectiveMapping invalidMapping; ASSERT_FALSE(invalidMapping.addAction("")); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that ActionsToDirectiveMapping::setDirective() checks for an empty name. */ TEST_F(CapabilitySemanticsTest, test_actions_emptyDirectiveName) { ActionsToDirectiveMapping invalidMapping; ASSERT_FALSE(invalidMapping.setDirective("", JSON_EMPTY_OBJECT)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that ActionsToDirectiveMapping::addAction() skips duplicate actions. */ TEST_F(CapabilitySemanticsTest, test_actions_duplicateAction) { ActionsToDirectiveMapping invalidMapping; ASSERT_TRUE(invalidMapping.setDirective(DIRECTIVE_TURNOFF, JSON_EMPTY_OBJECT)); ASSERT_TRUE(invalidMapping.addAction(ACTION_CLOSE)); ASSERT_TRUE(invalidMapping.isValid()); ASSERT_FALSE(invalidMapping.addAction(ACTION_CLOSE)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that ActionsToDirectiveMapping without actions is invalid. */ TEST_F(CapabilitySemanticsTest, test_actions_noActions) { ActionsToDirectiveMapping invalidMapping; ASSERT_TRUE(invalidMapping.setDirective(DIRECTIVE_TURNOFF, JSON_EMPTY_OBJECT)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that ActionsToDirectiveMapping without a directive is invalid. */ TEST_F(CapabilitySemanticsTest, test_actions_noDirective) { ActionsToDirectiveMapping invalidMapping; ASSERT_TRUE(invalidMapping.addAction(ACTION_CLOSE)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToValueMapping::addState() checks for an empty state. */ TEST_F(CapabilitySemanticsTest, test_statesValue_emptyState) { StatesToValueMapping invalidMapping; ASSERT_FALSE(invalidMapping.addState("")); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToValueMapping::addState() skips duplicate states. */ TEST_F(CapabilitySemanticsTest, test_statesValue_duplicateState) { StatesToValueMapping invalidMapping; ASSERT_TRUE(invalidMapping.setValue("Position.Down")); ASSERT_TRUE(invalidMapping.addState(STATE_CLOSED)); ASSERT_TRUE(invalidMapping.isValid()); ASSERT_FALSE(invalidMapping.addState(STATE_CLOSED)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToValueMapping without states is invalid. */ TEST_F(CapabilitySemanticsTest, test_statesValue_noStates) { StatesToValueMapping invalidMapping; ASSERT_TRUE(invalidMapping.setValue(0)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToValueMapping without a value is invalid. */ TEST_F(CapabilitySemanticsTest, test_statesValue_noValue) { StatesToValueMapping invalidMapping; ASSERT_TRUE(invalidMapping.addState(STATE_CLOSED)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToRangeMapping::addState() checks for an empty state. */ TEST_F(CapabilitySemanticsTest, test_statesRange_emptyState) { StatesToRangeMapping invalidMapping; ASSERT_FALSE(invalidMapping.addState("")); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToRangeMapping::addState() skips duplicate states. */ TEST_F(CapabilitySemanticsTest, test_statesRange_duplicateState) { StatesToRangeMapping invalidMapping; ASSERT_TRUE(invalidMapping.setRange(0, 50)); ASSERT_TRUE(invalidMapping.addState(STATE_OPEN)); ASSERT_TRUE(invalidMapping.isValid()); ASSERT_FALSE(invalidMapping.addState(STATE_OPEN)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToRangeMapping without states is invalid. */ TEST_F(CapabilitySemanticsTest, test_statesRange_noStates) { StatesToRangeMapping invalidMapping; ASSERT_TRUE(invalidMapping.setRange(0, 50)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToRangeMapping without a range is invalid. */ TEST_F(CapabilitySemanticsTest, test_statesRange_noRange) { StatesToRangeMapping invalidMapping; ASSERT_TRUE(invalidMapping.addState(STATE_CLOSED)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that StatesToRangeMapping with min > max is invalid. */ TEST_F(CapabilitySemanticsTest, test_statesRange_invertedRange) { StatesToRangeMapping invalidMapping; ASSERT_TRUE(invalidMapping.addState(STATE_OPEN)); ASSERT_FALSE(invalidMapping.setRange(100, 1)); ASSERT_FALSE(invalidMapping.isValid()); ASSERT_EQ(invalidMapping.toJson(), JSON_EMPTY_OBJECT); } /** * Test that CapabilitySemantics without mappings is invalid. */ TEST_F(CapabilitySemanticsTest, test_semantics_noMappings) { CapabilitySemantics invalidSemantics; ASSERT_FALSE(invalidSemantics.isValid()); validateJson(invalidSemantics.toJson(), JSON_EMPTY_OBJECT); } /** * Test that CapabilitySemantics with an invalid ActionsToDirectiveMapping is invalid. */ TEST_F(CapabilitySemanticsTest, test_semantics_invalidActionsDirectiveMapping) { ActionsToDirectiveMapping invalidMapping; CapabilitySemantics invalidSemantics; ASSERT_FALSE(invalidSemantics.addActionsToDirectiveMapping(invalidMapping)); ASSERT_FALSE(invalidSemantics.isValid()); validateJson(invalidSemantics.toJson(), JSON_EMPTY_OBJECT); } /** * Test that CapabilitySemantics with an invalid StatesToValueMapping is invalid. */ TEST_F(CapabilitySemanticsTest, test_semantics_invalidStatesValueMapping) { StatesToValueMapping invalidMapping; CapabilitySemantics invalidSemantics; ASSERT_FALSE(invalidSemantics.addStatesToValueMapping(invalidMapping)); ASSERT_FALSE(invalidSemantics.isValid()); validateJson(invalidSemantics.toJson(), JSON_EMPTY_OBJECT); } /** * Test that CapabilitySemantics with an invalid StatesToRangeMapping is invalid. */ TEST_F(CapabilitySemanticsTest, test_semantics_invalidStatesRangeMapping) { StatesToRangeMapping invalidMapping; CapabilitySemantics invalidSemantics; ASSERT_FALSE(invalidSemantics.addStatesToRangeMapping(invalidMapping)); ASSERT_FALSE(invalidSemantics.isValid()); validateJson(invalidSemantics.toJson(), JSON_EMPTY_OBJECT); } /** * Test the JSON result of an ActionsToDirectiveMapping with multiple actions. */ TEST_F(CapabilitySemanticsTest, test_validateJson_semanticsMultipleActionMappings) { ActionsToDirectiveMapping setModeDownMapping; ASSERT_TRUE(setModeDownMapping.addAction(ACTION_CLOSE)); ASSERT_TRUE(setModeDownMapping.addAction(ACTION_LOWER)); ASSERT_TRUE(setModeDownMapping.setDirective(DIRECTIVE_SETMODE, "{\"mode\": \"Position.Down\"}")); ASSERT_TRUE(setModeDownMapping.isValid()); ActionsToDirectiveMapping setModeUpMapping; ASSERT_TRUE(setModeUpMapping.addAction(ACTION_OPEN)); ASSERT_TRUE(setModeUpMapping.addAction(ACTION_RAISE)); ASSERT_TRUE(setModeUpMapping.setDirective(DIRECTIVE_SETMODE, "{\"mode\": \"Position.Up\"}")); ASSERT_TRUE(setModeUpMapping.isValid()); CapabilitySemantics semantics; ASSERT_TRUE(semantics.addActionsToDirectiveMapping(setModeDownMapping)); ASSERT_TRUE(semantics.addActionsToDirectiveMapping(setModeUpMapping)); ASSERT_TRUE(semantics.isValid()); validateJson(semantics.toJson(), JSON_SEMANTICS_MULTIPLE_ACTIONS); } /** * Test the JSON result of a CapabilitySemantics with all mapping types. */ TEST_F(CapabilitySemanticsTest, test_validateJson_semanticsComplete) { ActionsToDirectiveMapping closeMapping; ASSERT_TRUE(closeMapping.addAction(ACTION_CLOSE)); ASSERT_TRUE(closeMapping.setDirective(DIRECTIVE_SETRANGE, "{\"rangeValue\" : 0}")); ASSERT_TRUE(closeMapping.isValid()); ActionsToDirectiveMapping openMapping; ASSERT_TRUE(openMapping.addAction(ACTION_OPEN)); ASSERT_TRUE(openMapping.setDirective(DIRECTIVE_SETRANGE, "{\"rangeValue\" : 100}")); ASSERT_TRUE(openMapping.isValid()); ActionsToDirectiveMapping lowerMapping; ASSERT_TRUE(lowerMapping.addAction(ACTION_LOWER)); ASSERT_TRUE(lowerMapping.setDirective(DIRECTIVE_ADJUSTRANGE, "{\"rangeValueDelta\" : -10}")); ASSERT_TRUE(lowerMapping.isValid()); ActionsToDirectiveMapping raiseMapping; ASSERT_TRUE(raiseMapping.addAction(ACTION_RAISE)); ASSERT_TRUE(raiseMapping.setDirective(DIRECTIVE_ADJUSTRANGE, "{\"rangeValueDelta\" : 10}")); ASSERT_TRUE(raiseMapping.isValid()); StatesToValueMapping closedMapping; ASSERT_TRUE(closedMapping.addState(STATE_CLOSED)); ASSERT_TRUE(closedMapping.setValue(0)); ASSERT_TRUE(closedMapping.isValid()); StatesToRangeMapping openedMapping; ASSERT_TRUE(openedMapping.addState(STATE_OPEN)); ASSERT_TRUE(openedMapping.setRange(1, 100)); ASSERT_TRUE(openedMapping.isValid()); CapabilitySemantics semantics; ASSERT_TRUE(semantics.addActionsToDirectiveMapping(closeMapping)); ASSERT_TRUE(semantics.addActionsToDirectiveMapping(openMapping)); ASSERT_TRUE(semantics.addActionsToDirectiveMapping(lowerMapping)); ASSERT_TRUE(semantics.addActionsToDirectiveMapping(raiseMapping)); ASSERT_TRUE(semantics.addStatesToValueMapping(closedMapping)); ASSERT_TRUE(semantics.addStatesToRangeMapping(openedMapping)); ASSERT_TRUE(semantics.isValid()); validateJson(semantics.toJson(), JSON_SEMANTICS_COMPLETE); } } // namespace test } // namespace avs } // namespace avsCommon } // namespace alexaClientSDK