| // Copyright 2015 The Weave Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "src/states/state_package.h" | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 |  | 
 | #include <base/values.h> | 
 | #include <gtest/gtest.h> | 
 |  | 
 | #include "src/commands/schema_constants.h" | 
 | #include "src/commands/unittest_utils.h" | 
 | #include "src/states/error_codes.h" | 
 |  | 
 | namespace weave { | 
 |  | 
 | using test::CreateDictionaryValue; | 
 |  | 
 | class StatePackageTestHelper { | 
 |  public: | 
 |   // Returns the state property definitions (types/constraints/etc). | 
 |   static const ObjectSchema& GetTypes(const StatePackage& package) { | 
 |     return package.types_; | 
 |   } | 
 |   // Returns the all state property values in this package. | 
 |   static const ValueMap& GetValues(const StatePackage& package) { | 
 |     return package.values_; | 
 |   } | 
 | }; | 
 |  | 
 | namespace { | 
 | std::unique_ptr<base::DictionaryValue> GetTestSchema() { | 
 |   return CreateDictionaryValue(R"({ | 
 |     'light': 'boolean', | 
 |     'color': 'string', | 
 |     'direction':{ | 
 |       'properties':{ | 
 |         'azimuth': {'type': 'number', 'isRequired': true}, | 
 |         'altitude': {'maximum': 90.0} | 
 |       } | 
 |     }, | 
 |     'iso': [50, 100, 200, 400] | 
 |   })"); | 
 | } | 
 |  | 
 | std::unique_ptr<base::DictionaryValue> GetTestValues() { | 
 |   return CreateDictionaryValue(R"({ | 
 |       'light': true, | 
 |       'color': 'white', | 
 |       'direction': {'azimuth':57.2957795, 'altitude':89.9}, | 
 |       'iso': 200 | 
 |   })"); | 
 | } | 
 |  | 
 | inline const ObjectSchema& GetTypes(const StatePackage& package) { | 
 |   return StatePackageTestHelper::GetTypes(package); | 
 | } | 
 | // Returns the all state property values in this package. | 
 | inline const ValueMap& GetValues(const StatePackage& package) { | 
 |   return StatePackageTestHelper::GetValues(package); | 
 | } | 
 |  | 
 | }  // anonymous namespace | 
 |  | 
 | class StatePackageTest : public ::testing::Test { | 
 |  public: | 
 |   void SetUp() override { | 
 |     package_.reset(new StatePackage("test")); | 
 |     ASSERT_TRUE(package_->AddSchemaFromJson(GetTestSchema().get(), nullptr)); | 
 |     ASSERT_TRUE(package_->AddValuesFromJson(GetTestValues().get(), nullptr)); | 
 |   } | 
 |   void TearDown() override { package_.reset(); } | 
 |   std::unique_ptr<StatePackage> package_; | 
 | }; | 
 |  | 
 | TEST(StatePackage, Empty) { | 
 |   StatePackage package("test"); | 
 |   EXPECT_EQ("test", package.GetName()); | 
 |   EXPECT_TRUE(GetTypes(package).GetProps().empty()); | 
 |   EXPECT_TRUE(GetValues(package).empty()); | 
 | } | 
 |  | 
 | TEST(StatePackage, AddSchemaFromJson_OnEmpty) { | 
 |   StatePackage package("test"); | 
 |   ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr)); | 
 |   EXPECT_EQ(4, GetTypes(package).GetProps().size()); | 
 |   EXPECT_EQ(4, GetValues(package).size()); | 
 |  | 
 |   auto expected = R"({ | 
 |     'color': { | 
 |       'type': 'string' | 
 |     }, | 
 |     'direction': { | 
 |       'additionalProperties': false, | 
 |       'properties': { | 
 |         'altitude': { | 
 |           'maximum': 90.0, | 
 |           'type': 'number' | 
 |         }, | 
 |         'azimuth': { | 
 |           'type': 'number' | 
 |         } | 
 |       }, | 
 |       'type': 'object', | 
 |       'required': [ 'azimuth' ] | 
 |     }, | 
 |     'iso': { | 
 |       'enum': [50, 100, 200, 400], | 
 |       'type': 'integer' | 
 |     }, | 
 |     'light': { | 
 |       'type': 'boolean' | 
 |     } | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *GetTypes(package).ToJson(true, false)); | 
 |  | 
 |   expected = R"({ | 
 |     'color': '', | 
 |     'direction': {}, | 
 |     'iso': 0, | 
 |     'light': false | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *package.GetValuesAsJson()); | 
 | } | 
 |  | 
 | TEST(StatePackage, AddValuesFromJson_OnEmpty) { | 
 |   StatePackage package("test"); | 
 |   ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr)); | 
 |   ASSERT_TRUE(package.AddValuesFromJson(GetTestValues().get(), nullptr)); | 
 |   EXPECT_EQ(4, GetValues(package).size()); | 
 |   auto expected = R"({ | 
 |     'color': 'white', | 
 |     'direction': { | 
 |       'altitude': 89.9, | 
 |       'azimuth': 57.2957795 | 
 |     }, | 
 |     'iso': 200, | 
 |     'light': true | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *package.GetValuesAsJson()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, AddSchemaFromJson_AddMore) { | 
 |   auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}"); | 
 |   ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr)); | 
 |   EXPECT_EQ(5, GetTypes(*package_).GetProps().size()); | 
 |   EXPECT_EQ(5, GetValues(*package_).size()); | 
 |   auto expected = R"({ | 
 |     'brightness': { | 
 |       'enum': ['low', 'medium', 'high'], | 
 |       'type': 'string' | 
 |     }, | 
 |     'color': { | 
 |       'type': 'string' | 
 |     }, | 
 |     'direction': { | 
 |       'additionalProperties': false, | 
 |       'properties': { | 
 |         'altitude': { | 
 |           'maximum': 90.0, | 
 |           'type': 'number' | 
 |         }, | 
 |         'azimuth': { | 
 |           'type': 'number' | 
 |         } | 
 |       }, | 
 |       'type': 'object', | 
 |       'required': [ 'azimuth' ] | 
 |     }, | 
 |     'iso': { | 
 |       'enum': [50, 100, 200, 400], | 
 |       'type': 'integer' | 
 |     }, | 
 |     'light': { | 
 |       'type': 'boolean' | 
 |     } | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *GetTypes(*package_).ToJson(true, false)); | 
 |  | 
 |   expected = R"({ | 
 |     'brightness': '', | 
 |     'color': 'white', | 
 |     'direction': { | 
 |       'altitude': 89.9, | 
 |       'azimuth': 57.2957795 | 
 |     }, | 
 |     'iso': 200, | 
 |     'light': true | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, AddValuesFromJson_AddMore) { | 
 |   auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}"); | 
 |   ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr)); | 
 |   dict = CreateDictionaryValue("{'brightness':'medium'}"); | 
 |   ASSERT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr)); | 
 |   EXPECT_EQ(5, GetValues(*package_).size()); | 
 |   auto expected = R"({ | 
 |     'brightness': 'medium', | 
 |     'color': 'white', | 
 |     'direction': { | 
 |       'altitude': 89.9, | 
 |       'azimuth': 57.2957795 | 
 |     }, | 
 |     'iso': 200, | 
 |     'light': true | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, AddSchemaFromJson_Error_Redefined) { | 
 |   auto dict = CreateDictionaryValue("{'color':['white', 'blue', 'red']}"); | 
 |   ErrorPtr error; | 
 |   EXPECT_FALSE(package_->AddSchemaFromJson(dict.get(), &error)); | 
 |   EXPECT_EQ(errors::state::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::state::kPropertyRedefinition, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, AddValuesFromJson_Error_Undefined) { | 
 |   auto dict = CreateDictionaryValue("{'brightness':'medium'}"); | 
 |   ErrorPtr error; | 
 |   EXPECT_FALSE(package_->AddValuesFromJson(dict.get(), &error)); | 
 |   EXPECT_EQ(errors::state::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, GetPropertyValue) { | 
 |   EXPECT_JSON_EQ("'white'", *package_->GetPropertyValue("color", nullptr)); | 
 |   EXPECT_JSON_EQ("true", *package_->GetPropertyValue("light", nullptr)); | 
 |   EXPECT_JSON_EQ("200", *package_->GetPropertyValue("iso", nullptr)); | 
 |   EXPECT_JSON_EQ("{'altitude': 89.9, 'azimuth': 57.2957795}", | 
 |                  *package_->GetPropertyValue("direction", nullptr)); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, GetPropertyValue_Unknown) { | 
 |   ErrorPtr error; | 
 |   EXPECT_EQ(nullptr, package_->GetPropertyValue("volume", &error)); | 
 |   EXPECT_EQ(errors::state::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Simple) { | 
 |   EXPECT_TRUE( | 
 |       package_->SetPropertyValue("color", base::StringValue{"blue"}, nullptr)); | 
 |   EXPECT_JSON_EQ("'blue'", *package_->GetPropertyValue("color", nullptr)); | 
 |   EXPECT_TRUE(package_->SetPropertyValue("light", base::FundamentalValue{false}, | 
 |                                          nullptr)); | 
 |   bool light = false; | 
 |   ASSERT_TRUE( | 
 |       package_->GetPropertyValue("light", nullptr)->GetAsBoolean(&light)); | 
 |   EXPECT_FALSE(light); | 
 |   EXPECT_TRUE( | 
 |       package_->SetPropertyValue("iso", base::FundamentalValue{400}, nullptr)); | 
 |   EXPECT_JSON_EQ("400", *package_->GetPropertyValue("iso", nullptr)); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Object) { | 
 |   EXPECT_TRUE(package_->SetPropertyValue( | 
 |       "direction", | 
 |       *CreateDictionaryValue("{'altitude': 45.0, 'azimuth': 15.0}"), nullptr)); | 
 |  | 
 |   auto expected = R"({ | 
 |     'color': 'white', | 
 |     'direction': { | 
 |       'altitude': 45.0, | 
 |       'azimuth': 15.0 | 
 |     }, | 
 |     'iso': 200, | 
 |     'light': true | 
 |   })"; | 
 |   EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_TypeMismatch) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE( | 
 |       package_->SetPropertyValue("color", base::FundamentalValue{12}, &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode()); | 
 |   error.reset(); | 
 |  | 
 |   ASSERT_FALSE( | 
 |       package_->SetPropertyValue("iso", base::FundamentalValue{false}, &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_OutOfRange) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE( | 
 |       package_->SetPropertyValue("iso", base::FundamentalValue{150}, &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kOutOfRange, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_TypeMismatch) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE(package_->SetPropertyValue( | 
 |       "direction", | 
 |       *CreateDictionaryValue("{'altitude': 45.0, 'azimuth': '15'}"), &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode()); | 
 |   const Error* inner = error->GetInnerError(); | 
 |   EXPECT_EQ(errors::commands::kDomain, inner->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kTypeMismatch, inner->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_OutOfRange) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE(package_->SetPropertyValue( | 
 |       "direction", | 
 |       *CreateDictionaryValue("{'altitude': 100.0, 'azimuth': 290.0}"), &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode()); | 
 |   const Error* inner = error->GetInnerError(); | 
 |   EXPECT_EQ(errors::commands::kDomain, inner->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kOutOfRange, inner->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_UnknownProperty) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE(package_->SetPropertyValue( | 
 |       "direction", *CreateDictionaryValue( | 
 |                        "{'altitude': 10.0, 'azimuth': 20.0, 'spin': 30.0}"), | 
 |       &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kUnknownProperty, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Object_OptionalProperty) { | 
 |   EXPECT_JSON_EQ("{'altitude': 89.9, 'azimuth': 57.2957795}", | 
 |                  *package_->GetProperty("direction")->ToJson()); | 
 |   ASSERT_TRUE(package_->SetPropertyValue( | 
 |       "direction", *CreateDictionaryValue("{'azimuth': 10.0}"), nullptr)); | 
 |   EXPECT_JSON_EQ("{'azimuth': 10.0}", | 
 |                  *package_->GetProperty("direction")->ToJson()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_MissingProperty) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE(package_->SetPropertyValue( | 
 |       "direction", *CreateDictionaryValue("{'altitude': 10.0}"), &error)); | 
 |   EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode()); | 
 | } | 
 |  | 
 | TEST_F(StatePackageTest, SetPropertyValue_Error_Unknown) { | 
 |   ErrorPtr error; | 
 |   ASSERT_FALSE(package_->SetPropertyValue("volume", base::FundamentalValue{100}, | 
 |                                           &error)); | 
 |   EXPECT_EQ(errors::state::kDomain, error->GetDomain()); | 
 |   EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); | 
 | } | 
 |  | 
 | }  // namespace weave |