Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 1 | // Copyright 2014 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Alex Deymo | f6cbe32 | 2014-11-10 19:55:35 -0800 | [diff] [blame] | 5 | #include "buffet/states/state_package.h" |
| 6 | |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 7 | #include <memory> |
| 8 | #include <string> |
| 9 | |
| 10 | #include <base/values.h> |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 11 | #include <chromeos/variant_dictionary.h> |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 12 | #include <gtest/gtest.h> |
| 13 | |
| 14 | #include "buffet/commands/schema_constants.h" |
| 15 | #include "buffet/commands/unittest_utils.h" |
| 16 | #include "buffet/states/error_codes.h" |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 17 | |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 18 | namespace buffet { |
| 19 | |
Vitaly Buka | 32005de | 2015-05-01 12:33:31 -0700 | [diff] [blame] | 20 | using unittests::CreateDictionaryValue; |
Vitaly Buka | 32005de | 2015-05-01 12:33:31 -0700 | [diff] [blame] | 21 | |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 22 | class StatePackageTestHelper { |
| 23 | public: |
| 24 | // Returns the state property definitions (types/constraints/etc). |
| 25 | static const ObjectSchema& GetTypes(const StatePackage& package) { |
| 26 | return package.types_; |
| 27 | } |
| 28 | // Returns the all state property values in this package. |
| 29 | static const native_types::Object& GetValues(const StatePackage& package) { |
| 30 | return package.values_; |
| 31 | } |
| 32 | }; |
| 33 | |
| 34 | namespace { |
| 35 | std::unique_ptr<base::DictionaryValue> GetTestSchema() { |
| 36 | return CreateDictionaryValue(R"({ |
| 37 | 'light': 'boolean', |
| 38 | 'color': 'string', |
| 39 | 'direction':{'properties':{'azimuth':'number','altitude':{'maximum':90.0}}}, |
| 40 | 'iso': [50, 100, 200, 400] |
| 41 | })"); |
| 42 | } |
| 43 | |
| 44 | std::unique_ptr<base::DictionaryValue> GetTestValues() { |
| 45 | return CreateDictionaryValue(R"({ |
| 46 | 'light': true, |
| 47 | 'color': 'white', |
| 48 | 'direction': {'azimuth':57.2957795, 'altitude':89.9}, |
| 49 | 'iso': 200 |
| 50 | })"); |
| 51 | } |
| 52 | |
| 53 | inline const ObjectSchema& GetTypes(const StatePackage& package) { |
| 54 | return StatePackageTestHelper::GetTypes(package); |
| 55 | } |
| 56 | // Returns the all state property values in this package. |
| 57 | inline const native_types::Object& GetValues(const StatePackage& package) { |
| 58 | return StatePackageTestHelper::GetValues(package); |
| 59 | } |
| 60 | |
| 61 | } // anonymous namespace |
| 62 | |
| 63 | class StatePackageTest : public ::testing::Test { |
| 64 | public: |
| 65 | void SetUp() override { |
| 66 | package_.reset(new StatePackage("test")); |
| 67 | ASSERT_TRUE(package_->AddSchemaFromJson(GetTestSchema().get(), nullptr)); |
| 68 | ASSERT_TRUE(package_->AddValuesFromJson(GetTestValues().get(), nullptr)); |
| 69 | } |
| 70 | void TearDown() override { |
| 71 | package_.reset(); |
| 72 | } |
| 73 | std::unique_ptr<StatePackage> package_; |
| 74 | }; |
| 75 | |
| 76 | TEST(StatePackage, Empty) { |
| 77 | StatePackage package("test"); |
| 78 | EXPECT_EQ("test", package.GetName()); |
| 79 | EXPECT_TRUE(GetTypes(package).GetProps().empty()); |
| 80 | EXPECT_TRUE(GetValues(package).empty()); |
| 81 | } |
| 82 | |
| 83 | TEST(StatePackage, AddSchemaFromJson_OnEmpty) { |
| 84 | StatePackage package("test"); |
| 85 | ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr)); |
| 86 | EXPECT_EQ(4, GetTypes(package).GetProps().size()); |
| 87 | EXPECT_EQ(4, GetValues(package).size()); |
Vitaly Buka | 7c82d29 | 2015-05-03 18:08:12 -0700 | [diff] [blame] | 88 | |
| 89 | auto expected = R"({ |
| 90 | 'color': { |
| 91 | 'type': 'string' |
| 92 | }, |
| 93 | 'direction': { |
| 94 | 'additionalProperties': false, |
| 95 | 'properties': { |
| 96 | 'altitude': { |
| 97 | 'maximum': 90.0, |
| 98 | 'type': 'number' |
| 99 | }, |
| 100 | 'azimuth': { |
| 101 | 'type': 'number' |
| 102 | } |
| 103 | }, |
| 104 | 'type': 'object' |
| 105 | }, |
| 106 | 'iso': { |
| 107 | 'enum': [50, 100, 200, 400], |
| 108 | 'type': 'integer' |
| 109 | }, |
| 110 | 'light': { |
| 111 | 'type': 'boolean' |
| 112 | } |
| 113 | })"; |
| 114 | EXPECT_JSON_EQ(expected, *GetTypes(package).ToJson(true, nullptr)); |
| 115 | |
| 116 | expected = R"({ |
| 117 | 'color': '', |
| 118 | 'direction': {}, |
| 119 | 'iso': 0, |
| 120 | 'light': false |
| 121 | })"; |
| 122 | EXPECT_JSON_EQ(expected, *package.GetValuesAsJson(nullptr)); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 123 | } |
| 124 | |
| 125 | TEST(StatePackage, AddValuesFromJson_OnEmpty) { |
| 126 | StatePackage package("test"); |
| 127 | ASSERT_TRUE(package.AddSchemaFromJson(GetTestSchema().get(), nullptr)); |
| 128 | ASSERT_TRUE(package.AddValuesFromJson(GetTestValues().get(), nullptr)); |
| 129 | EXPECT_EQ(4, GetValues(package).size()); |
Vitaly Buka | 7c82d29 | 2015-05-03 18:08:12 -0700 | [diff] [blame] | 130 | auto expected = R"({ |
| 131 | 'color': 'white', |
| 132 | 'direction': { |
| 133 | 'altitude': 89.9, |
| 134 | 'azimuth': 57.2957795 |
| 135 | }, |
| 136 | 'iso': 200, |
| 137 | 'light': true |
| 138 | })"; |
| 139 | EXPECT_JSON_EQ(expected, *package.GetValuesAsJson(nullptr)); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 140 | } |
| 141 | |
| 142 | TEST_F(StatePackageTest, AddSchemaFromJson_AddMore) { |
| 143 | auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}"); |
| 144 | ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr)); |
| 145 | EXPECT_EQ(5, GetTypes(*package_).GetProps().size()); |
| 146 | EXPECT_EQ(5, GetValues(*package_).size()); |
Vitaly Buka | 7c82d29 | 2015-05-03 18:08:12 -0700 | [diff] [blame] | 147 | auto expected = R"({ |
| 148 | 'brightness': { |
| 149 | 'enum': ['low', 'medium', 'high'], |
| 150 | 'type': 'string' |
| 151 | }, |
| 152 | 'color': { |
| 153 | 'type': 'string' |
| 154 | }, |
| 155 | 'direction': { |
| 156 | 'additionalProperties': false, |
| 157 | 'properties': { |
| 158 | 'altitude': { |
| 159 | 'maximum': 90.0, |
| 160 | 'type': 'number' |
| 161 | }, |
| 162 | 'azimuth': { |
| 163 | 'type': 'number' |
| 164 | } |
| 165 | }, |
| 166 | 'type': 'object' |
| 167 | }, |
| 168 | 'iso': { |
| 169 | 'enum': [50, 100, 200, 400], |
| 170 | 'type': 'integer' |
| 171 | }, |
| 172 | 'light': { |
| 173 | 'type': 'boolean' |
| 174 | } |
| 175 | })"; |
| 176 | EXPECT_JSON_EQ(expected, *GetTypes(*package_).ToJson(true, nullptr)); |
| 177 | |
| 178 | expected = R"({ |
| 179 | 'brightness': '', |
| 180 | 'color': 'white', |
| 181 | 'direction': { |
| 182 | 'altitude': 89.9, |
| 183 | 'azimuth': 57.2957795 |
| 184 | }, |
| 185 | 'iso': 200, |
| 186 | 'light': true |
| 187 | })"; |
| 188 | EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson(nullptr)); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 189 | } |
| 190 | |
| 191 | TEST_F(StatePackageTest, AddValuesFromJson_AddMore) { |
| 192 | auto dict = CreateDictionaryValue("{'brightness':['low', 'medium', 'high']}"); |
| 193 | ASSERT_TRUE(package_->AddSchemaFromJson(dict.get(), nullptr)); |
| 194 | dict = CreateDictionaryValue("{'brightness':'medium'}"); |
| 195 | ASSERT_TRUE(package_->AddValuesFromJson(dict.get(), nullptr)); |
| 196 | EXPECT_EQ(5, GetValues(*package_).size()); |
Vitaly Buka | 7c82d29 | 2015-05-03 18:08:12 -0700 | [diff] [blame] | 197 | auto expected = R"({ |
| 198 | 'brightness': 'medium', |
| 199 | 'color': 'white', |
| 200 | 'direction': { |
| 201 | 'altitude': 89.9, |
| 202 | 'azimuth': 57.2957795 |
| 203 | }, |
| 204 | 'iso': 200, |
| 205 | 'light': true |
| 206 | })"; |
| 207 | EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson(nullptr)); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 208 | } |
| 209 | |
| 210 | TEST_F(StatePackageTest, AddSchemaFromJson_Error_Redefined) { |
| 211 | auto dict = CreateDictionaryValue("{'color':['white', 'blue', 'red']}"); |
| 212 | chromeos::ErrorPtr error; |
| 213 | EXPECT_FALSE(package_->AddSchemaFromJson(dict.get(), &error)); |
| 214 | EXPECT_EQ(errors::state::kDomain, error->GetDomain()); |
| 215 | EXPECT_EQ(errors::state::kPropertyRedefinition, error->GetCode()); |
| 216 | EXPECT_EQ("State property 'test.color' is already defined", |
| 217 | error->GetMessage()); |
| 218 | } |
| 219 | |
| 220 | TEST_F(StatePackageTest, AddValuesFromJson_Error_Undefined) { |
| 221 | auto dict = CreateDictionaryValue("{'brightness':'medium'}"); |
| 222 | chromeos::ErrorPtr error; |
| 223 | EXPECT_FALSE(package_->AddValuesFromJson(dict.get(), &error)); |
| 224 | EXPECT_EQ(errors::state::kDomain, error->GetDomain()); |
| 225 | EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); |
| 226 | EXPECT_EQ("State property 'test.brightness' is not defined", |
| 227 | error->GetMessage()); |
| 228 | } |
| 229 | |
| 230 | TEST_F(StatePackageTest, GetPropertyValue) { |
| 231 | chromeos::Any value = package_->GetPropertyValue("color", nullptr); |
| 232 | EXPECT_EQ("white", value.TryGet<std::string>()); |
| 233 | |
| 234 | value = package_->GetPropertyValue("light", nullptr); |
| 235 | EXPECT_TRUE(value.TryGet<bool>()); |
| 236 | |
| 237 | value = package_->GetPropertyValue("iso", nullptr); |
| 238 | EXPECT_EQ(200, value.TryGet<int>()); |
| 239 | |
| 240 | value = package_->GetPropertyValue("direction", nullptr); |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 241 | auto direction = value.TryGet<chromeos::VariantDictionary>(); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 242 | ASSERT_FALSE(direction.empty()); |
| 243 | EXPECT_DOUBLE_EQ(89.9, direction["altitude"].TryGet<double>()); |
| 244 | EXPECT_DOUBLE_EQ(57.2957795, direction["azimuth"].TryGet<double>()); |
| 245 | } |
| 246 | |
| 247 | TEST_F(StatePackageTest, GetPropertyValue_Unknown) { |
| 248 | chromeos::ErrorPtr error; |
| 249 | chromeos::Any value = package_->GetPropertyValue("volume", &error); |
| 250 | EXPECT_TRUE(value.IsEmpty()); |
| 251 | EXPECT_EQ(errors::state::kDomain, error->GetDomain()); |
| 252 | EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); |
| 253 | EXPECT_EQ("State property 'test.volume' is not defined", |
| 254 | error->GetMessage()); |
| 255 | } |
| 256 | |
| 257 | TEST_F(StatePackageTest, SetPropertyValue_Simple) { |
| 258 | EXPECT_TRUE(package_->SetPropertyValue("color", std::string{"blue"}, |
| 259 | nullptr)); |
| 260 | chromeos::Any value = package_->GetPropertyValue("color", nullptr); |
| 261 | EXPECT_EQ("blue", value.TryGet<std::string>()); |
| 262 | |
| 263 | EXPECT_TRUE(package_->SetPropertyValue("light", bool{false}, nullptr)); |
| 264 | value = package_->GetPropertyValue("light", nullptr); |
| 265 | EXPECT_FALSE(value.TryGet<bool>()); |
| 266 | |
| 267 | EXPECT_TRUE(package_->SetPropertyValue("iso", int{400}, nullptr)); |
| 268 | value = package_->GetPropertyValue("iso", nullptr); |
| 269 | EXPECT_EQ(400, value.TryGet<int>()); |
| 270 | } |
| 271 | |
| 272 | TEST_F(StatePackageTest, SetPropertyValue_Object) { |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 273 | chromeos::VariantDictionary direction{ |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 274 | {"altitude", double{45.0}}, |
| 275 | {"azimuth", double{15.0}}, |
| 276 | }; |
| 277 | EXPECT_TRUE(package_->SetPropertyValue("direction", direction, nullptr)); |
Vitaly Buka | 7c82d29 | 2015-05-03 18:08:12 -0700 | [diff] [blame] | 278 | |
| 279 | auto expected = R"({ |
| 280 | 'color': 'white', |
| 281 | 'direction': { |
| 282 | 'altitude': 45.0, |
| 283 | 'azimuth': 15.0 |
| 284 | }, |
| 285 | 'iso': 200, |
| 286 | 'light': true |
| 287 | })"; |
| 288 | EXPECT_JSON_EQ(expected, *package_->GetValuesAsJson(nullptr)); |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 289 | } |
| 290 | |
| 291 | TEST_F(StatePackageTest, SetPropertyValue_Error_TypeMismatch) { |
| 292 | chromeos::ErrorPtr error; |
| 293 | ASSERT_FALSE(package_->SetPropertyValue("color", int{12}, &error)); |
| 294 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 295 | EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode()); |
| 296 | EXPECT_EQ("Unable to convert value to type 'string'", error->GetMessage()); |
| 297 | error.reset(); |
| 298 | |
| 299 | ASSERT_FALSE(package_->SetPropertyValue("iso", bool{false}, &error)); |
| 300 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 301 | EXPECT_EQ(errors::commands::kTypeMismatch, error->GetCode()); |
| 302 | EXPECT_EQ("Unable to convert value to type 'integer'", error->GetMessage()); |
| 303 | } |
| 304 | |
| 305 | TEST_F(StatePackageTest, SetPropertyValue_Error_OutOfRange) { |
| 306 | chromeos::ErrorPtr error; |
| 307 | ASSERT_FALSE(package_->SetPropertyValue("iso", int{150}, &error)); |
| 308 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 309 | EXPECT_EQ(errors::commands::kOutOfRange, error->GetCode()); |
| 310 | EXPECT_EQ("Value 150 is invalid. Expected one of [50,100,200,400]", |
| 311 | error->GetMessage()); |
| 312 | } |
| 313 | |
| 314 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_TypeMismatch) { |
| 315 | chromeos::ErrorPtr error; |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 316 | chromeos::VariantDictionary direction{ |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 317 | {"altitude", double{45.0}}, |
| 318 | {"azimuth", int{15}}, |
| 319 | }; |
| 320 | ASSERT_FALSE(package_->SetPropertyValue("direction", direction, &error)); |
| 321 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 322 | EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode()); |
| 323 | EXPECT_EQ("Invalid value for property 'azimuth'", error->GetMessage()); |
| 324 | const chromeos::Error* inner = error->GetInnerError(); |
| 325 | EXPECT_EQ(errors::commands::kDomain, inner->GetDomain()); |
| 326 | EXPECT_EQ(errors::commands::kTypeMismatch, inner->GetCode()); |
| 327 | EXPECT_EQ("Unable to convert value to type 'number'", inner->GetMessage()); |
| 328 | } |
| 329 | |
| 330 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_OutOfRange) { |
| 331 | chromeos::ErrorPtr error; |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 332 | chromeos::VariantDictionary direction{ |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 333 | {"altitude", double{100.0}}, |
| 334 | {"azimuth", double{290.0}}, |
| 335 | }; |
| 336 | ASSERT_FALSE(package_->SetPropertyValue("direction", direction, &error)); |
| 337 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 338 | EXPECT_EQ(errors::commands::kInvalidPropValue, error->GetCode()); |
| 339 | EXPECT_EQ("Invalid value for property 'altitude'", error->GetMessage()); |
| 340 | const chromeos::Error* inner = error->GetInnerError(); |
| 341 | EXPECT_EQ(errors::commands::kDomain, inner->GetDomain()); |
| 342 | EXPECT_EQ(errors::commands::kOutOfRange, inner->GetCode()); |
| 343 | EXPECT_EQ("Value 100 is out of range. It must not be greater than 90", |
| 344 | inner->GetMessage()); |
| 345 | } |
| 346 | |
| 347 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_UnknownProperty) { |
| 348 | chromeos::ErrorPtr error; |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 349 | chromeos::VariantDictionary direction{ |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 350 | {"altitude", double{10.0}}, |
| 351 | {"azimuth", double{20.0}}, |
| 352 | {"spin", double{30.0}}, |
| 353 | }; |
| 354 | ASSERT_FALSE(package_->SetPropertyValue("direction", direction, &error)); |
| 355 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 356 | EXPECT_EQ(errors::commands::kUnknownProperty, error->GetCode()); |
| 357 | EXPECT_EQ("Unrecognized property 'spin'", error->GetMessage()); |
| 358 | } |
| 359 | |
| 360 | TEST_F(StatePackageTest, SetPropertyValue_Error_Object_MissingProperty) { |
| 361 | chromeos::ErrorPtr error; |
Alex Vakulenko | 576c979 | 2014-09-22 16:49:45 -0700 | [diff] [blame] | 362 | chromeos::VariantDictionary direction{ |
Alex Vakulenko | 07216fe | 2014-09-19 15:31:09 -0700 | [diff] [blame] | 363 | {"altitude", double{10.0}}, |
| 364 | }; |
| 365 | ASSERT_FALSE(package_->SetPropertyValue("direction", direction, &error)); |
| 366 | EXPECT_EQ(errors::commands::kDomain, error->GetDomain()); |
| 367 | EXPECT_EQ(errors::commands::kPropertyMissing, error->GetCode()); |
| 368 | EXPECT_EQ("Required parameter missing: azimuth", error->GetMessage()); |
| 369 | } |
| 370 | |
| 371 | TEST_F(StatePackageTest, SetPropertyValue_Error_Unknown) { |
| 372 | chromeos::ErrorPtr error; |
| 373 | ASSERT_FALSE(package_->SetPropertyValue("volume", int{100}, &error)); |
| 374 | EXPECT_EQ(errors::state::kDomain, error->GetDomain()); |
| 375 | EXPECT_EQ(errors::state::kPropertyNotDefined, error->GetCode()); |
| 376 | EXPECT_EQ("State property 'test.volume' is not defined", |
| 377 | error->GetMessage()); |
| 378 | } |
| 379 | |
| 380 | } // namespace buffet |