// 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 "base/time.h"
#include "chrome/browser/prerender/prerender_contents.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "content/browser/browser_thread.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/renderer_host/render_process_host.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace prerender {

namespace {

class DummyPrerenderContents : public PrerenderContents {
 public:
  DummyPrerenderContents(PrerenderManager* prerender_manager,
                         const GURL& url,
                         FinalStatus expected_final_status)
      : PrerenderContents(prerender_manager, NULL, url,
                          std::vector<GURL>(), GURL()),
        has_started_(false),
        expected_final_status_(expected_final_status) {
  }

  DummyPrerenderContents(PrerenderManager* prerender_manager,
                         const GURL& url,
                         const std::vector<GURL> alias_urls,
                         FinalStatus expected_final_status)
      : PrerenderContents(prerender_manager, NULL, url, alias_urls, GURL()),
        has_started_(false),
        expected_final_status_(expected_final_status) {
  }

  virtual ~DummyPrerenderContents() {
    EXPECT_EQ(expected_final_status_, final_status());
  }

  virtual void StartPrerendering() OVERRIDE {
    has_started_ = true;
  }

  virtual bool GetChildId(int* child_id) const OVERRIDE {
    *child_id = 0;
    return true;
  }

  virtual bool GetRouteId(int* route_id) const OVERRIDE {
    *route_id = 0;
    return true;
  }

  bool has_started() const { return has_started_; }

 private:
  bool has_started_;
  FinalStatus expected_final_status_;
};

class TestPrerenderManager : public PrerenderManager {
 public:
  TestPrerenderManager()
      : PrerenderManager(NULL),
        time_(base::Time::Now()),
        time_ticks_(base::TimeTicks::Now()),
        next_pc_(NULL) {
    rate_limit_enabled_ = false;
  }

  void AdvanceTime(base::TimeDelta delta) {
    time_ += delta;
  }

  void AdvanceTimeTicks(base::TimeDelta delta) {
    time_ticks_ += delta;
  }

  void SetNextPrerenderContents(PrerenderContents* pc) {
    next_pc_.reset(pc);
  }

  // Shorthand to add a simple preload with no aliases.
  bool AddSimplePreload(const GURL& url) {
    return AddPreload(url, std::vector<GURL>(), GURL());
  }

  bool IsPendingEntry(const GURL& url) {
    return (PrerenderManager::FindPendingEntry(url) != NULL);
  }

  void set_rate_limit_enabled(bool enabled) { rate_limit_enabled_ = true; }

  PrerenderContents* next_pc() { return next_pc_.get(); }

 protected:
  virtual ~TestPrerenderManager() {
    if (next_pc()) {
      next_pc()->set_final_status(
          FINAL_STATUS_MANAGER_SHUTDOWN);
    }
  }

 private:
  virtual base::Time GetCurrentTime() const OVERRIDE {
    return time_;
  }

  virtual base::TimeTicks GetCurrentTimeTicks() const OVERRIDE {
    return time_ticks_;
  }

  virtual PrerenderContents* CreatePrerenderContents(
      const GURL& url,
      const std::vector<GURL>& alias_urls,
      const GURL& referrer) OVERRIDE {
    DCHECK(next_pc_.get());
    return next_pc_.release();
  }

  base::Time time_;
  base::TimeTicks time_ticks_;
  scoped_ptr<PrerenderContents> next_pc_;
};

}  // namespace

class PrerenderManagerTest : public testing::Test {
 public:
  PrerenderManagerTest() : prerender_manager_(new TestPrerenderManager()),
                           ui_thread_(BrowserThread::UI, &message_loop_) {
  }

 protected:
  scoped_refptr<TestPrerenderManager> prerender_manager_;

 private:
  // Needed to pass PrerenderManager's DCHECKs.
  MessageLoop message_loop_;
  BrowserThread ui_thread_;
};

TEST_F(PrerenderManagerTest, EmptyTest) {
  GURL url("http://www.google.com/");
  EXPECT_FALSE(prerender_manager_->MaybeUsePreloadedPage(NULL, url));
}

TEST_F(PrerenderManagerTest, FoundTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(),
                                 url,
                                 FINAL_STATUS_USED);
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_TRUE(pc->has_started());
  ASSERT_EQ(pc, prerender_manager_->GetEntry(url));
  pc->set_final_status(FINAL_STATUS_USED);
  delete pc;
}

// Make sure that if queue a request, and a second prerender request for the
// same URL comes in, that we drop the second request and keep the first one.
TEST_F(PrerenderManagerTest, DropSecondRequestTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url,
                                 FINAL_STATUS_USED);
  DummyPrerenderContents* null = NULL;
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc->has_started());
  DummyPrerenderContents* pc1 =
      new DummyPrerenderContents(
          prerender_manager_.get(), url,
          FINAL_STATUS_MANAGER_SHUTDOWN);
  prerender_manager_->SetNextPrerenderContents(pc1);
  EXPECT_FALSE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(pc1, prerender_manager_->next_pc());
  EXPECT_FALSE(pc1->has_started());
  ASSERT_EQ(pc, prerender_manager_->GetEntry(url));
  pc->set_final_status(FINAL_STATUS_USED);
  delete pc;
}

// Ensure that we expire a prerendered page after the max. permitted time.
TEST_F(PrerenderManagerTest, ExpireTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url,
                                 FINAL_STATUS_TIMED_OUT);
  DummyPrerenderContents* null = NULL;
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc->has_started());
  prerender_manager_->AdvanceTime(prerender_manager_->max_prerender_age()
                                  + base::TimeDelta::FromSeconds(1));
  ASSERT_EQ(null, prerender_manager_->GetEntry(url));
}

// LRU Test.  Make sure that if we prerender more than one request, that
// the oldest one will be dropped.
TEST_F(PrerenderManagerTest, DropOldestRequestTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url,
                                 FINAL_STATUS_EVICTED);
  DummyPrerenderContents* null = NULL;
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc->has_started());
  GURL url1("http://news.google.com/");
  DummyPrerenderContents* pc1 =
      new DummyPrerenderContents(prerender_manager_.get(), url1,
                                 FINAL_STATUS_USED);
  prerender_manager_->SetNextPrerenderContents(pc1);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url1));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc1->has_started());
  ASSERT_EQ(null, prerender_manager_->GetEntry(url));
  ASSERT_EQ(pc1, prerender_manager_->GetEntry(url1));
  pc1->set_final_status(FINAL_STATUS_USED);
  delete pc1;
}

// Two element prerender test.  Ensure that the LRU operates correctly if we
// permit 2 elements to be kept prerendered.
TEST_F(PrerenderManagerTest, TwoElementPrerenderTest) {
  prerender_manager_->set_max_elements(2);
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url,
                                 FINAL_STATUS_EVICTED);
  DummyPrerenderContents* null = NULL;
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc->has_started());
  GURL url1("http://news.google.com/");
  DummyPrerenderContents* pc1 =
      new DummyPrerenderContents(prerender_manager_.get(),  url1,
                                 FINAL_STATUS_USED);
  prerender_manager_->SetNextPrerenderContents(pc1);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url1));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc1->has_started());
  GURL url2("http://images.google.com/");
  DummyPrerenderContents* pc2 =
      new DummyPrerenderContents(prerender_manager_.get(), url2,
                                 FINAL_STATUS_USED);
  prerender_manager_->SetNextPrerenderContents(pc2);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url2));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc2->has_started());
  ASSERT_EQ(null, prerender_manager_->GetEntry(url));
  ASSERT_EQ(pc1, prerender_manager_->GetEntry(url1));
  ASSERT_EQ(pc2, prerender_manager_->GetEntry(url2));
  pc1->set_final_status(FINAL_STATUS_USED);
  delete pc1;
  pc2->set_final_status(FINAL_STATUS_USED);
  delete pc2;
}

TEST_F(PrerenderManagerTest, AliasURLTest) {
  GURL url("http://www.google.com/");
  GURL alias_url1("http://www.google.com/index.html");
  GURL alias_url2("http://google.com/");
  GURL not_an_alias_url("http://google.com/index.html");
  std::vector<GURL> alias_urls;
  alias_urls.push_back(alias_url1);
  alias_urls.push_back(alias_url2);
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url, alias_urls,
                                 FINAL_STATUS_USED);
  // Test that all of the aliases work, but nont_an_alias_url does not.
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddPreload(url, alias_urls, GURL()));
  ASSERT_EQ(NULL, prerender_manager_->GetEntry(not_an_alias_url));
  ASSERT_EQ(pc, prerender_manager_->GetEntry(alias_url1));
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddPreload(url, alias_urls, GURL()));
  ASSERT_EQ(pc, prerender_manager_->GetEntry(alias_url2));
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddPreload(url, alias_urls, GURL()));
  ASSERT_EQ(pc, prerender_manager_->GetEntry(url));

  // Test that alias URLs can not be added.
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddPreload(url, alias_urls, GURL()));
  EXPECT_FALSE(prerender_manager_->AddSimplePreload(url));
  EXPECT_FALSE(prerender_manager_->AddSimplePreload(alias_url1));
  EXPECT_FALSE(prerender_manager_->AddSimplePreload(alias_url2));
  ASSERT_EQ(pc, prerender_manager_->GetEntry(url));

  pc->set_final_status(FINAL_STATUS_USED);
  delete pc;
}

// Ensure that we ignore prerender requests within the rate limit.
TEST_F(PrerenderManagerTest, RateLimitInWindowTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url,
                                 FINAL_STATUS_MANAGER_SHUTDOWN);
  DummyPrerenderContents* null = NULL;
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc->has_started());

  prerender_manager_->set_rate_limit_enabled(true);
  prerender_manager_->AdvanceTimeTicks(base::TimeDelta::FromMilliseconds(1));

  GURL url1("http://news.google.com/");
  DummyPrerenderContents* rate_limit_pc =
      new DummyPrerenderContents(prerender_manager_.get(), url1,
                                 FINAL_STATUS_MANAGER_SHUTDOWN);
  prerender_manager_->SetNextPrerenderContents(rate_limit_pc);
  EXPECT_FALSE(prerender_manager_->AddSimplePreload(url1));
  prerender_manager_->set_rate_limit_enabled(false);
}

// Ensure that we don't ignore prerender requests outside the rate limit.
TEST_F(PrerenderManagerTest, RateLimitOutsideWindowTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(), url,
                                 FINAL_STATUS_EVICTED);
  DummyPrerenderContents* null = NULL;
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(pc->has_started());

  prerender_manager_->set_rate_limit_enabled(true);
  prerender_manager_->AdvanceTimeTicks(base::TimeDelta::FromMilliseconds(2000));

  GURL url1("http://news.google.com/");
  DummyPrerenderContents* rate_limit_pc =
      new DummyPrerenderContents(prerender_manager_.get(), url1,
                                 FINAL_STATUS_MANAGER_SHUTDOWN);
  prerender_manager_->SetNextPrerenderContents(rate_limit_pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url1));
  EXPECT_EQ(null, prerender_manager_->next_pc());
  EXPECT_TRUE(rate_limit_pc->has_started());
  prerender_manager_->set_rate_limit_enabled(false);
}

TEST_F(PrerenderManagerTest, PendingPreloadTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(),
                                 url,
                                 FINAL_STATUS_USED);
  prerender_manager_->SetNextPrerenderContents(pc);
  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));

  int child_id;
  int route_id;
  ASSERT_TRUE(pc->GetChildId(&child_id));
  ASSERT_TRUE(pc->GetRouteId(&route_id));

  GURL pending_url("http://news.google.com/");

  prerender_manager_->AddPendingPreload(std::make_pair(child_id, route_id),
                                        pending_url,
                                        std::vector<GURL>(),
                                        url);

  EXPECT_TRUE(prerender_manager_->IsPendingEntry(pending_url));
  EXPECT_TRUE(pc->has_started());
  ASSERT_EQ(pc, prerender_manager_->GetEntry(url));
  pc->set_final_status(FINAL_STATUS_USED);

  delete pc;
}

TEST_F(PrerenderManagerTest, PendingPreloadSkippedTest) {
  GURL url("http://www.google.com/");
  DummyPrerenderContents* pc =
      new DummyPrerenderContents(prerender_manager_.get(),
                                 url,
                                 FINAL_STATUS_TIMED_OUT);
  prerender_manager_->SetNextPrerenderContents(pc);

  int child_id;
  int route_id;
  ASSERT_TRUE(pc->GetChildId(&child_id));
  ASSERT_TRUE(pc->GetRouteId(&route_id));

  EXPECT_TRUE(prerender_manager_->AddSimplePreload(url));
  prerender_manager_->AdvanceTime(prerender_manager_->max_prerender_age()
                                  + base::TimeDelta::FromSeconds(1));
  // GetEntry will cull old entries which should now include pc.
  ASSERT_EQ(NULL, prerender_manager_->GetEntry(url));

  GURL pending_url("http://news.google.com/");

  prerender_manager_->AddPendingPreload(std::make_pair(child_id, route_id),
                                        pending_url,
                                        std::vector<GURL>(),
                                        url);
  EXPECT_FALSE(prerender_manager_->IsPendingEntry(pending_url));
}

// Ensure that extracting a urlencoded URL in the url= query string component
// works.
TEST_F(PrerenderManagerTest, ExtractURLInQueryStringTest) {
  GURL result;
  EXPECT_TRUE(PrerenderManager::MaybeGetQueryStringBasedAliasURL(
      GURL("http://www.google.com/url?sa=t&source=web&cd=1&ved=0CBcQFjAA&url=http%3A%2F%2Fwww.abercrombie.com%2Fwebapp%2Fwcs%2Fstores%2Fservlet%2FStoreLocator%3FcatalogId%3D%26storeId%3D10051%26langId%3D-1&rct=j&q=allinurl%3A%26&ei=KLyUTYGSEdTWiAKUmLCdCQ&usg=AFQjCNF8nJ2MpBFfr1ijO39_f22bcKyccw&sig2=2ymyGpO0unJwU1d4kdCUjQ"),
      &result));
  ASSERT_EQ(GURL("http://www.abercrombie.com/webapp/wcs/stores/servlet/StoreLocator?catalogId=&storeId=10051&langId=-1").spec(), result.spec());
  EXPECT_FALSE(PrerenderManager::MaybeGetQueryStringBasedAliasURL(
      GURL("http://www.google.com/url?sadf=test&blah=blahblahblah"), &result));
  EXPECT_FALSE(PrerenderManager::MaybeGetQueryStringBasedAliasURL(
      GURL("http://www.google.com/?url=INVALIDurlsAREsoMUCHfun.com"), &result));
  EXPECT_TRUE(PrerenderManager::MaybeGetQueryStringBasedAliasURL(
      GURL("http://www.google.com/?url=http://validURLSareGREAT.com"),
      &result));
  ASSERT_EQ(GURL("http://validURLSareGREAT.com").spec(), result.spec());
}

}  // namespace prerender