|  | // 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/base_api_handler.h" | 
|  |  | 
|  | #include <base/strings/string_number_conversions.h> | 
|  | #include <base/time/default_clock.h> | 
|  | #include <base/values.h> | 
|  | #include <gtest/gtest.h> | 
|  | #include <weave/provider/test/fake_task_runner.h> | 
|  | #include <weave/provider/test/mock_config_store.h> | 
|  | #include <weave/provider/test/mock_http_client.h> | 
|  | #include <weave/test/mock_device.h> | 
|  | #include <weave/test/unittest_utils.h> | 
|  |  | 
|  | #include "src/component_manager_impl.h" | 
|  | #include "src/config.h" | 
|  | #include "src/device_registration_info.h" | 
|  |  | 
|  | using testing::_; | 
|  | using testing::AnyOf; | 
|  | using testing::Eq; | 
|  | using testing::Invoke; | 
|  | using testing::Return; | 
|  | using testing::ReturnRef; | 
|  | using testing::StrictMock; | 
|  |  | 
|  | namespace weave { | 
|  |  | 
|  | class BaseApiHandlerTest : public ::testing::Test { | 
|  | protected: | 
|  | void SetUp() override { | 
|  | EXPECT_CALL(device_, AddTraitDefinitionsFromJson(_)) | 
|  | .WillRepeatedly(Invoke([this](const std::string& json) { | 
|  | EXPECT_TRUE(component_manager_.LoadTraits(json, nullptr)); | 
|  | })); | 
|  | EXPECT_CALL(device_, SetStateProperties(_, _, _)) | 
|  | .WillRepeatedly( | 
|  | Invoke(&component_manager_, &ComponentManager::SetStateProperties)); | 
|  | EXPECT_CALL(device_, SetStateProperty(_, _, _, _)) | 
|  | .WillRepeatedly( | 
|  | Invoke(&component_manager_, &ComponentManager::SetStateProperty)); | 
|  | EXPECT_CALL(device_, AddComponent(_, _, _)) | 
|  | .WillRepeatedly(Invoke([this](const std::string& name, | 
|  | const std::vector<std::string>& traits, | 
|  | ErrorPtr* error) { | 
|  | return component_manager_.AddComponent("", name, traits, error); | 
|  | })); | 
|  |  | 
|  | EXPECT_CALL(device_, | 
|  | AddCommandHandler(_, | 
|  | AnyOf("device.setConfig", "privet.setConfig"), | 
|  | _)) | 
|  | .WillRepeatedly( | 
|  | Invoke(&component_manager_, &ComponentManager::AddCommandHandler)); | 
|  |  | 
|  | dev_reg_.reset(new DeviceRegistrationInfo(&config_, &component_manager_, | 
|  | nullptr, &http_client_, nullptr, | 
|  | nullptr)); | 
|  |  | 
|  | EXPECT_CALL(device_, GetSettings()) | 
|  | .WillRepeatedly(ReturnRef(dev_reg_->GetSettings())); | 
|  |  | 
|  | handler_.reset(new BaseApiHandler{dev_reg_.get(), &device_}); | 
|  | } | 
|  |  | 
|  | void AddCommand(const std::string& command) { | 
|  | std::string id; | 
|  | auto command_instance = component_manager_.ParseCommandInstance( | 
|  | *test::CreateDictionaryValue(command.c_str()), Command::Origin::kLocal, | 
|  | UserRole::kOwner, &id, nullptr); | 
|  | ASSERT_NE(nullptr, command_instance.get()); | 
|  | component_manager_.AddCommand(std::move(command_instance)); | 
|  | EXPECT_EQ(Command::State::kDone, | 
|  | component_manager_.FindCommand(id)->GetState()); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> GetDeviceState() { | 
|  | std::unique_ptr<base::DictionaryValue> state; | 
|  | std::string path = component_manager_.FindComponentWithTrait("device"); | 
|  | EXPECT_FALSE(path.empty()); | 
|  | const auto* component = component_manager_.FindComponent(path, nullptr); | 
|  | CHECK(component); | 
|  | const base::DictionaryValue* base_state = nullptr; | 
|  | if (component->GetDictionary("state.device", &base_state)) | 
|  | state = base_state->CreateDeepCopy(); | 
|  | else | 
|  | state.reset(new base::DictionaryValue); | 
|  | return state; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> GetPrivetState() { | 
|  | std::unique_ptr<base::DictionaryValue> state; | 
|  | std::string path = component_manager_.FindComponentWithTrait("privet"); | 
|  | EXPECT_FALSE(path.empty()); | 
|  | const auto* component = component_manager_.FindComponent(path, nullptr); | 
|  | CHECK(component); | 
|  | const base::DictionaryValue* base_state = nullptr; | 
|  | if (component->GetDictionary("state.privet", &base_state)) | 
|  | state = base_state->CreateDeepCopy(); | 
|  | else | 
|  | state.reset(new base::DictionaryValue); | 
|  | return state; | 
|  | } | 
|  |  | 
|  | provider::test::MockConfigStore config_store_; | 
|  | Config config_{&config_store_}; | 
|  | StrictMock<provider::test::MockHttpClient> http_client_; | 
|  | std::unique_ptr<DeviceRegistrationInfo> dev_reg_; | 
|  | StrictMock<provider::test::FakeTaskRunner> task_runner_; | 
|  | ComponentManagerImpl component_manager_{&task_runner_}; | 
|  | std::unique_ptr<BaseApiHandler> handler_; | 
|  | StrictMock<test::MockDevice> device_; | 
|  | }; | 
|  |  | 
|  | TEST_F(BaseApiHandlerTest, Initialization) { | 
|  | const base::DictionaryValue* trait = nullptr; | 
|  | ASSERT_TRUE(component_manager_.GetTraits().GetDictionary("device", &trait)); | 
|  |  | 
|  | auto expected1 = R"({ | 
|  | "commands": { | 
|  | "setConfig": { | 
|  | "minimalRole": "user", | 
|  | "parameters": { | 
|  | "name": { | 
|  | "type": "string" | 
|  | }, | 
|  | "description": { | 
|  | "type": "string" | 
|  | }, | 
|  | "location": { | 
|  | "type": "string" | 
|  | } | 
|  | } | 
|  | } | 
|  | }, | 
|  | "state": { | 
|  | "name": { | 
|  | "isRequired": true, | 
|  | "type": "string" | 
|  | }, | 
|  | "description": { | 
|  | "isRequired": true, | 
|  | "type": "string" | 
|  | }, | 
|  | "location": { | 
|  | "type": "string" | 
|  | }, | 
|  | "hardwareId": { | 
|  | "isRequired": true, | 
|  | "type": "string" | 
|  | }, | 
|  | "serialNumber": { | 
|  | "isRequired": true, | 
|  | "type": "string" | 
|  | }, | 
|  | "firmwareVersion": { | 
|  | "isRequired": true, | 
|  | "type": "string" | 
|  | } | 
|  | } | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected1, *trait); | 
|  |  | 
|  | ASSERT_TRUE(component_manager_.GetTraits().GetDictionary("privet", &trait)); | 
|  |  | 
|  | auto expected2 = R"({ | 
|  | "commands": { | 
|  | "setConfig": { | 
|  | "minimalRole": "manager", | 
|  | "parameters": { | 
|  | "isLocalAccessEnabled": { | 
|  | "type": "boolean" | 
|  | }, | 
|  | "maxRoleForAnonymousAccess": { | 
|  | "type": "string", | 
|  | "enum": [ "none", "viewer", "user", "manager" ] | 
|  | } | 
|  | } | 
|  | } | 
|  | }, | 
|  | "state": { | 
|  | "apiVersion": { | 
|  | "isRequired": true, | 
|  | "type": "string" | 
|  | }, | 
|  | "isLocalAccessEnabled": { | 
|  | "isRequired": true, | 
|  | "type": "boolean" | 
|  | }, | 
|  | "maxRoleForAnonymousAccess": { | 
|  | "isRequired": true, | 
|  | "type": "string", | 
|  | "enum": [ "none", "viewer", "user", "manager" ] | 
|  | } | 
|  | } | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected2, *trait); | 
|  | } | 
|  |  | 
|  | TEST_F(BaseApiHandlerTest, PrivetSetConfig) { | 
|  | const Settings& settings = dev_reg_->GetSettings(); | 
|  |  | 
|  | AddCommand(R"({ | 
|  | 'name' : 'privet.setConfig', | 
|  | 'component': 'device', | 
|  | 'parameters': { | 
|  | 'isLocalAccessEnabled': false, | 
|  | 'maxRoleForAnonymousAccess': 'none' | 
|  | } | 
|  | })"); | 
|  | EXPECT_EQ(AuthScope::kNone, settings.local_anonymous_access_role); | 
|  | EXPECT_FALSE(settings.local_access_enabled); | 
|  |  | 
|  | auto expected = R"({ | 
|  | 'apiVersion': '3', | 
|  | 'maxRoleForAnonymousAccess': 'none', | 
|  | 'isLocalAccessEnabled': false | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected, *GetPrivetState()); | 
|  |  | 
|  | AddCommand(R"({ | 
|  | 'name' : 'privet.setConfig', | 
|  | 'component': 'device', | 
|  | 'parameters': { | 
|  | 'maxRoleForAnonymousAccess': 'user', | 
|  | 'isLocalAccessEnabled': true | 
|  | } | 
|  | })"); | 
|  | EXPECT_EQ(AuthScope::kUser, settings.local_anonymous_access_role); | 
|  | EXPECT_TRUE(settings.local_access_enabled); | 
|  | expected = R"({ | 
|  | 'apiVersion': '3', | 
|  | 'maxRoleForAnonymousAccess': 'user', | 
|  | 'isLocalAccessEnabled': true | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected, *GetPrivetState()); | 
|  |  | 
|  | { | 
|  | Config::Transaction change{dev_reg_->GetMutableConfig()}; | 
|  | change.set_local_anonymous_access_role(AuthScope::kViewer); | 
|  | } | 
|  | expected = R"({ | 
|  | 'apiVersion': '3', | 
|  | 'maxRoleForAnonymousAccess': 'viewer', | 
|  | 'isLocalAccessEnabled': true | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected, *GetPrivetState()); | 
|  | } | 
|  |  | 
|  | TEST_F(BaseApiHandlerTest, DeviceSetConfig) { | 
|  | AddCommand(R"({ | 
|  | 'name' : 'device.setConfig', | 
|  | 'component': 'device', | 
|  | 'parameters': { | 
|  | 'name': 'testName', | 
|  | 'description': 'testDescription', | 
|  | 'location': 'testLocation' | 
|  | } | 
|  | })"); | 
|  |  | 
|  | const Settings& config = dev_reg_->GetSettings(); | 
|  | EXPECT_EQ("testName", config.name); | 
|  | EXPECT_EQ("testDescription", config.description); | 
|  | EXPECT_EQ("testLocation", config.location); | 
|  | auto expected = R"({ | 
|  | 'name': 'testName', | 
|  | 'description': 'testDescription', | 
|  | 'location': 'testLocation', | 
|  | 'hardwareId': 'TEST_DEVICE_ID', | 
|  | 'serialNumber': 'TEST_SERIAL_NUMBER', | 
|  | 'firmwareVersion': 'TEST_FIRMWARE' | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected, *GetDeviceState()); | 
|  |  | 
|  | AddCommand(R"({ | 
|  | 'name' : 'device.setConfig', | 
|  | 'component': 'device', | 
|  | 'parameters': { | 
|  | 'location': 'newLocation' | 
|  | } | 
|  | })"); | 
|  |  | 
|  | EXPECT_EQ("testName", config.name); | 
|  | EXPECT_EQ("testDescription", config.description); | 
|  | EXPECT_EQ("newLocation", config.location); | 
|  |  | 
|  | expected = R"({ | 
|  | 'name': 'testName', | 
|  | 'description': 'testDescription', | 
|  | 'location': 'newLocation', | 
|  | 'hardwareId': 'TEST_DEVICE_ID', | 
|  | 'serialNumber': 'TEST_SERIAL_NUMBER', | 
|  | 'firmwareVersion': 'TEST_FIRMWARE' | 
|  | })"; | 
|  | EXPECT_JSON_EQ(expected, *GetDeviceState()); | 
|  | } | 
|  |  | 
|  | }  // namespace weave |