| // 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/device.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) { | 
 |   int error = 0; | 
 |   std::string message; | 
 |   std::unique_ptr<base::Value> value( | 
 |       base::JSONReader::ReadAndReturnError(test_json, base::JSON_PARSE_RFC, | 
 |                                            &error, &message)); | 
 |   EXPECT_TRUE(value.get()) << "\nError: " << message << "\n" << test_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 << R"({" << 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) { | 
 |   auto result = value.CreateDeepCopy(); | 
 |   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 = [](ErrorPtr* error) { | 
 |       Error::AddTo(error, FROM_HERE, "setupUnavailable", ""); | 
 |     }; | 
 |     EXPECT_CALL(cloud_, Setup(_, _)) | 
 |         .WillRepeatedly(DoAll(WithArgs<1>(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(WithArgs<2>(Invoke([](ErrorPtr* error) { | 
 |         return Error::AddTo(error, FROM_HERE, "authorizationExpired", ""); | 
 |       }))); | 
 |   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": "", | 
 |       "oauth_url": "https://oauths/", | 
 |       "service_url": "https://service/", | 
 |       "status": "disabled", | 
 |       "xmpp_endpoint": "xmpp:678" | 
 |     }, | 
 |     "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", | 
 |       "oauth_url": "https://oauths/", | 
 |       "service_url": "https://service/", | 
 |       "status": "online", | 
 |       "xmpp_endpoint": "xmpp:678" | 
 |     }, | 
 |     "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", | 
 |                     R"({"pairing":"embeddedCode","crypto":"crypto"})")); | 
 |  | 
 |   EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), | 
 |                HandleRequest("/privet/v3/pairing/start", | 
 |                              R"({"pairing":"code","crypto":"p224_spake2"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, PairingStart) { | 
 |   EXPECT_JSON_EQ( | 
 |       R"({"deviceCommitment": "testCommitment", "sessionId": "testSession"})", | 
 |       HandleRequest("/privet/v3/pairing/start", | 
 |                     R"({"pairing": "embeddedCode", "crypto": "p224_spake2"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, PairingConfirm) { | 
 |   EXPECT_JSON_EQ( | 
 |       R"({"certFingerprint":"testFingerprint","certSignature":"testSignature"})", | 
 |       HandleRequest( | 
 |           "/privet/v3/pairing/confirm", | 
 |           R"({"sessionId":"testSession","clientCommitment":"testCommitment"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, PairingCancel) { | 
 |   EXPECT_JSON_EQ("{}", HandleRequest("/privet/v3/pairing/cancel", | 
 |                                      R"({"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", R"({"mode":"unknown"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthErrorNoScope) { | 
 |   EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidRequestedScope"), | 
 |                HandleRequest("/privet/v3/auth", R"({"mode":"anonymous"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthErrorInvalidScope) { | 
 |   EXPECT_PRED2( | 
 |       IsEqualError, CodeWithReason(400, "invalidRequestedScope"), | 
 |       HandleRequest("/privet/v3/auth", | 
 |                     R"({"mode":"anonymous","requestedScope":"unknown"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthErrorAccessDenied) { | 
 |   EXPECT_PRED2( | 
 |       IsEqualError, CodeWithReason(403, "accessDenied"), | 
 |       HandleRequest("/privet/v3/auth", | 
 |                     R"({"mode":"anonymous","requestedScope":"owner"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, AuthErrorInvalidAuthCode) { | 
 |   auto set_error = [](ErrorPtr* error) { | 
 |     return Error::AddTo(error, FROM_HERE, "invalidAuthCode", ""); | 
 |   }; | 
 |   EXPECT_CALL(security_, CreateAccessToken(_, "testToken", _, _, _, _, _)) | 
 |       .WillRepeatedly(WithArgs<6>(Invoke(set_error))); | 
 |   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", | 
 |                     R"({"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)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTest, ComponentsForUser) { | 
 |   auth_header_ = "Privet 123"; | 
 |   const UserInfo kOwner{AuthScope::kOwner, TestUserId{"1"}}; | 
 |   const UserInfo kManager{AuthScope::kManager, TestUserId{"2"}}; | 
 |   const UserInfo kUser{AuthScope::kUser, TestUserId{"3"}}; | 
 |   const UserInfo kViewer{AuthScope::kViewer, TestUserId{"4"}}; | 
 |   const base::DictionaryValue components; | 
 |   const std::string expected = R"({"components": {}, "fingerprint": "1"})"; | 
 |  | 
 |   EXPECT_CALL(security_, ParseAccessToken(_, _, _)) | 
 |       .WillOnce(DoAll(SetArgPointee<1>(kOwner), Return(true))); | 
 |   EXPECT_CALL(cloud_, MockGetComponentsForUser(kOwner)) | 
 |       .WillOnce(ReturnRef(components)); | 
 |   EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}")); | 
 |  | 
 |   EXPECT_CALL(security_, ParseAccessToken(_, _, _)) | 
 |       .WillOnce(DoAll(SetArgPointee<1>(kManager), Return(true))); | 
 |   EXPECT_CALL(cloud_, MockGetComponentsForUser(kManager)) | 
 |       .WillOnce(ReturnRef(components)); | 
 |   EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}")); | 
 |  | 
 |   EXPECT_CALL(security_, ParseAccessToken(_, _, _)) | 
 |       .WillOnce(DoAll(SetArgPointee<1>(kUser), Return(true))); | 
 |   EXPECT_CALL(cloud_, MockGetComponentsForUser(kUser)) | 
 |       .WillOnce(ReturnRef(components)); | 
 |   EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}")); | 
 |  | 
 |   EXPECT_CALL(security_, ParseAccessToken(_, _, _)) | 
 |       .WillOnce(DoAll(SetArgPointee<1>(kViewer), Return(true))); | 
 |   EXPECT_CALL(cloud_, MockGetComponentsForUser(kViewer)) | 
 |       .WillOnce(ReturnRef(components)); | 
 |   EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}")); | 
 | } | 
 |  | 
 | 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, TestUserId{"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, "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", | 
 |         "oauth_url": "https://oauths/", | 
 |         "service_url": "https://service/", | 
 |         "status": "success", | 
 |         "xmpp_endpoint": "xmpp:678" | 
 |      } | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/status", "{}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerSetupTest, StatusGcdError) { | 
 |   ErrorPtr error; | 
 |   Error::AddTo(&error, FROM_HERE, "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", R"({"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) { | 
 |     return Error::AddTo(error, FROM_HERE, "deviceBusy", ""); | 
 |   }; | 
 |   EXPECT_CALL(wifi_, ConfigureCredentials(_, _, _)).WillOnce(Invoke(set_error)); | 
 |   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 = [](ErrorPtr* error) { | 
 |     return Error::AddTo(error, FROM_HERE, "deviceBusy", ""); | 
 |   }; | 
 |   EXPECT_CALL(cloud_, Setup(_, _)).WillOnce(WithArgs<1>(Invoke(set_error))); | 
 |   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(RegistrationData{"testTicket"}, _)) | 
 |       .WillOnce(Return(true)); | 
 |   EXPECT_JSON_EQ(kExpected, HandleRequest("/privet/v3/setup/start", kInput)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerSetupTest, GcdSetupWithEndpoints) { | 
 |   const char kInput[] = R"({ | 
 |     "gcd": { | 
 |       "api_key": "test_api_key", | 
 |       "client_id": "test_client_id", | 
 |       "client_secret": "test_client_secret", | 
 |       "oauth_url": "https://oauths/", | 
 |       "service_url": "https://service/", | 
 |       "xmpp_endpoint": "xmpp:678", | 
 |       "ticketId": "testTicket", | 
 |       "user": "testUser" | 
 |     } | 
 |   })"; | 
 |  | 
 |   const char kExpected[] = R"({ | 
 |     "gcd": { | 
 |       "status": "inProgress" | 
 |     } | 
 |   })"; | 
 |   cloud_.setup_state_ = SetupState{SetupState::kInProgress}; | 
 |  | 
 |   RegistrationData expected_reg_data; | 
 |   expected_reg_data.ticket_id = "testTicket"; | 
 |   expected_reg_data.oauth_url = "https://oauths/"; | 
 |   expected_reg_data.client_id = "test_client_id"; | 
 |   expected_reg_data.client_secret = "test_client_secret"; | 
 |   expected_reg_data.api_key = "test_api_key"; | 
 |   expected_reg_data.service_url = "https://service/"; | 
 |   expected_reg_data.xmpp_endpoint = "xmpp:678"; | 
 |  | 
 |   EXPECT_CALL(cloud_, Setup(expected_reg_data, _)).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, TestUserId{"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(R"({"clientToken": "RootClientAuthToken"})", | 
 |                  HandleRequest("/privet/v3/accessControl/claim", "{}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, ConfirmAccessControl) { | 
 |   EXPECT_JSON_EQ("{}", | 
 |                  HandleRequest("/privet/v3/accessControl/confirm", | 
 |                                R"({"clientToken": "DerivedClientAuthToken"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, Traits) { | 
 |   EXPECT_JSON_EQ(R"({"traits": {"test": {}}, "fingerprint": "1"})", | 
 |                  HandleRequest("/privet/v3/traits", "{}")); | 
 |  | 
 |   cloud_.NotifyOnTraitDefsChanged(); | 
 |  | 
 |   EXPECT_JSON_EQ(R"({"traits": {"test": {}}, "fingerprint": "2"})", | 
 |                  HandleRequest("/privet/v3/traits", "{}")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, Components) { | 
 |   EXPECT_JSON_EQ(R"({"components": {"test": {}}, "fingerprint": "1"})", | 
 |                  HandleRequest("/privet/v3/components", "{}")); | 
 |  | 
 |   cloud_.NotifyOnComponentTreeChanged(); | 
 |  | 
 |   EXPECT_JSON_EQ(R"({"components": {"test": {}}, "fingerprint": "2"})", | 
 |                  HandleRequest("/privet/v3/components", "{}")); | 
 |  | 
 |   // State change will also change the components fingerprint. | 
 |   cloud_.NotifyOnStateChanged(); | 
 |  | 
 |   EXPECT_JSON_EQ(R"({"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_, MockGetComponentsForUser(_)) | 
 |       .WillRepeatedly(ReturnRef(components)); | 
 |   const char kExpected1[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "state": { | 
 |           "a" : { | 
 |             "prop": 1 | 
 |           } | 
 |         } | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected1, HandleRequest("/privet/v3/components", | 
 |                                            R"({"filter":["state"]})")); | 
 |  | 
 |   const char kExpected2[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "traits": ["a", "b"] | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected2, HandleRequest("/privet/v3/components", | 
 |                                            R"({"filter":["traits"]})")); | 
 |  | 
 |   const char kExpected3[] = R"({ | 
 |     "components": { | 
 |       "comp1": { | 
 |         "components": { | 
 |           "comp2": { | 
 |             "components": { | 
 |               "comp4": {} | 
 |             } | 
 |           }, | 
 |           "comp3": {} | 
 |         } | 
 |       } | 
 |     }, | 
 |     "fingerprint": "1" | 
 |   })"; | 
 |   EXPECT_JSON_EQ(kExpected3, HandleRequest("/privet/v3/components", | 
 |                                            R"({"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", | 
 |                     R"({"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", | 
 |           R"({"path":"comp1.comp2", "filter":["traits", "components"]})")); | 
 |  | 
 |   auto error_handler = [](ErrorPtr* error) -> const base::DictionaryValue* { | 
 |     return Error::AddTo(error, FROM_HERE, "componentNotFound", ""); | 
 |   }; | 
 |   EXPECT_CALL(cloud_, FindComponent("comp7", _)) | 
 |       .WillOnce(WithArgs<1>(Invoke(error_handler))); | 
 |  | 
 |   EXPECT_PRED2( | 
 |       IsEqualError, CodeWithReason(500, "componentNotFound"), | 
 |       HandleRequest("/privet/v3/components", | 
 |                     R"({"path":"comp7", "filter":["traits", "components"]})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, CommandsExecute) { | 
 |   const char kInput[] = R"({"name": "test"})"; | 
 |   base::DictionaryValue command; | 
 |   LoadTestJson(kInput, &command); | 
 |   LoadTestJson(R"({"id":"5"})", &command); | 
 |   EXPECT_CALL(cloud_, AddCommand(_, _, _)) | 
 |       .WillOnce(WithArgs<2>(Invoke( | 
 |           [&command](const CloudDelegate::CommandDoneCallback& callback) { | 
 |             callback.Run(command, nullptr); | 
 |           }))); | 
 |  | 
 |   EXPECT_JSON_EQ(R"({"name":"test", "id":"5"})", | 
 |                  HandleRequest("/privet/v3/commands/execute", kInput)); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, CommandsStatus) { | 
 |   const char kInput[] = R"({"id": "5"})"; | 
 |   base::DictionaryValue command; | 
 |   LoadTestJson(kInput, &command); | 
 |   LoadTestJson(R"({"name":"test"})", &command); | 
 |   EXPECT_CALL(cloud_, GetCommand(_, _, _)) | 
 |       .WillOnce(WithArgs<2>(Invoke( | 
 |           [&command](const CloudDelegate::CommandDoneCallback& callback) { | 
 |             callback.Run(command, nullptr); | 
 |           }))); | 
 |  | 
 |   EXPECT_JSON_EQ(R"({"name":"test", "id":"5"})", | 
 |                  HandleRequest("/privet/v3/commands/status", kInput)); | 
 |  | 
 |   ErrorPtr error; | 
 |   Error::AddTo(&error, FROM_HERE, "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", R"({"id": "15"})")); | 
 | } | 
 |  | 
 | TEST_F(PrivetHandlerTestWithAuth, CommandsCancel) { | 
 |   const char kExpected[] = R"({"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", R"({"id": "8"})")); | 
 |  | 
 |   ErrorPtr error; | 
 |   Error::AddTo(&error, FROM_HERE, "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", R"({"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 |