Chromium Code Reviews| Index: remoting/host/setup/native_messaging_host_unittest.cc |
| diff --git a/remoting/host/setup/native_messaging_host_unittest.cc b/remoting/host/setup/native_messaging_host_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..7944e9d70818044acbc3db51bcf89edcb332d7bd |
| --- /dev/null |
| +++ b/remoting/host/setup/native_messaging_host_unittest.cc |
| @@ -0,0 +1,404 @@ |
| +// Copyright 2013 The Chromium 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 "base/compiler_specific.h" |
| +#include "base/file_util.h" |
| +#include "base/json/json_reader.h" |
| +#include "base/json/json_writer.h" |
| +#include "base/message_loop.h" |
| +#include "base/run_loop.h" |
| +#include "base/stl_util.h" |
| +#include "base/strings/stringize_macros.h" |
| +#include "base/values.h" |
| +#include "net/base/file_stream.h" |
| +#include "net/base/net_util.h" |
| +#include "remoting/host/pin_hash.h" |
| +#include "remoting/host/setup/mock_daemon_controller.h" |
| +#include "remoting/host/setup/native_messaging_host.h" |
|
Sergey Ulanov
2013/05/18 01:59:31
nit: this should be first include for this file.
Lambros
2013/05/22 21:42:18
Done.
|
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +namespace { |
| + |
| +void VerifyHelloResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "helloResponse"); |
| + EXPECT_TRUE(response->GetString("version", &value)); |
| + EXPECT_EQ(value, STRINGIZE(VERSION)); |
| +} |
| + |
| +void VerifyGetHostNameResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "getHostNameResponse"); |
| + EXPECT_TRUE(response->GetString("hostname", &value)); |
| + EXPECT_EQ(value, net::GetHostName()); |
| +} |
| + |
| +void VerifyGetPinHashResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "getPinHashResponse"); |
| + EXPECT_TRUE(response->GetString("hash", &value)); |
| + EXPECT_EQ(value, remoting::MakeHostPinHash("my_host", "1234")); |
| +} |
| + |
| +void VerifyGenerateKeyPairResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "generateKeyPairResponse"); |
| + EXPECT_TRUE(response->GetString("private_key", &value)); |
| + EXPECT_TRUE(response->GetString("public_key", &value)); |
| +} |
| + |
| +void VerifyGetDaemonConfigResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "getDaemonConfigResponse"); |
| + EXPECT_TRUE(response->GetString("config", &value)); |
| + EXPECT_EQ(value, "{}"); |
| +} |
| + |
| +void VerifyGetUsageStatsConsentResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "getUsageStatsConsentResponse"); |
| + bool supported, allowed, set_by_policy; |
| + EXPECT_TRUE(response->GetBoolean("supported", &supported)); |
| + EXPECT_TRUE(response->GetBoolean("allowed", &allowed)); |
| + EXPECT_TRUE(response->GetBoolean("set_by_policy", &set_by_policy)); |
| + EXPECT_TRUE(supported); |
| + EXPECT_TRUE(allowed); |
| + EXPECT_TRUE(set_by_policy); |
| +} |
| + |
| +void VerifyStopDaemonResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "stopDaemonResponse"); |
| + int result; |
| + EXPECT_TRUE(response->GetInteger("result", &result)); |
| + EXPECT_EQ(result, 0); |
| +} |
| + |
| +void VerifyGetDaemonStateResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "getDaemonStateResponse"); |
| + int result; |
| + EXPECT_TRUE(response->GetInteger("state", &result)); |
| + EXPECT_EQ(result, 4); |
| +} |
| + |
| +void VerifyUpdateDaemonConfigResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "updateDaemonConfigResponse"); |
| + int result; |
| + EXPECT_TRUE(response->GetInteger("result", &result)); |
| + EXPECT_EQ(result, 0); |
| +} |
| + |
| +void VerifyStartDaemonResponse(const base::DictionaryValue* response) { |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_TRUE(response->GetString("type", &value)); |
| + EXPECT_EQ(value, "startDaemonResponse"); |
| + int result; |
| + EXPECT_TRUE(response->GetInteger("result", &result)); |
| + EXPECT_EQ(result, 0); |
| +} |
| + |
| +scoped_ptr<base::DictionaryValue> ReadMessageFromFile( |
|
Sergey Ulanov
2013/05/18 01:59:31
Can you use FileStreams to read from files?
Lambros
2013/05/22 21:42:18
Probably, but not sure if there's any advantage, o
|
| + base::PlatformFile handle) { |
| + uint32 length; |
| + int read_result = base::ReadPlatformFileAtCurrentPos( |
| + handle, reinterpret_cast<char*>(&length), sizeof(length)); |
| + if (read_result != sizeof(length)) { |
| + return scoped_ptr<base::DictionaryValue>(); |
| + } |
| + |
| + std::string message_json(length, '\0'); |
| + read_result = base::ReadPlatformFileAtCurrentPos( |
| + handle, string_as_array(&message_json), length); |
| + if (read_result != static_cast<int>(length)) { |
| + return scoped_ptr<base::DictionaryValue>(); |
| + } |
| + |
| + scoped_ptr<base::Value> message(base::JSONReader::Read(message_json)); |
| + if (!message || !message->IsType(base::Value::TYPE_DICTIONARY)) { |
| + return scoped_ptr<base::DictionaryValue>(); |
| + } |
| + |
| + return scoped_ptr<base::DictionaryValue>( |
| + static_cast<base::DictionaryValue*>(message.release())); |
| +} |
| + |
| +} // namespace |
| + |
| +namespace remoting { |
| + |
| +class NativeMessagingHostTest : public testing::Test { |
| + public: |
| + NativeMessagingHostTest(); |
| + virtual ~NativeMessagingHostTest(); |
| + |
| + virtual void SetUp() OVERRIDE; |
| + virtual void TearDown() OVERRIDE; |
| + |
| + void Run(); |
| + |
| + void WriteMessageToInputFile(const base::Value& message); |
| + |
| + // The Host process should shut down when it receives a malformed request. |
| + // This is tested by sending a known-good request, followed by |message|, |
| + // followed by the known-good request again. The response file should only |
| + // contain a single response from the first good request. |
| + void TestBadRequest(const base::Value& message); |
| + |
| + base::FilePath output_path() const { return output_path_; } |
| + |
| + private: |
| + base::FilePath input_path_; |
| + base::PlatformFile input_handle_; |
| + base::FilePath output_path_; |
| + base::PlatformFile output_handle_; |
| + |
| + base::MessageLoop message_loop_; |
| + base::RunLoop run_loop_; |
| + scoped_ptr<remoting::NativeMessagingHost> host_; |
| +}; |
| + |
| +NativeMessagingHostTest::NativeMessagingHostTest() |
| + : message_loop_(base::MessageLoop::TYPE_IO) {} |
| + |
| +NativeMessagingHostTest::~NativeMessagingHostTest() {} |
| + |
| +void NativeMessagingHostTest::SetUp() { |
| + file_util::CreateTemporaryFile(&input_path_); |
| + input_handle_ = base::CreatePlatformFile( |
| + input_path_, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, |
| + NULL); |
| + |
| + file_util::CreateTemporaryFile(&output_path_); |
| + output_handle_ = base::CreatePlatformFile( |
| + output_path_, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE, NULL, |
| + NULL); |
| + |
| + host_.reset(new NativeMessagingHost(input_handle_, output_handle_, |
| + message_loop_.message_loop_proxy(), |
| + run_loop_.QuitClosure())); |
| + host_->SetDaemonControllerForTest( |
| + scoped_ptr<DaemonController>(new MockDaemonController())); |
| +} |
| + |
| +void NativeMessagingHostTest::TearDown() { |
| + base::ClosePlatformFile(input_handle_); |
| + base::ClosePlatformFile(output_handle_); |
| + EXPECT_TRUE(file_util::Delete(input_path_, false)); |
| + EXPECT_TRUE(file_util::Delete(output_path_, false)); |
| +} |
| + |
| +void NativeMessagingHostTest::Run() { |
| + host_->Start(); |
| + run_loop_.Run(); |
| +} |
| + |
| +void NativeMessagingHostTest::WriteMessageToInputFile( |
| + const base::Value& message) { |
| + std::string message_json; |
| + base::JSONWriter::Write(&message, &message_json); |
| + |
| + uint32 length = message_json.length(); |
| + file_util::AppendToFile(input_path_, reinterpret_cast<char*>(&length), |
| + sizeof(length)); |
| + file_util::AppendToFile(input_path_, message_json.data(), length); |
| +} |
| + |
| +void NativeMessagingHostTest::TestBadRequest(const base::Value& message) { |
| + base::DictionaryValue good_message; |
| + good_message.SetString("type", "hello"); |
| + |
| + WriteMessageToInputFile(good_message); |
| + WriteMessageToInputFile(message); |
| + WriteMessageToInputFile(good_message); |
| + |
| + Run(); |
| + |
| + // Read from output file, and verify responses. |
| + base::PlatformFile handle = base::CreatePlatformFile( |
| + output_path(), base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, |
| + NULL); |
| + |
| + scoped_ptr<base::DictionaryValue> response = ReadMessageFromFile(handle); |
| + VerifyHelloResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + EXPECT_FALSE(response); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, All) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "hello"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetString("type", "getHostName"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetString("type", "getPinHash"); |
| + message.SetString("hostId", "my_host"); |
| + message.SetString("pin", "1234"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.Clear(); |
| + message.SetString("type", "generateKeyPair"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetString("type", "getDaemonConfig"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetString("type", "getUsageStatsConsent"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetString("type", "stopDaemon"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetString("type", "getDaemonState"); |
| + WriteMessageToInputFile(message); |
| + |
| + // Following messages require a "config" dictionary. |
| + message.SetString("config", "{}"); |
| + message.SetString("type", "updateDaemonConfig"); |
| + WriteMessageToInputFile(message); |
| + |
| + message.SetBoolean("consent", true); |
| + message.SetString("type", "startDaemon"); |
| + WriteMessageToInputFile(message); |
| + |
| + Run(); |
| + |
| + // Read from output file, and verify responses. |
| + base::PlatformFile handle = base::CreatePlatformFile( |
| + output_path(), base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, |
| + NULL); |
| + |
| + scoped_ptr<base::DictionaryValue> response = ReadMessageFromFile(handle); |
| + VerifyHelloResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyGetHostNameResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyGetPinHashResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyGenerateKeyPairResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyGetDaemonConfigResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyGetUsageStatsConsentResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyStopDaemonResponse(response.get()); |
|
Sergey Ulanov
2013/05/18 01:59:31
This doesn't really verify that daemon controller
Lambros
2013/05/22 21:42:18
I've improved the mock object to try to address th
|
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyGetDaemonStateResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyUpdateDaemonConfigResponse(response.get()); |
| + |
| + response = ReadMessageFromFile(handle); |
| + VerifyStartDaemonResponse(response.get()); |
| + |
| + base::ClosePlatformFile(handle); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, Id) { |
|
Sergey Ulanov
2013/05/18 01:59:31
Please add short description for each test, e.g.
Lambros
2013/05/22 21:42:18
Done.
|
| + base::DictionaryValue message; |
| + message.SetString("type", "hello"); |
| + WriteMessageToInputFile(message); |
| + message.SetString("id", "42"); |
| + WriteMessageToInputFile(message); |
| + |
| + Run(); |
| + |
| + base::PlatformFile handle = base::CreatePlatformFile( |
| + output_path(), base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL, |
| + NULL); |
| + scoped_ptr<base::DictionaryValue> response = ReadMessageFromFile(handle); |
| + EXPECT_TRUE(response); |
| + std::string value; |
| + EXPECT_FALSE(response->GetString("id", &value)); |
| + |
| + response = ReadMessageFromFile(handle); |
| + EXPECT_TRUE(response); |
| + EXPECT_TRUE(response->GetString("id", &value)); |
| + EXPECT_EQ(value, "42"); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, WrongFormat) { |
| + // Request should be a Dictionary. |
| + base::ListValue message; |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, MissingType) { |
| + base::DictionaryValue message; |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, InvalidType) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "xxx"); |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, GetPinHashNoHostId) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "getPinHash"); |
| + message.SetString("pin", "1234"); |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, GetPinHashNoPin) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "getPinHash"); |
| + message.SetString("hostId", "my_host"); |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, UpdateDaemonConfigInvalidConfig) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "updateDaemonConfig"); |
| + message.SetString("config", "xxx"); |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, StartDaemonInvalidConfig) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "startDaemon"); |
| + message.SetString("config", "xxx"); |
| + message.SetBoolean("consent", true); |
| + TestBadRequest(message); |
| +} |
| + |
| +TEST_F(NativeMessagingHostTest, StartDaemonNoConsent) { |
| + base::DictionaryValue message; |
| + message.SetString("type", "startDaemon"); |
| + message.SetString("config", "{}"); |
| + TestBadRequest(message); |
| +} |
| + |
| +} // namespace remoting |