// 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/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) {
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)
.release());
EXPECT_TRUE(value.get()) << "\nError: " << message << "\n" << 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 << "{" << 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) {
std::unique_ptr<base::DictionaryValue> result{value.DeepCopy()};
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 = [](const std::string&, const std::string&,
ErrorPtr* error) {
Error::AddTo(error, FROM_HERE, "setupUnavailable", "");
};
EXPECT_CALL(cloud_, Setup(_, _, _))
.WillRepeatedly(DoAll(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': '',
'status': 'disabled'
},
'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',
'status': 'online'
},
'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",
"{'pairing':'embeddedCode','crypto':'crypto'}"));
EXPECT_PRED2(IsEqualError, CodeWithReason(400, "invalidParams"),
HandleRequest("/privet/v3/pairing/start",
"{'pairing':'code','crypto':'p224_spake2'}"));
}
TEST_F(PrivetHandlerTest, PairingStart) {
EXPECT_JSON_EQ(
"{'deviceCommitment': 'testCommitment', 'sessionId': 'testSession'}",
HandleRequest("/privet/v3/pairing/start",
"{'pairing': 'embeddedCode', 'crypto': 'p224_spake2'}"));
}
TEST_F(PrivetHandlerTest, PairingConfirm) {
EXPECT_JSON_EQ(
"{'certFingerprint':'testFingerprint','certSignature':'testSignature'}",
HandleRequest(
"/privet/v3/pairing/confirm",
"{'sessionId':'testSession','clientCommitment':'testCommitment'}"));
}
TEST_F(PrivetHandlerTest, PairingCancel) {
EXPECT_JSON_EQ("{}", 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) {
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",
"{'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));
}
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',
'status': 'success'
}
})";
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", "{'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 = [](const std::string&, const std::string&, ErrorPtr* error) {
return Error::AddTo(error, FROM_HERE, "deviceBusy", "");
};
EXPECT_CALL(cloud_, Setup(_, _, _)).WillOnce(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("testTicket", "testUser", _))
.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("{'clientToken': 'RootClientAuthToken'}",
HandleRequest("/privet/v3/accessControl/claim", "{}"));
}
TEST_F(PrivetHandlerTestWithAuth, ConfirmAccessControl) {
EXPECT_JSON_EQ("{}",
HandleRequest("/privet/v3/accessControl/confirm",
"{'clientToken': 'DerivedClientAuthToken'}"));
}
TEST_F(PrivetHandlerTestWithAuth, State) {
EXPECT_JSON_EQ("{'state': {'test': {}}, 'fingerprint': '1'}",
HandleRequest("/privet/v3/state", "{}"));
cloud_.NotifyOnStateChanged();
EXPECT_JSON_EQ("{'state': {'test': {}}, 'fingerprint': '2'}",
HandleRequest("/privet/v3/state", "{}"));
}
TEST_F(PrivetHandlerTestWithAuth, CommandsDefs) {
EXPECT_JSON_EQ("{'commands': {'test':{}}, 'fingerprint': '1'}",
HandleRequest("/privet/v3/commandDefs", "{}"));
cloud_.NotifyOnTraitDefsChanged();
EXPECT_JSON_EQ("{'commands': {'test':{}}, 'fingerprint': '2'}",
HandleRequest("/privet/v3/commandDefs", "{}"));
}
TEST_F(PrivetHandlerTestWithAuth, Traits) {
EXPECT_JSON_EQ("{'traits': {'test': {}}, 'fingerprint': '1'}",
HandleRequest("/privet/v3/traits", "{}"));
cloud_.NotifyOnTraitDefsChanged();
EXPECT_JSON_EQ("{'traits': {'test': {}}, 'fingerprint': '2'}",
HandleRequest("/privet/v3/traits", "{}"));
}
TEST_F(PrivetHandlerTestWithAuth, Components) {
EXPECT_JSON_EQ("{'components': {'test': {}}, 'fingerprint': '1'}",
HandleRequest("/privet/v3/components", "{}"));
cloud_.NotifyOnComponentTreeChanged();
EXPECT_JSON_EQ("{'components': {'test': {}}, 'fingerprint': '2'}",
HandleRequest("/privet/v3/components", "{}"));
// State change will also change the components fingerprint.
cloud_.NotifyOnStateChanged();
EXPECT_JSON_EQ("{'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_, GetComponents()).WillRepeatedly(ReturnRef(components));
const char kExpected1[] = R"({
"components": {
"comp1": {
"state": {
"a" : {
"prop": 1
}
}
}
},
"fingerprint": "1"
})";
EXPECT_JSON_EQ(kExpected1, HandleRequest("/privet/v3/components",
"{'filter':['state']}"));
const char kExpected2[] = R"({
"components": {
"comp1": {
"traits": ["a", "b"]
}
},
"fingerprint": "1"
})";
EXPECT_JSON_EQ(kExpected2, HandleRequest("/privet/v3/components",
"{'filter':['traits']}"));
const char kExpected3[] = R"({
"components": {
"comp1": {
"components": {
"comp2": {
"components": {
"comp4": {}
}
},
"comp3": {}
}
}
},
"fingerprint": "1"
})";
EXPECT_JSON_EQ(kExpected3, HandleRequest("/privet/v3/components",
"{'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",
"{'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",
"{'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",
"{'path':'comp7', 'filter':['traits', 'components']}"));
}
TEST_F(PrivetHandlerTestWithAuth, CommandsExecute) {
const char kInput[] = "{'name': 'test'}";
base::DictionaryValue command;
LoadTestJson(kInput, &command);
LoadTestJson("{'id':'5'}", &command);
EXPECT_CALL(cloud_, AddCommand(_, _, _))
.WillOnce(WithArgs<2>(Invoke(
[&command](const CloudDelegate::CommandDoneCallback& callback) {
callback.Run(command, nullptr);
})));
EXPECT_JSON_EQ("{'name':'test', 'id':'5'}",
HandleRequest("/privet/v3/commands/execute", kInput));
}
TEST_F(PrivetHandlerTestWithAuth, CommandsStatus) {
const char kInput[] = "{'id': '5'}";
base::DictionaryValue command;
LoadTestJson(kInput, &command);
LoadTestJson("{'name':'test'}", &command);
EXPECT_CALL(cloud_, GetCommand(_, _, _))
.WillOnce(WithArgs<2>(Invoke(
[&command](const CloudDelegate::CommandDoneCallback& callback) {
callback.Run(command, nullptr);
})));
EXPECT_JSON_EQ("{'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", "{'id': '15'}"));
}
TEST_F(PrivetHandlerTestWithAuth, CommandsCancel) {
const char kExpected[] = "{'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", "{'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", "{'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