|  | // 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 |