220 lines
7.9 KiB
C++
220 lines
7.9 KiB
C++
|
/*
|
||
|
* Copyright 2017-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.
|
||
|
*/
|
||
|
|
||
|
// @file ConfigurationNodeTest.cpp
|
||
|
|
||
|
#include <sstream>
|
||
|
|
||
|
#include <gtest/gtest.h>
|
||
|
|
||
|
#include "AVSCommon/Utils/Configuration/ConfigurationNode.h"
|
||
|
|
||
|
namespace alexaClientSDK {
|
||
|
namespace avsCommon {
|
||
|
namespace utils {
|
||
|
namespace configuration {
|
||
|
namespace test {
|
||
|
|
||
|
using namespace ::testing;
|
||
|
|
||
|
/// Name of non-existent object for exercising failure to find a @c ConfigurationNode.
|
||
|
static const std::string NON_OBJECT = "non-existent-object";
|
||
|
|
||
|
/// Name of first root level object.
|
||
|
static const std::string OBJECT1 = "object1";
|
||
|
|
||
|
/// Name of first bool value in first root level object.
|
||
|
static const std::string BOOL1_1 = "bool1.1";
|
||
|
|
||
|
/// Value of first bool value in first root level object.
|
||
|
static const bool BOOL_VALUE1_1 = true;
|
||
|
|
||
|
/// Name of first object inside first root level object.
|
||
|
static const std::string OBJECT1_1 = "object1.1";
|
||
|
|
||
|
/// Name of first string value in first object inside first root level object.
|
||
|
static const std::string STRING1_1_1 = "string1.1.1";
|
||
|
|
||
|
/// Value of first string value in first object inside first root level object.
|
||
|
static const std::string STRING_VALUE1_1_1 = "stringValue1.1.1";
|
||
|
|
||
|
/// Name of second root level object.
|
||
|
static const std::string OBJECT2 = "object2";
|
||
|
|
||
|
/// Name of first string in second root level object.
|
||
|
static const std::string STRING2_1 = "string2.1";
|
||
|
|
||
|
/// Replaced value of first string in second root level object.
|
||
|
static const std::string NEW_STRING_VALUE2_1 = "new-stringValue2.1";
|
||
|
|
||
|
/// Name for non-existent int value in second root level object.
|
||
|
static const std::string NON_EXISTENT_INT2_1 = "non-existent-int2.1";
|
||
|
|
||
|
/// Default value for non-existent int value in second root level object.
|
||
|
static const int NON_EXISTENT_INT_VALUE2_1 = 123;
|
||
|
|
||
|
/// Name of first int value in second root level object.
|
||
|
static const std::string INT2_1 = "int2.1";
|
||
|
|
||
|
/// Name of first object inside second root level object.
|
||
|
static const std::string OBJECT2_1 = "object2.1";
|
||
|
|
||
|
/// Name of first string inside first object inside second root level object.
|
||
|
static const std::string STRING2_1_1 = "string2.1.1";
|
||
|
|
||
|
/// Replaced value of first string inside first object inside second root level object.
|
||
|
static const std::string NEW_STRING_VALUE2_1_1 = "new-stringValue2.1.1";
|
||
|
|
||
|
/// Bad JSON string to verify handling the failure to parse JSON
|
||
|
static const std::string BAD_JSON = "{ bad json }";
|
||
|
|
||
|
/// First JSON string to parse, serving as default for configuration values.
|
||
|
// clang-format off
|
||
|
static const std::string FIRST_JSON = R"(
|
||
|
{
|
||
|
"object1" : {
|
||
|
"bool1.1" : true
|
||
|
},
|
||
|
"object2" : {
|
||
|
"int2.1" : 21,
|
||
|
"string2.1" : "stringValue2.1",
|
||
|
"object2.1" : {
|
||
|
"string2.1.1" : "stringValue2.1.1"
|
||
|
}
|
||
|
}
|
||
|
})";
|
||
|
// clang-format on
|
||
|
|
||
|
/// Second JSON string to parse, overlaying configuration values from FIRST_JSON.
|
||
|
// clang-format off
|
||
|
static const std::string SECOND_JSON = R"(
|
||
|
{
|
||
|
"object1" : {
|
||
|
"object1.1" : {
|
||
|
"string1.1.1" : "stringValue1.1.1"
|
||
|
},
|
||
|
"int1.1" : 11
|
||
|
}
|
||
|
})";
|
||
|
// clang-format on
|
||
|
|
||
|
/// Third JSON string to parse, overlaying configuration values from FIRST_JSON and SECOND_JSON.
|
||
|
// clang-format off
|
||
|
static const std::string THIRD_JSON = R"(
|
||
|
{
|
||
|
"object2" : {
|
||
|
"int2.1" : 21,
|
||
|
"string2.1" : "new-stringValue2.1",
|
||
|
"object2.1" : {
|
||
|
"string2.1.1" : "new-stringValue2.1.1"
|
||
|
}
|
||
|
}
|
||
|
})";
|
||
|
// clang-format on
|
||
|
|
||
|
/**
|
||
|
* Class for testing the ConfigurationNode class
|
||
|
*/
|
||
|
class ConfigurationNodeTest : public ::testing::Test {};
|
||
|
|
||
|
/**
|
||
|
* Verify initialization a configuration. Verify both the implementation of accessor methods and the results
|
||
|
* of merging JSON streams.
|
||
|
*/
|
||
|
TEST_F(ConfigurationNodeTest, testInitializationAndAccess) {
|
||
|
// Verify a null configuration results in failure
|
||
|
std::vector<std::shared_ptr<std::istream>> jsonStream;
|
||
|
jsonStream.push_back(nullptr);
|
||
|
ASSERT_FALSE(ConfigurationNode::initialize(jsonStream));
|
||
|
jsonStream.clear();
|
||
|
|
||
|
// Verify invalid JSON results in failure
|
||
|
auto badStream = std::shared_ptr<std::stringstream>(new std::stringstream());
|
||
|
(*badStream) << BAD_JSON;
|
||
|
jsonStream.push_back(badStream);
|
||
|
ASSERT_FALSE(ConfigurationNode::initialize(jsonStream));
|
||
|
jsonStream.clear();
|
||
|
|
||
|
// Combine valid JSON streams with overlapping values. Verify reported success.
|
||
|
auto firstStream = std::shared_ptr<std::stringstream>(new std::stringstream());
|
||
|
(*firstStream) << FIRST_JSON;
|
||
|
auto secondStream = std::shared_ptr<std::stringstream>(new std::stringstream());
|
||
|
(*secondStream) << SECOND_JSON;
|
||
|
auto thirdStream = std::shared_ptr<std::stringstream>(new std::stringstream());
|
||
|
(*thirdStream) << THIRD_JSON;
|
||
|
jsonStream.push_back(firstStream);
|
||
|
jsonStream.push_back(secondStream);
|
||
|
jsonStream.push_back(thirdStream);
|
||
|
ASSERT_TRUE(ConfigurationNode::initialize(jsonStream));
|
||
|
jsonStream.clear();
|
||
|
|
||
|
// Verify failure reported for subsequent initializations.
|
||
|
auto firstStream1 = std::shared_ptr<std::stringstream>(new std::stringstream());
|
||
|
(*firstStream1) << FIRST_JSON;
|
||
|
jsonStream.push_back(firstStream1);
|
||
|
ASSERT_FALSE(ConfigurationNode::initialize(jsonStream));
|
||
|
jsonStream.clear();
|
||
|
|
||
|
// Verify non-found name results in a ConfigurationNode that evaluates to false.
|
||
|
ASSERT_FALSE(ConfigurationNode::getRoot()[NON_OBJECT]);
|
||
|
|
||
|
// Verify found name results in a ConfigurationNode that evaluates to true.
|
||
|
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT1]);
|
||
|
|
||
|
// Verify extraction of bool value.
|
||
|
bool bool11 = true;
|
||
|
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT1].getBool(BOOL1_1, &bool11));
|
||
|
ASSERT_EQ(bool11, BOOL_VALUE1_1);
|
||
|
|
||
|
// Verify traversal of multiple levels of ConfigurationNode and extraction of a string value.
|
||
|
std::string string111;
|
||
|
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT1][OBJECT1_1].getString(STRING1_1_1, &string111));
|
||
|
ASSERT_EQ(string111, STRING_VALUE1_1_1);
|
||
|
|
||
|
// Verify retrieval of default value when name does not match any value.
|
||
|
int nonExistentInt21 = 0;
|
||
|
ASSERT_NE(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||
|
ASSERT_FALSE(ConfigurationNode::getRoot()[OBJECT2].getInt(
|
||
|
NON_EXISTENT_INT2_1, &nonExistentInt21, NON_EXISTENT_INT_VALUE2_1));
|
||
|
ASSERT_EQ(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||
|
|
||
|
// Verify extraction if an integer value.
|
||
|
int int21;
|
||
|
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2].getInt(INT2_1, &int21));
|
||
|
ASSERT_EQ(int21, 21);
|
||
|
|
||
|
// Verify overwrite of string value by subsequent JSON.
|
||
|
std::string newString21;
|
||
|
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2].getString(STRING2_1, &newString21));
|
||
|
ASSERT_EQ(newString21, NEW_STRING_VALUE2_1);
|
||
|
|
||
|
// Verify retrieval of default value when type does not match an existing value.
|
||
|
nonExistentInt21 = 0;
|
||
|
ASSERT_NE(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||
|
ASSERT_FALSE(ConfigurationNode::getRoot()[OBJECT2].getInt(STRING2_1, &nonExistentInt21, NON_EXISTENT_INT_VALUE2_1));
|
||
|
ASSERT_EQ(nonExistentInt21, NON_EXISTENT_INT_VALUE2_1);
|
||
|
|
||
|
// Verify overwrite of string value in nested Configuration node.
|
||
|
std::string string211;
|
||
|
ASSERT_TRUE(ConfigurationNode::getRoot()[OBJECT2][OBJECT2_1].getString(STRING2_1_1, &string211));
|
||
|
ASSERT_EQ(string211, NEW_STRING_VALUE2_1_1);
|
||
|
}
|
||
|
|
||
|
} // namespace test
|
||
|
} // namespace configuration
|
||
|
} // namespace utils
|
||
|
} // namespace avsCommon
|
||
|
} // namespace alexaClientSDK
|