// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/omnibox/browser/favicon_cache.h"

#include "base/functional/bind.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "components/favicon/core/test/mock_favicon_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/variations/scoped_variations_ids_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/favicon_size.h"

using testing::_;
using testing::DoAll;
using testing::Return;

namespace {

favicon_base::FaviconImageResult GetDummyFaviconResult() {
  favicon_base::FaviconImageResult result;

  result.icon_url = GURL("http://example.com/favicon.ico");

  SkBitmap bitmap;
  bitmap.allocN32Pixels(gfx::kFaviconSize, gfx::kFaviconSize);
  bitmap.eraseColor(SK_ColorBLUE);
  result.image = gfx::Image::CreateFrom1xBitmap(bitmap);

  return result;
}

void VerifyFetchedFaviconAndCount(int* count, const gfx::Image& favicon) {
  DCHECK(count);
  EXPECT_FALSE(favicon.IsEmpty());
  ++(*count);
}

void VerifyFetchedFavicon(const gfx::Image& favicon) {
  EXPECT_FALSE(favicon.IsEmpty());
}

void Fail(const gfx::Image& favicon) {
  FAIL() << "This asynchronous callback should never have been called.";
}

}  // namespace

class FaviconCacheTest : public testing::Test {
 protected:
  const GURL kUrlA = GURL("http://www.a.com/");
  const GURL kUrlB = GURL("http://www.b.com/");
  const GURL kIconUrl = GURL("http://a.com/favicon.ico");

  FaviconCacheTest()
      : cache_(&favicon_service_, nullptr /* history_service */) {}

  testing::NiceMock<favicon::MockFaviconService> favicon_service_;

  void ExpectFaviconServiceForPageUrlCalls(int a_site_calls, int b_site_calls) {
    if (a_site_calls > 0) {
      EXPECT_CALL(
          favicon_service_,
          GetFaviconImageForPageURL(kUrlA, _ /* callback */, _ /* tracker */))
          .Times(a_site_calls)
          .WillRepeatedly(
              [&](auto, favicon_base::FaviconImageCallback callback, auto) {
                favicon_service_a_site_response_ = std::move(callback);
                return base::CancelableTaskTracker::kBadTaskId;
              });
    }

    if (b_site_calls > 0) {
      EXPECT_CALL(
          favicon_service_,
          GetFaviconImageForPageURL(kUrlB, _ /* callback */, _ /* tracker */))
          .Times(b_site_calls)
          .WillRepeatedly(
              [&](auto, favicon_base::FaviconImageCallback callback, auto) {
                favicon_service_b_site_response_ = std::move(callback);
                return base::CancelableTaskTracker::kBadTaskId;
              });
    }
  }

  void ExpectFaviconServiceForIconUrlCalls(int calls) {
    EXPECT_CALL(favicon_service_,
                GetFaviconImage(kIconUrl, _ /* callback */, _ /* tracker */))
        .Times(calls);
  }

  variations::test::ScopedVariationsIdsProvider scoped_variations_ids_provider_{
      variations::VariationsIdsProvider::Mode::kUseSignedInState};
  favicon_base::FaviconImageCallback favicon_service_a_site_response_;
  favicon_base::FaviconImageCallback favicon_service_b_site_response_;
  favicon_base::FaviconRawBitmapCallback
      favicon_service_a_site_raw_favicon_response_;
  favicon_base::FaviconRawBitmapCallback
      favicon_service_b_site_raw_favicon_response_;

  FaviconCache cache_;
};

TEST_F(FaviconCacheTest, Basic) {
  ExpectFaviconServiceForPageUrlCalls(1, 0);
  ExpectFaviconServiceForIconUrlCalls(0);

  int response_count = 0;
  gfx::Image result = cache_.GetFaviconForPageUrl(
      kUrlA, base::BindOnce(&VerifyFetchedFaviconAndCount, &response_count));

  // Expect the synchronous result to be empty.
  EXPECT_TRUE(result.IsEmpty());

  std::move(favicon_service_a_site_response_).Run(GetDummyFaviconResult());

  // Re-request the same favicon and expect a non-empty result now that the
  // cache is populated. The above EXPECT_CALL will also verify that the
  // backing FaviconService is not hit again.
  result = cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail));

  EXPECT_FALSE(result.IsEmpty());
  EXPECT_EQ(1, response_count);
}

TEST_F(FaviconCacheTest, GetFaviconForIconUrl) {
  // Verify that the service receives a request by the icon URL.
  ExpectFaviconServiceForPageUrlCalls(0, 0);
  ExpectFaviconServiceForIconUrlCalls(1);

  // Since the other tests are comprehensive, we don't simulate or verify the
  // actual result.
  gfx::Image result = cache_.GetFaviconForIconUrl(
      kIconUrl, base::BindOnce(&Fail), /*notify_on_empty=*/false);
  EXPECT_TRUE(result.IsEmpty());
}

TEST_F(FaviconCacheTest, MultipleRequestsAreCoalesced) {
  ExpectFaviconServiceForPageUrlCalls(1, 0);

  int response_count = 0;
  for (int i = 0; i < 10; ++i) {
    cache_.GetFaviconForPageUrl(
        kUrlA, base::BindOnce(&VerifyFetchedFaviconAndCount, &response_count));
  }

  std::move(favicon_service_a_site_response_).Run(GetDummyFaviconResult());

  EXPECT_EQ(10, response_count);
}

TEST_F(FaviconCacheTest, SeparateOriginsAreCachedSeparately) {
  ExpectFaviconServiceForPageUrlCalls(1, 1);

  int a_site_response_count = 0;
  int b_site_response_count = 0;

  gfx::Image a_site_return = cache_.GetFaviconForPageUrl(
      kUrlA,
      base::BindOnce(&VerifyFetchedFaviconAndCount, &a_site_response_count));
  gfx::Image b_site_return = cache_.GetFaviconForPageUrl(
      kUrlB,
      base::BindOnce(&VerifyFetchedFaviconAndCount, &b_site_response_count));

  EXPECT_TRUE(a_site_return.IsEmpty());
  EXPECT_TRUE(b_site_return.IsEmpty());
  EXPECT_EQ(0, a_site_response_count);
  EXPECT_EQ(0, b_site_response_count);

  std::move(favicon_service_b_site_response_).Run(GetDummyFaviconResult());

  EXPECT_EQ(0, a_site_response_count);
  EXPECT_EQ(1, b_site_response_count);

  a_site_return = cache_.GetFaviconForPageUrl(
      kUrlA,
      base::BindOnce(&VerifyFetchedFaviconAndCount, &a_site_response_count));
  b_site_return = cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail));

  EXPECT_TRUE(a_site_return.IsEmpty());
  EXPECT_FALSE(b_site_return.IsEmpty());
  EXPECT_EQ(0, a_site_response_count);
  EXPECT_EQ(1, b_site_response_count);

  std::move(favicon_service_a_site_response_).Run(GetDummyFaviconResult());

  EXPECT_EQ(2, a_site_response_count);
  EXPECT_EQ(1, b_site_response_count);

  a_site_return = cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail));
  b_site_return = cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail));

  EXPECT_FALSE(a_site_return.IsEmpty());
  EXPECT_FALSE(b_site_return.IsEmpty());
}

TEST_F(FaviconCacheTest, ClearIconsWithHistoryDeletions) {
  ExpectFaviconServiceForPageUrlCalls(3, 2);

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
          .IsEmpty());
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&VerifyFetchedFavicon))
          .IsEmpty());

  std::move(favicon_service_a_site_response_).Run(GetDummyFaviconResult());
  std::move(favicon_service_b_site_response_).Run(GetDummyFaviconResult());

  EXPECT_FALSE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
  EXPECT_FALSE(
      cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());

  // Delete just the entry for kUrlA.
  history::URLRows a_rows = {history::URLRow(kUrlA)};
  cache_.OnHistoryDeletions(
      nullptr /* history_service */,
      history::DeletionInfo::ForUrls(a_rows, {} /* favicon_urls */));

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
          .IsEmpty());
  EXPECT_FALSE(
      cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());

  // Restore the cache entry for kUrlA.
  std::move(favicon_service_a_site_response_).Run(GetDummyFaviconResult());

  // Delete all history.
  cache_.OnHistoryDeletions(nullptr /* history_service */,
                            history::DeletionInfo::ForAllHistory());

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
          .IsEmpty());
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&VerifyFetchedFavicon))
          .IsEmpty());
}

TEST_F(FaviconCacheTest, CacheNullFavicons) {
  ExpectFaviconServiceForPageUrlCalls(1, 0);

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
  std::move(favicon_service_a_site_response_)
      .Run(favicon_base::FaviconImageResult());

  // The mock FaviconService's EXPECT_CALL verifies that we do not make another
  // call to FaviconService.
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
}

TEST_F(FaviconCacheTest, ExpireNullFaviconsByHistory) {
  ExpectFaviconServiceForPageUrlCalls(2, 0);

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
  std::move(favicon_service_a_site_response_)
      .Run(favicon_base::FaviconImageResult());

  cache_.OnURLVisited(
      nullptr /* history_service */,
      history::VisitedURLInfo(history::URLRow(kUrlA), history::VisitRow(),
                              history::VisitResponseCodeCategory::kNot404));

  // Now the empty favicon should have been expired and we expect our second
  // call to the mock underlying FaviconService.
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&VerifyFetchedFavicon))
          .IsEmpty());
  std::move(favicon_service_a_site_response_).Run(GetDummyFaviconResult());
  EXPECT_FALSE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
}

TEST_F(FaviconCacheTest, ObserveFaviconsChanged) {
  ExpectFaviconServiceForPageUrlCalls(2, 1);

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());

  // Simulate responses to both requests.
  std::move(favicon_service_a_site_response_)
      .Run(favicon_base::FaviconImageResult());
  std::move(favicon_service_b_site_response_)
      .Run(favicon_base::FaviconImageResult());

  cache_.OnFaviconsChanged({kUrlA}, GURL());

  // Request the two favicons again.
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlB, base::BindOnce(&Fail)).IsEmpty());

  // Our call to |ExpectFaviconServiceForPageUrlCalls(expected A calls, expected
  // B calls)| above should verify that we re-request the icon for kUrlA only
  // (because the null result has been invalidated by OnFaviconsChanged).
}

TEST_F(FaviconCacheTest, DoNotExpireNullFaviconsFor404) {
  ExpectFaviconServiceForPageUrlCalls(1, 0);

  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
  std::move(favicon_service_a_site_response_)
      .Run(favicon_base::FaviconImageResult());

  cache_.OnURLVisited(
      nullptr /* history_service */,
      history::VisitedURLInfo(history::URLRow(kUrlA), history::VisitRow(),
                              history::VisitResponseCodeCategory::k404));

  // The empty favicon should not have been expired. We do not expect another
  // call to the mock underlying FaviconService.
  EXPECT_TRUE(
      cache_.GetFaviconForPageUrl(kUrlA, base::BindOnce(&Fail)).IsEmpty());
}

TEST_F(FaviconCacheTest, GetFaviconForIconUrl_NotifyOnEmpty) {
  // `GetFaviconForIconUrl()` should call `FaviconService::GetFaviconImage()`.
  favicon_base::FaviconImageCallback icon_url_callback;
  EXPECT_CALL(favicon_service_, GetFaviconImage(kIconUrl, _, _))
      .WillOnce([&](auto, favicon_base::FaviconImageCallback callback, auto) {
        icon_url_callback = std::move(callback);
        return base::CancelableTaskTracker::kBadTaskId;
      });

  // `GetFaviconForIconUrl()` should not call the callback sync since the
  // request is not yet cached.
  base::test::TestFuture<const gfx::Image&> future;
  cache_.GetFaviconForIconUrl(kIconUrl, future.GetCallback(),
                              /*notify_on_empty=*/true);
  EXPECT_FALSE(future.IsReady());

  // After `FaviconService::GetFaviconImage()` returns, the callback should be
  // called with an empty image.
  std::move(icon_url_callback).Run(favicon_base::FaviconImageResult());
  EXPECT_TRUE(future.IsReady());
  EXPECT_TRUE(future.Get().IsEmpty());

  // Now that the request is cached, `GetFaviconForIconUrl()` should call the
  // callback sync.
  future.Clear();
  cache_.GetFaviconForIconUrl(kIconUrl, future.GetCallback(),
                              /*notify_on_empty=*/true);
  EXPECT_TRUE(future.IsReady());
  EXPECT_TRUE(future.Get().IsEmpty());
}

TEST_F(FaviconCacheTest, GetFaviconForIconUrl_PartialNotifyOnEmpty) {
  // `GetFaviconForIconUrl()` should call `FaviconService::GetFaviconImage()`.
  favicon_base::FaviconImageCallback icon_url_callback;
  EXPECT_CALL(favicon_service_, GetFaviconImage(kIconUrl, _, _))
      .WillOnce([&](auto, favicon_base::FaviconImageCallback callback, auto) {
        icon_url_callback = std::move(callback);
        return base::CancelableTaskTracker::kBadTaskId;
      });

  // Call `GetFaviconForIconUrl()` twice, with `notify_on_empty` false and true.
  // Only the former's callback should be called.
  base::test::TestFuture<const gfx::Image&> future;
  cache_.GetFaviconForIconUrl(kIconUrl, base::BindOnce(&Fail),
                              /*notify_on_empty=*/false);
  cache_.GetFaviconForIconUrl(kIconUrl, future.GetCallback(),
                              /*notify_on_empty=*/true);
  EXPECT_FALSE(future.IsReady());

  // After `FaviconService::GetFaviconImage()` returns, the callback should be
  // called with an empty image.
  std::move(icon_url_callback).Run(favicon_base::FaviconImageResult());
  EXPECT_TRUE(future.IsReady());
  EXPECT_TRUE(future.Get().IsEmpty());
}
