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