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 "chrome/browser/printing/print_dialog_cloud.h"
6#include "chrome/browser/printing/print_dialog_cloud_internal.h"
7
8#include <functional>
9
10#include "base/bind.h"
11#include "base/file_util.h"
12#include "base/files/file_path.h"
13#include "base/memory/singleton.h"
14#include "base/path_service.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/threading/thread_restrictions.h"
17#include "base/values.h"
18#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/browser/ui/browser.h"
21#include "chrome/browser/ui/browser_window.h"
22#include "chrome/common/chrome_paths.h"
23#include "chrome/common/url_constants.h"
24#include "chrome/test/base/in_process_browser_test.h"
25#include "chrome/test/base/ui_test_utils.h"
26#include "content/public/browser/notification_service.h"
27#include "content/public/browser/notification_types.h"
28#include "content/public/browser/render_view_host.h"
29#include "content/public/browser/web_contents.h"
30#include "content/public/test/test_browser_thread.h"
31#include "net/url_request/url_request.h"
32#include "net/url_request/url_request_filter.h"
33#include "net/url_request/url_request_test_job.h"
34#include "net/url_request/url_request_test_util.h"
35#include "ui/base/test/ui_controls.h"
36
37using content::BrowserThread;
38
39namespace {
40
41class TestData {
42 public:
43  static TestData* GetInstance() {
44    return Singleton<TestData>::get();
45  }
46
47  const char* GetTestData() {
48    // Fetching this data blocks the IO thread, but we don't really care because
49    // this is a test.
50    base::ThreadRestrictions::ScopedAllowIO allow_io;
51
52    if (test_data_.empty()) {
53      base::FilePath test_data_directory;
54      PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory);
55      base::FilePath test_file =
56          test_data_directory.AppendASCII("printing/cloud_print_uitest.html");
57      file_util::ReadFileToString(test_file, &test_data_);
58    }
59    return test_data_.c_str();
60  }
61 private:
62  TestData() {}
63
64  std::string test_data_;
65
66  friend struct DefaultSingletonTraits<TestData>;
67};
68
69// A simple test net::URLRequestJob. We don't care what it does, only that
70// whether it starts and finishes.
71class SimpleTestJob : public net::URLRequestTestJob {
72 public:
73  SimpleTestJob(net::URLRequest* request,
74                net::NetworkDelegate* network_delegate)
75      : net::URLRequestTestJob(request,
76                               network_delegate,
77                               test_headers(),
78                               TestData::GetInstance()->GetTestData(),
79                               true) {}
80
81  virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
82    net::URLRequestTestJob::GetResponseInfo(info);
83    if (request_->url().SchemeIsSecure()) {
84      // Make up a fake certificate for this response since we don't have
85      // access to the real SSL info.
86      const char* kCertIssuer = "Chrome Internal";
87      const int kLifetimeDays = 100;
88
89      info->ssl_info.cert =
90          new net::X509Certificate(request_->url().GetWithEmptyPath().spec(),
91                                   kCertIssuer,
92                                   base::Time::Now(),
93                                   base::Time::Now() +
94                                   base::TimeDelta::FromDays(kLifetimeDays));
95      info->ssl_info.cert_status = 0;
96      info->ssl_info.security_bits = -1;
97    }
98  }
99
100 private:
101  virtual ~SimpleTestJob() {}
102};
103
104class TestController {
105 public:
106  static TestController* GetInstance() {
107    return Singleton<TestController>::get();
108  }
109  void set_result(bool value) {
110    result_ = value;
111  }
112  bool result() {
113    return result_;
114  }
115  void set_expected_url(const GURL& url) {
116    expected_url_ = url;
117  }
118  const GURL expected_url() {
119    return expected_url_;
120  }
121  void set_delegate(net::TestDelegate* delegate) {
122    delegate_ = delegate;
123  }
124  net::TestDelegate* delegate() {
125    return delegate_;
126  }
127  void set_use_delegate(bool value) {
128    use_delegate_ = value;
129  }
130  bool use_delegate() {
131    return use_delegate_;
132  }
133 private:
134  TestController()
135      : result_(false),
136        use_delegate_(false),
137        delegate_(NULL) {}
138
139  bool result_;
140  bool use_delegate_;
141  GURL expected_url_;
142  net::TestDelegate* delegate_;
143
144  friend struct DefaultSingletonTraits<TestController>;
145};
146
147}  // namespace
148
149class PrintDialogCloudTest : public InProcessBrowserTest {
150 public:
151  PrintDialogCloudTest() : handler_added_(false) {
152    PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory_);
153  }
154
155  // Must be static for handing into AddHostnameHandler.
156  static net::URLRequest::ProtocolFactory Factory;
157
158  class AutoQuitDelegate : public net::TestDelegate {
159   public:
160    AutoQuitDelegate() {}
161
162    virtual void OnResponseCompleted(net::URLRequest* request) OVERRIDE {
163      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
164                              base::MessageLoop::QuitClosure());
165    }
166  };
167
168  virtual void SetUp() OVERRIDE {
169    TestController::GetInstance()->set_result(false);
170    InProcessBrowserTest::SetUp();
171  }
172
173  virtual void TearDown() OVERRIDE {
174    if (handler_added_) {
175      BrowserThread::PostTask(
176          BrowserThread::IO, FROM_HERE,
177          base::Bind(UnregisterTestHandlers, scheme_, host_name_));
178      handler_added_ = false;
179      TestController::GetInstance()->set_delegate(NULL);
180    }
181    InProcessBrowserTest::TearDown();
182  }
183
184  // Normally this is something I would expect could go into SetUp(),
185  // but there seems to be some timing or ordering related issue with
186  // the test harness that made that flaky.  Calling this from the
187  // individual test functions seems to fix that.
188  void AddTestHandlers() {
189    if (!handler_added_) {
190      GURL cloud_print_service_url =
191          CloudPrintURL(browser()->profile()).
192          GetCloudPrintServiceURL();
193      scheme_ = cloud_print_service_url.scheme();
194      host_name_ = cloud_print_service_url.host();
195      BrowserThread::PostTask(
196          BrowserThread::IO, FROM_HERE,
197          base::Bind(RegisterTestHandlers, scheme_, host_name_));
198      handler_added_ = true;
199
200      GURL cloud_print_dialog_url =
201          CloudPrintURL(browser()->profile()).
202          GetCloudPrintServiceDialogURL();
203      TestController::GetInstance()->set_expected_url(cloud_print_dialog_url);
204      TestController::GetInstance()->set_delegate(&delegate_);
205    }
206
207    CreateDialogForTest();
208  }
209
210  void CreateDialogForTest() {
211    base::FilePath path_to_pdf =
212        test_data_directory_.AppendASCII("printing/cloud_print_uitest.pdf");
213    BrowserThread::PostTask(
214        BrowserThread::UI, FROM_HERE,
215        base::Bind(&print_dialog_cloud::CreatePrintDialogForFile,
216                   browser()->profile(), browser()->window()->GetNativeWindow(),
217                   path_to_pdf, string16(), string16(),
218                   std::string("application/pdf"), false));
219  }
220
221 private:
222  static void RegisterTestHandlers(const std::string& scheme,
223                       const std::string& host_name) {
224    net::URLRequestFilter::GetInstance()->AddHostnameHandler(
225        scheme, host_name, &PrintDialogCloudTest::Factory);
226  }
227  static void UnregisterTestHandlers(const std::string& scheme,
228                         const std::string& host_name) {
229    net::URLRequestFilter::GetInstance()->RemoveHostnameHandler(scheme,
230                                                                host_name);
231  }
232
233  bool handler_added_;
234  std::string scheme_;
235  std::string host_name_;
236  base::FilePath test_data_directory_;
237  AutoQuitDelegate delegate_;
238};
239
240net::URLRequestJob* PrintDialogCloudTest::Factory(
241    net::URLRequest* request,
242    net::NetworkDelegate* network_delegate,
243    const std::string& scheme) {
244  if (request &&
245      (request->url() == TestController::GetInstance()->expected_url())) {
246    if (TestController::GetInstance()->use_delegate())
247      request->set_delegate(TestController::GetInstance()->delegate());
248    TestController::GetInstance()->set_result(true);
249    return new SimpleTestJob(request, network_delegate);
250  }
251  return new net::URLRequestTestJob(request,
252                                    network_delegate,
253                                    net::URLRequestTestJob::test_headers(),
254                                    std::string(),
255                                    true);
256}
257
258IN_PROC_BROWSER_TEST_F(PrintDialogCloudTest, HandlersRegistered) {
259  AddTestHandlers();
260
261  TestController::GetInstance()->set_use_delegate(true);
262
263  content::RunMessageLoop();
264
265  ASSERT_TRUE(TestController::GetInstance()->result());
266
267  // Close the dialog before finishing the test.
268  content::WindowedNotificationObserver tab_closed_observer(
269      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
270      content::NotificationService::AllSources());
271
272  // Can't use ui_test_utils::SendKeyPressSync or
273  // ui_test_utils::SendKeyPressAndWait due to a race condition with closing
274  // the window. See http://crbug.com/111269
275  BrowserWindow* window = browser()->window();
276  ASSERT_TRUE(window);
277  gfx::NativeWindow native_window = window->GetNativeWindow();
278  ASSERT_TRUE(native_window);
279  bool key_sent = ui_controls::SendKeyPress(native_window, ui::VKEY_ESCAPE,
280                                            false, false, false, false);
281  EXPECT_TRUE(key_sent);
282  if (key_sent)
283    tab_closed_observer.Wait();
284}
285