1// Copyright 2014 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.
4
5#include "base/bind.h"
6#include "base/command_line.h"
7#include "base/files/file_path.h"
8#include "chrome/browser/extensions/extension_apitest.h"
9#include "chrome/browser/extensions/extension_function_test_utils.h"
10#include "chrome/browser/extensions/extension_service.h"
11#include "chrome/browser/ui/browser.h"
12#include "content/public/browser/browser_thread.h"
13#include "extensions/browser/api/cast_channel/cast_channel_api.h"
14#include "extensions/browser/api/cast_channel/cast_socket.h"
15#include "extensions/browser/api/cast_channel/logger.h"
16#include "extensions/common/api/cast_channel.h"
17#include "extensions/common/switches.h"
18#include "extensions/common/test_util.h"
19#include "extensions/test/result_catcher.h"
20#include "net/base/capturing_net_log.h"
21#include "net/base/completion_callback.h"
22#include "net/base/net_errors.h"
23#include "testing/gmock/include/gmock/gmock.h"
24#include "testing/gmock_mutant.h"
25
26// TODO(mfoltz): Mock out the ApiResourceManager to resolve threading issues
27// (crbug.com/398242) and simulate unloading of the extension.
28
29namespace cast_channel = extensions::core_api::cast_channel;
30using cast_channel::CastSocket;
31using cast_channel::ChannelError;
32using cast_channel::ErrorInfo;
33using cast_channel::Logger;
34using cast_channel::MessageInfo;
35using cast_channel::ReadyState;
36using extensions::Extension;
37
38namespace utils = extension_function_test_utils;
39
40using ::testing::_;
41using ::testing::A;
42using ::testing::DoAll;
43using ::testing::Invoke;
44using ::testing::InSequence;
45using ::testing::Return;
46
47namespace {
48
49const char kTestExtensionId[] = "ddchlicdkolnonkihahngkmmmjnjlkkf";
50const int64 kTimeoutMs = 10000;
51
52static void FillMessageInfo(MessageInfo* message_info,
53                            const std::string& message) {
54  message_info->namespace_ = "foo";
55  message_info->source_id = "src";
56  message_info->destination_id = "dest";
57  message_info->data.reset(new base::StringValue(message));
58}
59
60ACTION_TEMPLATE(InvokeCompletionCallback,
61                HAS_1_TEMPLATE_PARAMS(int, k),
62                AND_1_VALUE_PARAMS(result)) {
63  ::std::tr1::get<k>(args).Run(result);
64}
65
66ACTION_P2(InvokeDelegateOnError, api_test, api) {
67  api_test->CallOnError(api);
68}
69
70class MockCastSocket : public CastSocket {
71 public:
72  explicit MockCastSocket(CastSocket::Delegate* delegate,
73                          net::IPEndPoint ip_endpoint,
74                          net::NetLog* net_log,
75                          const scoped_refptr<Logger>& logger)
76      : CastSocket(kTestExtensionId,
77                   ip_endpoint,
78                   cast_channel::CHANNEL_AUTH_TYPE_SSL,
79                   delegate,
80                   net_log,
81                   base::TimeDelta::FromMilliseconds(kTimeoutMs),
82                   logger) {}
83  virtual ~MockCastSocket() {}
84
85  MOCK_METHOD1(Connect, void(const net::CompletionCallback& callback));
86  MOCK_METHOD2(SendMessage, void(const MessageInfo& message,
87                                 const net::CompletionCallback& callback));
88  MOCK_METHOD1(Close, void(const net::CompletionCallback& callback));
89  MOCK_CONST_METHOD0(ready_state, cast_channel::ReadyState());
90  MOCK_CONST_METHOD0(error_state, cast_channel::ChannelError());
91};
92
93}  // namespace
94
95class CastChannelAPITest : public ExtensionApiTest {
96 public:
97  CastChannelAPITest() {}
98
99  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
100    ExtensionApiTest::SetUpCommandLine(command_line);
101    command_line->AppendSwitchASCII(
102        extensions::switches::kWhitelistedExtensionID,
103        kTestExtensionId);
104  }
105
106  void SetUpMockCastSocket() {
107    extensions::CastChannelAPI* api = GetApi();
108    net::IPAddressNumber ip_number;
109    net::ParseIPLiteralToNumber("192.168.1.1", &ip_number);
110    net::IPEndPoint ip_endpoint(ip_number, 8009);
111    mock_cast_socket_ = new MockCastSocket(
112        api, ip_endpoint, &capturing_net_log_, api->GetLogger());
113    // Transfers ownership of the socket.
114    api->SetSocketForTest(
115        make_scoped_ptr<CastSocket>(mock_cast_socket_).Pass());
116  }
117
118  void SetUpOpenSendClose() {
119    SetUpMockCastSocket();
120    EXPECT_CALL(*mock_cast_socket_, error_state())
121        .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
122    {
123      InSequence sequence;
124      EXPECT_CALL(*mock_cast_socket_, Connect(_))
125          .WillOnce(InvokeCompletionCallback<0>(net::OK));
126      EXPECT_CALL(*mock_cast_socket_, ready_state())
127          .WillOnce(Return(cast_channel::READY_STATE_OPEN));
128      EXPECT_CALL(*mock_cast_socket_, SendMessage(A<const MessageInfo&>(), _))
129          .WillOnce(InvokeCompletionCallback<1>(net::OK));
130      EXPECT_CALL(*mock_cast_socket_, ready_state())
131          .WillOnce(Return(cast_channel::READY_STATE_OPEN));
132      EXPECT_CALL(*mock_cast_socket_, Close(_))
133          .WillOnce(InvokeCompletionCallback<0>(net::OK));
134      EXPECT_CALL(*mock_cast_socket_, ready_state())
135          .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
136    }
137  }
138
139  extensions::CastChannelAPI* GetApi() {
140    return extensions::CastChannelAPI::Get(profile());
141  }
142
143  void CallOnError(extensions::CastChannelAPI* api) {
144    cast_channel::LastErrors last_errors;
145    last_errors.challenge_reply_error_type =
146        cast_channel::proto::CHALLENGE_REPLY_ERROR_NSS_CERT_PARSING_FAILED;
147    last_errors.nss_error_code = -8164;
148    api->OnError(mock_cast_socket_,
149                 cast_channel::CHANNEL_ERROR_CONNECT_ERROR,
150                 last_errors);
151  }
152
153 protected:
154  void CallOnMessage(const std::string& message) {
155    content::BrowserThread::PostTask(
156        content::BrowserThread::IO,
157        FROM_HERE,
158        base::Bind(&CastChannelAPITest::DoCallOnMessage, this,
159                   GetApi(), mock_cast_socket_, message));
160  }
161
162  void DoCallOnMessage(extensions::CastChannelAPI* api,
163                       MockCastSocket* cast_socket,
164                       const std::string& message) {
165    MessageInfo message_info;
166    FillMessageInfo(&message_info, message);
167    api->OnMessage(cast_socket, message_info);
168  }
169
170  extensions::CastChannelOpenFunction* CreateOpenFunction(
171        scoped_refptr<Extension> extension) {
172    extensions::CastChannelOpenFunction* cast_channel_open_function =
173      new extensions::CastChannelOpenFunction;
174    cast_channel_open_function->set_extension(extension.get());
175    return cast_channel_open_function;
176  }
177
178  extensions::CastChannelSendFunction* CreateSendFunction(
179        scoped_refptr<Extension> extension) {
180    extensions::CastChannelSendFunction* cast_channel_send_function =
181      new extensions::CastChannelSendFunction;
182    cast_channel_send_function->set_extension(extension.get());
183    return cast_channel_send_function;
184  }
185
186  MockCastSocket* mock_cast_socket_;
187  net::CapturingNetLog capturing_net_log_;
188};
189
190// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest
191// always return true without actually running the test. Remove when fixed.
192#if defined(OS_WIN) && !defined(NDEBUG)
193#define MAYBE_TestOpenSendClose DISABLED_TestOpenSendClose
194#else
195#define MAYBE_TestOpenSendClose TestOpenSendClose
196#endif
197// Test loading extension, opening a channel with ConnectInfo, adding a
198// listener, writing, reading, and closing.
199IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenSendClose) {
200  SetUpOpenSendClose();
201
202  EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
203                                  "test_open_send_close.html"));
204}
205
206// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest
207// always return true without actually running the test. Remove when fixed.
208#if defined(OS_WIN) && !defined(NDEBUG)
209#define MAYBE_TestOpenSendCloseWithUrl DISABLED_TestOpenSendCloseWithUrl
210#else
211#define MAYBE_TestOpenSendCloseWithUrl TestOpenSendCloseWithUrl
212#endif
213// Test loading extension, opening a channel with a URL, adding a listener,
214// writing, reading, and closing.
215IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenSendCloseWithUrl) {
216  SetUpOpenSendClose();
217
218  EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
219                                  "test_open_send_close_url.html"));
220}
221
222// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest
223// always return true without actually running the test. Remove when fixed.
224#if defined(OS_WIN) && !defined(NDEBUG)
225#define MAYBE_TestOpenReceiveClose DISABLED_TestOpenReceiveClose
226#else
227#define MAYBE_TestOpenReceiveClose TestOpenReceiveClose
228#endif
229// Test loading extension, opening a channel, adding a listener,
230// writing, reading, and closing.
231IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenReceiveClose) {
232  SetUpMockCastSocket();
233  EXPECT_CALL(*mock_cast_socket_, error_state())
234      .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_NONE));
235
236  {
237    InSequence sequence;
238    EXPECT_CALL(*mock_cast_socket_, Connect(_))
239        .WillOnce(InvokeCompletionCallback<0>(net::OK));
240    EXPECT_CALL(*mock_cast_socket_, ready_state())
241        .Times(3)
242        .WillRepeatedly(Return(cast_channel::READY_STATE_OPEN));
243    EXPECT_CALL(*mock_cast_socket_, Close(_))
244        .WillOnce(InvokeCompletionCallback<0>(net::OK));
245    EXPECT_CALL(*mock_cast_socket_, ready_state())
246        .WillOnce(Return(cast_channel::READY_STATE_CLOSED));
247  }
248
249  EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
250                                  "test_open_receive_close.html"));
251
252  extensions::ResultCatcher catcher;
253  CallOnMessage("some-message");
254  CallOnMessage("some-message");
255  EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
256}
257
258// TODO(imcheng): Win Dbg has a workaround that makes RunExtensionSubtest
259// always return true without actually running the test. Remove when fixed.
260#if defined(OS_WIN) && !defined(NDEBUG)
261#define MAYBE_TestGetLogs DISABLED_TestGetLogs
262#else
263#define MAYBE_TestGetLogs TestGetLogs
264#endif
265// Test loading extension, execute a open-send-close sequence, then get logs.
266IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestGetLogs) {
267  SetUpOpenSendClose();
268
269  EXPECT_TRUE(RunExtensionSubtest("cast_channel/api", "test_get_logs.html"));
270}
271
272// TODO(munjal): Win Dbg has a workaround that makes RunExtensionSubtest
273// always return true without actually running the test. Remove when fixed.
274#if defined(OS_WIN) && !defined(NDEBUG)
275#define MAYBE_TestOpenError DISABLED_TestOpenError
276#else
277#define MAYBE_TestOpenError TestOpenError
278#endif
279// Test the case when socket open results in an error.
280IN_PROC_BROWSER_TEST_F(CastChannelAPITest, MAYBE_TestOpenError) {
281  SetUpMockCastSocket();
282
283  EXPECT_CALL(*mock_cast_socket_, Connect(_))
284      .WillOnce(DoAll(InvokeDelegateOnError(this, GetApi()),
285                      InvokeCompletionCallback<0>(net::ERR_CONNECTION_FAILED)));
286  EXPECT_CALL(*mock_cast_socket_, error_state())
287      .WillRepeatedly(Return(cast_channel::CHANNEL_ERROR_CONNECT_ERROR));
288  EXPECT_CALL(*mock_cast_socket_, ready_state())
289      .WillRepeatedly(Return(cast_channel::READY_STATE_CLOSED));
290  EXPECT_CALL(*mock_cast_socket_, Close(_))
291      .WillOnce(InvokeCompletionCallback<0>(net::OK));
292
293  EXPECT_TRUE(RunExtensionSubtest("cast_channel/api",
294                                  "test_open_error.html"));
295}
296
297IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestOpenInvalidConnectInfo) {
298  scoped_refptr<Extension> empty_extension =
299      extensions::test_util::CreateEmptyExtension();
300  scoped_refptr<extensions::CastChannelOpenFunction> cast_channel_open_function;
301
302  // Invalid URL
303  // TODO(mfoltz): Remove this test case when fixing crbug.com/331905
304  cast_channel_open_function = CreateOpenFunction(empty_extension);
305  std::string error(utils::RunFunctionAndReturnError(
306      cast_channel_open_function.get(), "[\"blargh\"]", browser()));
307  EXPECT_EQ(error, "Invalid connect_info (invalid Cast URL blargh)");
308
309  // Wrong type
310  // TODO(mfoltz): Remove this test case when fixing crbug.com/331905
311  cast_channel_open_function = CreateOpenFunction(empty_extension);
312  error = utils::RunFunctionAndReturnError(
313      cast_channel_open_function.get(),
314      "[123]", browser());
315  EXPECT_EQ(error, "Invalid connect_info (unknown type)");
316
317  // Invalid IP address
318  cast_channel_open_function = CreateOpenFunction(empty_extension);
319  error = utils::RunFunctionAndReturnError(
320      cast_channel_open_function.get(),
321      "[{\"ipAddress\": \"invalid_ip\", \"port\": 8009, \"auth\": \"ssl\"}]",
322      browser());
323  EXPECT_EQ(error, "Invalid connect_info (invalid IP address)");
324
325  // Invalid port
326  cast_channel_open_function = CreateOpenFunction(empty_extension);
327  error = utils::RunFunctionAndReturnError(
328      cast_channel_open_function.get(),
329      "[{\"ipAddress\": \"127.0.0.1\", \"port\": -200, \"auth\": \"ssl\"}]",
330      browser());
331  EXPECT_EQ(error, "Invalid connect_info (invalid port)");
332
333  // Auth not set
334  cast_channel_open_function = CreateOpenFunction(empty_extension);
335  error = utils::RunFunctionAndReturnError(
336      cast_channel_open_function.get(),
337      "[{\"ipAddress\": \"127.0.0.1\", \"port\": 8009}]",
338      browser());
339  EXPECT_EQ(error, "connect_info.auth is required");
340}
341
342IN_PROC_BROWSER_TEST_F(CastChannelAPITest, TestSendInvalidMessageInfo) {
343  scoped_refptr<Extension> empty_extension(
344      extensions::test_util::CreateEmptyExtension());
345  scoped_refptr<extensions::CastChannelSendFunction> cast_channel_send_function;
346
347  // Numbers are not supported
348  cast_channel_send_function = CreateSendFunction(empty_extension);
349  std::string error(utils::RunFunctionAndReturnError(
350      cast_channel_send_function.get(),
351      "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", "
352      "\"connectInfo\": "
353      "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
354      "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
355      "{\"namespace_\": \"foo\", \"sourceId\": \"src\", "
356      "\"destinationId\": \"dest\", \"data\": 1235}]",
357      browser()));
358  EXPECT_EQ(error, "Invalid type of message_info.data");
359
360  // Missing namespace_
361  cast_channel_send_function = CreateSendFunction(empty_extension);
362  error = utils::RunFunctionAndReturnError(
363      cast_channel_send_function.get(),
364      "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", "
365      "\"connectInfo\": "
366      "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
367      "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
368      "{\"namespace_\": \"\", \"sourceId\": \"src\", "
369      "\"destinationId\": \"dest\", \"data\": \"data\"}]",
370      browser());
371  EXPECT_EQ(error, "message_info.namespace_ is required");
372
373  // Missing source_id
374  cast_channel_send_function = CreateSendFunction(empty_extension);
375  error = utils::RunFunctionAndReturnError(
376      cast_channel_send_function.get(),
377      "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", "
378      "\"connectInfo\": "
379      "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
380      "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
381      "{\"namespace_\": \"foo\", \"sourceId\": \"\", "
382      "\"destinationId\": \"dest\", \"data\": \"data\"}]",
383      browser());
384  EXPECT_EQ(error, "message_info.source_id is required");
385
386  // Missing destination_id
387  cast_channel_send_function = CreateSendFunction(empty_extension);
388  error = utils::RunFunctionAndReturnError(
389      cast_channel_send_function.get(),
390      "[{\"channelId\": 1, \"url\": \"cast://127.0.0.1:8009\", "
391      "\"connectInfo\": "
392      "{\"ipAddress\": \"127.0.0.1\", \"port\": 8009, "
393      "\"auth\": \"ssl\"}, \"readyState\": \"open\"}, "
394      "{\"namespace_\": \"foo\", \"sourceId\": \"src\", "
395      "\"destinationId\": \"\", \"data\": \"data\"}]",
396      browser());
397  EXPECT_EQ(error, "message_info.destination_id is required");
398}
399