blob: d7a97cdce4ecf184dda7b82874ea42d14e74b12b [file] [log] [blame]
// 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 "privetd/privet_handler.h"
#include <string>
#include <utility>
#include <vector>
#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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "privetd/cloud_delegate.h"
#include "privetd/device_delegate.h"
#include "privetd/security_delegate.h"
#include "privetd/wifi_delegate.h"
using testing::_;
using testing::Return;
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 IsEqualJson(const std::string& test_json,
const base::DictionaryValue& dictionary) {
base::DictionaryValue dictionary2;
LoadTestJson(test_json, &dictionary2);
return dictionary2.Equals(&dictionary);
}
} // namespace
class MockPrivetHandlerDelegate : public DeviceDelegate,
public WifiDelegate,
public CloudDelegate,
public SecurityDelegate {
using IntPair = std::pair<int, int>;
public:
// Device
MOCK_CONST_METHOD0(GetId, std::string());
MOCK_CONST_METHOD0(GetName, std::string());
MOCK_CONST_METHOD0(GetDescription, std::string());
MOCK_CONST_METHOD0(GetLocation, std::string());
MOCK_CONST_METHOD0(GetTypes, std::vector<std::string>());
MOCK_CONST_METHOD0(GetHttpEnpoint, IntPair());
MOCK_CONST_METHOD0(GetHttpsEnpoint, IntPair());
MOCK_CONST_METHOD0(GetUptime, base::TimeDelta());
MOCK_METHOD1(SetName, void(const std::string&));
MOCK_METHOD1(SetDescription, void(const std::string&));
MOCK_METHOD1(SetLocation, void(const std::string&));
// Wifi
MOCK_CONST_METHOD0(GetWifiSsid, std::string());
MOCK_CONST_METHOD0(GetWifiTypes, std::vector<WifiType>());
MOCK_CONST_METHOD0(IsWifiRequired, bool());
MOCK_CONST_METHOD0(GetWifiSetupState, WifiSetupState());
MOCK_METHOD2(SetupWifi, bool(const std::string&, const std::string&));
// Cloud
MOCK_CONST_METHOD0(IsRegistrationRequired, bool());
MOCK_CONST_METHOD0(GetCloudId, std::string());
MOCK_CONST_METHOD0(GetConnectionState, CloudState());
MOCK_CONST_METHOD0(GetRegistrationState, RegistrationState());
MOCK_METHOD2(RegisterDevice, bool(const std::string&, const std::string&));
// Security
MOCK_CONST_METHOD1(CreateAccessToken, std::string(AuthScope));
MOCK_CONST_METHOD2(GetScopeFromAccessToken,
AuthScope(const std::string&, const base::Time&));
MOCK_CONST_METHOD0(GetPairingTypes, std::vector<PairingType>());
MOCK_CONST_METHOD1(IsValidPairingCode, bool(const std::string&));
};
class PrivetHandlerTest : public testing::Test {
public:
PrivetHandlerTest()
: handler_(&delegate_, &delegate_, &delegate_, &delegate_) {}
protected:
void SetUp() override { auth_header_ = "Privet anonymous"; }
const base::DictionaryValue& HandleRequest(const std::string& api,
const std::string& json_input) {
output_.Clear();
base::DictionaryValue dictionary;
LoadTestJson(json_input, &dictionary);
EXPECT_TRUE(
handler_.HandleRequest(api,
auth_header_,
dictionary,
base::Bind(&PrivetHandlerTest::HandlerCallback,
base::Unretained(this))));
base::RunLoop().RunUntilIdle();
return output_;
}
bool HandleUnknownRequest(const std::string& api) {
output_.Clear();
base::DictionaryValue dictionary;
return handler_.HandleRequest(
api, auth_header_, dictionary, PrivetHandler::RequestCallback());
}
testing::StrictMock<MockPrivetHandlerDelegate> delegate_;
std::string auth_header_;
private:
void HandlerCallback(const base::DictionaryValue& output, bool success) {
EXPECT_NE(output.HasKey("reason"), success);
output_.MergeDictionary(&output);
}
base::MessageLoop message_loop_;
PrivetHandler handler_;
base::DictionaryValue output_;
};
TEST_F(PrivetHandlerTest, UnknownApi) {
EXPECT_FALSE(HandleUnknownRequest("/privet/foo"));
}
TEST_F(PrivetHandlerTest, MissingAuth) {
auth_header_ = "";
EXPECT_PRED2(IsEqualJson,
"{'reason': 'missingAuthorization'}",
HandleRequest("/privet/info", "{}"));
}
TEST_F(PrivetHandlerTest, InvalidAuth) {
auth_header_ = "foo";
EXPECT_PRED2(IsEqualJson,
"{'reason': 'invalidAuthorization'}",
HandleRequest("/privet/info", "{}"));
}
TEST_F(PrivetHandlerTest, InvalidAuthScope) {
EXPECT_PRED2(IsEqualJson,
"{'reason': 'invalidAuthorizationScope'}",
HandleRequest("/privet/v3/setup/start", "{}"));
}
TEST_F(PrivetHandlerTest, Info) {
EXPECT_CALL(delegate_, GetId()).WillRepeatedly(Return("TestId"));
EXPECT_CALL(delegate_, GetName()).WillRepeatedly(Return("TestDevice"));
EXPECT_CALL(delegate_, GetDescription())
.WillRepeatedly(Return("TestDescription"));
EXPECT_CALL(delegate_, GetLocation()).WillRepeatedly(Return("TestLocation"));
EXPECT_CALL(delegate_, GetTypes())
.WillRepeatedly(Return(std::vector<std::string>(1, "TestType")));
EXPECT_CALL(delegate_, GetCloudId()).WillRepeatedly(Return("TestCloudId"));
EXPECT_CALL(delegate_, GetConnectionState())
.WillRepeatedly(Return(CloudState::kOnline));
EXPECT_CALL(delegate_, GetHttpEnpoint())
.WillRepeatedly(Return(std::make_pair(80, 10080)));
EXPECT_CALL(delegate_, GetHttpsEnpoint())
.WillRepeatedly(Return(std::make_pair(443, 10443)));
EXPECT_CALL(delegate_, GetWifiSsid()).WillRepeatedly(Return("TestSsid"));
EXPECT_CALL(delegate_, GetUptime())
.WillRepeatedly(Return(base::TimeDelta::FromHours(1)));
EXPECT_CALL(delegate_, GetWifiTypes())
.WillRepeatedly(Return(std::vector<WifiType>(1, WifiType::kWifi24)));
EXPECT_CALL(delegate_, GetPairingTypes())
.WillRepeatedly(
Return(std::vector<PairingType>(1, PairingType::kEmbeddedCode)));
const char kExpected[] = R"({
'version': '3.0',
'id': 'TestId',
'name': 'TestDevice',
'description': 'TestDescription',
'location': 'TestLocation',
'type': [
'TestType'
],
'authentication': [
'anonymous',
'pairing',
'cloud'
],
'cloudId': 'TestCloudId',
'cloudConnection': 'online',
'capabilities': {
'wireless': [
'wifi2.4'
],
'pairing': [
'embeddedCode'
]
},
'endpoints': {
'httpPort': 80,
'httpUpdatesPort': 10080,
'httpsPort': 443,
'httpsUpdatesPort': 10443
},
'wifiSSID':'TestSsid',
'uptime': 3600,
'api': [
'/privet/info',
'/privet/v3/auth',
'/privet/v3/setup/start',
'/privet/v3/setup/status'
]
})";
EXPECT_PRED2(IsEqualJson, kExpected, HandleRequest("/privet/info", "{}"));
}
TEST_F(PrivetHandlerTest, AuthErrorNoType) {
EXPECT_PRED2(IsEqualJson,
"{'reason': 'invalidAuthCodeType'}",
HandleRequest("/privet/v3/auth", "{}"));
}
TEST_F(PrivetHandlerTest, AuthErrorInvalidType) {
EXPECT_PRED2(IsEqualJson,
"{'reason':'invalidAuthCodeType'}",
HandleRequest("/privet/v3/auth", "{'authCodeType':'unknown'}"));
}
TEST_F(PrivetHandlerTest, AuthErrorNoScope) {
EXPECT_PRED2(
IsEqualJson,
"{'reason':'invalidRequestedScope'}",
HandleRequest("/privet/v3/auth", "{'authCodeType':'anonymous'}"));
}
TEST_F(PrivetHandlerTest, AuthErrorInvalidScope) {
EXPECT_PRED2(
IsEqualJson,
"{'reason':'invalidRequestedScope'}",
HandleRequest("/privet/v3/auth",
"{'authCodeType':'anonymous','requestedScope':'unknown'}"));
}
TEST_F(PrivetHandlerTest, AuthErrorAccessDenied) {
// TODO(vitalybuka): Should fail when pairing is implemented.
EXPECT_CALL(delegate_, CreateAccessToken(AuthScope::kOwner))
.WillRepeatedly(Return("TestAccessToken"));
EXPECT_PRED2(
std::not2(std::ref(IsEqualJson)),
"{'reason':'accessDenied'}",
HandleRequest("/privet/v3/auth",
"{'authCodeType':'anonymous','requestedScope':'owner'}"));
}
TEST_F(PrivetHandlerTest, AuthErrorInvalidAuthCode) {
EXPECT_CALL(delegate_, IsValidPairingCode("testToken"))
.WillRepeatedly(Return(false));
const char kInput[] = R"({
'authCodeType': 'pairing',
'requestedScope': 'user',
'authCode': 'testToken'
})";
EXPECT_PRED2(IsEqualJson,
"{'reason':'invalidAuthCode'}",
HandleRequest("/privet/v3/auth", kInput));
}
TEST_F(PrivetHandlerTest, AuthAnonymous) {
EXPECT_CALL(delegate_, CreateAccessToken(AuthScope::kOwner))
.WillRepeatedly(Return("TestAccessToken"));
// TODO(vitalybuka): Should have anonymous scope when pairing is implemented.
const char kExpected[] = R"({
'accessToken': 'TestAccessToken',
'expiresIn': 3600,
'scope': 'owner',
'tokenType': 'Privet'
})";
EXPECT_PRED2(
IsEqualJson,
kExpected,
HandleRequest("/privet/v3/auth",
"{'authCodeType':'anonymous','requestedScope':'auto'}"));
}
TEST_F(PrivetHandlerTest, AuthPairing) {
EXPECT_CALL(delegate_, CreateAccessToken(AuthScope::kOwner))
.WillRepeatedly(Return("TestAccessToken"));
EXPECT_CALL(delegate_, IsValidPairingCode("testToken"))
.WillRepeatedly(Return(true));
const char kInput[] = R"({
'authCodeType': 'pairing',
'requestedScope': 'owner',
'authCode': 'testToken'
})";
const char kExpected[] = R"({
'accessToken': 'TestAccessToken',
'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(delegate_, GetScopeFromAccessToken(_, _))
.WillRepeatedly(Return(AuthScope::kOwner));
EXPECT_CALL(delegate_, GetConnectionState())
.WillRepeatedly(Return(CloudState::kDisabled));
EXPECT_CALL(delegate_, GetWifiTypes())
.WillRepeatedly(Return(std::vector<WifiType>()));
}
void SetupWifiStatus() {
EXPECT_CALL(delegate_, GetWifiTypes())
.WillRepeatedly(Return(std::vector<WifiType>(1, WifiType::kWifi24)));
EXPECT_CALL(delegate_, IsWifiRequired()).WillRepeatedly(Return(true));
EXPECT_CALL(delegate_, GetWifiSetupState())
.WillRepeatedly(Return(WifiSetupState::kCompleted));
EXPECT_CALL(delegate_, GetWifiSsid()).WillRepeatedly(Return("TestSsid"));
}
void SetupRegistrationStatus() {
EXPECT_CALL(delegate_, GetConnectionState())
.WillRepeatedly(Return(CloudState::kOnline));
EXPECT_CALL(delegate_, IsRegistrationRequired())
.WillRepeatedly(Return(false));
EXPECT_CALL(delegate_, GetRegistrationState())
.WillRepeatedly(Return(RegistrationState::kCompleted));
EXPECT_CALL(delegate_, GetCloudId()).WillRepeatedly(Return("TestCloudId"));
}
};
TEST_F(PrivetHandlerSetupTest, StatusEmpty) {
EXPECT_PRED2(
IsEqualJson, "{}", HandleRequest("/privet/v3/setup/status", "{}"));
}
TEST_F(PrivetHandlerSetupTest, StatusWifi) {
SetupWifiStatus();
const char kExpected[] = R"({
'wifi': {
'required': true,
'ssid': 'TestSsid',
'status': 'complete'
}
})";
EXPECT_PRED2(
IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
}
TEST_F(PrivetHandlerSetupTest, StatusWifiError) {
SetupWifiStatus();
EXPECT_CALL(delegate_, GetWifiSetupState())
.WillRepeatedly(Return(WifiSetupState::kInvalidPassword));
const char kExpected[] = R"({
'wifi': {
'required': true,
'status': 'error',
'error': {
'reason': 'invalidPassphrase'
}
}
})";
EXPECT_PRED2(
IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
}
TEST_F(PrivetHandlerSetupTest, StatusRegistration) {
SetupRegistrationStatus();
const char kExpected[] = R"({
'registration': {
'required': false,
'id': 'TestCloudId',
'status': 'complete'
}
})";
EXPECT_PRED2(
IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
}
TEST_F(PrivetHandlerSetupTest, StatusRegistrationError) {
SetupRegistrationStatus();
EXPECT_CALL(delegate_, GetRegistrationState())
.WillRepeatedly(Return(RegistrationState::kInvalidTicket));
const char kExpected[] = R"({
'registration': {
'required': false,
'status': 'error',
'error': {
'reason': 'invalidTicket'
}
}
})";
EXPECT_PRED2(
IsEqualJson, kExpected, HandleRequest("/privet/v3/setup/status", "{}"));
}
TEST_F(PrivetHandlerSetupTest, StartNameDescriptionLocation) {
EXPECT_CALL(delegate_, SetName("testName")).Times(1);
EXPECT_CALL(delegate_, SetDescription("testDescription")).Times(1);
EXPECT_CALL(delegate_, SetLocation("testLocation")).Times(1);
const char kInput[] = R"({
'name': 'testName',
'description': 'testDescription',
'location': 'testLocation'
})";
SetupWifiStatus();
SetupRegistrationStatus();
EXPECT_FALSE(HandleRequest("/privet/v3/setup/start", kInput).empty());
}
TEST_F(PrivetHandlerSetupTest, InvalidParams) {
EXPECT_CALL(delegate_, GetRegistrationState())
.WillRepeatedly(Return(RegistrationState::kAvalible));
EXPECT_CALL(delegate_, GetWifiTypes())
.WillRepeatedly(Return(std::vector<WifiType>(1, WifiType::kWifi50)));
const char kInputWifi[] = R"({
'wifi': {
'ssid': ''
}
})";
EXPECT_PRED2(IsEqualJson,
"{'reason':'invalidParams'}",
HandleRequest("/privet/v3/setup/start", kInputWifi));
const char kInputRegistration[] = R"({
'registration': {
'ticketID': ''
}
})";
EXPECT_PRED2(IsEqualJson,
"{'reason':'invalidParams'}",
HandleRequest("/privet/v3/setup/start", kInputRegistration));
}
TEST_F(PrivetHandlerSetupTest, WifiSetup) {
const char kInput[] = R"({
'wifi': {
'ssid': 'testSsid',
'passphrase': 'testPass'
}
})";
EXPECT_PRED2(IsEqualJson,
"{'reason':'setupUnavailable'}",
HandleRequest("/privet/v3/setup/start", kInput));
SetupWifiStatus();
EXPECT_CALL(delegate_, SetupWifi(_, _)).WillOnce(Return(false));
EXPECT_PRED2(IsEqualJson,
"{'reason':'deviceBusy'}",
HandleRequest("/privet/v3/setup/start", kInput));
EXPECT_CALL(delegate_, SetupWifi("testSsid", "testPass"))
.WillOnce(Return(true));
EXPECT_FALSE(HandleRequest("/privet/v3/setup/start", kInput).empty());
}
TEST_F(PrivetHandlerSetupTest, Registration) {
const char kInput[] = R"({
'registration': {
'ticketID': 'testTicket',
'user': 'testUser'
}
})";
EXPECT_CALL(delegate_, GetRegistrationState())
.WillRepeatedly(Return(RegistrationState::kCompleted));
EXPECT_PRED2(IsEqualJson,
"{'reason':'setupUnavailable'}",
HandleRequest("/privet/v3/setup/start", kInput));
SetupRegistrationStatus();
EXPECT_CALL(delegate_, GetRegistrationState())
.WillRepeatedly(Return(RegistrationState::kAvalible));
EXPECT_CALL(delegate_, RegisterDevice(_, _)).WillOnce(Return(false));
EXPECT_PRED2(IsEqualJson,
"{'reason':'deviceBusy'}",
HandleRequest("/privet/v3/setup/start", kInput));
EXPECT_CALL(delegate_, RegisterDevice("testTicket", "testUser"))
.WillOnce(Return(true));
EXPECT_FALSE(HandleRequest("/privet/v3/setup/start", kInput).empty());
}
} // namespace privetd