/*
 * 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/js_defer_disabled_filter.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 "net/instaweb/rewriter/public/static_asset_manager.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/http/user_agent_matcher_test_base.h"

namespace net_instaweb {

const char * kDeferJsCodeNonGStatic = "<script type=\"text/javascript\" "
    "src=\"/psajs/js_defer.0.js\"></script>";

class JsDeferDisabledFilterTest : public RewriteTestBase {
 protected:
  // TODO(matterbury): Delete this method as it should be redundant.
  virtual void SetUp() {
    RewriteTestBase::SetUp();
    SetHtmlMimetype();  // Prevent insertion of CDATA tags to static JS.
  }

  virtual void InitJsDeferDisabledFilter(bool debug) {
    if (debug) {
      options()->EnableFilter(RewriteOptions::kDebug);
    }
    js_defer_disabled_filter_.reset(
        new JsDeferDisabledFilter(rewrite_driver()));
    rewrite_driver()->AddFilter(js_defer_disabled_filter_.get());
  }

  virtual bool AddHtmlTags() const { return false; }
  virtual bool AddBody() const { return false; }

  scoped_ptr<JsDeferDisabledFilter> js_defer_disabled_filter_;
};

// TODO(atulvasu): Write tests that actually execute script.
TEST_F(JsDeferDisabledFilterTest, DeferScript) {
  InitJsDeferDisabledFilter(false);

  ValidateExpected("defer_script",
      "<html><head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'"
      "> func();</script>"
      "</head><body>Hello, world!</body></html>",
      StrCat("<html><head>"
             "<script type='text/psajs' "
             "src='http://www.google.com/javascript/ajax_apis.js'></script>"
             "<script type='text/psajs'"
             "> func();</script>"
             "</head><body>Hello, world!",
             kDeferJsCodeNonGStatic,
             "</body></html>"));
}

TEST_F(JsDeferDisabledFilterTest, JsDeferPreserveURLsOn) {
  // Make sure that we don't defer when preserve urls is on.
  options()->set_js_preserve_urls(true);
  options()->set_support_noscript_enabled(false);
  options()->SoftEnableFilterForTesting(RewriteOptions::kDeferJavascript);
  rewrite_driver()->AddFilters();
  GoogleString before = "<head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'"
      "> func();</script>"
      "</head><body>Hello, world!</body>";
  ValidateNoChanges("js_defer_preserve_urls_on", before);
}

TEST_F(JsDeferDisabledFilterTest, DeferScriptMultiBody) {
  InitJsDeferDisabledFilter(false);
  ValidateExpected("defer_script_multi_body",
      "<html><head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'> func(); </script>"
      "</head><body>Hello, world!</body><body>"
      "<script type='text/psajs'> func2(); </script></body></html>",
      StrCat("<html><head>"
             "<script type='text/psajs' "
             "src='http://www.google.com/javascript/ajax_apis.js'></script>"
             "<script type='text/psajs'> func(); </script>"
             "</head><body>Hello, world!"
             "</body><body><script type='text/psajs'> func2(); "
             "</script>",
             kDeferJsCodeNonGStatic,
             "</body></html>"));
}

TEST_F(JsDeferDisabledFilterTest, DeferScriptOptimized) {
  InitJsDeferDisabledFilter(false);
  Parse("optimized",
        "<body><script type='text/psajs' src='foo.js'></script></body>");
  EXPECT_NE(GoogleString::npos, output_buffer_.find("js_defer.0.js"))
      << "js_defer.js should have been included";
}

TEST_F(JsDeferDisabledFilterTest, DeferScriptDebug) {
  InitJsDeferDisabledFilter(true);
  Parse("optimized",
        "<head></head><body><script type='text/psajs' src='foo.js'>"
        "</script></body>");
  EXPECT_NE(GoogleString::npos, output_buffer_.find("js_defer_debug.0.js"))
      << "js_defer_debug.js should have been included";
}

TEST_F(JsDeferDisabledFilterTest, InvalidUserAgent) {
  InitJsDeferDisabledFilter(false);
  SetCurrentUserAgent("BlackListUserAgent");
  const char script[] = "<head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'"
      "> func();</script>"
      "</head><body>Hello, world!</body>";

  ValidateNoChanges("defer_script", script);
}

TEST_F(JsDeferDisabledFilterTest, AllowMobileUserAgent) {
  InitJsDeferDisabledFilter(false);
  SetCurrentUserAgent(UserAgentMatcherTestBase::kIPhone4Safari);
  const char script[] = "<head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'"
      "> func();</script>"
      "</head><body>Hello, world!</body>";

  GoogleString expected = StrCat("<head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'"
      "> func();</script></head><body>"
      "Hello, world!",
      kDeferJsCodeNonGStatic,
      "</body>");

  ValidateExpected("defer_script", script, expected);
}

TEST_F(JsDeferDisabledFilterTest, DisAllowMobileUserAgent) {
  InitJsDeferDisabledFilter(false);
  options_->ClearSignatureForTesting();
  options_->set_enable_aggressive_rewriters_for_mobile(false);
  SetCurrentUserAgent(UserAgentMatcherTestBase::kIPhone4Safari);
  const char script[] = "<head>"
      "<script type='text/psajs' "
      "src='http://www.google.com/javascript/ajax_apis.js'></script>"
      "<script type='text/psajs'"
      "> func();</script>"
      "</head><body>Hello, world!</body>";

  ValidateNoChanges("defer_script", script);
}

TEST_F(JsDeferDisabledFilterTest, TestDeferJsUrlFromGStatic) {
  StaticAssetManager static_asset_manager("",
                                          server_context()->thread_system(),
                                          server_context()->hasher(),
                                          server_context()->message_handler());
  static_asset_manager.ServeAssetsFromGStatic(StaticAssetManager::kGStaticBase);
  static_asset_manager.SetGStaticHashForTest(StaticAssetEnum::DEFER_JS, "1");

  server_context()->set_static_asset_manager(&static_asset_manager);

  InitJsDeferDisabledFilter(false);
  ValidateExpected(
      "defer_script_url",
      "<html><body>Hello, world!</body><body>"
      "<script type='text/psajs'> func2(); </script></body></html>",
      "<html><body>Hello, world!"
      "</body><body><script type='text/psajs'> func2(); "
      "</script>"
      "<script type=\"text/javascript\" "
      "src=\"//www.gstatic.com/psa/static/1-js_defer.js\">"
      "</script></body></html>");
}

TEST_F(JsDeferDisabledFilterTest, TestDeferJsUrlFromNonGStatic) {
  InitJsDeferDisabledFilter(false);

  ValidateExpected(
      "defer_script_url",
      "<html><body>Hello, world!</body><body>"
      "<script type='text/psajs'> func2(); </script></body></html>",
      StrCat("<html><body>Hello, world!",
             "</body><body><script type='text/psajs'> func2(); "
             "</script>",
             kDeferJsCodeNonGStatic,
             "</body></html>"));
}

}  // namespace net_instaweb
