/*
 * 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.
 */


#include "net/instaweb/rewriter/public/google_font_service_input_resource.h"

#include "net/instaweb/http/public/counting_url_async_fetcher.h"
#include "net/instaweb/http/public/ua_sensitive_test_fetcher.h"
#include "net/instaweb/rewriter/public/mock_resource_callback.h"
#include "net/instaweb/rewriter/public/resource.h"
#include "net/instaweb/rewriter/public/rewrite_driver.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/ref_counted_ptr.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/http/response_headers.h"

namespace net_instaweb {

namespace {

const char kRoboto[] = "http://fonts.googleapis.com/css?family=Roboto";
const char kRobotoSsl[] = "https://fonts.googleapis.com/css?family=Roboto";
const char kNonCss[] = "http://fonts.googleapis.com/some.txt";

class GoogleFontServiceInputResourceTest : public RewriteTestBase {
 protected:
  virtual void SetUp() {
    RewriteTestBase::SetUp();
    GoogleFontServiceInputResource::InitStats(statistics());

    // 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");

    // Set them up in spots where UaSensitiveFetcher would direct them.
    SetFetchResponse(StrCat(kRoboto, "&UA=Chromezilla"),
                     response_headers, "font_chromezilla");

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

    SetFetchResponse(StrCat(kRobotoSsl, "&UA=Chromezilla"),
                     response_headers, "sfont_chromezilla");

    SetFetchResponse(StrCat(kRobotoSsl, "&UA=Safieri"),
                     response_headers, "sfont_safieri");

    ResponseHeaders non_css;
    SetDefaultLongCacheHeaders(&kContentTypeText, &non_css);
    SetFetchResponse(StrCat(kNonCss, "?UA=Chromezilla"),
                     non_css, "something weird");
  }

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

TEST_F(GoogleFontServiceInputResourceTest, Creation) {
  ResetUserAgent("Chromezilla");

  scoped_ptr<GoogleFontServiceInputResource> resource;

  GoogleUrl url1("efpeRO#@($@#K$!@($");
  resource.reset(GoogleFontServiceInputResource::Make(url1, rewrite_driver()));
  EXPECT_TRUE(resource.get() == NULL);

  GoogleUrl url2("http://example.com/foo.css");
  resource.reset(GoogleFontServiceInputResource::Make(url2, rewrite_driver()));
  EXPECT_TRUE(resource.get() == NULL);

  GoogleUrl url3(kRoboto);
  resource.reset(GoogleFontServiceInputResource::Make(url3, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  EXPECT_EQ(kRoboto, resource->url());
  EXPECT_EQ(
      "gfnt://fonts.googleapis.com/css?family=Roboto&X-PS-UA=Chromezilla",
      resource->cache_key());

  GoogleUrl url4(kRobotoSsl);
  resource.reset(GoogleFontServiceInputResource::Make(url4, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  EXPECT_EQ(kRobotoSsl, resource->url());
  EXPECT_EQ(
      "gfnts://fonts.googleapis.com/css?family=Roboto&X-PS-UA=Chromezilla",
      resource->cache_key());
}

TEST_F(GoogleFontServiceInputResourceTest, Load) {
  GoogleUrl url(kRoboto);
  ResetUserAgent("Chromezilla");

  ResourcePtr resource(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  MockResourceCallback callback(resource,
                                server_context()->thread_system());
  resource->LoadAsync(Resource::kReportFailureIfNotCacheable,
                      rewrite_driver()->request_context(),
                      &callback);
  EXPECT_TRUE(callback.done());
  EXPECT_TRUE(callback.success());
  EXPECT_EQ("font_chromezilla", resource->ExtractUncompressedContents());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());

  // Make sure it's cached.
  ResourcePtr resource2(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource2.get() != NULL);
  MockResourceCallback callback2(resource2,
                                 server_context()->thread_system());
  resource2->LoadAsync(Resource::kReportFailureIfNotCacheable,
                       rewrite_driver()->request_context(),
                       &callback2);
  EXPECT_TRUE(callback2.done());
  EXPECT_TRUE(callback2.success());
  EXPECT_EQ("font_chromezilla", resource2->ExtractUncompressedContents());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());

  // But that different UA gets different string.
  ResetUserAgent("Safieri");
  ResourcePtr resource3(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource3.get() != NULL);
  MockResourceCallback callback3(resource3,
                                 server_context()->thread_system());
  resource3->LoadAsync(Resource::kReportFailureIfNotCacheable,
                       rewrite_driver()->request_context(),
                       &callback3);
  EXPECT_TRUE(callback3.done());
  EXPECT_TRUE(callback3.success());
  EXPECT_EQ("font_safieri", resource3->ExtractUncompressedContents());
  EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
}

TEST_F(GoogleFontServiceInputResourceTest, UANormalization) {
  const char kIE7a[] =
      "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; "
      ".NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)";

  GoogleUrl url(kRoboto);
  scoped_ptr<GoogleUrl> url_plus_ua(url.CopyAndAddQueryParam("UA", kIE7a));
  ResponseHeaders response_headers;
  SetDefaultLongCacheHeaders(&kContentTypeCss, &response_headers);
  response_headers.SetDateAndCaching(
      timer()->NowMs(), 86400 * Timer::kSecondMs, ", private");
  SetFetchResponse(url_plus_ua->Spec(), response_headers, "font_IE7");

  // Try fetches with a couple of possible aliases. The one we uploaded it under
  // is first, since it's the only one the fetcher replies to.
  ResetUserAgent(kIE7a);

  ResourcePtr resource(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  MockResourceCallback callback(resource,
                                server_context()->thread_system());
  resource->LoadAsync(Resource::kReportFailureIfNotCacheable,
                      rewrite_driver()->request_context(),
                      &callback);
  EXPECT_TRUE(callback.done());
  EXPECT_TRUE(callback.success());
  EXPECT_EQ("font_IE7", resource->ExtractUncompressedContents());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());

  // Different list of .NET versions.
  const char kIE7b[] =
      "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; "
      ".NET CLR 2.0.50727; .NET4.0C; .NET4.0E)";
  ResetUserAgent(kIE7b);

  ResourcePtr resource2(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource2.get() != NULL);
  MockResourceCallback callback2(resource2,
                                 server_context()->thread_system());
  resource2->LoadAsync(Resource::kReportFailureIfNotCacheable,
                       rewrite_driver()->request_context(),
                       &callback2);
  EXPECT_TRUE(callback2.done());
  EXPECT_TRUE(callback2.success());
  EXPECT_EQ("font_IE7", resource->ExtractUncompressedContents());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
}

TEST_F(GoogleFontServiceInputResourceTest, LoadParallel) {
  GoogleUrl url(kRoboto);
  SetupWaitFetcher();

  ResetUserAgent("Chromezilla");
  ResourcePtr resource(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  MockResourceCallback callback(resource,
                                server_context()->thread_system());
  resource->LoadAsync(Resource::kReportFailureIfNotCacheable,
                      rewrite_driver()->request_context(),
                      &callback);
  EXPECT_FALSE(callback.done());

  ResetUserAgent("Safieri");
  ResourcePtr resource2(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource2.get() != NULL);
  MockResourceCallback callback2(resource2,
                                 server_context()->thread_system());
  resource2->LoadAsync(Resource::kReportFailureIfNotCacheable,
                       rewrite_driver()->request_context(),
                       &callback2);
  EXPECT_FALSE(callback2.done());

  CallFetcherCallbacks();

  ASSERT_TRUE(callback.done());
  EXPECT_TRUE(callback.success());
  EXPECT_EQ("font_chromezilla", resource->ExtractUncompressedContents());

  ASSERT_TRUE(callback2.done());
  EXPECT_TRUE(callback2.success());
  EXPECT_EQ("font_safieri", resource2->ExtractUncompressedContents());
}

TEST_F(GoogleFontServiceInputResourceTest, FetchFailure) {
  SetFetchFailOnUnexpected(false);

  // Regression test --- don't crash when fetch fails.
  // Bug discovered by accident due to a bug in a test.
  ResetUserAgent("Huhzilla");
  GoogleUrl url(kRoboto);
  ResourcePtr resource(
      GoogleFontServiceInputResource::Make(url, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  MockResourceCallback callback(resource,
                                server_context()->thread_system());
  resource->LoadAsync(Resource::kLoadEvenIfNotCacheable,
                      rewrite_driver()->request_context(),
                      &callback);
  EXPECT_TRUE(callback.done());
  EXPECT_FALSE(callback.success());
  EXPECT_EQ("", resource->ExtractUncompressedContents());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
}

TEST_F(GoogleFontServiceInputResourceTest, DontLoadNonCss) {
  ResetUserAgent("Chromezilla");

  GoogleUrl non_css_url(kNonCss);
  ResourcePtr resource(
      GoogleFontServiceInputResource::Make(non_css_url, rewrite_driver()));
  ASSERT_TRUE(resource.get() != NULL);
  MockResourceCallback callback(resource,
                                server_context()->thread_system());
  resource->LoadAsync(Resource::kLoadEvenIfNotCacheable,
                      rewrite_driver()->request_context(),
                      &callback);
  EXPECT_TRUE(callback.done());
  EXPECT_FALSE(resource->HttpStatusOk());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());

  // Make sure we don't end up caching a success, either.
  ResourcePtr resource2(
      GoogleFontServiceInputResource::Make(non_css_url, rewrite_driver()));
  ASSERT_TRUE(resource2.get() != NULL);
  MockResourceCallback callback2(resource2,
                                 server_context()->thread_system());
  resource2->LoadAsync(Resource::kLoadEvenIfNotCacheable,
                       rewrite_driver()->request_context(),
                       &callback2);
  EXPECT_TRUE(callback2.done());
  EXPECT_FALSE(resource->HttpStatusOk());
  EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
}

TEST_F(GoogleFontServiceInputResourceTest, IsFontServiceUrlTest) {
  GoogleUrl roboto_url(kRoboto);
  EXPECT_TRUE(GoogleFontServiceInputResource::IsFontServiceUrl(roboto_url));

  GoogleUrl roboto_ssl_url(kRobotoSsl);
  EXPECT_TRUE(GoogleFontServiceInputResource::IsFontServiceUrl(roboto_ssl_url));

  GoogleUrl example_url("http://example.com/");
  EXPECT_FALSE(GoogleFontServiceInputResource::IsFontServiceUrl(example_url));
}

}  // namespace

}  // namespace net_instaweb
