buffet: Move privetd sources into buffet No functional changes, only renaming, fixed include paths and include guards to avoid resubmit warnings. BUG=brillo:1161 CQ-DEPEND=CL:276521 TEST=none Change-Id: Icacff92aef47fdd46542bc96eba3ffbb4df6241a Reviewed-on: https://chromium-review.googlesource.com/276319 Reviewed-by: Vitaly Buka <vitalybuka@chromium.org> Commit-Queue: Vitaly Buka <vitalybuka@chromium.org> Tested-by: Vitaly Buka <vitalybuka@chromium.org>
diff --git a/buffet/privet/privet_handler_unittest.cc b/buffet/privet/privet_handler_unittest.cc new file mode 100644 index 0000000..e2dcfd3 --- /dev/null +++ b/buffet/privet/privet_handler_unittest.cc
@@ -0,0 +1,724 @@ +// Copyright 2014 The Chromium OS 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 "buffet/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/run_loop.h> +#include <base/strings/string_util.h> +#include <base/values.h> +#include <chromeos/http/http_request.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "buffet/privet/constants.h" +#include "buffet/privet/mock_delegates.h" + +using testing::_; +using testing::DoAll; +using testing::Invoke; +using testing::Return; +using testing::SetArgPointee; + +namespace privetd { + +namespace { + +void LoadTestJson(const std::string& test_json, + base::DictionaryValue* dictionary) { + std::string json = test_json; + base::ReplaceChars(json, "'", "\"", &json); + int error = 0; + std::string message; + std::unique_ptr<base::Value> value(base::JSONReader::ReadAndReturnError( + json, base::JSON_PARSE_RFC, &error, &message)); + EXPECT_TRUE(value.get()) << "\nError: " << message << "\n" << json; + base::DictionaryValue* dictionary_ptr = nullptr; + if (value->GetAsDictionary(&dictionary_ptr)) + dictionary->MergeDictionary(dictionary_ptr); +} + +bool IsEqualValue(const base::Value& val1, const base::Value& val2) { + return val1.Equals(&val2); +} + +struct CodeWithReason { + CodeWithReason(int code_in, const std::string& reason_in) + : code(code_in), reason(reason_in) {} + int code; + std::string reason; +}; + +std::ostream& operator<<(std::ostream& stream, const CodeWithReason& error) { + return stream << "{" << error.code << ", " << error.reason << "}"; +} + +bool IsEqualError(const CodeWithReason& expected, + const base::DictionaryValue& dictionary) { + std::string reason; + int code = 0; + return dictionary.GetInteger("error.http_status", &code) && + code == expected.code && dictionary.GetString("error.code", &reason) && + reason == expected.reason; +} + +bool IsEqualDictionary(const base::DictionaryValue& dictionary1, + const base::DictionaryValue& dictionary2) { + base::DictionaryValue::Iterator it1(dictionary1); + base::DictionaryValue::Iterator it2(dictionary2); + for (; !it1.IsAtEnd() && !it2.IsAtEnd(); it1.Advance(), it2.Advance()) { + // Output mismatched keys. + EXPECT_EQ(it1.key(), it2.key()); + if (it1.key() != it2.key()) + return false; + + if (it1.key() == "error") { + std::string code1; + std::string code2; + const char kCodeKey[] = "error.code"; + if (!dictionary1.GetString(kCodeKey, &code1) || + !dictionary2.GetString(kCodeKey, &code2) || code1 != code2) { + return false; + } + continue; + } + + const base::DictionaryValue* d1{nullptr}; + const base::DictionaryValue* d2{nullptr}; + if (it1.value().GetAsDictionary(&d1) && it2.value().GetAsDictionary(&d2)) { + if (!IsEqualDictionary(*d1, *d2)) + return false; + continue; + } + + // Output mismatched values. + EXPECT_PRED2(IsEqualValue, it1.value(), it2.value()); + if (!IsEqualValue(it1.value(), it2.value())) + return false; + } + + return it1.IsAtEnd() && it2.IsAtEnd(); +} + +bool IsEqualJson(const std::string& test_json, + const base::DictionaryValue& dictionary) { + base::DictionaryValue dictionary2; + LoadTestJson(test_json, &dictionary2); + return IsEqualDictionary(dictionary2, dictionary); +} + +} // namespace + +class PrivetHandlerTest : public testing::Test { + public: + PrivetHandlerTest() {} + + protected: + void SetUp() override { + auth_header_ = "Privet anonymous"; + handler_.reset( + new PrivetHandler(&cloud_, &device_, &security_, &wifi_, &identity_)); + } + + 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))); + base::RunLoop().RunUntilIdle(); + 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)); + base::RunLoop().RunUntilIdle(); + } + + void SetNoWifiAndGcd() { + handler_.reset( + new PrivetHandler(&cloud_, &device_, &security_, nullptr, &identity_)); + EXPECT_CALL(cloud_, GetCloudId()).WillRepeatedly(Return("")); + EXPECT_CALL(cloud_, GetConnectionState()) + .WillRepeatedly(ReturnRef(gcd_disabled_state_)); + auto set_error = + [](const std::string&, const std::string&, chromeos::ErrorPtr* error) { + chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain, + "setupUnavailable", ""); + }; + EXPECT_CALL(cloud_, Setup(_, _, _)) + .WillRepeatedly(DoAll(Invoke(set_error), Return(false))); + } + + testing::StrictMock<MockCloudDelegate> cloud_; + testing::StrictMock<MockDeviceDelegate> device_; + testing::StrictMock<MockSecurityDelegate> security_; + testing::StrictMock<MockWifiDelegate> wifi_; + testing::StrictMock<MockIdentityDelegate> identity_; + std::string auth_header_; + + private: + void HandlerCallback(int status, const base::DictionaryValue& output) { + output_.MergeDictionary(&output); + if (!output_.HasKey("error")) { + EXPECT_EQ(chromeos::http::status_code::Ok, status); + return; + } + EXPECT_NE(chromeos::http::status_code::Ok, status); + output_.SetInteger("error.http_status", status); + } + + static void HandlerNoFound(int status, const base::DictionaryValue&) { + EXPECT_EQ(status, 404); + } + + base::MessageLoop message_loop_; + std::unique_ptr<PrivetHandler> handler_; + base::DictionaryValue output_; + ConnectionState gcd_disabled_state_{ConnectionState::kDisabled}; +}; + +TEST_F(PrivetHandlerTest, UnknownApi) { + HandleUnknownRequest("/privet/foo"); +} + +TEST_F(PrivetHandlerTest, InvalidFormat) { + auth_header_ = ""; + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidFormat"), + HandleRequest("/privet/info", nullptr)); +} + +TEST_F(PrivetHandlerTest, MissingAuth) { + auth_header_ = ""; + EXPECT_PRED2(IsEqualError, CodeWithReason(401, "missingAuthorization"), + HandleRequest("/privet/info", "{}")); +} + +TEST_F(PrivetHandlerTest, InvalidAuth) { + auth_header_ = "foo"; + EXPECT_PRED2(IsEqualError, CodeWithReason(401, "invalidAuthorization"), + HandleRequest("/privet/info", "{}")); +} + +TEST_F(PrivetHandlerTest, ExpiredAuth) { + auth_header_ = "Privet 123"; + EXPECT_CALL(security_, ParseAccessToken(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time()), + Return(UserInfo{AuthScope::kOwner, 1}))); + EXPECT_PRED2(IsEqualError, CodeWithReason(403, "authorizationExpired"), + HandleRequest("/privet/info", "{}")); +} + +TEST_F(PrivetHandlerTest, InvalidAuthScope) { + EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthorizationScope"), + HandleRequest("/privet/v3/setup/start", "{}")); +} + +TEST_F(PrivetHandlerTest, InfoMinimal) { + SetNoWifiAndGcd(); + EXPECT_CALL(security_, GetPairingTypes()) + .WillRepeatedly(Return(std::set<PairingType>{})); + EXPECT_CALL(security_, GetCryptoTypes()) + .WillRepeatedly(Return(std::set<CryptoType>{})); + + const char kExpected[] = R"({ + 'version': '3.0', + 'id': 'TestId', + 'name': 'TestDevice', + 'services': [], + 'modelManifestId': "ABMID", + 'basicModelManifest': { + 'uiDeviceKind': 'developmentBoard', + 'oemName': 'Chromium', + 'modelName': 'Brillo' + }, + 'endpoints': { + 'httpPort': 0, + 'httpUpdatesPort': 0, + 'httpsPort': 0, + 'httpsUpdatesPort': 0 + }, + 'authentication': { + 'anonymousMaxScope': 'user', + 'mode': [ + 'anonymous', + 'pairing' + ], + 'pairing': [ + ], + 'crypto': [ + ] + }, + 'gcd': { + 'id': '', + 'status': 'disabled' + }, + 'uptime': 3600 + })"; + EXPECT_PRED2(IsEqualJson, kExpected, HandleRequest("/privet/info", "{}")); +} + +TEST_F(PrivetHandlerTest, Info) { + EXPECT_CALL(cloud_, GetDescription()) + .WillRepeatedly(Return("TestDescription")); + EXPECT_CALL(cloud_, GetLocation()).WillRepeatedly(Return("TestLocation")); + EXPECT_CALL(cloud_, GetServices()) + .WillRepeatedly(Return(std::set<std::string>{"service1", "service2"})); + 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': [ + "service1", + "service2" + ], + 'modelManifestId': "ABMID", + 'basicModelManifest': { + 'uiDeviceKind': 'developmentBoard', + 'oemName': 'Chromium', + 'modelName': 'Brillo' + }, + 'endpoints': { + 'httpPort': 80, + 'httpUpdatesPort': 10080, + 'httpsPort': 443, + 'httpsUpdatesPort': 10443 + }, + 'authentication': { + 'anonymousMaxScope': 'none', + 'mode': [ + 'anonymous', + 'pairing' + ], + 'pairing': [ + 'pinCode', + 'embeddedCode', + 'ultrasound32', + 'audible32' + ], + 'crypto': [ + 'p224_spake2', + 'p256_spake2' + ] + }, + 'wifi': { + 'capabilities': [ + '2.4GHz' + ], + 'ssid': 'TestSsid', + 'hostedSsid': 'Test_device.BBABCLAprv', + 'status': 'offline' + }, + 'gcd': { + 'id': 'TestCloudId', + 'status': 'online' + }, + 'uptime': 3600 + })"; + EXPECT_PRED2(IsEqualJson, kExpected, HandleRequest("/privet/info", "{}")); +} + +TEST_F(PrivetHandlerTest, PairingStartInvalidParams) { + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), + HandleRequest("/privet/v3/pairing/start", + "{'pairing':'embeddedCode','crypto':'crypto'}")); + + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), + HandleRequest("/privet/v3/pairing/start", + "{'pairing':'code','crypto':'p256_spake2'}")); +} + +TEST_F(PrivetHandlerTest, PairingStart) { + EXPECT_PRED2( + IsEqualJson, + "{'deviceCommitment': 'testCommitment', 'sessionId': 'testSession'}", + HandleRequest("/privet/v3/pairing/start", + "{'pairing': 'embeddedCode', 'crypto': 'p256_spake2'}")); +} + +TEST_F(PrivetHandlerTest, PairingConfirm) { + EXPECT_PRED2( + IsEqualJson, + "{'certFingerprint':'testFingerprint','certSignature':'testSignature'}", + HandleRequest( + "/privet/v3/pairing/confirm", + "{'sessionId':'testSession','clientCommitment':'testCommitment'}")); +} + +TEST_F(PrivetHandlerTest, PairingCancel) { + EXPECT_PRED2(IsEqualJson, "{}", + HandleRequest("/privet/v3/pairing/cancel", + "{'sessionId': 'testSession'}")); +} + +TEST_F(PrivetHandlerTest, AuthErrorNoType) { + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidAuthMode"), + HandleRequest("/privet/v3/auth", "{}")); +} + +TEST_F(PrivetHandlerTest, AuthErrorInvalidType) { + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidAuthMode"), + HandleRequest("/privet/v3/auth", "{'mode':'unknown'}")); +} + +TEST_F(PrivetHandlerTest, AuthErrorNoScope) { + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidRequestedScope"), + HandleRequest("/privet/v3/auth", "{'mode':'anonymous'}")); +} + +TEST_F(PrivetHandlerTest, AuthErrorInvalidScope) { + EXPECT_PRED2( + IsEqualError, CodeWithReason(400, "invalidRequestedScope"), + HandleRequest("/privet/v3/auth", + "{'mode':'anonymous','requestedScope':'unknown'}")); +} + +TEST_F(PrivetHandlerTest, AuthErrorAccessDenied) { + EXPECT_PRED2(IsEqualError, CodeWithReason(403, "accessDenied"), + HandleRequest("/privet/v3/auth", + "{'mode':'anonymous','requestedScope':'owner'}")); +} + +TEST_F(PrivetHandlerTest, AuthErrorInvalidAuthCode) { + EXPECT_CALL(security_, IsValidPairingCode("testToken")) + .WillRepeatedly(Return(false)); + const char kInput[] = R"({ + 'mode': 'pairing', + 'requestedScope': 'user', + 'authCode': 'testToken' + })"; + EXPECT_PRED2(IsEqualError, CodeWithReason(403, "invalidAuthCode"), + HandleRequest("/privet/v3/auth", kInput)); +} + +TEST_F(PrivetHandlerTest, AuthAnonymous) { + const char kExpected[] = R"({ + 'accessToken': 'GuestAccessToken', + 'expiresIn': 3600, + 'scope': 'user', + 'tokenType': 'Privet' + })"; + EXPECT_PRED2(IsEqualJson, kExpected, + HandleRequest("/privet/v3/auth", + "{'mode':'anonymous','requestedScope':'auto'}")); +} + +TEST_F(PrivetHandlerTest, AuthPairing) { + EXPECT_CALL(security_, IsValidPairingCode("testToken")) + .WillRepeatedly(Return(true)); + EXPECT_CALL(security_, CreateAccessToken(_, _)) + .WillRepeatedly(Return("OwnerAccessToken")); + const char kInput[] = R"({ + 'mode': 'pairing', + 'requestedScope': 'owner', + 'authCode': 'testToken' + })"; + const char kExpected[] = R"({ + 'accessToken': 'OwnerAccessToken', + 'expiresIn': 3600, + 'scope': 'owner', + 'tokenType': 'Privet' + })"; + EXPECT_PRED2( + IsEqualJson, kExpected, HandleRequest("/privet/v3/auth", kInput)); +} + +class PrivetHandlerSetupTest : public PrivetHandlerTest { + public: + void SetUp() override { + PrivetHandlerTest::SetUp(); + auth_header_ = "Privet 123"; + EXPECT_CALL(security_, ParseAccessToken(_, _)) + .WillRepeatedly(DoAll(SetArgPointee<1>(base::Time::Now()), + Return(UserInfo{AuthScope::kOwner, 1}))); + } +}; + +TEST_F(PrivetHandlerSetupTest, StatusEmpty) { + SetNoWifiAndGcd(); + EXPECT_PRED2( + IsEqualJson, "{}", 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_PRED2( + IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}")); +} + +TEST_F(PrivetHandlerSetupTest, StatusWifiError) { + chromeos::ErrorPtr error; + chromeos::Error::AddTo(&error, FROM_HERE, "test", "invalidPassphrase", ""); + wifi_.setup_state_ = SetupState{std::move(error)}; + + const char kExpected[] = R"({ + 'wifi': { + 'status': 'error', + 'error': { + 'code': 'invalidPassphrase' + } + } + })"; + EXPECT_PRED2( + IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}")); +} + +TEST_F(PrivetHandlerSetupTest, StatusGcd) { + cloud_.setup_state_ = SetupState{SetupState::kSuccess}; + + const char kExpected[] = R"({ + 'gcd': { + 'id': 'TestCloudId', + 'status': 'success' + } + })"; + EXPECT_PRED2( + IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}")); +} + +TEST_F(PrivetHandlerSetupTest, StatusGcdError) { + chromeos::ErrorPtr error; + chromeos::Error::AddTo(&error, FROM_HERE, "test", "invalidTicket", ""); + cloud_.setup_state_ = SetupState{std::move(error)}; + + const char kExpected[] = R"({ + 'gcd': { + 'status': 'error', + 'error': { + 'code': 'invalidTicket' + } + } + })"; + EXPECT_PRED2( + IsEqualJson, kExpected, 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_PRED2(IsEqualJson, "{}", + HandleRequest("/privet/v3/setup/start", kInput)); +} + +TEST_F(PrivetHandlerSetupTest, InvalidParams) { + const char kInputWifi[] = R"({ + 'wifi': { + 'ssid': '' + } + })"; + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), + HandleRequest("/privet/v3/setup/start", kInputWifi)); + + const char kInputRegistration[] = R"({ + 'gcd': { + 'ticketId': '' + } + })"; + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"), + HandleRequest("/privet/v3/setup/start", kInputRegistration)); +} + +TEST_F(PrivetHandlerSetupTest, WifiSetupUnavailable) { + SetNoWifiAndGcd(); + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "setupUnavailable"), + HandleRequest("/privet/v3/setup/start", "{'wifi': {}}")); +} + +TEST_F(PrivetHandlerSetupTest, WifiSetup) { + const char kInput[] = R"({ + 'wifi': { + 'ssid': 'testSsid', + 'passphrase': 'testPass' + } + })"; + auto set_error = [](const std::string&, const std::string&, + chromeos::ErrorPtr* error) { + chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain, "deviceBusy", ""); + }; + EXPECT_CALL(wifi_, ConfigureCredentials(_, _, _)) + .WillOnce(DoAll(Invoke(set_error), Return(false))); + EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"), + HandleRequest("/privet/v3/setup/start", kInput)); + + const char kExpected[] = R"({ + 'wifi': { + 'status': 'inProgress' + } + })"; + wifi_.setup_state_ = SetupState{SetupState::kInProgress}; + EXPECT_CALL(wifi_, ConfigureCredentials("testSsid", "testPass", _)) + .WillOnce(Return(true)); + EXPECT_PRED2(IsEqualJson, kExpected, + HandleRequest("/privet/v3/setup/start", kInput)); +} + +TEST_F(PrivetHandlerSetupTest, GcdSetupUnavailable) { + SetNoWifiAndGcd(); + const char kInput[] = R"({ + 'gcd': { + 'ticketId': 'testTicket', + 'user': 'testUser' + } + })"; + + EXPECT_PRED2(IsEqualError, CodeWithReason(400, "setupUnavailable"), + HandleRequest("/privet/v3/setup/start", kInput)); +} + +TEST_F(PrivetHandlerSetupTest, GcdSetup) { + const char kInput[] = R"({ + 'gcd': { + 'ticketId': 'testTicket', + 'user': 'testUser' + } + })"; + + auto set_error = [](const std::string&, const std::string&, + chromeos::ErrorPtr* error) { + chromeos::Error::AddTo(error, FROM_HERE, errors::kDomain, "deviceBusy", ""); + }; + EXPECT_CALL(cloud_, Setup(_, _, _)) + .WillOnce(DoAll(Invoke(set_error), Return(false))); + EXPECT_PRED2(IsEqualError, CodeWithReason(503, "deviceBusy"), + HandleRequest("/privet/v3/setup/start", kInput)); + + const char kExpected[] = R"({ + 'gcd': { + 'status': 'inProgress' + } + })"; + cloud_.setup_state_ = SetupState{SetupState::kInProgress}; + EXPECT_CALL(cloud_, Setup("testTicket", "testUser", _)) + .WillOnce(Return(true)); + EXPECT_PRED2(IsEqualJson, kExpected, + HandleRequest("/privet/v3/setup/start", kInput)); +} + +TEST_F(PrivetHandlerSetupTest, State) { + EXPECT_PRED2(IsEqualJson, "{'state': {'test': {}}, 'fingerprint': '0'}", + HandleRequest("/privet/v3/state", "{}")); + + cloud_.NotifyOnStateChanged(); + + EXPECT_PRED2(IsEqualJson, "{'state': {'test': {}}, 'fingerprint': '1'}", + HandleRequest("/privet/v3/state", "{}")); +} + +TEST_F(PrivetHandlerSetupTest, CommandsDefs) { + EXPECT_PRED2(IsEqualJson, "{'commands': {'test':{}}, 'fingerprint': '0'}", + HandleRequest("/privet/v3/commandDefs", "{}")); + + cloud_.NotifyOnCommandDefsChanged(); + + EXPECT_PRED2(IsEqualJson, "{'commands': {'test':{}}, 'fingerprint': '1'}", + HandleRequest("/privet/v3/commandDefs", "{}")); +} + +TEST_F(PrivetHandlerSetupTest, CommandsExecute) { + const char kInput[] = "{'name': 'test'}"; + base::DictionaryValue command; + LoadTestJson(kInput, &command); + LoadTestJson("{'id':'5'}", &command); + EXPECT_CALL(cloud_, AddCommand(_, _, _, _)) + .WillOnce(RunCallback<2, const base::DictionaryValue&>(command)); + + EXPECT_PRED2(IsEqualJson, "{'name':'test', 'id':'5'}", + HandleRequest("/privet/v3/commands/execute", kInput)); +} + +TEST_F(PrivetHandlerSetupTest, CommandsStatus) { + const char kInput[] = "{'id': '5'}"; + base::DictionaryValue command; + LoadTestJson(kInput, &command); + LoadTestJson("{'name':'test'}", &command); + EXPECT_CALL(cloud_, GetCommand(_, _, _, _)) + .WillOnce(RunCallback<2, const base::DictionaryValue&>(command)); + + EXPECT_PRED2(IsEqualJson, "{'name':'test', 'id':'5'}", + HandleRequest("/privet/v3/commands/status", kInput)); + + chromeos::ErrorPtr error; + chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain, "notFound", ""); + EXPECT_CALL(cloud_, GetCommand(_, _, _, _)) + .WillOnce(RunCallback<3>(error.get())); + + EXPECT_PRED2(IsEqualError, CodeWithReason(404, "notFound"), + HandleRequest("/privet/v3/commands/status", "{'id': '15'}")); +} + +TEST_F(PrivetHandlerSetupTest, CommandsCancel) { + const char kExpected[] = "{'id': '5', 'name':'test', 'state':'cancelled'}"; + base::DictionaryValue command; + LoadTestJson(kExpected, &command); + EXPECT_CALL(cloud_, CancelCommand(_, _, _, _)) + .WillOnce(RunCallback<2, const base::DictionaryValue&>(command)); + + EXPECT_PRED2(IsEqualJson, kExpected, + HandleRequest("/privet/v3/commands/cancel", "{'id': '8'}")); + + chromeos::ErrorPtr error; + chromeos::Error::AddTo(&error, FROM_HERE, errors::kDomain, "notFound", ""); + EXPECT_CALL(cloud_, CancelCommand(_, _, _, _)) + .WillOnce(RunCallback<3>(error.get())); + + EXPECT_PRED2(IsEqualError, CodeWithReason(404, "notFound"), + HandleRequest("/privet/v3/commands/cancel", "{'id': '11'}")); +} + +TEST_F(PrivetHandlerSetupTest, CommandsList) { + const char kExpected[] = R"({ + 'commands' : [ + {'id':'5', 'state':'cancelled'}, + {'id':'15', 'state':'inProgress'} + ]})"; + + base::DictionaryValue commands; + LoadTestJson(kExpected, &commands); + + EXPECT_CALL(cloud_, ListCommands(_, _, _)) + .WillOnce(RunCallback<1, const base::DictionaryValue&>(commands)); + + EXPECT_PRED2(IsEqualJson, kExpected, + HandleRequest("/privet/v3/commands/list", "{}")); +} + +} // namespace privetd