| // 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/privet/privet_handler.h" | |
| #include <set> | |
| #include <string> | |
| #include <utility> | |
| #include <base/bind.h> | |
| #include <base/json/json_reader.h> | |
| #include <base/json/json_writer.h> | |
| #include <base/strings/string_util.h> | |
| #include <base/values.h> | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include <weave/test/unittest_utils.h> | |
| #include "src/privet/constants.h" | |
| #include "src/privet/mock_delegates.h" | |
| using testing::_; | |
| using testing::DoAll; | |
| using testing::Invoke; | |
| using testing::Return; | |
| using testing::SetArgPointee; | |
| using testing::SaveArg; | |
| using testing::WithArgs; | |
| namespace weave { | |
| namespace privet { | |
| namespace { | |
| void LoadTestJson(const std::string& test_json, | |
| base::DictionaryValue* dictionary) { | |
| std::string json = test_json; | |
| base::ReplaceChars(json, "'", "\"", &json); | |
| int error = 0; | |
| std::string message; | |
| std::unique_ptr<base::Value> value( | |
| base::JSONReader::ReadAndReturnError(json, base::JSON_PARSE_RFC, &error, | |
| &message) | |
| .release()); | |
| EXPECT_TRUE(value.get()) << "\nError: " << message << "\n" << json; | |
| base::DictionaryValue* dictionary_ptr = nullptr; | |
| if (value->GetAsDictionary(&dictionary_ptr)) | |
| dictionary->MergeDictionary(dictionary_ptr); | |
| } | |
| struct CodeWithReason { | |
| CodeWithReason(int code_in, const std::string& reason_in) | |
| : code(code_in), reason(reason_in) {} | |
| int code; | |
| std::string reason; | |
| }; | |
| std::ostream& operator<<(std::ostream& stream, const CodeWithReason& error) { | |
| return stream << "{" << error.code << ", " << error.reason << "}"; | |
| } | |
| bool IsEqualError(const CodeWithReason& expected, | |
| const base::DictionaryValue& dictionary) { | |
| std::string reason; | |
| int code = 0; | |
| return dictionary.GetInteger("error.http_status", &code) && | |
| code == expected.code && dictionary.GetString("error.code", &reason) && | |
| reason == expected.reason; | |
| } | |
| // Some error sections in response JSON objects contained debugging information | |
| // which is of no interest for this test. So, remove the debug info from the JSON | |
| // before running validation logic on it. | |
| std::unique_ptr<base::DictionaryValue> StripDebugErrorDetails( | |
| const std::string& path_to_error_object, | |
| const base::DictionaryValue& value) { | |
| std::unique_ptr<base::DictionaryValue> result{value.DeepCopy()}; | |
| base::DictionaryValue* error_dict = nullptr; | |
| EXPECT_TRUE(result->GetDictionary(path_to_error_object, &error_dict)); | |
| scoped_ptr<base::Value> dummy; | |
| error_dict->RemovePath("error.debugInfo", &dummy); | |
| error_dict->RemovePath("error.message", &dummy); | |
| return result; | |
| } | |
| } // namespace | |
| class PrivetHandlerTest : public testing::Test { | |
| public: | |
| PrivetHandlerTest() {} | |
| protected: | |
| void SetUp() override { | |
| auth_header_ = "Privet anonymous"; | |
| handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, &wifi_)); | |
| } | |
| const base::DictionaryValue& HandleRequest( | |
| const std::string& api, | |
| const base::DictionaryValue* input) { | |
| output_.Clear(); | |
| handler_->HandleRequest(api, auth_header_, input, | |
| base::Bind(&PrivetHandlerTest::HandlerCallback, | |
| base::Unretained(this))); | |
| return output_; | |
| } | |
| const base::DictionaryValue& HandleRequest(const std::string& api, | |
| const std::string& json_input) { | |
| base::DictionaryValue dictionary; | |
| LoadTestJson(json_input, &dictionary); | |
| return HandleRequest(api, &dictionary); | |
| } | |
| void HandleUnknownRequest(const std::string& api) { | |
| output_.Clear(); | |
| base::DictionaryValue dictionary; | |
| handler_->HandleRequest(api, auth_header_, &dictionary, | |
| base::Bind(&PrivetHandlerTest::HandlerNoFound)); | |
| } | |
| const base::DictionaryValue& GetResponse() const { return output_; } | |
| int GetResponseCount() const { return response_count_; } | |
| void SetNoWifiAndGcd() { | |
| handler_.reset(new PrivetHandler(&cloud_, &device_, &security_, nullptr)); | |
| EXPECT_CALL(cloud_, GetCloudId()).WillRepeatedly(Return("")); | |
| EXPECT_CALL(cloud_, GetConnectionState()) | |
| .WillRepeatedly(ReturnRef(gcd_disabled_state_)); | |
| auto set_error = [](const std::string&, const std::string&, | |
| ErrorPtr* error) { | |
| Error::AddTo(error, FROM_HERE, errors::kDomain, "setupUnavailable", ""); | |
| }; | |
| EXPECT_CALL(cloud_, Setup(_, _, _)) | |
| .WillRepeatedly(DoAll(Invoke(set_error), Return(false))); | |
| } | |
| testing::StrictMock<MockCloudDelegate> cloud_; | |
| testing::StrictMock<MockDeviceDelegate> device_; | |
| testing::StrictMock<MockSecurityDelegate> security_; | |
| testing::StrictMock<MockWifiDelegate> wifi_; | |
| std::string auth_header_; | |
| private: | |
| void HandlerCallback(int status, const base::DictionaryValue& output) { | |
| output_.Clear(); | |
| ++response_count_; | |
| output_.MergeDictionary(&output); | |
| if (!output_.HasKey("error")) { | |
| EXPECT_EQ(200, status); | |
| return; | |
| } | |
| EXPECT_NE(200, status); | |
| output_.SetInteger("error.http_status", status); | |
| } | |
| static void HandlerNoFound(int status, const base::DictionaryValue&) { | |
| EXPECT_EQ(404, status); | |
| } | |
| std::unique_ptr<PrivetHandler> handler_; | |
| base::DictionaryValue output_; | |
| int response_count_{0}; | |
| ConnectionState gcd_disabled_state_{ConnectionState::kDisabled}; | |
| }; | |
| TEST_F(PrivetHandlerTest, UnknownApi) { | |
| HandleUnknownRequest("/privet/foo"); | |
| } | |
| TEST_F(PrivetHandlerTest, InvalidFormat) { | |
| auth_header_ = ""; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidFormat"), | |
| HandleRequest("/privet/info", nullptr)); | |
| } | |
| TEST_F(PrivetHandlerTest, MissingAuth) { | |
| auth_header_ = ""; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(401, "missingAuthorization"), | |
| HandleRequest("/privet/info", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, InvalidAuth) { | |
| auth_header_ = "foo"; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(401, "invalidAuthorization"), | |
| HandleRequest("/privet/info", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, ExpiredAuth) { | |
| auth_header_ = "Privet 123"; | |
| EXPECT_CALL(security_, ParseAccessToken(_, _)) | |
| .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time()), | |
| Return(UserInfo{AuthScope::kOwner, 1}))); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(403, "authorizationExpired"), | |
| HandleRequest("/privet/info", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, InvalidAuthScope) { | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthorizationScope"), | |
| HandleRequest("/privet/v3/setup/start", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, InfoMinimal) { | |
| SetNoWifiAndGcd(); | |
| EXPECT_CALL(security_, GetPairingTypes()) | |
| .WillRepeatedly(Return(std::set<PairingType>{})); | |
| EXPECT_CALL(security_, GetCryptoTypes()) | |
| .WillRepeatedly(Return(std::set<CryptoType>{})); | |
| const char kExpected[] = R"({ | |
| 'version': '3.0', | |
| 'id': 'TestId', | |
| 'name': 'TestDevice', | |
| 'services': [ "developmentBoard" ], | |
| 'modelManifestId': "ABMID", | |
| 'basicModelManifest': { | |
| 'uiDeviceKind': 'developmentBoard', | |
| 'oemName': 'Chromium', | |
| 'modelName': 'Brillo' | |
| }, | |
| 'endpoints': { | |
| 'httpPort': 0, | |
| 'httpUpdatesPort': 0, | |
| 'httpsPort': 0, | |
| 'httpsUpdatesPort': 0 | |
| }, | |
| 'authentication': { | |
| 'anonymousMaxScope': 'user', | |
| 'mode': [ | |
| 'anonymous', | |
| 'pairing' | |
| ], | |
| 'pairing': [ | |
| ], | |
| 'crypto': [ | |
| ] | |
| }, | |
| 'gcd': { | |
| 'id': '', | |
| 'status': 'disabled' | |
| }, | |
| 'uptime': 3600 | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/info", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, Info) { | |
| EXPECT_CALL(cloud_, GetDescription()) | |
| .WillRepeatedly(Return("TestDescription")); | |
| EXPECT_CALL(cloud_, GetLocation()).WillRepeatedly(Return("TestLocation")); | |
| EXPECT_CALL(device_, GetHttpEnpoint()) | |
| .WillRepeatedly(Return(std::make_pair(80, 10080))); | |
| EXPECT_CALL(device_, GetHttpsEnpoint()) | |
| .WillRepeatedly(Return(std::make_pair(443, 10443))); | |
| EXPECT_CALL(wifi_, GetHostedSsid()) | |
| .WillRepeatedly(Return("Test_device.BBABCLAprv")); | |
| const char kExpected[] = R"({ | |
| 'version': '3.0', | |
| 'id': 'TestId', | |
| 'name': 'TestDevice', | |
| 'description': 'TestDescription', | |
| 'location': 'TestLocation', | |
| 'services': [ "developmentBoard" ], | |
| 'modelManifestId': "ABMID", | |
| 'basicModelManifest': { | |
| 'uiDeviceKind': 'developmentBoard', | |
| 'oemName': 'Chromium', | |
| 'modelName': 'Brillo' | |
| }, | |
| 'endpoints': { | |
| 'httpPort': 80, | |
| 'httpUpdatesPort': 10080, | |
| 'httpsPort': 443, | |
| 'httpsUpdatesPort': 10443 | |
| }, | |
| 'authentication': { | |
| 'anonymousMaxScope': 'none', | |
| 'mode': [ | |
| 'anonymous', | |
| 'pairing' | |
| ], | |
| 'pairing': [ | |
| 'pinCode', | |
| 'embeddedCode' | |
| ], | |
| 'crypto': [ | |
| 'p224_spake2' | |
| ] | |
| }, | |
| 'wifi': { | |
| 'capabilities': [ | |
| '2.4GHz' | |
| ], | |
| 'ssid': 'TestSsid', | |
| 'hostedSsid': 'Test_device.BBABCLAprv', | |
| 'status': 'offline' | |
| }, | |
| 'gcd': { | |
| 'id': 'TestCloudId', | |
| 'status': 'online' | |
| }, | |
| 'uptime': 3600 | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/info", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, PairingStartInvalidParams) { | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), | |
| HandleRequest("/privet/v3/pairing/start", | |
| "{'pairing':'embeddedCode','crypto':'crypto'}")); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), | |
| HandleRequest("/privet/v3/pairing/start", | |
| "{'pairing':'code','crypto':'p224_spake2'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, PairingStart) { | |
| EXPECT_JSON_EQ( | |
| "{'deviceCommitment': 'testCommitment', 'sessionId': 'testSession'}", | |
| HandleRequest("/privet/v3/pairing/start", | |
| "{'pairing': 'embeddedCode', 'crypto': 'p224_spake2'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, PairingConfirm) { | |
| EXPECT_JSON_EQ( | |
| "{'certFingerprint':'testFingerprint','certSignature':'testSignature'}", | |
| HandleRequest( | |
| "/privet/v3/pairing/confirm", | |
| "{'sessionId':'testSession','clientCommitment':'testCommitment'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, PairingCancel) { | |
| EXPECT_JSON_EQ("{}", | |
| HandleRequest("/privet/v3/pairing/cancel", | |
| "{'sessionId': 'testSession'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthErrorNoType) { | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidAuthMode"), | |
| HandleRequest("/privet/v3/auth", "{}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthErrorInvalidType) { | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidAuthMode"), | |
| HandleRequest("/privet/v3/auth", "{'mode':'unknown'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthErrorNoScope) { | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidRequestedScope"), | |
| HandleRequest("/privet/v3/auth", "{'mode':'anonymous'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthErrorInvalidScope) { | |
| EXPECT_PRED2( | |
| IsEqualError, CodeWithReason(400, "invalidRequestedScope"), | |
| HandleRequest("/privet/v3/auth", | |
| "{'mode':'anonymous','requestedScope':'unknown'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthErrorAccessDenied) { | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(403, "accessDenied"), | |
| HandleRequest("/privet/v3/auth", | |
| "{'mode':'anonymous','requestedScope':'owner'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthErrorInvalidAuthCode) { | |
| EXPECT_CALL(security_, IsValidPairingCode("testToken")) | |
| .WillRepeatedly(Return(false)); | |
| const char kInput[] = R"({ | |
| 'mode': 'pairing', | |
| 'requestedScope': 'user', | |
| 'authCode': 'testToken' | |
| })"; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthCode"), | |
| HandleRequest("/privet/v3/auth", kInput)); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthAnonymous) { | |
| const char kExpected[] = R"({ | |
| 'accessToken': 'GuestAccessToken', | |
| 'expiresIn': 3600, | |
| 'scope': 'user', | |
| 'tokenType': 'Privet' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, | |
| HandleRequest("/privet/v3/auth", | |
| "{'mode':'anonymous','requestedScope':'auto'}")); | |
| } | |
| TEST_F(PrivetHandlerTest, AuthPairing) { | |
| EXPECT_CALL(security_, IsValidPairingCode("testToken")) | |
| .WillRepeatedly(Return(true)); | |
| EXPECT_CALL(security_, CreateAccessToken(_)) | |
| .WillRepeatedly(Return("OwnerAccessToken")); | |
| const char kInput[] = R"({ | |
| 'mode': 'pairing', | |
| 'requestedScope': 'owner', | |
| 'authCode': 'testToken' | |
| })"; | |
| const char kExpected[] = R"({ | |
| 'accessToken': 'OwnerAccessToken', | |
| 'expiresIn': 3600, | |
| 'scope': 'owner', | |
| 'tokenType': 'Privet' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput)); | |
| } | |
| class PrivetHandlerTestWithAuth : public PrivetHandlerTest { | |
| public: | |
| void SetUp() override { | |
| PrivetHandlerTest::SetUp(); | |
| auth_header_ = "Privet 123"; | |
| EXPECT_CALL(security_, ParseAccessToken(_, _)) | |
| .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()), | |
| Return(UserInfo{AuthScope::kOwner, 1}))); | |
| } | |
| }; | |
| class PrivetHandlerSetupTest : public PrivetHandlerTestWithAuth {}; | |
| TEST_F(PrivetHandlerSetupTest, StatusEmpty) { | |
| SetNoWifiAndGcd(); | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/setup/status", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, StatusWifi) { | |
| wifi_.setup_state_ = SetupState{SetupState::kSuccess}; | |
| const char kExpected[] = R"({ | |
| 'wifi': { | |
| 'ssid': 'TestSsid', | |
| 'status': 'success' | |
| } | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/status", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, StatusWifiError) { | |
| ErrorPtr error; | |
| Error::AddTo(&error, FROM_HERE, "test", "invalidPassphrase", ""); | |
| wifi_.setup_state_ = SetupState{std::move(error)}; | |
| const char kExpected[] = R"({ | |
| 'wifi': { | |
| 'status': 'error', | |
| 'error': { | |
| 'code': 'invalidPassphrase' | |
| } | |
| } | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, | |
| *StripDebugErrorDetails("wifi", | |
| HandleRequest( | |
| "/privet/v3/setup/status", "{}"))); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, StatusGcd) { | |
| cloud_.setup_state_ = SetupState{SetupState::kSuccess}; | |
| const char kExpected[] = R"({ | |
| 'gcd': { | |
| 'id': 'TestCloudId', | |
| 'status': 'success' | |
| } | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/status", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, StatusGcdError) { | |
| ErrorPtr error; | |
| Error::AddTo(&error, FROM_HERE, "test", "invalidTicket", ""); | |
| cloud_.setup_state_ = SetupState{std::move(error)}; | |
| const char kExpected[] = R"({ | |
| 'gcd': { | |
| 'status': 'error', | |
| 'error': { | |
| 'code': 'invalidTicket' | |
| } | |
| } | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, | |
| *StripDebugErrorDetails("gcd", | |
| HandleRequest( | |
| "/privet/v3/setup/status", "{}"))); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, SetupNameDescriptionLocation) { | |
| EXPECT_CALL(cloud_, | |
| UpdateDeviceInfo("testName", "testDescription", "testLocation")) | |
| .Times(1); | |
| const char kInput[] = R"({ | |
| 'name': 'testName', | |
| 'description': 'testDescription', | |
| 'location': 'testLocation' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/setup/start", kInput)); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, InvalidParams) { | |
| const char kInputWifi[] = R"({ | |
| 'wifi': { | |
| 'ssid': '' | |
| } | |
| })"; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), | |
| HandleRequest("/privet/v3/setup/start", kInputWifi)); | |
| const char kInputRegistration[] = R"({ | |
| 'gcd': { | |
| 'ticketId': '' | |
| } | |
| })"; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), | |
| HandleRequest("/privet/v3/setup/start", kInputRegistration)); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, WifiSetupUnavailable) { | |
| SetNoWifiAndGcd(); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "setupUnavailable"), | |
| HandleRequest("/privet/v3/setup/start", "{'wifi': {}}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, WifiSetup) { | |
| const char kInput[] = R"({ | |
| 'wifi': { | |
| 'ssid': 'testSsid', | |
| 'passphrase': 'testPass' | |
| } | |
| })"; | |
| auto set_error = [](const std::string&, const std::string&, ErrorPtr* error) { | |
| Error::AddTo(error, FROM_HERE, errors::kDomain, "deviceBusy", ""); | |
| }; | |
| EXPECT_CALL(wifi_, ConfigureCredentials(_, _, _)) | |
| .WillOnce(DoAll(Invoke(set_error), Return(false))); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"), | |
| HandleRequest("/privet/v3/setup/start", kInput)); | |
| const char kExpected[] = R"({ | |
| 'wifi': { | |
| 'status': 'inProgress' | |
| } | |
| })"; | |
| wifi_.setup_state_ = SetupState{SetupState::kInProgress}; | |
| EXPECT_CALL(wifi_, ConfigureCredentials("testSsid", "testPass", _)) | |
| .WillOnce(Return(true)); | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/start", kInput)); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, GcdSetupUnavailable) { | |
| SetNoWifiAndGcd(); | |
| const char kInput[] = R"({ | |
| 'gcd': { | |
| 'ticketId': 'testTicket', | |
| 'user': 'testUser' | |
| } | |
| })"; | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(400, "setupUnavailable"), | |
| HandleRequest("/privet/v3/setup/start", kInput)); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, GcdSetup) { | |
| const char kInput[] = R"({ | |
| 'gcd': { | |
| 'ticketId': 'testTicket', | |
| 'user': 'testUser' | |
| } | |
| })"; | |
| auto set_error = [](const std::string&, const std::string&, ErrorPtr* error) { | |
| Error::AddTo(error, FROM_HERE, errors::kDomain, "deviceBusy", ""); | |
| }; | |
| EXPECT_CALL(cloud_, Setup(_, _, _)) | |
| .WillOnce(DoAll(Invoke(set_error), Return(false))); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"), | |
| HandleRequest("/privet/v3/setup/start", kInput)); | |
| const char kExpected[] = R"({ | |
| 'gcd': { | |
| 'status': 'inProgress' | |
| } | |
| })"; | |
| cloud_.setup_state_ = SetupState{SetupState::kInProgress}; | |
| EXPECT_CALL(cloud_, Setup("testTicket", "testUser", _)) | |
| .WillOnce(Return(true)); | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/start", kInput)); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, State) { | |
| EXPECT_JSON_EQ("{'state': {'test': {}}, 'fingerprint': '1'}", | |
| HandleRequest("/privet/v3/state", "{}")); | |
| cloud_.NotifyOnStateChanged(); | |
| EXPECT_JSON_EQ("{'state': {'test': {}}, 'fingerprint': '2'}", | |
| HandleRequest("/privet/v3/state", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, CommandsDefs) { | |
| EXPECT_JSON_EQ("{'commands': {'test':{}}, 'fingerprint': '1'}", | |
| HandleRequest("/privet/v3/commandDefs", "{}")); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_JSON_EQ("{'commands': {'test':{}}, 'fingerprint': '2'}", | |
| HandleRequest("/privet/v3/commandDefs", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, Traits) { | |
| EXPECT_JSON_EQ("{'traits': {'test': {}}, 'fingerprint': '1'}", | |
| HandleRequest("/privet/v3/traits", "{}")); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_JSON_EQ("{'traits': {'test': {}}, 'fingerprint': '2'}", | |
| HandleRequest("/privet/v3/traits", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, Components) { | |
| EXPECT_JSON_EQ("{'components': {'test': {}}, 'fingerprint': '1'}", | |
| HandleRequest("/privet/v3/components", "{}")); | |
| cloud_.NotifyOnComponentTreeChanged(); | |
| EXPECT_JSON_EQ("{'components': {'test': {}}, 'fingerprint': '2'}", | |
| HandleRequest("/privet/v3/components", "{}")); | |
| // State change will also change the components fingerprint. | |
| cloud_.NotifyOnStateChanged(); | |
| EXPECT_JSON_EQ("{'components': {'test': {}}, 'fingerprint': '3'}", | |
| HandleRequest("/privet/v3/components", "{}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, CommandsExecute) { | |
| const char kInput[] = "{'name': 'test'}"; | |
| base::DictionaryValue command; | |
| LoadTestJson(kInput, &command); | |
| LoadTestJson("{'id':'5'}", &command); | |
| EXPECT_CALL(cloud_, AddCommand(_, _, _)) | |
| .WillOnce(WithArgs<2>(Invoke( | |
| [&command](const CloudDelegate::CommandDoneCallback& callback) { | |
| callback.Run(command, nullptr); | |
| }))); | |
| EXPECT_JSON_EQ("{'name':'test', 'id':'5'}", | |
| HandleRequest("/privet/v3/commands/execute", kInput)); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, CommandsStatus) { | |
| const char kInput[] = "{'id': '5'}"; | |
| base::DictionaryValue command; | |
| LoadTestJson(kInput, &command); | |
| LoadTestJson("{'name':'test'}", &command); | |
| EXPECT_CALL(cloud_, GetCommand(_, _, _)) | |
| .WillOnce(WithArgs<2>(Invoke( | |
| [&command](const CloudDelegate::CommandDoneCallback& callback) { | |
| callback.Run(command, nullptr); | |
| }))); | |
| EXPECT_JSON_EQ("{'name':'test', 'id':'5'}", | |
| HandleRequest("/privet/v3/commands/status", kInput)); | |
| ErrorPtr error; | |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, "notFound", ""); | |
| EXPECT_CALL(cloud_, GetCommand(_, _, _)) | |
| .WillOnce(WithArgs<2>( | |
| Invoke([&error](const CloudDelegate::CommandDoneCallback& callback) { | |
| callback.Run({}, std::move(error)); | |
| }))); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(404, "notFound"), | |
| HandleRequest("/privet/v3/commands/status", "{'id': '15'}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, CommandsCancel) { | |
| const char kExpected[] = "{'id': '5', 'name':'test', 'state':'cancelled'}"; | |
| base::DictionaryValue command; | |
| LoadTestJson(kExpected, &command); | |
| EXPECT_CALL(cloud_, CancelCommand(_, _, _)) | |
| .WillOnce(WithArgs<2>(Invoke( | |
| [&command](const CloudDelegate::CommandDoneCallback& callback) { | |
| callback.Run(command, nullptr); | |
| }))); | |
| EXPECT_JSON_EQ(kExpected, | |
| HandleRequest("/privet/v3/commands/cancel", "{'id': '8'}")); | |
| ErrorPtr error; | |
| Error::AddTo(&error, FROM_HERE, errors::kDomain, "notFound", ""); | |
| EXPECT_CALL(cloud_, CancelCommand(_, _, _)) | |
| .WillOnce(WithArgs<2>( | |
| Invoke([&error](const CloudDelegate::CommandDoneCallback& callback) { | |
| callback.Run({}, std::move(error)); | |
| }))); | |
| EXPECT_PRED2(IsEqualError, CodeWithReason(404, "notFound"), | |
| HandleRequest("/privet/v3/commands/cancel", "{'id': '11'}")); | |
| } | |
| TEST_F(PrivetHandlerSetupTest, CommandsList) { | |
| const char kExpected[] = R"({ | |
| 'commands' : [ | |
| {'id':'5', 'state':'cancelled'}, | |
| {'id':'15', 'state':'inProgress'} | |
| ]})"; | |
| base::DictionaryValue commands; | |
| LoadTestJson(kExpected, &commands); | |
| EXPECT_CALL(cloud_, ListCommands(_, _)) | |
| .WillOnce(WithArgs<1>(Invoke( | |
| [&commands](const CloudDelegate::CommandDoneCallback& callback) { | |
| callback.Run(commands, nullptr); | |
| }))); | |
| EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/commands/list", "{}")); | |
| } | |
| class PrivetHandlerCheckForUpdatesTest : public PrivetHandlerTestWithAuth {}; | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, NoInput) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| cloud_.NotifyOnComponentTreeChanged(); | |
| cloud_.NotifyOnStateChanged(); | |
| const char kInput[] = "{}"; | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '2', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '3' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, | |
| HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, AlreadyChanged) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| cloud_.NotifyOnComponentTreeChanged(); | |
| cloud_.NotifyOnStateChanged(); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '2', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '3' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, | |
| HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, LongPollCommands) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, LongPollTraits) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, LongPollState) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnStateChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '2', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '2' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, LongPollComponents) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnComponentTreeChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '2' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, LongPollIgnoreTraits) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'stateFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnComponentTreeChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '2' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, LongPollIgnoreState) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'traitsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnStateChanged(); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnComponentTreeChanged(); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '2', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '3' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, InstantTimeout) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1', | |
| 'waitTimeout': 0 | |
| })"; | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, | |
| HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, UserTimeout) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1', | |
| 'waitTimeout': 3 | |
| })"; | |
| base::Closure callback; | |
| EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(3))) | |
| .WillOnce(SaveArg<1>(&callback)); | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| callback.Run(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, ServerTimeout) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::FromMinutes(1))); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| base::Closure callback; | |
| EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(50))) | |
| .WillOnce(SaveArg<1>(&callback)); | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| callback.Run(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, VeryShortServerTimeout) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::FromSeconds(5))); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kInput, HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, ServerAndUserTimeout) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::FromMinutes(1))); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1', | |
| 'waitTimeout': 10 | |
| })"; | |
| base::Closure callback; | |
| EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(10))) | |
| .WillOnce(SaveArg<1>(&callback)); | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| callback.Run(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| } | |
| TEST_F(PrivetHandlerCheckForUpdatesTest, ChangeBeforeTimeout) { | |
| EXPECT_CALL(device_, GetHttpRequestTimeout()) | |
| .WillOnce(Return(base::TimeDelta::Max())); | |
| const char kInput[] = R"({ | |
| 'commandsFingerprint': '1', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '1', | |
| 'componentsFingerprint': '1', | |
| 'waitTimeout': 10 | |
| })"; | |
| base::Closure callback; | |
| EXPECT_CALL(device_, PostDelayedTask(_, _, base::TimeDelta::FromSeconds(10))) | |
| .WillOnce(SaveArg<1>(&callback)); | |
| EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/checkForUpdates", kInput)); | |
| EXPECT_EQ(0, GetResponseCount()); | |
| cloud_.NotifyOnTraitDefsChanged(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| const char kExpected[] = R"({ | |
| 'commandsFingerprint': '2', | |
| 'stateFingerprint': '1', | |
| 'traitsFingerprint': '2', | |
| 'componentsFingerprint': '1' | |
| })"; | |
| EXPECT_JSON_EQ(kExpected, GetResponse()); | |
| callback.Run(); | |
| EXPECT_EQ(1, GetResponseCount()); | |
| } | |
| } // namespace privet | |
| } // namespace weave |