// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/prerender/prerender_resource_handler.h"
#include "content/common/resource_response.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace prerender {

namespace {

class MockResourceHandler : public ResourceHandler {
 public:
  MockResourceHandler() {}

  virtual bool OnUploadProgress(int request_id,
                                uint64 position,
                                uint64 size) {
    return true;
  }

  virtual bool OnRequestRedirected(int request_id, const GURL& url,
                                   ResourceResponse* response,
                                   bool* defer) {
    *defer = false;
    return true;
  }

  virtual bool OnResponseStarted(int request_id,
                                 ResourceResponse* response) {
    return true;
  }

  virtual bool OnWillStart(int request_id, const GURL& url, bool* defer) {
    *defer = false;
    return true;
  }

  virtual bool OnWillRead(int request_id,
                          net::IOBuffer** buf,
                          int* buf_size,
                          int min_size) {
    return true;
  }

  virtual bool OnReadCompleted(int request_id, int* bytes_read) {
    return true;
  }

  virtual bool OnResponseCompleted(int request_id,
                                   const net::URLRequestStatus& status,
                                   const std::string& security_info) {
    return true;
  }

  virtual void OnRequestClosed() {
  }

  virtual void OnDataDownloaded(int request_id, int bytes_downloaded) {}
};

// HttpResponseHeaders expects the raw input for it's constructor
// to be a NUL ('\0') separated string for each line. This is a little
// difficult to do for string literals, so this helper function accepts
// newline-separated string literals and does the substitution. The
// returned object is expected to be deleted by the caller.
net::HttpResponseHeaders* CreateResponseHeaders(
    const char* newline_separated_headers) {
  std::string headers(newline_separated_headers);
  std::string::iterator i = headers.begin();
  std::string::iterator end = headers.end();
  while (i != end) {
    if (*i == '\n')
      *i = '\0';
    ++i;
  }
  return new net::HttpResponseHeaders(headers);
}

}  // namespace

class PrerenderResourceHandlerTest : public testing::Test {
 protected:
  PrerenderResourceHandlerTest()
      : loop_(MessageLoop::TYPE_IO),
        ui_thread_(BrowserThread::UI, &loop_),
        io_thread_(BrowserThread::IO, &loop_),
        test_url_request_(GURL("http://www.referrer.com"),
                          &test_url_request_delegate_),
        ALLOW_THIS_IN_INITIALIZER_LIST(
            pre_handler_(new PrerenderResourceHandler(
                test_url_request_,
                new MockResourceHandler(),
                NewCallback(
                    this,
                    &PrerenderResourceHandlerTest::SetLastHandledURL)))),
        default_url_("http://www.prerender.com") {
  }

  virtual ~PrerenderResourceHandlerTest() {
    // When a ResourceHandler's reference count drops to 0, it is not
    // deleted immediately. Instead, a task is posted to the IO thread's
    // message loop to delete it.
    // So, drop the reference count to 0 and run the message loop once
    // to ensure that all resources are cleaned up before the test exits.
    pre_handler_ = NULL;
    loop_.RunAllPending();
  }

  void SetLastHandledURL(const std::pair<int, int>& child_route_id_pair,
                         const GURL& url, const std::vector<GURL>& alias_urls,
                         const GURL& referrer, bool make_pending) {
    last_handled_url_ = url;
    alias_urls_ = alias_urls;
    referrer_ = referrer;
  }

  // Common logic shared by many of the tests
  void StartPrerendering(const std::string& mime_type,
                         const char* headers) {
    int request_id = 1;
    bool defer = false;
    EXPECT_TRUE(pre_handler_->OnWillStart(request_id, default_url_, &defer));
    EXPECT_FALSE(defer);
    scoped_refptr<ResourceResponse> response(new ResourceResponse);
    response->response_head.mime_type = mime_type;
    response->response_head.headers = CreateResponseHeaders(headers);
    EXPECT_TRUE(last_handled_url_.is_empty());

    // Start the response. If it is able to prerender, a task will
    // be posted to the UI thread and |SetLastHandledURL| will be called.
    EXPECT_TRUE(pre_handler_->OnResponseStarted(request_id, response));
    loop_.RunAllPending();
  }

  // Test whether a given URL is part of alias_urls_.
  bool ContainsAliasURL(const GURL& url) {
    return std::find(alias_urls_.begin(), alias_urls_.end(), url)
        != alias_urls_.end();
  }

  // Must be initialized before |test_url_request_|.
  MessageLoop loop_;
  BrowserThread ui_thread_;
  BrowserThread io_thread_;

  TestDelegate test_url_request_delegate_;
  TestURLRequest test_url_request_;

  scoped_refptr<PrerenderResourceHandler> pre_handler_;
  GURL last_handled_url_;
  GURL default_url_;
  std::vector<GURL> alias_urls_;
  GURL referrer_;
};

namespace {

TEST_F(PrerenderResourceHandlerTest, NoOp) {
}

// Tests that a valid HTML resource will correctly get diverted
// to the PrerenderManager.
TEST_F(PrerenderResourceHandlerTest, Prerender) {
  StartPrerendering("text/html",
                    "HTTP/1.1 200 OK\n");
  EXPECT_EQ(default_url_, last_handled_url_);
}

static const int kRequestId = 1;

// Tests that the final request in a redirect chain will
// get diverted to the PrerenderManager.
TEST_F(PrerenderResourceHandlerTest, PrerenderRedirect) {
  GURL url_redirect("http://www.redirect.com");
  bool defer = false;
  EXPECT_TRUE(pre_handler_->OnWillStart(kRequestId, default_url_, &defer));
  EXPECT_FALSE(defer);
  EXPECT_TRUE(pre_handler_->OnRequestRedirected(kRequestId,
                                                url_redirect,
                                                NULL,
                                                &defer));
  EXPECT_FALSE(defer);
  scoped_refptr<ResourceResponse> response(new ResourceResponse);
  response->response_head.mime_type = "text/html";
  response->response_head.headers = CreateResponseHeaders(
      "HTTP/1.1 200 OK\n");
  EXPECT_TRUE(pre_handler_->OnResponseStarted(kRequestId, response));
  EXPECT_TRUE(last_handled_url_.is_empty());
  loop_.RunAllPending();
  EXPECT_EQ(url_redirect, last_handled_url_);
  EXPECT_EQ(true, ContainsAliasURL(url_redirect));
  EXPECT_EQ(true, ContainsAliasURL(default_url_));
  EXPECT_EQ(2, static_cast<int>(alias_urls_.size()));
}

// Tests that https requests will not be prerendered.
TEST_F(PrerenderResourceHandlerTest, PrerenderHttps) {
  GURL url_https("https://www.google.com");
  bool defer = false;
  EXPECT_FALSE(pre_handler_->OnWillStart(kRequestId, url_https, &defer));
  EXPECT_FALSE(defer);
}

TEST_F(PrerenderResourceHandlerTest, PrerenderRedirectToHttps) {
  bool defer = false;
  EXPECT_TRUE(pre_handler_->OnWillStart(kRequestId, default_url_, &defer));
  EXPECT_FALSE(defer);
  GURL url_https("https://www.google.com");
  EXPECT_FALSE(pre_handler_->OnRequestRedirected(kRequestId,
                                                 url_https,
                                                 NULL,
                                                 &defer));
  EXPECT_FALSE(defer);
}

}  // namespace

}  // namespace prerender