1// Copyright (c) 2012 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 "net/proxy/dhcp_proxy_script_adapter_fetcher_win.h"
6
7#include "base/synchronization/waitable_event.h"
8#include "base/test/test_timeouts.h"
9#include "base/threading/sequenced_worker_pool.h"
10#include "base/timer/elapsed_timer.h"
11#include "base/timer/timer.h"
12#include "net/base/net_errors.h"
13#include "net/base/test_completion_callback.h"
14#include "net/proxy/mock_proxy_script_fetcher.h"
15#include "net/proxy/proxy_script_fetcher_impl.h"
16#include "net/test/spawned_test_server/spawned_test_server.h"
17#include "net/url_request/url_request_test_util.h"
18#include "testing/gtest/include/gtest/gtest.h"
19
20namespace net {
21
22namespace {
23
24const char* const kPacUrl = "http://pacserver/script.pac";
25
26// In net/proxy/dhcp_proxy_script_fetcher_win_unittest.cc there are a few
27// tests that exercise DhcpProxyScriptAdapterFetcher end-to-end along with
28// DhcpProxyScriptFetcherWin, i.e. it tests the end-to-end usage of Win32
29// APIs and the network.  In this file we test only by stubbing out
30// functionality.
31
32// Version of DhcpProxyScriptAdapterFetcher that mocks out dependencies
33// to allow unit testing.
34class MockDhcpProxyScriptAdapterFetcher
35    : public DhcpProxyScriptAdapterFetcher {
36 public:
37  explicit MockDhcpProxyScriptAdapterFetcher(
38      URLRequestContext* context,
39      scoped_refptr<base::TaskRunner> task_runner)
40      : DhcpProxyScriptAdapterFetcher(context, task_runner),
41        dhcp_delay_(base::TimeDelta::FromMilliseconds(1)),
42        timeout_(TestTimeouts::action_timeout()),
43        configured_url_(kPacUrl),
44        fetcher_delay_ms_(1),
45        fetcher_result_(OK),
46        pac_script_("bingo") {
47  }
48
49  void Cancel() {
50    DhcpProxyScriptAdapterFetcher::Cancel();
51    fetcher_ = NULL;
52  }
53
54  virtual ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE {
55    // We don't maintain ownership of the fetcher, it is transferred to
56    // the caller.
57    fetcher_ = new MockProxyScriptFetcher();
58    if (fetcher_delay_ms_ != -1) {
59      fetcher_timer_.Start(FROM_HERE,
60          base::TimeDelta::FromMilliseconds(fetcher_delay_ms_),
61          this, &MockDhcpProxyScriptAdapterFetcher::OnFetcherTimer);
62    }
63    return fetcher_;
64  }
65
66  class DelayingDhcpQuery : public DhcpQuery {
67   public:
68    explicit DelayingDhcpQuery()
69        : DhcpQuery(),
70          test_finished_event_(true, false) {
71    }
72
73    std::string ImplGetPacURLFromDhcp(
74        const std::string& adapter_name) OVERRIDE {
75      base::ElapsedTimer timer;
76      test_finished_event_.TimedWait(dhcp_delay_);
77      return configured_url_;
78    }
79
80    base::WaitableEvent test_finished_event_;
81    base::TimeDelta dhcp_delay_;
82    std::string configured_url_;
83  };
84
85  virtual DhcpQuery* ImplCreateDhcpQuery() OVERRIDE {
86    dhcp_query_ = new DelayingDhcpQuery();
87    dhcp_query_->dhcp_delay_ = dhcp_delay_;
88    dhcp_query_->configured_url_ = configured_url_;
89    return dhcp_query_;
90  }
91
92  // Use a shorter timeout so tests can finish more quickly.
93  virtual base::TimeDelta ImplGetTimeout() const OVERRIDE {
94    return timeout_;
95  }
96
97  void OnFetcherTimer() {
98    // Note that there is an assumption by this mock implementation that
99    // DhcpProxyScriptAdapterFetcher::Fetch will call ImplCreateScriptFetcher
100    // and call Fetch on the fetcher before the message loop is re-entered.
101    // This holds true today, but if you hit this DCHECK the problem can
102    // possibly be resolved by having a separate subclass of
103    // MockProxyScriptFetcher that adds the delay internally (instead of
104    // the simple approach currently used in ImplCreateScriptFetcher above).
105    DCHECK(fetcher_ && fetcher_->has_pending_request());
106    fetcher_->NotifyFetchCompletion(fetcher_result_, pac_script_);
107    fetcher_ = NULL;
108  }
109
110  bool IsWaitingForFetcher() const {
111    return state() == STATE_WAIT_URL;
112  }
113
114  bool WasCancelled() const {
115    return state() == STATE_CANCEL;
116  }
117
118  void FinishTest() {
119    DCHECK(dhcp_query_);
120    dhcp_query_->test_finished_event_.Signal();
121  }
122
123  base::TimeDelta dhcp_delay_;
124  base::TimeDelta timeout_;
125  std::string configured_url_;
126  int fetcher_delay_ms_;
127  int fetcher_result_;
128  std::string pac_script_;
129  MockProxyScriptFetcher* fetcher_;
130  base::OneShotTimer<MockDhcpProxyScriptAdapterFetcher> fetcher_timer_;
131  scoped_refptr<DelayingDhcpQuery> dhcp_query_;
132};
133
134class FetcherClient {
135 public:
136  FetcherClient()
137      : url_request_context_(new TestURLRequestContext()),
138        worker_pool_(
139            new base::SequencedWorkerPool(4, "DhcpAdapterFetcherTest")),
140        fetcher_(new MockDhcpProxyScriptAdapterFetcher(
141            url_request_context_.get(),
142            worker_pool_->GetTaskRunnerWithShutdownBehavior(
143                base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN))) {
144  }
145
146  ~FetcherClient() {
147    worker_pool_->Shutdown();
148  }
149
150  void WaitForResult(int expected_error) {
151    EXPECT_EQ(expected_error, callback_.WaitForResult());
152  }
153
154  void RunTest() {
155    fetcher_->Fetch("adapter name", callback_.callback());
156  }
157
158  void FinishTestAllowCleanup() {
159    fetcher_->FinishTest();
160    base::MessageLoop::current()->RunUntilIdle();
161  }
162
163  TestCompletionCallback callback_;
164  scoped_ptr<URLRequestContext> url_request_context_;
165  scoped_refptr<base::SequencedWorkerPool> worker_pool_;
166  scoped_ptr<MockDhcpProxyScriptAdapterFetcher> fetcher_;
167  base::string16 pac_text_;
168};
169
170TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLNotInDhcp) {
171  FetcherClient client;
172  client.fetcher_->configured_url_ = "";
173  client.RunTest();
174  client.WaitForResult(ERR_PAC_NOT_IN_DHCP);
175  ASSERT_TRUE(client.fetcher_->DidFinish());
176  EXPECT_EQ(ERR_PAC_NOT_IN_DHCP, client.fetcher_->GetResult());
177  EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
178}
179
180TEST(DhcpProxyScriptAdapterFetcher, NormalCaseURLInDhcp) {
181  FetcherClient client;
182  client.RunTest();
183  client.WaitForResult(OK);
184  ASSERT_TRUE(client.fetcher_->DidFinish());
185  EXPECT_EQ(OK, client.fetcher_->GetResult());
186  EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript());
187  EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
188}
189
190TEST(DhcpProxyScriptAdapterFetcher, TimeoutDuringDhcp) {
191  // Does a Fetch() with a long enough delay on accessing DHCP that the
192  // fetcher should time out.  This is to test a case manual testing found,
193  // where under certain circumstances (e.g. adapter enabled for DHCP and
194  // needs to retrieve its configuration from DHCP, but no DHCP server
195  // present on the network) accessing DHCP can take on the order of tens
196  // of seconds.
197  FetcherClient client;
198  client.fetcher_->dhcp_delay_ = TestTimeouts::action_max_timeout();
199  client.fetcher_->timeout_ = base::TimeDelta::FromMilliseconds(25);
200
201  base::ElapsedTimer timer;
202  client.RunTest();
203  // An error different from this would be received if the timeout didn't
204  // kick in.
205  client.WaitForResult(ERR_TIMED_OUT);
206
207  ASSERT_TRUE(client.fetcher_->DidFinish());
208  EXPECT_EQ(ERR_TIMED_OUT, client.fetcher_->GetResult());
209  EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
210  EXPECT_EQ(GURL(), client.fetcher_->GetPacURL());
211  client.FinishTestAllowCleanup();
212}
213
214TEST(DhcpProxyScriptAdapterFetcher, CancelWhileDhcp) {
215  FetcherClient client;
216  client.RunTest();
217  client.fetcher_->Cancel();
218  base::MessageLoop::current()->RunUntilIdle();
219  ASSERT_FALSE(client.fetcher_->DidFinish());
220  ASSERT_TRUE(client.fetcher_->WasCancelled());
221  EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult());
222  EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
223  EXPECT_EQ(GURL(), client.fetcher_->GetPacURL());
224  client.FinishTestAllowCleanup();
225}
226
227TEST(DhcpProxyScriptAdapterFetcher, CancelWhileFetcher) {
228  FetcherClient client;
229  // This causes the mock fetcher not to pretend the
230  // fetcher finishes after a timeout.
231  client.fetcher_->fetcher_delay_ms_ = -1;
232  client.RunTest();
233  int max_loops = 4;
234  while (!client.fetcher_->IsWaitingForFetcher() && max_loops--) {
235    base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10));
236    base::MessageLoop::current()->RunUntilIdle();
237  }
238  client.fetcher_->Cancel();
239  base::MessageLoop::current()->RunUntilIdle();
240  ASSERT_FALSE(client.fetcher_->DidFinish());
241  ASSERT_TRUE(client.fetcher_->WasCancelled());
242  EXPECT_EQ(ERR_ABORTED, client.fetcher_->GetResult());
243  EXPECT_EQ(base::string16(L""), client.fetcher_->GetPacScript());
244  // GetPacURL() still returns the URL fetched in this case.
245  EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
246  client.FinishTestAllowCleanup();
247}
248
249TEST(DhcpProxyScriptAdapterFetcher, CancelAtCompletion) {
250  FetcherClient client;
251  client.RunTest();
252  client.WaitForResult(OK);
253  client.fetcher_->Cancel();
254  // Canceling after you're done should have no effect, so these
255  // are identical expectations to the NormalCaseURLInDhcp test.
256  ASSERT_TRUE(client.fetcher_->DidFinish());
257  EXPECT_EQ(OK, client.fetcher_->GetResult());
258  EXPECT_EQ(base::string16(L"bingo"), client.fetcher_->GetPacScript());
259  EXPECT_EQ(GURL(kPacUrl), client.fetcher_->GetPacURL());
260  client.FinishTestAllowCleanup();
261}
262
263// Does a real fetch on a mock DHCP configuration.
264class MockDhcpRealFetchProxyScriptAdapterFetcher
265    : public MockDhcpProxyScriptAdapterFetcher {
266 public:
267  explicit MockDhcpRealFetchProxyScriptAdapterFetcher(
268      URLRequestContext* context,
269      scoped_refptr<base::TaskRunner> task_runner)
270      : MockDhcpProxyScriptAdapterFetcher(context, task_runner),
271        url_request_context_(context) {
272  }
273
274  // Returns a real proxy script fetcher.
275  ProxyScriptFetcher* ImplCreateScriptFetcher() OVERRIDE {
276    ProxyScriptFetcher* fetcher =
277        new ProxyScriptFetcherImpl(url_request_context_);
278    return fetcher;
279  }
280
281  URLRequestContext* url_request_context_;
282};
283
284TEST(DhcpProxyScriptAdapterFetcher, MockDhcpRealFetch) {
285  SpawnedTestServer test_server(
286      SpawnedTestServer::TYPE_HTTP,
287      SpawnedTestServer::kLocalhost,
288      base::FilePath(
289          FILE_PATH_LITERAL("net/data/proxy_script_fetcher_unittest")));
290  ASSERT_TRUE(test_server.Start());
291
292  GURL configured_url = test_server.GetURL("files/downloadable.pac");
293
294  FetcherClient client;
295  TestURLRequestContext url_request_context;
296  scoped_refptr<base::TaskRunner> runner =
297      client.worker_pool_->GetTaskRunnerWithShutdownBehavior(
298          base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
299  client.fetcher_.reset(
300      new MockDhcpRealFetchProxyScriptAdapterFetcher(
301          &url_request_context, runner));
302  client.fetcher_->configured_url_ = configured_url.spec();
303  client.RunTest();
304  client.WaitForResult(OK);
305  ASSERT_TRUE(client.fetcher_->DidFinish());
306  EXPECT_EQ(OK, client.fetcher_->GetResult());
307  EXPECT_EQ(base::string16(L"-downloadable.pac-\n"),
308            client.fetcher_->GetPacScript());
309  EXPECT_EQ(configured_url,
310            client.fetcher_->GetPacURL());
311}
312
313#define BASE_URL "http://corpserver/proxy.pac"
314
315TEST(DhcpProxyScriptAdapterFetcher, SanitizeDhcpApiString) {
316  const size_t kBaseUrlLen = strlen(BASE_URL);
317
318  // Default case.
319  EXPECT_EQ(BASE_URL,
320            DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString(
321                BASE_URL, kBaseUrlLen));
322
323  // Trailing \n and no null-termination.
324  EXPECT_EQ(BASE_URL,
325            DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString(
326                BASE_URL "\nblablabla", kBaseUrlLen + 1));
327
328  // Embedded NULLs.
329  EXPECT_EQ(BASE_URL,
330            DhcpProxyScriptAdapterFetcher::SanitizeDhcpApiString(
331                BASE_URL "\0foo\0blat", kBaseUrlLen + 9));
332}
333
334#undef BASE_URL
335
336}  // namespace
337
338}  // namespace net
339