blob: 0c9f158644024a61decfbd315b8e552a89cba7f8 [file] [log] [blame]
// 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