text: /*
   * Copyright 2011 Google Inc.
   *
   * Licensed 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.
   */
  
  // Author: mmohabey@google.com (Megha Mohabey)
  
  #ifndef PAGESPEED_AUTOMATIC_PROXY_INTERFACE_TEST_BASE_H_
  #define PAGESPEED_AUTOMATIC_PROXY_INTERFACE_TEST_BASE_H_
  
  #include "net/instaweb/http/public/async_fetch.h"
  #include "net/instaweb/http/public/url_async_fetcher.h"
  #include "net/instaweb/rewriter/public/rewrite_test_base.h"
  #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h"
  #include "net/instaweb/rewriter/public/url_namer.h"
  #include "pagespeed/automatic/proxy_interface.h"
  #include "pagespeed/kernel/base/basictypes.h"
  #include "pagespeed/kernel/base/scoped_ptr.h"
  #include "pagespeed/kernel/base/string.h"
  #include "pagespeed/kernel/base/string_util.h"
  #include "pagespeed/kernel/html/empty_html_filter.h"
  #include "pagespeed/kernel/http/response_headers.h"
  #include "pagespeed/kernel/thread/worker_test_base.h"
  
  namespace net_instaweb {
  
  class MockCriticalImagesFinder;
  class GoogleUrl;
  class HtmlElement;
  class HtmlFilter;
  class MessageHandler;
  class PropertyValue;
  class RequestHeaders;
  class RewriteDriver;
  class RewriteOptions;
  
  const char kPageUrl[] = "page.html";
  const char kBackgroundFetchHeader[] = "X-Background-Fetch";
  
  // Creates a proxy URL naming rule that encodes an "owner" domain and an
  // "origin" domain, all inside a fixed proxy-domain.
  class ProxyUrlNamer : public UrlNamer {
   public:
    static const char kProxyHost[];
  
    ProxyUrlNamer() : authorized_(true) {}
  
    // Given the request_url, generate the original url.
    virtual bool Decode(const GoogleUrl& gurl,
                        const RewriteOptions* rewrite_options,
                        GoogleUrl* domain,
                        GoogleString* decoded) const;
  
    virtual bool IsAuthorized(const GoogleUrl& gurl,
                              const RewriteOptions& options) const {
      return authorized_;
    }
  
    void set_authorized(bool authorized) { authorized_ = authorized; }
  
   private:
    bool authorized_;
    DISALLOW_COPY_AND_ASSIGN(ProxyUrlNamer);
  };
  
  // Mock filter which gets passed to the new rewrite driver created in
  // proxy_fetch.
  //
  // This is used to check the flow for injecting data into filters via the
  // ProxyInterface, including:
  //     property_cache.
  class MockFilter : public EmptyHtmlFilter {
   public:
    explicit MockFilter(RewriteDriver* driver)
        : driver_(driver),
          num_elements_(0),
          num_elements_property_(NULL) {
    }
  
    virtual void StartDocument();
  
    virtual void StartElement(HtmlElement* element);
  
    virtual void EndDocument();
  
    virtual const char* Name() const { return "MockFilter"; }
  
   private:
    RewriteDriver* driver_;
    int num_elements_;
    PropertyValue* num_elements_property_;
    DISALLOW_COPY_AND_ASSIGN(MockFilter);
  };
  
  // Hook provided to TestRewriteDriverFactory to add a new filter when
  // a rewrite_driver is created.
  class CreateFilterCallback
      : public TestRewriteDriverFactory::CreateFilterCallback {
   public:
    CreateFilterCallback() {}
    virtual ~CreateFilterCallback() {}
  
    virtual HtmlFilter* Done(RewriteDriver* driver) {
      return new MockFilter(driver);
    }
  
   private:
    DISALLOW_COPY_AND_ASSIGN(CreateFilterCallback);
  };
  
  // Subclass of AsyncFetch that adds a response header indicating whether the
  // fetch is for a user-facing request, or a background rewrite.
  class BackgroundFetchCheckingAsyncFetch : public SharedAsyncFetch {
   public:
    explicit BackgroundFetchCheckingAsyncFetch(AsyncFetch* base_fetch)
        : SharedAsyncFetch(base_fetch),
          async_fetch_(base_fetch) {}
    virtual ~BackgroundFetchCheckingAsyncFetch() {}
  
    virtual void HandleHeadersComplete() {
      SharedAsyncFetch::HandleHeadersComplete();
      response_headers()->Add(kBackgroundFetchHeader,
                              async_fetch_->IsBackgroundFetch() ? "1" : "0");
      // Call ComputeCaching again since Add sets cache_fields_dirty_ to true.
      response_headers()->ComputeCaching();
    }
  
    virtual void HandleDone(bool success) {
      SharedAsyncFetch::HandleDone(success);
      delete this;
    }
  
   private:
    AsyncFetch* async_fetch_;
    DISALLOW_COPY_AND_ASSIGN(BackgroundFetchCheckingAsyncFetch);
  };
  
  // Subclass of UrlAsyncFetcher that wraps the AsyncFetch with a
  // BackgroundFetchCheckingAsyncFetch.
  class BackgroundFetchCheckingUrlAsyncFetcher : public UrlAsyncFetcher {
   public:
    explicit BackgroundFetchCheckingUrlAsyncFetcher(UrlAsyncFetcher* fetcher)
        : base_fetcher_(fetcher),
          num_background_fetches_(0) {}
    virtual ~BackgroundFetchCheckingUrlAsyncFetcher() {}
  
    virtual void Fetch(const GoogleString& url,
                       MessageHandler* message_handler,
                       AsyncFetch* fetch) {
      if (fetch->IsBackgroundFetch()) {
        num_background_fetches_++;
      }
      BackgroundFetchCheckingAsyncFetch* new_fetch =
          new BackgroundFetchCheckingAsyncFetch(fetch);
      base_fetcher_->Fetch(url, message_handler, new_fetch);
    }
  
    int num_background_fetches() { return num_background_fetches_; }
    void clear_num_background_fetches() { num_background_fetches_ = 0; }
  
   private:
    UrlAsyncFetcher* base_fetcher_;
    int num_background_fetches_;
    DISALLOW_COPY_AND_ASSIGN(BackgroundFetchCheckingUrlAsyncFetcher);
  };
  
  class ProxyInterfaceTestBase : public RewriteTestBase {
   public:
    void TestHeadersSetupRace();
  
   protected:
    static const int kHtmlCacheTimeSec = 5000;
  
    ProxyInterfaceTestBase();
    virtual void SetUp();
    virtual void TearDown();
  
    void FetchFromProxy(
        const StringPiece& url,
        const RequestHeaders& request_headers,
        bool expect_success,
        GoogleString* string_out,
        ResponseHeaders* headers_out,
        bool proxy_fetch_property_callback_collector_created);
  
    void FetchFromProxy(const StringPiece& url,
                        const RequestHeaders& request_headers,
                        bool expect_success,
                        GoogleString* string_out,
                        ResponseHeaders* headers_out);
  
    void FetchFromProxy(const StringPiece& url,
                        bool expect_success,
                        GoogleString* string_out,
                        ResponseHeaders* headers_out);
  
    void FetchFromProxyLoggingFlushes(const StringPiece& url,
                                      bool expect_success,
                                      GoogleString* string_out);
  
    void FetchFromProxyNoWait(const StringPiece& url,
                              const RequestHeaders& request_headers,
                              bool expect_success,
                              bool log_flush,
                              ResponseHeaders* headers_out);
  
    void WaitForFetch(bool proxy_fetch_property_callback_collector_created);
  
    void TestPropertyCache(const StringPiece& url,
                           bool delay_pcache, bool thread_pcache,
                           bool expect_success);
  
    void TestPropertyCacheWithHeadersAndOutput(
        const StringPiece& url, bool delay_pcache, bool thread_pcache,
        bool expect_success, bool check_stats, bool add_create_filter_callback,
        bool expect_detach_before_pcache, const RequestHeaders& request_headers,
        ResponseHeaders* response_headers, GoogleString* output);
  
    void SetCriticalImagesInFinder(StringSet* critical_images);
    void SetCssCriticalImagesInFinder(StringSet* css_critical_images);
  
    scoped_ptr<ProxyInterface> proxy_interface_;
    scoped_ptr<WorkerTestBase::SyncPoint> sync_;
    ResponseHeaders callback_response_headers_;
    GoogleString callback_buffer_;
    bool callback_done_value_;
  
   private:
    friend class FilterCallback;
  
    MockCriticalImagesFinder* mock_critical_images_finder_;
  };
  
  }  // namespace net_instaweb
  #endif  // PAGESPEED_AUTOMATIC_PROXY_INTERFACE_TEST_BASE_H_
  