| // 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" | 
 | #include "src/test/mock_clock.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 { | 
 |     EXPECT_CALL(clock_, Now()) | 
 |         .WillRepeatedly(Return(base::Time::FromTimeT(1410000001))); | 
 |  | 
 |     auth_header_ = "Privet anonymous"; | 
 |     handler_.reset( | 
 |         new PrivetHandler(&cloud_, &device_, &security_, &wifi_, &clock_)); | 
 |   } | 
 |  | 
 |   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, &clock_)); | 
 |     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))); | 
 |   } | 
 |  | 
 |   test::MockClock clock_; | 
 |   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(WithArgs<2>(Invoke([](ErrorPtr* error) { | 
 |                               Error::AddTo(error, FROM_HERE, errors::kDomain, | 
 |                                            "authorizationExpired", ""); | 
 |                             })), | 
 |                             Return(false))); | 
 |   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>{})); | 
 |   EXPECT_CALL(security_, GetAuthTypes()) | 
 |       .WillRepeatedly(Return(std::set<AuthType>{})); | 
 |  | 
 |   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': [ | 
 |       ], | 
 |       'pairing': [ | 
 |       ], | 
 |       'crypto': [ | 
 |       ] | 
 |     }, | 
 |     'gcd': { | 
 |       'id': '', | 
 |       'status': 'disabled' | 
 |     }, | 
 |     'time': 1410000001000.0, | 
 |     'sessionId': 'SessionId' | 
 |   })"; | 
 |   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', | 
 |         'local' | 
 |       ], | 
 |       'pairing': [ | 
 |         'pinCode', | 
 |         'embeddedCode' | 
 |       ], | 
 |       'crypto': [ | 
 |         'p224_spake2' | 
 |       ] | 
 |     }, | 
 |     'wifi': { | 
 |       'capabilities': [ | 
 |         '2.4GHz' | 
 |       ], | 
 |       'ssid': 'TestSsid', | 
 |       'hostedSsid': 'Test_device.BBABCLAprv', | 
 |       'status': 'offline' | 
 |     }, | 
 |     'gcd': { | 
 |       'id': 'TestCloudId', | 
 |       'status': 'online' | 
 |     }, | 
 |     'time': 1410000001000.0, | 
 |     'sessionId': 'SessionId' | 
 |   })"; | 
 |   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) { | 
 |   auto set_error = [](ErrorPtr* error) { | 
 |     Error::AddTo(error, FROM_HERE, errors::kDomain, "invalidAuthCode", ""); | 
 |   }; | 
 |   EXPECT_CALL(security_, CreateAccessToken(_, "testToken", _, _, _, _, _)) | 
 |       .WillRepeatedly(DoAll(WithArgs<6>(Invoke(set_error)), 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': 15, | 
 |     'scope': 'viewer', | 
 |     'tokenType': 'Privet' | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected, | 
 |                  HandleRequest("/privet/v3/auth", | 
 |                                "{'mode':'anonymous','requestedScope':'auto'}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthPairing) { | 
 |   EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _)) | 
 |       .WillRepeatedly(DoAll(SetArgPointee<3>("OwnerAccessToken"), | 
 |                             SetArgPointee<4>(AuthScope::kOwner), | 
 |                             SetArgPointee<5>(base::TimeDelta::FromSeconds(15)), | 
 |                             Return(true))); | 
 |   const char kInput[] = R"({ | 
 |     'mode': 'pairing', | 
 |     'requestedScope': 'owner', | 
 |     'authCode': 'testToken' | 
 |   })"; | 
 |   const char kExpected[] = R"({ | 
 |     'accessToken': 'OwnerAccessToken', | 
 |     'expiresIn': 15, | 
 |     'scope': 'owner', | 
 |     'tokenType': 'Privet' | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthLocalAuto) { | 
 |   EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _)) | 
 |       .WillRepeatedly(DoAll(SetArgPointee<3>("UserAccessToken"), | 
 |                             SetArgPointee<4>(AuthScope::kUser), | 
 |                             SetArgPointee<5>(base::TimeDelta::FromSeconds(15)), | 
 |                             Return(true))); | 
 |   const char kInput[] = R"({ | 
 |     'mode': 'local', | 
 |     'requestedScope': 'auto', | 
 |     'authCode': 'localAuthToken' | 
 |   })"; | 
 |   const char kExpected[] = R"({ | 
 |     'accessToken': 'UserAccessToken', | 
 |     'expiresIn': 15, | 
 |     'scope': 'user', | 
 |     'tokenType': 'Privet' | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthLocal) { | 
 |   EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _)) | 
 |       .WillRepeatedly(DoAll(SetArgPointee<3>("ManagerAccessToken"), | 
 |                             SetArgPointee<4>(AuthScope::kManager), | 
 |                             SetArgPointee<5>(base::TimeDelta::FromSeconds(15)), | 
 |                             Return(true))); | 
 |   const char kInput[] = R"({ | 
 |     'mode': 'local', | 
 |     'requestedScope': 'manager', | 
 |     'authCode': 'localAuthToken' | 
 |   })"; | 
 |   const char kExpected[] = R"({ | 
 |     'accessToken': 'ManagerAccessToken', | 
 |     'expiresIn': 15, | 
 |     'scope': 'manager', | 
 |     'tokenType': 'Privet' | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/auth", kInput)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthLocalHighScope) { | 
 |   EXPECT_CALL(security_, CreateAccessToken(_, _, _, _, _, _, _)) | 
 |       .WillRepeatedly(DoAll(SetArgPointee<3>("UserAccessToken"), | 
 |                             SetArgPointee<4>(AuthScope::kUser), | 
 |                             SetArgPointee<5>(base::TimeDelta::FromSeconds(1)), | 
 |                             Return(true))); | 
 |   const char kInput[] = R"({ | 
 |     'mode': 'local', | 
 |     'requestedScope': 'manager', | 
 |     'authCode': 'localAuthToken' | 
 |   })"; | 
 |   EXPECT_PRED2(IsEqualError, CodeWithReason(403, "accessDenied"), | 
 |                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>(UserInfo{AuthScope::kOwner, "1"}), Return(true))); | 
 |   } | 
 | }; | 
 |  | 
 | 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, GcdSetupAsMaster) { | 
 |   EXPECT_CALL(security_, ParseAccessToken(_, _, _)) | 
 |       .WillRepeatedly(DoAll( | 
 |           SetArgPointee<1>(UserInfo{AuthScope::kManager, "1"}), Return(true))); | 
 |   const char kInput[] = R"({ | 
 |     'gcd': { | 
 |       'ticketId': 'testTicket', | 
 |       'user': 'testUser' | 
 |     } | 
 |   })"; | 
 |  | 
 |   EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthorizationScope"), | 
 |                HandleRequest("/privet/v3/setup/start", kInput)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, ClaimAccessControl) { | 
 |   EXPECT_JSON_EQ("{'clientToken': 'RootClientAuthToken'}", | 
 |                  HandleRequest("/privet/v3/accessControl/claim", "{}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, ConfirmAccessControl) { | 
 |   EXPECT_JSON_EQ("{}", | 
 |                  HandleRequest("/privet/v3/accessControl/confirm", | 
 |                                "{'clientToken': 'DerivedClientAuthToken'}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, ComponentsWithFiltersAndPaths) { | 
 |   const char kComponents[] = R"({ | 
 |     "comp1": { | 
 |       "traits": ["a", "b"], | 
 |       "state": { | 
 |         "a" : { | 
 |           "prop": 1 | 
 |         } | 
 |       }, | 
 |       "components": { | 
 |         "comp2": { | 
 |           "traits": ["c"], | 
 |           "components": { | 
 |             "comp4": { | 
 |               "traits": ["d"] | 
 |             } | 
 |           } | 
 |         }, | 
 |         "comp3": { | 
 |           "traits": ["e"] | 
 |         } | 
 |       } | 
 |     } | 
 |   })"; | 
 |   base::DictionaryValue components; | 
 |   LoadTestJson(kComponents, &components); | 
 |   EXPECT_CALL(cloud_, FindComponent(_, _)).WillRepeatedly(Return(nullptr)); | 
 |   EXPECT_CALL(cloud_, GetComponents()).WillRepeatedly(ReturnRef(components)); | 
 |   const char kExpected1[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "state": { | 
 |           "a" : { | 
 |             "prop": 1 | 
 |           } | 
 |         } | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected1, HandleRequest("/privet/v3/components", | 
 |                                            "{'filter':['state']}")); | 
 |  | 
 |   const char kExpected2[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "traits": ["a", "b"] | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected2, HandleRequest("/privet/v3/components", | 
 |                                            "{'filter':['traits']}")); | 
 |  | 
 |   const char kExpected3[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "components": { | 
 |           "comp2": { | 
 |             "components": { | 
 |               "comp4": {} | 
 |             } | 
 |           }, | 
 |           "comp3": {} | 
 |         } | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected3, HandleRequest("/privet/v3/components", | 
 |                                            "{'filter':['components']}")); | 
 |  | 
 |   const char kExpected4[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "traits": ["a", "b"], | 
 |         "state": { | 
 |           "a" : { | 
 |             "prop": 1 | 
 |           } | 
 |         }, | 
 |         "components": { | 
 |           "comp2": { | 
 |             "traits": ["c"], | 
 |             "components": { | 
 |               "comp4": { | 
 |                 "traits": ["d"] | 
 |               } | 
 |             } | 
 |           }, | 
 |           "comp3": { | 
 |             "traits": ["e"] | 
 |           } | 
 |         } | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected4, | 
 |                  HandleRequest("/privet/v3/components", | 
 |                                "{'filter':['traits', 'components', 'state']}")); | 
 |  | 
 |   const base::DictionaryValue* comp2 = nullptr; | 
 |   ASSERT_TRUE(components.GetDictionary("comp1.components.comp2", &comp2)); | 
 |   EXPECT_CALL(cloud_, FindComponent("comp1.comp2", _)).WillOnce(Return(comp2)); | 
 |  | 
 |   const char kExpected5[] = R"({ | 
 |     "components": { | 
 |       "comp2": { | 
 |         "traits": ["c"], | 
 |         "components": { | 
 |           "comp4": { | 
 |             "traits": ["d"] | 
 |           } | 
 |         } | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ( | 
 |       kExpected5, | 
 |       HandleRequest( | 
 |           "/privet/v3/components", | 
 |           "{'path':'comp1.comp2', 'filter':['traits', 'components']}")); | 
 |  | 
 |   auto error_handler = [](ErrorPtr* error) -> const base::DictionaryValue* { | 
 |     Error::AddTo(error, FROM_HERE, errors::kDomain, "componentNotFound", ""); | 
 |     return nullptr; | 
 |   }; | 
 |   EXPECT_CALL(cloud_, FindComponent("comp7", _)) | 
 |       .WillOnce(WithArgs<1>(Invoke(error_handler))); | 
 |  | 
 |   EXPECT_PRED2( | 
 |       IsEqualError, CodeWithReason(500, "componentNotFound"), | 
 |       HandleRequest("/privet/v3/components", | 
 |                     "{'path':'comp7', 'filter':['traits', 'components']}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, 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(PrivetHandlerTestWithAuth, 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 |