// 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 #include #include #include #include #include #include #include #include #include #include #include #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 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 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)); error_dict->RemovePath("error.debugInfo", nullptr); error_dict->RemovePath("error.message", nullptr); 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 cloud_; testing::StrictMock device_; testing::StrictMock security_; testing::StrictMock 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 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{})); EXPECT_CALL(security_, GetCryptoTypes()) .WillRepeatedly(Return(std::set{})); EXPECT_CALL(security_, GetAuthTypes()) .WillRepeatedly(Return(std::set{})); 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