/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

// Unit tests for GoogleFontCssInlineFilter

#include "net/instaweb/http/public/ua_sensitive_test_fetcher.h"
#include "net/instaweb/rewriter/public/domain_lawyer.h"
#include "net/instaweb/rewriter/public/rewrite_driver.h"
#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/public/rewrite_test_base.h"
#include "net/instaweb/rewriter/public/server_context.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_message_handler.h"
#include "pagespeed/kernel/base/ref_counted_ptr.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/opt/http/request_context.h"

namespace net_instaweb {

namespace {

const char kRoboto[] = "http://fonts.googleapis.com/css?family=Roboto";

class GoogleFontCssInlineFilterTestBase : public RewriteTestBase {
 protected:
  virtual void SetUp() {
    RewriteTestBase::SetUp();
  }

  void SetUpForFontFilterTest(RewriteOptions::Filter filter_to_enable) {
    AddFilter(filter_to_enable);

    // We want to enable debug after AddFilters, since we don't want the
    // standard debug filter output, just that from the font filter.
    options()->ClearSignatureForTesting();
    options()->EnableFilter(RewriteOptions::kDebug);
    server_context()->ComputeSignature(options());
  }

  void ResetUserAgent(StringPiece user_agent) {
    ClearRewriteDriver();
    rewrite_driver()->SetSessionFetcher(
        new UserAgentSensitiveTestFetcher(rewrite_driver()->async_fetcher()));

    // Font loader CSS gets Cache-Control:private, max-age=86400
    ResponseHeaders response_headers;
    SetDefaultLongCacheHeaders(&kContentTypeCss, &response_headers);
    response_headers.SetDateAndCaching(
        timer()->NowMs(), 86400 * Timer::kSecondMs, ", private");

    // Now upload some UA-specific CSS where UaSensitiveFetcher will find it,
    // for two fake UAs: Chromezilla and Safieri, used since they are short,
    // unlike real UA strings.
    SetFetchResponse(StrCat(kRoboto, "&UA=Chromezilla"),
                     response_headers, "font_chromezilla");

    SetFetchResponse(StrCat(kRoboto, "&UA=Safieri"),
                     response_headers, "font_safieri");

    // If other filters will try to fetch this, they won't have a UA.
    SetFetchResponse(StrCat(kRoboto, "&UA=unknown"),
                     response_headers, "font_huh");
    SetCurrentUserAgent(user_agent);
  }
};

class GoogleFontCssInlineFilterTest : public GoogleFontCssInlineFilterTestBase {
 protected:
  virtual void SetUp() {
    GoogleFontCssInlineFilterTestBase::SetUp();
    SetUpForFontFilterTest(RewriteOptions::kInlineGoogleFontCss);
  }
};

TEST_F(GoogleFontCssInlineFilterTest, BasicOperation) {
  ResetUserAgent("Chromezilla");
  ValidateExpected("simple",
                   CssLinkHref(kRoboto),
                   "<style>font_chromezilla</style>");

  // Different UAs get different cache entries
  ResetUserAgent("Safieri");
  ValidateExpected("simple2",
                   CssLinkHref(kRoboto),
                   "<style>font_safieri</style>");
}

TEST_F(GoogleFontCssInlineFilterTest, UsageRestrictions) {
  ResetUserAgent("Chromezilla");

  options()->ClearSignatureForTesting();
  options()->set_modify_caching_headers(false);
  server_context()->ComputeSignature(options());
  ValidateExpected("incompat1",
                   CssLinkHref(kRoboto),
                   StrCat(CssLinkHref(kRoboto),
                          "<!--Cannot inline font loader CSS when "
                          "ModifyCachingHeaders is off-->"));

  options()->ClearSignatureForTesting();
  options()->set_modify_caching_headers(true);
  options()->set_downstream_cache_purge_location_prefix("foo");
  server_context()->ComputeSignature(options());
  ValidateExpected("incompat2",
                   CssLinkHref(kRoboto),
                   StrCat(CssLinkHref(kRoboto),
                          "<!--Cannot inline font loader CSS when "
                          "using downstream cache-->"));
}

TEST_F(GoogleFontCssInlineFilterTest, ProtocolRelative) {
  ResetUserAgent("Chromezilla");
  ValidateExpected("proto_rel",
                   CssLinkHref("//fonts.googleapis.com/css?family=Roboto"),
                   "<style>font_chromezilla</style>");
}

class GoogleFontCssInlineFilterSizeLimitTest
    : public GoogleFontCssInlineFilterTestBase {
 protected:
  virtual void SetUp() {
    GoogleFontCssInlineFilterTestBase::SetUp();
    // GoogleFontCssInlineFilter uses google_font_css_inline_max_bytes.
    // Set a threshold at font_safieri, which should prevent longer
    // font_chromezilla from inlining.
    options()->set_google_font_css_inline_max_bytes(
        STATIC_STRLEN("font_safieri"));
    SetUpForFontFilterTest(RewriteOptions::kInlineGoogleFontCss);
  }
};

TEST_F(GoogleFontCssInlineFilterSizeLimitTest, SizeLimit) {
  ResetUserAgent("Chromezilla");
  ValidateExpected(
      "slightly_long",
      CssLinkHref(kRoboto),
      StrCat(CssLinkHref(kRoboto),
             "<!--CSS not inlined since it&#39;s bigger than 12 bytes-->"));

  ResetUserAgent("Safieri");
  ValidateExpected("short",
                   CssLinkHref(kRoboto),
                   "<style>font_safieri</style>");
}

class GoogleFontCssInlineFilterAndImportTest
    : public GoogleFontCssInlineFilterTest {
 protected:
  virtual void SetUp() {
    RewriteTestBase::SetUp();  // skipped base on purpose
    options()->EnableFilter(
        RewriteOptions::RewriteOptions::kInlineImportToLink);
    SetUpForFontFilterTest(RewriteOptions::kInlineGoogleFontCss);
  }
};

TEST_F(GoogleFontCssInlineFilterAndImportTest, ViaInlineImport) {
  // Tests to make sure that if InlineImportToLink filter is on we
  // convert a <style>@import'ing </style> this as well.
  ResetUserAgent("Chromezilla");
  ValidateExpected("import",
                   StringPrintf("<style>@import \"%s\";</style>", kRoboto),
                   "<style>font_chromezilla</style>");

  ResetUserAgent("Safieri");
  ValidateExpected("import",
                   StringPrintf("<style>@import \"%s\";</style>", kRoboto),
                   "<style>font_safieri</style>");
}

class GoogleFontCssInlineFilterAndWidePermissionsTest
    : public GoogleFontCssInlineFilterTestBase {
  virtual void SetUp() {
    GoogleFontCssInlineFilterTestBase::SetUp();
    // Check that we don't rely solely on authorization to properly
    // dispatch the URL to us.
    options()->WriteableDomainLawyer()->AddDomain("*", message_handler());
    rewrite_driver()->request_context()->AddSessionAuthorizedFetchOrigin(
        "http://fonts.googleapis.com");
    options()->EnableFilter(RewriteOptions::kInlineCss);
    SetUpForFontFilterTest(RewriteOptions::kInlineGoogleFontCss);
  }
};

TEST_F(GoogleFontCssInlineFilterAndWidePermissionsTest, WithWideAuthorization) {
  ResetUserAgent("Chromezilla");
  ValidateExpected("with_domain_*",
                   CssLinkHref(kRoboto),
                   "<style>font_chromezilla</style>");
}

// Negative test for the above, with font filter off, to make sure
// it's not inline_css doing stuff.
class NoGoogleFontCssInlineFilterAndWidePermissionsTest
    : public GoogleFontCssInlineFilterTestBase {
 protected:
  void SetupForAuthorizedFetchOrigin() {
    // Check that we don't rely solely on authorization to properly
    // dispatch the URL to us. Note that we can't only use DomainLawyer here
    // since UserAgentSensitiveTestFetcher is at http layer so is simply
    // unaware of it.
    options()->WriteableDomainLawyer()->AddDomain("*", message_handler());
    rewrite_driver()->request_context()->AddSessionAuthorizedFetchOrigin(
        "http://fonts.googleapis.com");
    SetUpForFontFilterTest(RewriteOptions::kInlineCss);
  }
};

TEST_F(NoGoogleFontCssInlineFilterAndWidePermissionsTest,
       WithWideAuthorization) {
  // Since font inlining isn't on, the regular inliner complains. This isn't
  // ideal, but doing otherwise requires inline_css to know about
  // inline_google_font_css, which also seems suboptimal.
  ResetUserAgent("Chromezilla");
  SetupForAuthorizedFetchOrigin();
  ValidateExpected("with_domain_*_without_font_filter", CssLinkHref(kRoboto),
                   StrCat(CssLinkHref(kRoboto),
                          "<!--Uncacheable content, preventing rewriting of "
                          "http://fonts.googleapis.com/css?family=Roboto-->"));
}

}  // namespace

}  // namespace net_instaweb
