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#ifndef CHROME_FRAME_TEST_TEST_WITH_WEB_SERVER_H_
6#define CHROME_FRAME_TEST_TEST_WITH_WEB_SERVER_H_
7
8#include <windows.h>
9#include <string>
10
11#include "base/files/scoped_temp_dir.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/win/scoped_handle.h"
16#include "chrome_frame/chrome_tab.h"
17#include "chrome_frame/test/chrome_frame_test_utils.h"
18#include "chrome_frame/test/test_server.h"
19#include "testing/gmock/include/gmock/gmock.h"
20#include "testing/gtest/include/gtest/gtest.h"
21
22// Specifies the invocation method for CF.
23class CFInvocation {
24 public:
25  enum Type {
26    NONE = 0,
27    META_TAG,
28    HTTP_HEADER
29  };
30
31  CFInvocation(): method_(NONE) {}
32  explicit CFInvocation(Type method): method_(method) {}
33
34  // Convience methods for creating this class.
35  static CFInvocation None() { return CFInvocation(NONE); }
36  static CFInvocation MetaTag() { return CFInvocation(META_TAG); }
37  static CFInvocation HttpHeader() { return CFInvocation(HTTP_HEADER); }
38
39  // Returns whether this page does invoke CF.
40  bool invokes_cf() const {
41    return method_ != NONE;
42  }
43
44  Type type() const { return method_; }
45
46 private:
47  Type method_;
48};
49
50// An interface for listeners of interesting events on a MockWebServer.
51class WebServerListener {
52 public:
53  virtual ~WebServerListener() {}
54
55  // Invoked when a MockWebServer receives an expected response; see
56  // MockWebServer::ExpectAndHandlePostedResult.
57  virtual void OnExpectedResponse() = 0;
58};
59
60// Simple Gmock friendly web server. Sample usage:
61// MockWebServer mock(9999, "0.0.0.0");
62// EXPECT_CALL(mock, Get(_, StrEq("/favicon.ico"), _)).WillRepeatedly(SendFast(
63//     "HTTP/1.1 404 Not Found"
64//     "text/html; charset=UTF-8", EmptyString()));
65//
66// EXPECT_CALL(mock, Get(_, StrEq("/book"), _)).WillRepeatedly(Send(
67//     "HTTP/1.1 302 Found\r\n"
68//     "Connection: close\r\n"
69//     "Content-Type: text/html\r\n"
70//     "Location: library\r\n",
71//     "<html>Lalalala</html>", 3, 1000));
72//
73// EXPECT_CALL(mock, Get(_, StrEq("/library"), _)).WillRepeatedly(Send(
74//     "HTTP/1.1 200 OK\r\n"
75//     "Connection: close\r\n"
76//     "Content-Type: text/html\r\n",
77//     "<html><meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />"
78//     "<body>Rendered in CF.</body></html>", 4, 1000));
79class MockWebServer : public test_server::HTTPTestServer {
80 public:
81  MockWebServer(int port, const std::wstring& address, base::FilePath root_dir)
82      : test_server::HTTPTestServer(port, address, root_dir), listener_(NULL) {}
83
84  // Overriden from test_server::HTTPTestServer.
85  MOCK_METHOD3(Get, void(test_server::ConfigurableConnection* connection,
86                         const std::wstring& path,
87                         const test_server::Request& r));
88  MOCK_METHOD3(Post, void(test_server::ConfigurableConnection* connection,
89                          const std::wstring& path,
90                          const test_server::Request& r));
91
92  // Expect a GET request for |url|. Respond with the file appropriate for
93  // the given |url|. Modify the file to follow the given CFInvocation method.
94  // The response includes a no-cache header. |allow_meta_tag_double_req|
95  // specifies whether to allow the request to happen twice if the invocation
96  // is using the CF meta tag.
97  void ExpectAndServeRequest(CFInvocation invocation, const std::wstring& url);
98
99  // Expect a number of GET requests for |url|. Rest is similar to the function
100  // ExpectAndServeRequest.
101  void ExpectAndServeRequestWithCardinality(CFInvocation invocation,
102                                            const std::wstring& url,
103                                            testing::Cardinality cardinality);
104
105  // Same as above except do not include the no-cache header.
106  void ExpectAndServeRequestAllowCache(CFInvocation invocation,
107                                       const std::wstring& url);
108
109  // Expect any number of GETs for the given resource path (e.g, /favicon.ico)
110  // and respond with the file, if it exists, or a 404 if it does not.
111  void ExpectAndServeRequestAnyNumberTimes(CFInvocation invocation,
112                                           const std::wstring& path_prefix);
113
114  void set_listener(WebServerListener* listener) { listener_ = listener; }
115
116  // Expect a POST to an URL containing |post_suffix|, saving the response
117  // contents for retrieval by posted_result(). Invokes the listener's
118  // OnExpectedResponse method if the posted response matches the expected
119  // result.
120  void ExpectAndHandlePostedResult(CFInvocation invocation,
121                                   const std::wstring& post_suffix);
122
123  // Expect and serve all incoming GET requests.
124  void ExpectAndServeAnyRequests(CFInvocation invocation) {
125    ExpectAndServeRequestAnyNumberTimes(invocation, L"");
126  }
127
128
129  // Send a response on the given connection appropriate for |resource_uri|.
130  // If the file referred to by |path| exists, send the file data, otherwise
131  // send 404. Modify the file data according to the given invocation method.
132  void SendResponseHelper(test_server::ConfigurableConnection* connection,
133                          const std::wstring& resource_uri,
134                          const test_server::Request& request,
135                          CFInvocation invocation,
136                          bool add_no_cache_header);
137  // Handles the posted /writefile response
138  void HandlePostedResponse(test_server::ConfigurableConnection* connection,
139                            const test_server::Request& request);
140
141  void ClearResults() {
142    posted_result_.clear();
143    expected_result_.clear();
144  }
145
146  void set_expected_result(const std::string& expected_result) {
147    expected_result_  = expected_result;
148  }
149
150  const std::string& posted_result() const {
151    return posted_result_;
152  }
153
154 private:
155  WebServerListener* listener_;
156  // Holds the results of tests which post success/failure.
157  std::string posted_result_;
158  std::string expected_result_;
159};
160
161class MockWebServerListener : public WebServerListener {
162 public:
163  MOCK_METHOD0(OnExpectedResponse, void());
164};
165
166// Class that:
167// 1) Starts the local webserver,
168// 2) Supports launching browsers - Internet Explorer with local url
169// 3) Wait the webserver to finish. It is supposed the test webpage to shutdown
170//    the server by navigating to "kill" page
171// 4) Supports read the posted results from the test webpage to the "dump"
172//    webserver directory
173class ChromeFrameTestWithWebServer : public testing::Test {
174 public:
175  ChromeFrameTestWithWebServer();
176
177 protected:
178  enum BrowserKind { INVALID, IE, CHROME };
179
180  bool LaunchBrowser(BrowserKind browser, const wchar_t* url);
181
182  // Returns true if the test completed in time, or false if it timed out.
183  bool WaitForTestToComplete(base::TimeDelta duration);
184
185  // Waits for the page to notify us of the window.onload event firing.
186  // Note that the milliseconds value is only approximate.
187  bool WaitForOnLoad(int milliseconds);
188
189  // Launches the specified browser and waits for the test to complete (see
190  // WaitForTestToComplete).  Then checks that the outcome is equal to the
191  // expected result.  The test is repeated once if it fails due to a timeout.
192  // This function uses EXPECT_TRUE and ASSERT_TRUE for all steps performed
193  // hence no return value.
194  void SimpleBrowserTestExpectedResult(BrowserKind browser,
195      const wchar_t* page, const char* result);
196  void SimpleBrowserTest(BrowserKind browser, const wchar_t* page);
197
198  // Sets up expectations for a page to post back a result.
199  void ExpectAndHandlePostedResult();
200
201  // Test if chrome frame correctly reports its version.
202  void VersionTest(BrowserKind browser, const wchar_t* page);
203
204  void CloseBrowser();
205
206  // Ensures (well, at least tries to ensure) that the browser window has focus.
207  bool BringBrowserToTop();
208
209  const base::FilePath& GetCFTestFilePath() {
210    return test_file_path_;
211  }
212
213  static chrome_frame_test::TimedMsgLoop& loop() {
214    return *loop_;
215  }
216
217  static testing::StrictMock<MockWebServerListener>& listener_mock() {
218    return *listener_mock_;
219  }
220
221  static testing::StrictMock<MockWebServer>& server_mock() {
222    return *server_mock_;
223  }
224
225  static void SetUpTestCase();
226  static void TearDownTestCase();
227
228  static const base::FilePath& GetChromeUserDataDirectory();
229
230  virtual void SetUp() OVERRIDE;
231  virtual void TearDown() OVERRIDE;
232
233  // The on-disk path to our html test files.
234  static base::FilePath test_file_path_;
235  static base::FilePath results_dir_;
236  static base::FilePath CFInstall_path_;
237  static base::FilePath CFInstance_path_;
238  static base::FilePath chrome_user_data_dir_;
239
240  // The user data directory used for Chrome instances.
241  static base::ScopedTempDir temp_dir_;
242
243  // The web server from which we serve the web!
244  static chrome_frame_test::TimedMsgLoop* loop_;
245  static std::string local_address_;
246  static testing::StrictMock<MockWebServerListener>* listener_mock_;
247  static testing::StrictMock<MockWebServer>* server_mock_;
248
249  BrowserKind browser_;
250  base::win::ScopedHandle browser_handle_;
251};
252
253// A helper class for doing some bookkeeping when using the
254// SimpleWebServer class.
255class SimpleWebServerTest {
256 public:
257  SimpleWebServerTest(const std::string& address, int port)
258      : server_(address, port), port_(port) {
259  }
260
261  ~SimpleWebServerTest() {
262    server_.DeleteAllResponses();
263  }
264
265  template <class ResponseClass>
266  void PopulateStaticFileListT(const wchar_t* pages[], int count,
267                               const base::FilePath& directory) {
268    for (int i = 0; i < count; ++i) {
269      server_.AddResponse(new ResponseClass(
270          base::StringPrintf("/%ls", pages[i]).c_str(),
271                             directory.Append(pages[i])));
272    }
273  }
274
275  std::wstring FormatHttpPath(const wchar_t* document_path) {
276    return base::StringPrintf(L"http://%ls:%i/%ls",
277                              ASCIIToWide(server_.host()).c_str(), port_,
278                              document_path);
279  }
280
281  // Returns the last client request object.
282  // Under normal circumstances this will be the request for /quit.
283  const test_server::Request& last_request() const {
284    const test_server::ConnectionList& connections = server_.connections();
285    DCHECK(connections.size());
286    const test_server::Connection* c = connections.back();
287    return c->request();
288  }
289
290  bool FindRequest(const std::string& path,
291                   const test_server::Request** request) {
292    test_server::ConnectionList::const_iterator index;
293    for (index = server_.connections().begin();
294         index != server_.connections().end(); index++) {
295      const test_server::Connection* connection = *index;
296      if (!lstrcmpiA(connection->request().path().c_str(), path.c_str())) {
297        if (request)
298          *request = &connection->request();
299        return true;
300      }
301    }
302    return false;
303  }
304
305  // Counts the number of times a page was requested.
306  // Optionally checks if the request method for each is equal to
307  // |expected_method|.  If expected_method is NULL no such check is made.
308  int GetRequestCountForPage(const wchar_t* page, const char* expected_method) {
309    // Check how many requests we got for the cf page.
310    test_server::ConnectionList::const_iterator it;
311    int requests = 0;
312    const test_server::ConnectionList& connections = server_.connections();
313    for (it = connections.begin(); it != connections.end(); ++it) {
314      const test_server::Connection* c = (*it);
315      const test_server::Request& r = c->request();
316      if (!r.path().empty() &&
317          ASCIIToWide(r.path().substr(1)).compare(page) == 0) {
318        if (expected_method) {
319          EXPECT_EQ(expected_method, r.method());
320        }
321        requests++;
322      }
323    }
324    return requests;
325  }
326
327  test_server::SimpleWebServer* web_server() {
328    return &server_;
329  }
330
331 protected:
332  test_server::SimpleWebServer server_;
333  int port_;
334};
335
336ACTION_P2(SendFast, headers, content) {
337  arg0->Send(headers, content);
338}
339
340ACTION_P4(Send, headers, content, chunk, timeout) {
341  test_server::ConfigurableConnection::SendOptions options(
342      test_server::ConfigurableConnection::SendOptions::
343        IMMEDIATE_HEADERS_DELAYED_CONTENT, chunk, timeout);
344  arg0->SendWithOptions(std::string(headers),
345                        std::string(content),
346                        options);
347}
348
349ACTION_P4(SendSlow, headers, content, chunk, timeout) {
350  test_server::ConfigurableConnection::SendOptions options(
351    test_server::ConfigurableConnection::SendOptions::DELAYED, chunk, timeout);
352  arg0->SendWithOptions(std::string(headers),
353                        std::string(content),
354                        options);
355}
356
357// Sends a response with the file data for the given path, if the file exists,
358// or a 404 if the file does not. This response includes a no-cache header.
359ACTION_P2(SendResponse, server, invocation) {
360  server->SendResponseHelper(arg0, arg1, arg2, invocation, true);
361}
362
363// Same as above except that the response does not include the no-cache header.
364ACTION_P2(SendAllowCacheResponse, server, invocation) {
365  server->SendResponseHelper(arg0, arg1, invocation, false);
366}
367
368ACTION_P2(HandlePostedResponseHelper, server, invocation) {
369  server->HandlePostedResponse(arg0, arg2);
370}
371
372#endif  // CHROME_FRAME_TEST_TEST_WITH_WEB_SERVER_H_
373