Explicitly add python-numpy dependency to install-build-deps.
[chromium-blink-merge.git] / remoting / host / it2me / it2me_native_messaging_host_unittest.cc
blob18d5c85edb80e2ba81d76c705e2b1dc019395d28
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/native_messaging_pipe.h"
21 #include "remoting/host/native_messaging/pipe_messaging_channel.h"
22 #include "remoting/host/policy_hack/policy_watcher.h"
23 #include "remoting/host/setup/test_util.h"
24 #include "testing/gtest/include/gtest/gtest.h"
26 namespace remoting {
28 namespace {
30 const char kTestAccessCode[] = "888888";
31 const int kTestAccessCodeLifetimeInSeconds = 666;
32 const char kTestClientUsername[] = "some_user@gmail.com";
34 void VerifyId(scoped_ptr<base::DictionaryValue> response, int expected_value) {
35 ASSERT_TRUE(response);
37 int value;
38 EXPECT_TRUE(response->GetInteger("id", &value));
39 EXPECT_EQ(expected_value, value);
42 void VerifyStringProperty(scoped_ptr<base::DictionaryValue> response,
43 const std::string& name,
44 const std::string& expected_value) {
45 ASSERT_TRUE(response);
47 std::string value;
48 EXPECT_TRUE(response->GetString(name, &value));
49 EXPECT_EQ(expected_value, value);
52 // Verity the values of the "type" and "id" properties
53 void VerifyCommonProperties(scoped_ptr<base::DictionaryValue> response,
54 const std::string& type,
55 int id) {
56 ASSERT_TRUE(response);
58 std::string string_value;
59 EXPECT_TRUE(response->GetString("type", &string_value));
60 EXPECT_EQ(type, string_value);
62 int int_value;
63 EXPECT_TRUE(response->GetInteger("id", &int_value));
64 EXPECT_EQ(id, int_value);
67 } // namespace
69 class MockIt2MeHost : public It2MeHost {
70 public:
71 MockIt2MeHost(scoped_ptr<ChromotingHostContext> context,
72 scoped_ptr<policy_hack::PolicyWatcher> policy_watcher,
73 base::WeakPtr<It2MeHost::Observer> observer,
74 const XmppSignalStrategy::XmppServerConfig& xmpp_server_config,
75 const std::string& directory_bot_jid)
76 : It2MeHost(context.Pass(),
77 policy_watcher.Pass(),
78 observer,
79 xmpp_server_config,
80 directory_bot_jid) {}
82 // It2MeHost overrides
83 void Connect() override;
84 void Disconnect() override;
85 void RequestNatPolicy() override;
87 private:
88 ~MockIt2MeHost() override {}
90 void RunSetState(It2MeHostState state);
92 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHost);
95 void MockIt2MeHost::Connect() {
96 if (!host_context()->ui_task_runner()->BelongsToCurrentThread()) {
97 DCHECK(task_runner()->BelongsToCurrentThread());
98 host_context()->ui_task_runner()->PostTask(
99 FROM_HERE, base::Bind(&MockIt2MeHost::Connect, this));
100 return;
103 RunSetState(kStarting);
104 RunSetState(kRequestedAccessCode);
106 std::string access_code(kTestAccessCode);
107 base::TimeDelta lifetime =
108 base::TimeDelta::FromSeconds(kTestAccessCodeLifetimeInSeconds);
109 task_runner()->PostTask(FROM_HERE,
110 base::Bind(&It2MeHost::Observer::OnStoreAccessCode,
111 observer(),
112 access_code,
113 lifetime));
115 RunSetState(kReceivedAccessCode);
117 std::string client_username(kTestClientUsername);
118 task_runner()->PostTask(
119 FROM_HERE,
120 base::Bind(&It2MeHost::Observer::OnClientAuthenticated,
121 observer(),
122 client_username));
124 RunSetState(kConnected);
127 void MockIt2MeHost::Disconnect() {
128 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
129 DCHECK(task_runner()->BelongsToCurrentThread());
130 host_context()->network_task_runner()->PostTask(
131 FROM_HERE, base::Bind(&MockIt2MeHost::Disconnect, this));
132 return;
135 RunSetState(kDisconnecting);
136 RunSetState(kDisconnected);
139 void MockIt2MeHost::RequestNatPolicy() {}
141 void MockIt2MeHost::RunSetState(It2MeHostState state) {
142 if (!host_context()->network_task_runner()->BelongsToCurrentThread()) {
143 host_context()->network_task_runner()->PostTask(
144 FROM_HERE, base::Bind(&It2MeHost::SetStateForTesting, this, state));
145 } else {
146 SetStateForTesting(state);
150 class MockIt2MeHostFactory : public It2MeHostFactory {
151 public:
152 MockIt2MeHostFactory() : It2MeHostFactory() {}
153 scoped_refptr<It2MeHost> CreateIt2MeHost(
154 scoped_ptr<ChromotingHostContext> context,
155 base::WeakPtr<It2MeHost::Observer> observer,
156 const XmppSignalStrategy::XmppServerConfig& xmpp_server_config,
157 const std::string& directory_bot_jid) override {
158 return new MockIt2MeHost(context.Pass(), nullptr, observer,
159 xmpp_server_config, directory_bot_jid);
162 private:
163 DISALLOW_COPY_AND_ASSIGN(MockIt2MeHostFactory);
164 }; // MockIt2MeHostFactory
166 class It2MeNativeMessagingHostTest : public testing::Test {
167 public:
168 It2MeNativeMessagingHostTest() {}
169 ~It2MeNativeMessagingHostTest() override {}
171 void SetUp() override;
172 void TearDown() override;
174 protected:
175 scoped_ptr<base::DictionaryValue> ReadMessageFromOutputPipe();
176 void WriteMessageToInputPipe(const base::Value& message);
178 void VerifyHelloResponse(int request_id);
179 void VerifyErrorResponse();
180 void VerifyConnectResponses(int request_id);
181 void VerifyDisconnectResponses(int request_id);
183 // The Host process should shut down when it receives a malformed request.
184 // This is tested by sending a known-good request, followed by |message|,
185 // followed by the known-good request again. The response file should only
186 // contain a single response from the first good request.
187 void TestBadRequest(const base::Value& message, bool expect_error_response);
188 void TestConnect();
190 private:
191 void StartHost();
192 void StopHost();
193 void ExitTest();
195 // Each test creates two unidirectional pipes: "input" and "output".
196 // It2MeNativeMessagingHost reads from input_read_file and writes to
197 // output_write_file. The unittest supplies data to input_write_handle, and
198 // verifies output from output_read_handle.
200 // unittest -> [input] -> It2MeNativeMessagingHost -> [output] -> unittest
201 base::File input_write_file_;
202 base::File output_read_file_;
204 // Message loop of the test thread.
205 scoped_ptr<base::MessageLoop> test_message_loop_;
206 scoped_ptr<base::RunLoop> test_run_loop_;
208 scoped_ptr<base::Thread> host_thread_;
209 scoped_ptr<base::RunLoop> host_run_loop_;
211 // Task runner of the host thread.
212 scoped_refptr<AutoThreadTaskRunner> host_task_runner_;
213 scoped_ptr<remoting::NativeMessagingPipe> pipe_;
215 DISALLOW_COPY_AND_ASSIGN(It2MeNativeMessagingHostTest);
218 void It2MeNativeMessagingHostTest::SetUp() {
219 test_message_loop_.reset(new base::MessageLoop());
220 test_run_loop_.reset(new base::RunLoop());
222 // Run the host on a dedicated thread.
223 host_thread_.reset(new base::Thread("host_thread"));
224 host_thread_->Start();
226 host_task_runner_ = new AutoThreadTaskRunner(
227 host_thread_->message_loop_proxy(),
228 base::Bind(&It2MeNativeMessagingHostTest::ExitTest,
229 base::Unretained(this)));
231 host_task_runner_->PostTask(
232 FROM_HERE,
233 base::Bind(&It2MeNativeMessagingHostTest::StartHost,
234 base::Unretained(this)));
236 // Wait until the host finishes starting.
237 test_run_loop_->Run();
240 void It2MeNativeMessagingHostTest::TearDown() {
241 // Closing the write-end of the input will send an EOF to the native
242 // messaging reader. This will trigger a host shutdown.
243 input_write_file_.Close();
245 // Start a new RunLoop and Wait until the host finishes shutting down.
246 test_run_loop_.reset(new base::RunLoop());
247 test_run_loop_->Run();
249 // Verify there are no more message in the output pipe.
250 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
251 EXPECT_FALSE(response);
253 // The It2MeNativeMessagingHost dtor closes the handles that are passed to it.
254 // So the only handle left to close is |output_read_file_|.
255 output_read_file_.Close();
258 scoped_ptr<base::DictionaryValue>
259 It2MeNativeMessagingHostTest::ReadMessageFromOutputPipe() {
260 uint32 length;
261 int read_result = output_read_file_.ReadAtCurrentPos(
262 reinterpret_cast<char*>(&length), sizeof(length));
263 if (read_result != sizeof(length)) {
264 // The output pipe has been closed, return an empty message.
265 return nullptr;
268 std::string message_json(length, '\0');
269 read_result = output_read_file_.ReadAtCurrentPos(
270 string_as_array(&message_json), length);
271 if (read_result != static_cast<int>(length)) {
272 LOG(ERROR) << "Message size (" << read_result
273 << ") doesn't match the header (" << length << ").";
274 return nullptr;
277 scoped_ptr<base::Value> message(base::JSONReader::Read(message_json));
278 if (!message || !message->IsType(base::Value::TYPE_DICTIONARY)) {
279 LOG(ERROR) << "Malformed message:" << message_json;
280 return nullptr;
283 return make_scoped_ptr(
284 static_cast<base::DictionaryValue*>(message.release()));
287 void It2MeNativeMessagingHostTest::WriteMessageToInputPipe(
288 const base::Value& message) {
289 std::string message_json;
290 base::JSONWriter::Write(&message, &message_json);
292 uint32 length = message_json.length();
293 input_write_file_.WriteAtCurrentPos(reinterpret_cast<char*>(&length),
294 sizeof(length));
295 input_write_file_.WriteAtCurrentPos(message_json.data(), length);
298 void It2MeNativeMessagingHostTest::VerifyHelloResponse(int request_id) {
299 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
300 VerifyCommonProperties(response.Pass(), "helloResponse", request_id);
303 void It2MeNativeMessagingHostTest::VerifyErrorResponse() {
304 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
305 VerifyStringProperty(response.Pass(), "type", "error");
308 void It2MeNativeMessagingHostTest::VerifyConnectResponses(int request_id) {
309 bool connect_response_received = false;
310 bool starting_received = false;
311 bool requestedAccessCode_received = false;
312 bool receivedAccessCode_received = false;
313 bool connected_received = false;
315 // We expect a total of 5 messages: 1 connectResponse and 4 hostStateChanged.
316 for (int i = 0; i < 5; ++i) {
317 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
318 ASSERT_TRUE(response);
320 std::string type;
321 ASSERT_TRUE(response->GetString("type", &type));
323 if (type == "connectResponse") {
324 EXPECT_FALSE(connect_response_received);
325 connect_response_received = true;
326 VerifyId(response.Pass(), request_id);
327 } else if (type == "hostStateChanged") {
328 std::string state;
329 ASSERT_TRUE(response->GetString("state", &state));
331 std::string value;
332 if (state == It2MeNativeMessagingHost::HostStateToString(kStarting)) {
333 EXPECT_FALSE(starting_received);
334 starting_received = true;
335 } else if (state == It2MeNativeMessagingHost::HostStateToString(
336 kRequestedAccessCode)) {
337 EXPECT_FALSE(requestedAccessCode_received);
338 requestedAccessCode_received = true;
339 } else if (state == It2MeNativeMessagingHost::HostStateToString(
340 kReceivedAccessCode)) {
341 EXPECT_FALSE(receivedAccessCode_received);
342 receivedAccessCode_received = true;
344 EXPECT_TRUE(response->GetString("accessCode", &value));
345 EXPECT_EQ(kTestAccessCode, value);
347 int accessCodeLifetime;
348 EXPECT_TRUE(
349 response->GetInteger("accessCodeLifetime", &accessCodeLifetime));
350 EXPECT_EQ(kTestAccessCodeLifetimeInSeconds, accessCodeLifetime);
351 } else if (state ==
352 It2MeNativeMessagingHost::HostStateToString(kConnected)) {
353 EXPECT_FALSE(connected_received);
354 connected_received = true;
356 EXPECT_TRUE(response->GetString("client", &value));
357 EXPECT_EQ(kTestClientUsername, value);
358 } else {
359 ADD_FAILURE() << "Unexpected host state: " << state;
361 } else {
362 ADD_FAILURE() << "Unexpected message type: " << type;
367 void It2MeNativeMessagingHostTest::VerifyDisconnectResponses(int request_id) {
368 bool disconnect_response_received = false;
369 bool disconnecting_received = false;
370 bool disconnected_received = false;
372 // We expect a total of 3 messages: 1 connectResponse and 2 hostStateChanged.
373 for (int i = 0; i < 3; ++i) {
374 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
375 ASSERT_TRUE(response);
377 std::string type;
378 ASSERT_TRUE(response->GetString("type", &type));
380 if (type == "disconnectResponse") {
381 EXPECT_FALSE(disconnect_response_received);
382 disconnect_response_received = true;
383 VerifyId(response.Pass(), request_id);
384 } else if (type == "hostStateChanged") {
385 std::string state;
386 ASSERT_TRUE(response->GetString("state", &state));
387 if (state ==
388 It2MeNativeMessagingHost::HostStateToString(kDisconnecting)) {
389 EXPECT_FALSE(disconnecting_received);
390 disconnecting_received = true;
391 } else if (state ==
392 It2MeNativeMessagingHost::HostStateToString(kDisconnected)) {
393 EXPECT_FALSE(disconnected_received);
394 disconnected_received = true;
395 } else {
396 ADD_FAILURE() << "Unexpected host state: " << state;
398 } else {
399 ADD_FAILURE() << "Unexpected message type: " << type;
404 void It2MeNativeMessagingHostTest::TestBadRequest(const base::Value& message,
405 bool expect_error_response) {
406 base::DictionaryValue good_message;
407 good_message.SetString("type", "hello");
408 good_message.SetInteger("id", 1);
410 WriteMessageToInputPipe(good_message);
411 WriteMessageToInputPipe(message);
412 WriteMessageToInputPipe(good_message);
414 VerifyHelloResponse(1);
416 if (expect_error_response)
417 VerifyErrorResponse();
419 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
420 EXPECT_FALSE(response);
423 void It2MeNativeMessagingHostTest::StartHost() {
424 DCHECK(host_task_runner_->RunsTasksOnCurrentThread());
426 base::File input_read_file;
427 base::File output_write_file;
429 ASSERT_TRUE(MakePipe(&input_read_file, &input_write_file_));
430 ASSERT_TRUE(MakePipe(&output_read_file_, &output_write_file));
432 pipe_.reset(new NativeMessagingPipe());
434 scoped_ptr<extensions::NativeMessagingChannel> channel(
435 new PipeMessagingChannel(input_read_file.Pass(),
436 output_write_file.Pass()));
438 // Creating a native messaging host with a mock It2MeHostFactory.
439 scoped_ptr<extensions::NativeMessageHost> it2me_host(
440 new It2MeNativeMessagingHost(
441 ChromotingHostContext::Create(host_task_runner_),
442 make_scoped_ptr(new MockIt2MeHostFactory())));
443 it2me_host->Start(pipe_.get());
445 pipe_->Start(it2me_host.Pass(),
446 channel.Pass(),
447 base::Bind(&It2MeNativeMessagingHostTest::StopHost,
448 base::Unretained(this)));
450 // Notify the test that the host has finished starting up.
451 test_message_loop_->message_loop_proxy()->PostTask(
452 FROM_HERE, test_run_loop_->QuitClosure());
455 void It2MeNativeMessagingHostTest::StopHost() {
456 DCHECK(host_task_runner_->RunsTasksOnCurrentThread());
458 pipe_.reset();
460 // Wait till all shutdown tasks have completed.
461 base::RunLoop().RunUntilIdle();
463 // Trigger a test shutdown via ExitTest().
464 host_task_runner_ = NULL;
467 void It2MeNativeMessagingHostTest::ExitTest() {
468 if (!test_message_loop_->message_loop_proxy()->RunsTasksOnCurrentThread()) {
469 test_message_loop_->message_loop_proxy()->PostTask(
470 FROM_HERE,
471 base::Bind(&It2MeNativeMessagingHostTest::ExitTest,
472 base::Unretained(this)));
473 return;
475 test_run_loop_->Quit();
478 void It2MeNativeMessagingHostTest::TestConnect() {
479 base::DictionaryValue connect_message;
480 int next_id = 0;
482 // Send the "connect" request.
483 connect_message.SetInteger("id", ++next_id);
484 connect_message.SetString("type", "connect");
485 connect_message.SetString("xmppServerAddress", "talk.google.com:5222");
486 connect_message.SetBoolean("xmppServerUseTls", true);
487 connect_message.SetString("directoryBotJid", "remoting@bot.talk.google.com");
488 connect_message.SetString("userName", "chromo.pyauto@gmail.com");
489 connect_message.SetString("authServiceWithToken", "oauth2:sometoken");
490 WriteMessageToInputPipe(connect_message);
492 VerifyConnectResponses(next_id);
494 base::DictionaryValue disconnect_message;
495 disconnect_message.SetInteger("id", ++next_id);
496 disconnect_message.SetString("type", "disconnect");
497 WriteMessageToInputPipe(disconnect_message);
499 VerifyDisconnectResponses(next_id);
502 // Test hello request.
503 TEST_F(It2MeNativeMessagingHostTest, Hello) {
504 int next_id = 0;
505 base::DictionaryValue message;
506 message.SetInteger("id", ++next_id);
507 message.SetString("type", "hello");
508 WriteMessageToInputPipe(message);
510 VerifyHelloResponse(next_id);
513 // Verify that response ID matches request ID.
514 TEST_F(It2MeNativeMessagingHostTest, Id) {
515 base::DictionaryValue message;
516 message.SetString("type", "hello");
517 WriteMessageToInputPipe(message);
518 message.SetString("id", "42");
519 WriteMessageToInputPipe(message);
521 scoped_ptr<base::DictionaryValue> response = ReadMessageFromOutputPipe();
522 EXPECT_TRUE(response);
523 std::string value;
524 EXPECT_FALSE(response->GetString("id", &value));
526 response = ReadMessageFromOutputPipe();
527 EXPECT_TRUE(response);
528 EXPECT_TRUE(response->GetString("id", &value));
529 EXPECT_EQ("42", value);
532 TEST_F(It2MeNativeMessagingHostTest, Connect) {
533 // A new It2MeHost instance is created for every it2me session. The native
534 // messaging host, on the other hand, is long lived. This test verifies
535 // multiple It2Me host startup and shutdowns.
536 for (int i = 0; i < 3; ++i)
537 TestConnect();
540 // Verify non-Dictionary requests are rejected.
541 TEST_F(It2MeNativeMessagingHostTest, WrongFormat) {
542 base::ListValue message;
543 // No "error" response will be sent for non-Dictionary messages.
544 TestBadRequest(message, false);
547 // Verify requests with no type are rejected.
548 TEST_F(It2MeNativeMessagingHostTest, MissingType) {
549 base::DictionaryValue message;
550 TestBadRequest(message, true);
553 // Verify rejection if type is unrecognized.
554 TEST_F(It2MeNativeMessagingHostTest, InvalidType) {
555 base::DictionaryValue message;
556 message.SetString("type", "xxx");
557 TestBadRequest(message, true);
560 } // namespace remoting