Infobar material design refresh: bg color
[chromium-blink-merge.git] / remoting / host / it2me / it2me_native_messaging_host_unittest.cc
blob94861e4f95831b531bf616be66b74c99dee10dec
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/host/it2me/it2me_native_messaging_host.h"
7 #include "base/basictypes.h"
8 #include "base/compiler_specific.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/json_writer.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/run_loop.h"
13 #include "base/stl_util.h"
14 #include "base/strings/stringize_macros.h"
15 #include "base/values.h"
16 #include "net/base/file_stream.h"
17 #include "net/base/net_util.h"
18 #include "remoting/base/auto_thread_task_runner.h"
19 #include "remoting/host/chromoting_host_context.h"
20 #include "remoting/host/native_messaging/log_message_handler.h"
21 #include "remoting/host/native_messaging/native_messaging_pipe.h"
22 #include "remoting/host/native_messaging/pipe_messaging_channel.h"
23 #include "remoting/host/policy_watcher.h"
24 #include "remoting/host/setup/test_util.h"
25 #include "testing/gtest/include/gtest/gtest.h"
27 namespace remoting {
29 namespace {
31 const char kTestAccessCode[] = "888888";
32 const int kTestAccessCodeLifetimeInSeconds = 666;
33 const char kTestClientUsername[] = "some_user@gmail.com";
35 void VerifyId(scoped_ptr<base::DictionaryValue> response, int expected_value) {
36 ASSERT_TRUE(response);
38 int value;
39 EXPECT_TRUE(response->GetInteger("id", &value));
40 EXPECT_EQ(expected_value, value);
43 void VerifyStringProperty(scoped_ptr<base::DictionaryValue> response,
44 const std::string& name,
45 const std::string& expected_value) {
46 ASSERT_TRUE(response);
48 std::string value;
49 EXPECT_TRUE(response->GetString(name, &value));
50 EXPECT_EQ(expected_value, value);
53 // Verity the values of the "type" and "id" properties
54 void VerifyCommonProperties(scoped_ptr<base::DictionaryValue> response,
55 const std::string& type,
56 int id) {
57 ASSERT_TRUE(response);
59 std::string string_value;
60 EXPECT_TRUE(response->GetString("type", &string_value));
61 EXPECT_EQ(type, string_value);
63 int int_value;
64 EXPECT_TRUE(response->GetInteger("id", &int_value));
65 EXPECT_EQ(id, int_value);
68 } // namespace
70 class MockIt2MeHost : public It2MeHost {
71 public:
72 MockIt2MeHost(scoped_ptr<ChromotingHostContext> context,
73 scoped_ptr<PolicyWatcher> policy_watcher,
74 base::WeakPtr<It2MeHost::Observer> observer,
75 const XmppSignalStrategy::XmppServerConfig& xmpp_server_config,
76 const std::string& directory_bot_jid)
77 : It2MeHost(context.Pass(),
78 policy_watcher.Pass(),
79 nullptr,
80 observer,
81 xmpp_server_config,
82 directory_bot_jid) {}
84 // It2MeHost overrides
85 void Connect() override;
86 void Disconnect() override;
87 void RequestNatPolicy() override;
89 private:
90 ~MockIt2MeHost() override {}
92 void RunSetState(It2MeHostState state);
94 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHost);
97 void MockIt2MeHost::Connect() {
98 if (!host_context()->ui_task_runner()->BelongsToCurrentThread()) {
99 DCHECK(task_runner()->BelongsToCurrentThread());
100 host_context()->ui_task_runner()->PostTask(
101 FROM_HERE, base::Bind(&MockIt2MeHost::Connect, this));
102 return;
105 RunSetState(kStarting);
106 RunSetState(kRequestedAccessCode);
108 std::string access_code(kTestAccessCode);
109 base::TimeDelta lifetime =
110 base::TimeDelta::FromSeconds(kTestAccessCodeLifetimeInSeconds);
111 task_runner()->PostTask(FROM_HERE,
112 base::Bind(&It2MeHost::Observer::OnStoreAccessCode,
113 observer(),
114 access_code,
115 lifetime));
117 RunSetState(kReceivedAccessCode);
119 std::string client_username(kTestClientUsername);
120 task_runner()->PostTask(
121 FROM_HERE,
122 base::Bind(&It2MeHost::Observer::OnClientAuthenticated,
123 observer(),
124 client_username));
126 RunSetState(kConnected);
129 void MockIt2MeHost::Disconnect() {
130 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
131 DCHECK(task_runner()->BelongsToCurrentThread());
132 host_context()->network_task_runner()->PostTask(
133 FROM_HERE, base::Bind(&MockIt2MeHost::Disconnect, this));
134 return;
137 RunSetState(kDisconnecting);
138 RunSetState(kDisconnected);
141 void MockIt2MeHost::RequestNatPolicy() {}
143 void MockIt2MeHost::RunSetState(It2MeHostState state) {
144 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
145 host_context()->network_task_runner()->PostTask(
146 FROM_HERE, base::Bind(&It2MeHost::SetStateForTesting, this, state, ""));
147 } else {
148 SetStateForTesting(state, "");
152 class MockIt2MeHostFactory : public It2MeHostFactory {
153 public:
154 MockIt2MeHostFactory() : It2MeHostFactory() {}
155 scoped_refptr<It2MeHost> CreateIt2MeHost(
156 scoped_ptr<ChromotingHostContext> context,
157 base::WeakPtr<It2MeHost::Observer> observer,
158 const XmppSignalStrategy::XmppServerConfig& xmpp_server_config,
159 const std::string& directory_bot_jid) override {
160 return new MockIt2MeHost(context.Pass(), nullptr, observer,
161 xmpp_server_config, directory_bot_jid);
164 private:
165 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHostFactory);
166 }; // MockIt2MeHostFactory
168 class It2MeNativeMessagingHostTest : public testing::Test {
169 public:
170 It2MeNativeMessagingHostTest() {}
171 ~It2MeNativeMessagingHostTest() override {}
173 void SetUp() override;
174 void TearDown() override;
176 protected:
177 scoped_ptr<base::DictionaryValue> ReadMessageFromOutputPipe();
178 void WriteMessageToInputPipe(const base::Value& message);
180 void VerifyHelloResponse(int request_id);
181 void VerifyErrorResponse();
182 void VerifyConnectResponses(int request_id);
183 void VerifyDisconnectResponses(int request_id);
185 // The Host process should shut down when it receives a malformed request.
186 // This is tested by sending a known-good request, followed by |message|,
187 // followed by the known-good request again. The response file should only
188 // contain a single response from the first good request.
189 void TestBadRequest(const base::Value& message, bool expect_error_response);
190 void TestConnect();
192 private:
193 void StartHost();
194 void ExitTest();
196 // Each test creates two unidirectional pipes: "input" and "output".
197 // It2MeNativeMessagingHost reads from input_read_file and writes to
198 // output_write_file. The unittest supplies data to input_write_handle, and
199 // verifies output from output_read_handle.
201 // unittest -> [input] -> It2MeNativeMessagingHost -> [output] -> unittest
202 base::File input_write_file_;
203 base::File output_read_file_;
205 // Message loop of the test thread.
206 scoped_ptr<base::MessageLoop> test_message_loop_;
207 scoped_ptr<base::RunLoop> test_run_loop_;
209 scoped_ptr<base::Thread> host_thread_;
210 scoped_ptr<base::RunLoop> host_run_loop_;
212 // Task runner of the host thread.
213 scoped_refptr<AutoThreadTaskRunner> host_task_runner_;
214 scoped_ptr<remoting::NativeMessagingPipe> pipe_;
216 DISALLOW_COPY_AND_ASSIGN(It2MeNativeMessagingHostTest);
219 void It2MeNativeMessagingHostTest::SetUp() {
220 test_message_loop_.reset(new base::MessageLoop());
221 test_run_loop_.reset(new base::RunLoop());
223 // Run the host on a dedicated thread.
224 host_thread_.reset(new base::Thread("host_thread"));
225 host_thread_->Start();
227 host_task_runner_ = new AutoThreadTaskRunner(
228 host_thread_->task_runner(),
229 base::Bind(&It2MeNativeMessagingHostTest::ExitTest,
230 base::Unretained(this)));
232 host_task_runner_->PostTask(
233 FROM_HERE,
234 base::Bind(&It2MeNativeMessagingHostTest::StartHost,
235 base::Unretained(this)));
237 // Wait until the host finishes starting.
238 test_run_loop_->Run();
241 void It2MeNativeMessagingHostTest::TearDown() {
242 // Release reference to AutoThreadTaskRunner, so the host thread can be shut
243 // down.
244 host_task_runner_ = nullptr;
246 // Closing the write-end of the input will send an EOF to the native
247 // messaging reader. This will trigger a host shutdown.
248 input_write_file_.Close();
250 // Start a new RunLoop and Wait until the host finishes shutting down.
251 test_run_loop_.reset(new base::RunLoop());
252 test_run_loop_->Run();
254 // Verify there are no more message in the output pipe.
255 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
256 EXPECT_FALSE(response);
258 // The It2MeNativeMessagingHost dtor closes the handles that are passed to it.
259 // So the only handle left to close is |output_read_file_|.
260 output_read_file_.Close();
263 scoped_ptr<base::DictionaryValue>
264 It2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() {
265 while (true) {
266 uint32 length;
267 int read_result = output_read_file_.ReadAtCurrentPos(
268 reinterpret_cast<char*>(&length), sizeof(length));
269 if (read_result != sizeof(length)) {
270 // The output pipe has been closed, return an empty message.
271 return nullptr;
274 std::string message_json(length, '\0');
275 read_result = output_read_file_.ReadAtCurrentPos(
276 string_as_array(&message_json), length);
277 if (read_result != static_cast<int>(length)) {
278 LOG(ERROR) << "Message size (" << read_result
279 << ") doesn't match the header (" << length << ").";
280 return nullptr;
283 scoped_ptr<base::Value> message = base::JSONReader::Read(message_json);
284 if (!message || !message->IsType(base::Value::TYPE_DICTIONARY)) {
285 LOG(ERROR) << "Malformed message:" << message_json;
286 return nullptr;
289 scoped_ptr<base::DictionaryValue> result = make_scoped_ptr(
290 static_cast<base::DictionaryValue*>(message.release()));
291 std::string type;
292 // If this is a debug message log, ignore it, otherwise return it.
293 if (!result->GetString("type", &type) ||
294 type != LogMessageHandler::kDebugMessageTypeName) {
295 return result;
300 void It2MeNativeMessagingHostTest::WriteMessageToInputPipe(
301 const base::Value& message) {
302 std::string message_json;
303 base::JSONWriter::Write(message, &message_json);
305 uint32 length = message_json.length();
306 input_write_file_.WriteAtCurrentPos(reinterpret_cast<char*>(&length),
307 sizeof(length));
308 input_write_file_.WriteAtCurrentPos(message_json.data(), length);
311 void It2MeNativeMessagingHostTest::VerifyHelloResponse(int request_id) {
312 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
313 VerifyCommonProperties(response.Pass(), "helloResponse", request_id);
316 void It2MeNativeMessagingHostTest::VerifyErrorResponse() {
317 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
318 VerifyStringProperty(response.Pass(), "type", "error");
321 void It2MeNativeMessagingHostTest::VerifyConnectResponses(int request_id) {
322 bool connect_response_received = false;
323 bool starting_received = false;
324 bool requestedAccessCode_received = false;
325 bool receivedAccessCode_received = false;
326 bool connected_received = false;
328 // We expect a total of 5 messages: 1 connectResponse and 4 hostStateChanged.
329 for (int i = 0; i < 5; ++i) {
330 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
331 ASSERT_TRUE(response);
333 std::string type;
334 ASSERT_TRUE(response->GetString("type", &type));
336 if (type == "connectResponse") {
337 EXPECT_FALSE(connect_response_received);
338 connect_response_received = true;
339 VerifyId(response.Pass(), request_id);
340 } else if (type == "hostStateChanged") {
341 std::string state;
342 ASSERT_TRUE(response->GetString("state", &state));
344 std::string value;
345 if (state == It2MeNativeMessagingHost::HostStateToString(kStarting)) {
346 EXPECT_FALSE(starting_received);
347 starting_received = true;
348 } else if (state == It2MeNativeMessagingHost::HostStateToString(
349 kRequestedAccessCode)) {
350 EXPECT_FALSE(requestedAccessCode_received);
351 requestedAccessCode_received = true;
352 } else if (state == It2MeNativeMessagingHost::HostStateToString(
353 kReceivedAccessCode)) {
354 EXPECT_FALSE(receivedAccessCode_received);
355 receivedAccessCode_received = true;
357 EXPECT_TRUE(response->GetString("accessCode", &value));
358 EXPECT_EQ(kTestAccessCode, value);
360 int accessCodeLifetime;
361 EXPECT_TRUE(
362 response->GetInteger("accessCodeLifetime", &accessCodeLifetime));
363 EXPECT_EQ(kTestAccessCodeLifetimeInSeconds, accessCodeLifetime);
364 } else if (state ==
365 It2MeNativeMessagingHost::HostStateToString(kConnected)) {
366 EXPECT_FALSE(connected_received);
367 connected_received = true;
369 EXPECT_TRUE(response->GetString("client", &value));
370 EXPECT_EQ(kTestClientUsername, value);
371 } else {
372 ADD_FAILURE() << "Unexpected host state: " << state;
374 } else {
375 ADD_FAILURE() << "Unexpected message type: " << type;
380 void It2MeNativeMessagingHostTest::VerifyDisconnectResponses(int request_id) {
381 bool disconnect_response_received = false;
382 bool disconnecting_received = false;
383 bool disconnected_received = false;
385 // We expect a total of 3 messages: 1 connectResponse and 2 hostStateChanged.
386 for (int i = 0; i < 3; ++i) {
387 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
388 ASSERT_TRUE(response);
390 std::string type;
391 ASSERT_TRUE(response->GetString("type", &type));
393 if (type == "disconnectResponse") {
394 EXPECT_FALSE(disconnect_response_received);
395 disconnect_response_received = true;
396 VerifyId(response.Pass(), request_id);
397 } else if (type == "hostStateChanged") {
398 std::string state;
399 ASSERT_TRUE(response->GetString("state", &state));
400 if (state ==
401 It2MeNativeMessagingHost::HostStateToString(kDisconnecting)) {
402 EXPECT_FALSE(disconnecting_received);
403 disconnecting_received = true;
404 } else if (state ==
405 It2MeNativeMessagingHost::HostStateToString(kDisconnected)) {
406 EXPECT_FALSE(disconnected_received);
407 disconnected_received = true;
408 } else {
409 ADD_FAILURE() << "Unexpected host state: " << state;
411 } else {
412 ADD_FAILURE() << "Unexpected message type: " << type;
417 void It2MeNativeMessagingHostTest::TestBadRequest(const base::Value& message,
418 bool expect_error_response) {
419 base::DictionaryValue good_message;
420 good_message.SetString("type", "hello");
421 good_message.SetInteger("id", 1);
423 WriteMessageToInputPipe(good_message);
424 WriteMessageToInputPipe(message);
425 WriteMessageToInputPipe(good_message);
427 VerifyHelloResponse(1);
429 if (expect_error_response)
430 VerifyErrorResponse();
432 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
433 EXPECT_FALSE(response);
436 void It2MeNativeMessagingHostTest::StartHost() {
437 DCHECK(host_task_runner_->RunsTasksOnCurrentThread());
439 base::File input_read_file;
440 base::File output_write_file;
442 ASSERT_TRUE(MakePipe(&input_read_file, &input_write_file_));
443 ASSERT_TRUE(MakePipe(&output_read_file_, &output_write_file));
445 pipe_.reset(new NativeMessagingPipe());
447 scoped_ptr<extensions::NativeMessagingChannel> channel(
448 new PipeMessagingChannel(input_read_file.Pass(),
449 output_write_file.Pass()));
451 // Creating a native messaging host with a mock It2MeHostFactory.
452 scoped_ptr<extensions::NativeMessageHost> it2me_host(
453 new It2MeNativeMessagingHost(
454 ChromotingHostContext::Create(host_task_runner_),
455 make_scoped_ptr(new MockIt2MeHostFactory())));
456 it2me_host->Start(pipe_.get());
458 pipe_->Start(it2me_host.Pass(), channel.Pass());
460 // Notify the test that the host has finished starting up.
461 test_message_loop_->task_runner()->PostTask(
462 FROM_HERE, test_run_loop_->QuitClosure());
465 void It2MeNativeMessagingHostTest::ExitTest() {
466 if (!test_message_loop_->task_runner()->RunsTasksOnCurrentThread()) {
467 test_message_loop_->task_runner()->PostTask(
468 FROM_HERE,
469 base::Bind(&It2MeNativeMessagingHostTest::ExitTest,
470 base::Unretained(this)));
471 return;
473 test_run_loop_->Quit();
476 void It2MeNativeMessagingHostTest::TestConnect() {
477 base::DictionaryValue connect_message;
478 int next_id = 0;
480 // Send the "connect" request.
481 connect_message.SetInteger("id", ++next_id);
482 connect_message.SetString("type", "connect");
483 connect_message.SetString("xmppServerAddress", "talk.google.com:5222");
484 connect_message.SetBoolean("xmppServerUseTls", true);
485 connect_message.SetString("directoryBotJid", "remoting@bot.talk.google.com");
486 connect_message.SetString("userName", "chromo.pyauto@gmail.com");
487 connect_message.SetString("authServiceWithToken", "oauth2:sometoken");
488 WriteMessageToInputPipe(connect_message);
490 VerifyConnectResponses(next_id);
492 base::DictionaryValue disconnect_message;
493 disconnect_message.SetInteger("id", ++next_id);
494 disconnect_message.SetString("type", "disconnect");
495 WriteMessageToInputPipe(disconnect_message);
497 VerifyDisconnectResponses(next_id);
500 // Test hello request.
501 TEST_F(It2MeNativeMessagingHostTest, Hello) {
502 int next_id = 0;
503 base::DictionaryValue message;
504 message.SetInteger("id", ++next_id);
505 message.SetString("type", "hello");
506 WriteMessageToInputPipe(message);
508 VerifyHelloResponse(next_id);
511 // Verify that response ID matches request ID.
512 TEST_F(It2MeNativeMessagingHostTest, Id) {
513 base::DictionaryValue message;
514 message.SetString("type", "hello");
515 WriteMessageToInputPipe(message);
516 message.SetString("id", "42");
517 WriteMessageToInputPipe(message);
519 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
520 EXPECT_TRUE(response);
521 std::string value;
522 EXPECT_FALSE(response->GetString("id", &value));
524 response = ReadMessageFromOutputPipe();
525 EXPECT_TRUE(response);
526 EXPECT_TRUE(response->GetString("id", &value));
527 EXPECT_EQ("42", value);
530 TEST_F(It2MeNativeMessagingHostTest, Connect) {
531 // A new It2MeHost instance is created for every it2me session. The native
532 // messaging host, on the other hand, is long lived. This test verifies
533 // multiple It2Me host startup and shutdowns.
534 for (int i = 0; i < 3; ++i)
535 TestConnect();
538 // Verify non-Dictionary requests are rejected.
539 TEST_F(It2MeNativeMessagingHostTest, WrongFormat) {
540 base::ListValue message;
541 // No "error" response will be sent for non-Dictionary messages.
542 TestBadRequest(message, false);
545 // Verify requests with no type are rejected.
546 TEST_F(It2MeNativeMessagingHostTest, MissingType) {
547 base::DictionaryValue message;
548 TestBadRequest(message, true);
551 // Verify rejection if type is unrecognized.
552 TEST_F(It2MeNativeMessagingHostTest, InvalidType) {
553 base::DictionaryValue message;
554 message.SetString("type", "xxx");
555 TestBadRequest(message, true);
558 } // namespace remoting