text: /*
   * Copyright 2012 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: mdw@google.com (Matt Welsh)
  
  // Unit-tests for ProxyFetch and related classes
  
  #include "pagespeed/automatic/proxy_fetch.h"
  
  #include "base/logging.h"
  #include "net/instaweb/http/public/log_record.h"
  #include "net/instaweb/http/public/logging_proto_impl.h"
  #include "net/instaweb/http/public/mock_callback.h"
  #include "net/instaweb/http/public/request_context.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/abstract_mutex.h"
  #include "pagespeed/kernel/base/function.h"
  #include "pagespeed/kernel/base/gtest.h"
  #include "pagespeed/kernel/base/message_handler.h"
  #include "pagespeed/kernel/base/null_message_handler.h"
  #include "pagespeed/kernel/base/scoped_ptr.h"
  #include "pagespeed/kernel/base/thread_system.h"
  #include "pagespeed/kernel/html/html_parse_test_base.h"
  #include "pagespeed/kernel/http/http_names.h"
  #include "pagespeed/kernel/http/request_headers.h"
  #include "pagespeed/kernel/http/response_headers.h"
  #include "pagespeed/kernel/thread/mock_scheduler.h"
  #include "pagespeed/kernel/thread/thread_synchronizer.h"
  #include "pagespeed/kernel/thread/worker_test_base.h"
  #include "pagespeed/kernel/util/platform.h"
  
  namespace net_instaweb {
  
  // A stripped-down mock of ProxyFetch, used for testing PropertyCacheComplete().
  class MockProxyFetch : public ProxyFetch {
   public:
    MockProxyFetch(AsyncFetch* async_fetch,
                   ProxyFetchFactory* factory,
                   ServerContext* server_context)
        : ProxyFetch("http://www.google.com", false,
                     NULL,  // callback
                     async_fetch,
                     NULL,  // no original content fetch
                     GetNewRewriteDriver(server_context, async_fetch),
                     server_context,
                     NULL,  // timer
                     factory),
          complete_(false) {
      response_headers()->set_status_code(HttpStatus::kOK);
    }
  
    ~MockProxyFetch() { }
  
    static RewriteDriver* GetNewRewriteDriver(ServerContext* server_context,
                                       AsyncFetch* async_fetch) {
      RewriteDriver* driver = server_context->NewRewriteDriver(
                                  async_fetch->request_context());
      RequestHeaders headers;
      driver->SetRequestHeaders(headers);
      return driver;
    }
  
    void PropertyCacheComplete(
        ProxyFetchPropertyCallbackCollector* callback_collector) {
      complete_ = true;
    }
  
    void Done(bool success) {
      HandleDone(success);
    }
  
    bool complete() const { return complete_; }
  
    RewriteDriver* driver() const { return driver_; }
  
   private:
    bool complete_;
    DISALLOW_COPY_AND_ASSIGN(MockProxyFetch);
  };
  
  // A wrapper around MockProxyFetch that manages all objects on the heap and
  // initializes things for no parsing and then parses a token script. You must
  // NOT call Done() on the MockProxyFetch object as we do it in our destructor.
  class ManagedMockProxyFetch {
   public:
    ManagedMockProxyFetch(ServerContext* server_context,
                          bool allow_options_to_be_set_by_cookies,
                          StringPiece sticky_query_parameters,
                          StringPiece sticky_query_parameters_token,
                          StringPiece query_params, StringPiece option_cookies) {
      server_context->global_options()->ClearSignatureForTesting();
      server_context->global_options()->set_allow_options_to_be_set_by_cookies(
          allow_options_to_be_set_by_cookies);
      server_context->global_options()->set_sticky_query_parameters(
          sticky_query_parameters);
      // Suppresses parsing and the invocation of mock_proxy_fetch_->Done().
      server_context->global_options()->set_max_html_parse_bytes(0L);
      server_context->global_options()->ComputeSignature();
      message_handler_.reset(new NullMessageHandler());
      async_fetch_.reset(new StringAsyncFetch(
          RequestContext::NewTestRequestContext(
              server_context->thread_system())));
      async_fetch_->response_headers()->Add("Content-Type", "text/html");
      async_fetch_->response_headers()->ComputeCaching();
      async_fetch_->request_context()->set_sticky_query_parameters_token(
          sticky_query_parameters_token);
      fetch_factory_.reset(new ProxyFetchFactory(server_context));
      mock_proxy_fetch_ = new MockProxyFetch(async_fetch_.get(),
                                             fetch_factory_.get(),
                                             server_context);
      mock_proxy_fetch_->driver()->set_pagespeed_query_params(query_params);
      mock_proxy_fetch_->driver()->set_pagespeed_option_cookies(option_cookies);
      mock_proxy_fetch_->Write("<html>HTML</html>.", message_handler_.get());
      mock_proxy_fetch_->Flush(message_handler_.get());
    }
  
    ~ManagedMockProxyFetch() {
      mock_proxy_fetch_->Done(true);
    }
  
    MockProxyFetch* mock_proxy_fetch() const { return mock_proxy_fetch_; }
  
   private:
    scoped_ptr<NullMessageHandler> message_handler_;
    scoped_ptr<StringAsyncFetch> async_fetch_;
    scoped_ptr<ProxyFetchFactory> fetch_factory_;
    MockProxyFetch* mock_proxy_fetch_;  // Not scoped_ptr as it self-deletes.
  };
  
  class ProxyFetchPropertyCallbackCollectorTest : public RewriteTestBase {
   public:
    void PostLookupTask() {
      post_lookup_called_ = true;
    }
    void CheckPageNotNullPostLookupTask(
        ProxyFetchPropertyCallbackCollector* collector,
        WorkerTestBase::SyncPoint* sync_point) {
      EXPECT_TRUE(collector->fallback_property_page() != NULL);
      sync_point->Notify();
    }
  
    void AddCheckPageNotNullPostLookupTask(
        ProxyFetchPropertyCallbackCollector* collector,
        WorkerTestBase::SyncPoint* sync_point) {
      collector->AddPostLookupTask(MakeFunction(
          this,
          &ProxyFetchPropertyCallbackCollectorTest::
              CheckPageNotNullPostLookupTask,
          collector,
          sync_point));
    }
  
   protected:
    ProxyFetchPropertyCallbackCollectorTest() :
      thread_system_(Platform::CreateThreadSystem()),
      server_context_(server_context()),
      post_lookup_called_(false) {
    }
  
    scoped_ptr<ThreadSystem> thread_system_;
    ServerContext* server_context_;
  
    // Create a collector.
    ProxyFetchPropertyCallbackCollector* MakeCollector() {
      ProxyFetchPropertyCallbackCollector* collector =
          new ProxyFetchPropertyCallbackCollector(
              server_context_, RewriteTestBase::kTestDomain,
              RequestContext::NewTestRequestContext(thread_system_.get()),
              options(), UserAgentMatcher::kDesktop);
      // Collector should not contain any PropertyPages
      EXPECT_EQ(NULL, collector->ReleasePropertyPage(
          ProxyFetchPropertyCallback::kPropertyCachePage));
  
      return collector;
    }
  
    void EnableCollectorPrefix() {
      ThreadSynchronizer* sync = server_context()->thread_synchronizer();
      sync->EnableForPrefix(ProxyFetch::kCollectorDoneFinish);
      sync->EnableForPrefix(ProxyFetch::kCollectorDetachFinish);
      sync->EnableForPrefix(ProxyFetch::kCollectorConnectProxyFetchFinish);
      sync->EnableForPrefix(ProxyFetch::kCollectorRequestHeadersCompleteFinish);
    }
  
    // Add a callback to the collector.
    ProxyFetchPropertyCallback* AddCallback(
        ProxyFetchPropertyCallbackCollector* collector,
        ProxyFetchPropertyCallback::PageType page_type) {
      AbstractMutex* mutex = thread_system_->NewMutex();
      DCHECK(page_type == ProxyFetchPropertyCallback::kPropertyCachePage);
      ProxyFetchPropertyCallback* callback =
          new ProxyFetchPropertyCallback(
              page_type, page_property_cache(), RewriteTestBase::kTestDomain,
              "hash", UserAgentMatcher::kDesktop, collector, mutex);
      EXPECT_EQ(page_type, callback->page_type());
      collector->AddCallback(callback);
      return callback;
    }
  
    void AddPostLookupConnectProxyFetchCallDone(
        ProxyFetchPropertyCallbackCollector* collector,
        MockProxyFetch* mock_proxy_fetch,
        ProxyFetchPropertyCallback* callback) {
      collector->AddPostLookupTask(MakeFunction(
          this, &ProxyFetchPropertyCallbackCollectorTest::PostLookupTask));
      collector->ConnectProxyFetch(mock_proxy_fetch);
      callback->Done(true);
    }
  
    void ConnectProxyFetchAddPostLookupCallDone(
        ProxyFetchPropertyCallbackCollector* collector,
        MockProxyFetch* mock_proxy_fetch,
        ProxyFetchPropertyCallback* callback) {
      collector->ConnectProxyFetch(mock_proxy_fetch);
      collector->AddPostLookupTask(MakeFunction(
          this, &ProxyFetchPropertyCallbackCollectorTest::PostLookupTask));
      callback->Done(true);
    }
  
    void CallDoneAddPostLookupConnectProxyFetch(
        ProxyFetchPropertyCallbackCollector* collector,
        MockProxyFetch* mock_proxy_fetch,
        ProxyFetchPropertyCallback* callback) {
      callback->Done(true);
      collector->AddPostLookupTask(MakeFunction(
          this, &ProxyFetchPropertyCallbackCollectorTest::PostLookupTask));
      collector->ConnectProxyFetch(mock_proxy_fetch);
    }
  
    void TestAddPostlookupTask(bool add_before_done,
                               bool add_before_proxy_fetch) {
      EnableCollectorPrefix();
      scoped_ptr<ProxyFetchPropertyCallbackCollector> collector;
      collector.reset(MakeCollector());
      ProxyFetchPropertyCallback* page_callback = AddCallback(
          collector.get(), ProxyFetchPropertyCallback::kPropertyCachePage);
      ExpectStringAsyncFetch async_fetch(
          true, RequestContext::NewTestRequestContext(thread_system_.get()));
      ProxyFetchFactory factory(server_context_);
      MockProxyFetch* mock_proxy_fetch = new MockProxyFetch(
          &async_fetch, &factory, server_context_);
      if (add_before_done && add_before_proxy_fetch) {
        AddPostLookupConnectProxyFetchCallDone(
            collector.get(), mock_proxy_fetch, page_callback);
      } else if (add_before_done && !add_before_proxy_fetch) {
        ConnectProxyFetchAddPostLookupCallDone(
            collector.get(), mock_proxy_fetch, page_callback);
      } else if (!add_before_done && add_before_proxy_fetch) {
        CallDoneAddPostLookupConnectProxyFetch(
            collector.get(), mock_proxy_fetch, page_callback);
      } else {
        // Not handled. Make this fail.
      }
      collector->RequestHeadersComplete();
      EXPECT_TRUE(post_lookup_called_);
      mock_proxy_fetch->Done(true);
    }
  
   private:
    bool post_lookup_called_;
  
    DISALLOW_COPY_AND_ASSIGN(ProxyFetchPropertyCallbackCollectorTest);
  };
  
  
  // Test fixture for ProxyFetch.
  class ProxyFetchTest : public RewriteTestBase {
  };
  
  TEST_F(ProxyFetchTest, TestInhibitParsing) {
    NullMessageHandler handler;
    server_context()->global_options()->ClearSignatureForTesting();
    server_context()->global_options()->set_max_html_parse_bytes(0L);
    server_context()->global_options()->ComputeSignature();
    StringAsyncFetch fetch(
        RequestContext::NewTestRequestContext(
            server_context()->thread_system()));
    fetch.response_headers()->Add("Content-Type", "text/html");
    fetch.response_headers()->ComputeCaching();
    ProxyFetchFactory factory(server_context_);
    MockProxyFetch* mock_proxy_fetch = new MockProxyFetch(
        &fetch, &factory, server_context());
    mock_proxy_fetch->Write("<html>HTML</html>.", &handler);
    mock_proxy_fetch->Flush(&handler);
  
    // We never parsed the HTML, but we did log HTML content type.
    EXPECT_FALSE(mock_proxy_fetch->started_parse_);
    {
      AbstractLogRecord* log_record =
          mock_proxy_fetch->request_context()->log_record();
      ScopedMutex lock(log_record->mutex());
      LoggingInfo* info = log_record->logging_info();
      EXPECT_TRUE(info->is_html_response());
    }
  
    mock_proxy_fetch->Done(true);
  }
  
  namespace {
  
  class FlushLoggingStringAsyncFetch : public StringAsyncFetch {
   public:
    explicit FlushLoggingStringAsyncFetch(
        const RequestContextPtr& request_context)
        : StringAsyncFetch(request_context) {}
  
    virtual ~FlushLoggingStringAsyncFetch() {}
  
    bool HandleFlush(MessageHandler* handler) override {
      HandleWrite("|Flush|", handler);
      return true;
    }
  
   private:
    DISALLOW_COPY_AND_ASSIGN(FlushLoggingStringAsyncFetch);
  };
  
  }  // namespace
  
  TEST_F(ProxyFetchTest, TestFollowFlushes) {
    NullMessageHandler handler;
    RewriteOptions* options = server_context()->global_options();
    options->ClearSignatureForTesting();
    options->DisableFilter(RewriteOptions::kAddHead);
    options->set_follow_flushes(true);
    options->ComputeSignature();
    FlushLoggingStringAsyncFetch fetch(
        RequestContext::NewTestRequestContext(
            server_context()->thread_system()));
    fetch.response_headers()->Add("Content-Type", "text/html");
    ProxyFetchFactory factory(server_context_);
    MockProxyFetch* mock_proxy_fetch = new MockProxyFetch(
        &fetch, &factory, server_context());
    mock_proxy_fetch->response_headers()->ComputeCaching();
  
    // To get a more predictable output with regard to flush markers, we wait
    // for flushes to be fully processed before continuing. ProxyFetch may
    // otherwise aggregate, reposition, or ignore induced flushes.
    mock_proxy_fetch->Write("<html><d>1</d>", &handler);
    mock_proxy_fetch->Flush(&handler);
    mock_scheduler()->AwaitQuiescence();
    mock_proxy_fetch->Write("<d>2</d></html>", &handler);
    mock_proxy_fetch->Flush(&handler);
    mock_scheduler()->AwaitQuiescence();
  
    // Verify we are parsing HTML.
    EXPECT_TRUE(mock_proxy_fetch->started_parse_);
  
    mock_proxy_fetch->Done(true);
    mock_scheduler()->AwaitQuiescence();
    EXPECT_EQ(0, server_context()->num_active_rewrite_drivers());
    EXPECT_EQ("<html><d>1</d>|Flush|<d>2</d></html>|Flush||Flush|",
              fetch.buffer());
  
    // Test following flushes off behaves as expected.
    options->ClearSignatureForTesting();
    options->set_follow_flushes(false);
    options->ComputeSignature();
  
    FlushLoggingStringAsyncFetch fetch_off(
        RequestContext::NewTestRequestContext(
            server_context()->thread_system()));
    fetch_off.response_headers()->Add("Content-Type", "text/html");
  
    mock_proxy_fetch = new MockProxyFetch(
        &fetch_off, &factory, server_context());
    mock_proxy_fetch->response_headers()->ComputeCaching();
  
    mock_proxy_fetch->Write("<html><d>1</d>", &handler);
    mock_proxy_fetch->Flush(&handler);
    mock_scheduler()->AwaitQuiescence();
    mock_proxy_fetch->Write("<d>2</d></html>", &handler);
    mock_proxy_fetch->Flush(&handler);
    mock_scheduler()->AwaitQuiescence();
  
    EXPECT_TRUE(mock_proxy_fetch->started_parse_);
  
    mock_proxy_fetch->Done(true);
    mock_scheduler()->AwaitQuiescence();
    EXPECT_EQ(0, server_context()->num_active_rewrite_drivers());
    EXPECT_EQ("<html><d>1</d><d>2</d></html>|Flush|", fetch_off.buffer());
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, EmptyCollectorTest) {
    // Test that creating an empty collector works.
    EnableCollectorPrefix();
    scoped_ptr<ProxyFetchPropertyCallbackCollector> collector;
    collector.reset(MakeCollector());
    // This should not fail
    collector->Detach(HttpStatus::kUnknownStatusCode);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, DoneBeforeDetach) {
    EnableCollectorPrefix();
    // Test that calling Done() before Detach() works.
    ProxyFetchPropertyCallbackCollector* collector = MakeCollector();
    ProxyFetchPropertyCallback* callback = AddCallback(
        collector, ProxyFetchPropertyCallback::kPropertyCachePage);
  
    // callback->IsCacheValid could be called anytime before callback->Done.
    // Will return true because there are no cache invalidation URL patterns.
    EXPECT_TRUE(callback->IsCacheValid(1L));
  
    // Invoke the callback.
    callback->Done(true);
  
    // Collector should now have a page property.
    scoped_ptr<AbstractPropertyPage> page;
    page.reset(collector->ReleaseFallbackPropertyPage());
    EXPECT_TRUE(NULL != page.get());
  
    // This should not fail - will also delete the collector.
    collector->Detach(HttpStatus::kUnknownStatusCode);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, UrlInvalidDoneBeforeDetach) {
    EnableCollectorPrefix();
    // Invalidate all URLs cached before timestamp 2.
    options_->AddUrlCacheInvalidationEntry("*", 2L, true);
    // Test that calling Done() before Detach() works.
    ProxyFetchPropertyCallbackCollector* collector = MakeCollector();
    ProxyFetchPropertyCallback* callback = AddCallback(
        collector, ProxyFetchPropertyCallback::kPropertyCachePage);
  
    // callback->IsCacheValid could be called anytime before callback->Done.
    // Will return false due to the invalidation entry.
    EXPECT_FALSE(callback->IsCacheValid(1L));
  
    // Invoke the callback.
    callback->Done(true);
  
    // Collector should now have a page property.
    scoped_ptr<AbstractPropertyPage> page;
    page.reset(collector->ReleaseFallbackPropertyPage());
    EXPECT_TRUE(NULL != page.get());
  
    // This should not fail - will also delete the collector.
    collector->Detach(HttpStatus::kUnknownStatusCode);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, DetachBeforeDone) {
    EnableCollectorPrefix();
    // Test that calling Detach() before Done() works.
    ProxyFetchPropertyCallbackCollector* collector = MakeCollector();
    collector->RequestHeadersComplete();
    ProxyFetchPropertyCallback* callback = AddCallback(
        collector, ProxyFetchPropertyCallback::kPropertyCachePage);
  
    // callback->IsCacheValid could be called anytime before callback->Done.
    // Will return true because there are no cache invalidation URL patterns.
    EXPECT_TRUE(callback->IsCacheValid(1L));
  
    // Will not delete the collector since we did not call Done yet.
    collector->Detach(HttpStatus::kUnknownStatusCode);
  
    // This call is after Detach (but before callback->Done).
    // ProxyFetchPropertyCallbackCollector::IsCacheValid returns false if
    // detached.
    EXPECT_FALSE(callback->IsCacheValid(1L));
  
    // This will delete the collector.
    callback->Done(true);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, DoneBeforeSetProxyFetch) {
    EnableCollectorPrefix();
    // Test that calling Done() before SetProxyFetch() works.
    scoped_ptr<ProxyFetchPropertyCallbackCollector> collector;
    collector.reset(MakeCollector());
    ProxyFetchPropertyCallback* callback = AddCallback(
        collector.get(), ProxyFetchPropertyCallback::kPropertyCachePage);
  
    // callback->IsCacheValid could be called anytime before callback->Done.
    // Will return true because there are no cache invalidation URL patterns.
    EXPECT_TRUE(callback->IsCacheValid(1L));
  
    // Invoke the callback.
    callback->Done(true);
  
    // Construct mock ProxyFetch to test SetProxyFetch().
    ExpectStringAsyncFetch async_fetch(
        true, RequestContext::NewTestRequestContext(thread_system_.get()));
    ProxyFetchFactory factory(server_context_);
    MockProxyFetch* mock_proxy_fetch = new MockProxyFetch(
        &async_fetch, &factory, server_context_);
  
    // Should not be complete since SetProxyFetch() called first.
    EXPECT_FALSE(mock_proxy_fetch->complete());
  
    // Collector should now have a page property.
    scoped_ptr<AbstractPropertyPage> page;
    page.reset(collector->ReleaseFallbackPropertyPage());
    EXPECT_TRUE(NULL != page.get());
  
    collector.get()->ConnectProxyFetch(mock_proxy_fetch);
    // Should be complete since SetProxyFetch() called after Done().
    EXPECT_TRUE(mock_proxy_fetch->complete());
  
    // Needed for cleanup.
    mock_proxy_fetch->Done(true);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, SetProxyFetchBeforeDone) {
    EnableCollectorPrefix();
    // Test that calling SetProxyFetch() before Done() works.
    scoped_ptr<ProxyFetchPropertyCallbackCollector> collector;
    collector.reset(MakeCollector());
    ProxyFetchPropertyCallback* callback = AddCallback(
        collector.get(), ProxyFetchPropertyCallback::kPropertyCachePage);
  
    // Construct mock ProxyFetch to test SetProxyFetch().
    ExpectStringAsyncFetch async_fetch(
        true, RequestContext::NewTestRequestContext(thread_system_.get()));
    ProxyFetchFactory factory(server_context_);
    MockProxyFetch* mock_proxy_fetch = new MockProxyFetch(
        &async_fetch, &factory, server_context_);
  
    // callback->IsCacheValid could be called anytime before callback->Done.
    // Will return true because there are no cache invalidation URL patterns.
    EXPECT_TRUE(callback->IsCacheValid(1L));
  
    collector.get()->ConnectProxyFetch(mock_proxy_fetch);
    // Should not be complete since SetProxyFetch() called first.
    EXPECT_FALSE(mock_proxy_fetch->complete());
  
    EXPECT_TRUE(callback->IsCacheValid(1L));
  
    // Now invoke the callback.
    callback->Done(true);
  
    // Collector should now have a page property.
    scoped_ptr<AbstractPropertyPage> page;
    page.reset(collector->ReleaseFallbackPropertyPage());
    EXPECT_TRUE(NULL != page.get());
  
    // Not yet complete since RequestHeadersComplete() not called yet.
    EXPECT_FALSE(mock_proxy_fetch->complete());
  
    collector->RequestHeadersComplete();
  
    // Should be complete since both Done() and RequestHeadersComplete() called.
    EXPECT_TRUE(mock_proxy_fetch->complete());
  
    // Needed for cleanup.
    mock_proxy_fetch->Done(true);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, PostLookupProxyFetchDone) {
    TestAddPostlookupTask(true, true);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, DonePostLookupProxyFetch) {
    TestAddPostlookupTask(false, true);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, ProxyFetchPostLookupDone) {
    TestAddPostlookupTask(true, false);
  }
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, FallbackPagePostLookupRace) {
    EnableCollectorPrefix();
    // This test will check PostLookup tasks should not have Null
    // fallback_property_page.
    scoped_ptr<ProxyFetchPropertyCallbackCollector> collector;
    collector.reset(MakeCollector());
    collector->RequestHeadersComplete();
    ProxyFetchPropertyCallback* page_callback = AddCallback(
        collector.get(), ProxyFetchPropertyCallback::kPropertyCachePage);
    ExpectStringAsyncFetch async_fetch(
        true, RequestContext::NewTestRequestContext(thread_system_.get()));
    ProxyFetchFactory factory(server_context_);
    MockProxyFetch* mock_proxy_fetch = new MockProxyFetch(
        &async_fetch, &factory, server_context_);
  
    ThreadSystem* thread_system = server_context()->thread_system();
    QueuedWorkerPool pool(1, "test", thread_system);
    QueuedWorkerPool::Sequence* sequence = pool.NewSequence();
    WorkerTestBase::SyncPoint sync_point(thread_system);
    sequence->Add(
        MakeFunction(
            static_cast<ProxyFetchPropertyCallbackCollectorTest*>(this),
            &ProxyFetchPropertyCallbackCollectorTest::
                AddCheckPageNotNullPostLookupTask,
            collector.get(), &sync_point));
    page_callback->Done(true);
    sync_point.Wait();
    collector->ConnectProxyFetch(mock_proxy_fetch);
    mock_proxy_fetch->Done(true);
  }
  
  
  TEST_F(ProxyFetchPropertyCallbackCollectorTest, TestOptionsValid) {
    RewriteOptions* options = new RewriteOptions(thread_system_.get());
    ProxyFetchPropertyCallbackCollector* collector =
        new ProxyFetchPropertyCallbackCollector(
            server_context_,
            RewriteTestBase::kTestDomain,
            RequestContext::NewTestRequestContext(thread_system_.get()),
            options,
            UserAgentMatcher::kDesktop);
    ThreadSynchronizer* sync = server_context()->thread_synchronizer();
    sync->EnableForPrefix(ProxyFetch::kCollectorFinish);
    sync->EnableForPrefix(ProxyFetch::kCollectorDetachStart);
    sync->EnableForPrefix(ProxyFetch::kCollectorRequestHeadersCompleteFinish);
    collector->RequestHeadersComplete();
    ProxyFetchPropertyCallback* page_callback = AddCallback(
        collector, ProxyFetchPropertyCallback::kPropertyCachePage);
  
    collector->Detach(HttpStatus::kUnknownStatusCode);
    delete options;
    collector->IsCacheValid(1L);
    sync->Signal(ProxyFetch::kCollectorDetachStart);
    page_callback->Done(true);
    sync->Wait(ProxyFetch::kCollectorFinish);
  }
  
  TEST_F(ProxyFetchTest, TestStickyPageSpeedOptions) {
    // The various options we set as cookies.
    StringPieceVector pagespeed_options;
    pagespeed_options.push_back("PageSpeedImageRecompressionQuality=77");
    pagespeed_options.push_back("ModPagespeedImageLimitOptimizedPercent=55");
    pagespeed_options.push_back("ModPagespeedCssInlineMaxBytes=19");
    GoogleString cookies = JoinCollection(pagespeed_options, "&");
    // Add the sticky query param as a QP to show that it's not set as a cookie.
    GoogleString params = StrCat("PageSpeedStickyQueryParameters=on&", cookies);
    // For producing the expected response headers cookies string.
    const char kCookiesPrefix[] = "[\"";
    const char kCookieSetExpires[] = "; Expires=Tue, 02 Feb 2010 19:01:26 GMT";
    const char kCookieClearExpires[] = "; Expires=Thu, 01 Jan 1970 00:00:00 GMT";
    const char kCookieAttrs[] = "; Domain=www.google.com; Path=/; HttpOnly";
    const char kCookiesSeparator[] = "\",\"";
    const char kCookiesSuffix[] = "\"]";
    GoogleString current_cookies =
        StrCat(kCookiesPrefix,
               JoinCollection(pagespeed_options, StrCat(kCookieSetExpires,
                                                        kCookieAttrs,
                                                        kCookiesSeparator)),
               kCookieSetExpires, kCookieAttrs, kCookiesSuffix);
    GoogleString expired_cookies =
        StrCat(kCookiesPrefix,
               JoinCollection(pagespeed_options, StrCat(kCookieClearExpires,
                                                        kCookieAttrs,
                                                        kCookiesSeparator)),
               kCookieClearExpires, kCookieAttrs, kCookiesSuffix);
    // Strip out the values from the expiration cookies. Pardon the hackiness.
    GlobalReplaceSubstring("=77;", ";", &expired_cookies);
    GlobalReplaceSubstring("=55;", ";", &expired_cookies);
    GlobalReplaceSubstring("=19;", ";", &expired_cookies);
  
    // 0. Allow options to be set by cookie but don't allow them to be sticky.
    {
      ManagedMockProxyFetch managed_mock(server_context_, true, "", "right value",
                                         params, "");
      MockProxyFetch* mock_proxy_fetch = managed_mock.mock_proxy_fetch();
      GoogleString response_cookies;
      EXPECT_FALSE(mock_proxy_fetch->response_headers()->GetCookieString(
          &response_cookies));
      EXPECT_STREQ("", response_cookies);
    }
    // 1. Allow options to be set by cookie, allow them to be sticky, but don't
    //    ask for stickiness in the request.
    {
      ManagedMockProxyFetch managed_mock(server_context_, true, "right value", "",
                                         params, "");
      MockProxyFetch* mock_proxy_fetch = managed_mock.mock_proxy_fetch();
      GoogleString response_cookies;
      EXPECT_FALSE(mock_proxy_fetch->response_headers()->GetCookieString(
          &response_cookies));
      EXPECT_STREQ("", response_cookies);
    }
    // 2. Don't allow options to be set by cookie, allow them to be made sticky,
    //    don't ask for stickiness in the request, but pass in some already-set
    //    cookies - these should be expired for us.
    {
      ManagedMockProxyFetch managed_mock(server_context_, false, "right value",
                                         "", "", cookies);
      MockProxyFetch* mock_proxy_fetch = managed_mock.mock_proxy_fetch();
      GoogleString response_cookies;
      EXPECT_TRUE(mock_proxy_fetch->response_headers()->GetCookieString(
          &response_cookies));
      EXPECT_STREQ(expired_cookies, response_cookies);
    }
    // 3. Allow options to be set by cookie, allow them to be made sticky, and
    //    ask for stickiness in the request.
    {
      ManagedMockProxyFetch managed_mock(server_context_, true, "right value",
                                         "right value", params, "");
      MockProxyFetch* mock_proxy_fetch = managed_mock.mock_proxy_fetch();
      GoogleString response_cookies;
      EXPECT_TRUE(mock_proxy_fetch->response_headers()->GetCookieString(
          &response_cookies));
      EXPECT_STREQ(current_cookies, response_cookies);
    }
    // 4. Pass in some options-as-cookies without any query params or changes.
    //    We expect no cookies in the response as the browser has them already.
    {
      ManagedMockProxyFetch managed_mock(server_context_, true, "right value", "",
                                         "", cookies);
      MockProxyFetch* mock_proxy_fetch = managed_mock.mock_proxy_fetch();
      GoogleString response_cookies;
      EXPECT_FALSE(mock_proxy_fetch->response_headers()->GetCookieString(
          &response_cookies));
      EXPECT_STREQ("", response_cookies);
    }
    // 5. Explicitly ask for sticky options-by-cookies to be cleared.
    {
      ManagedMockProxyFetch managed_mock(server_context_, true, "right value",
                                         "different value", "", cookies);
      MockProxyFetch* mock_proxy_fetch = managed_mock.mock_proxy_fetch();
      GoogleString response_cookies;
      EXPECT_TRUE(mock_proxy_fetch->response_headers()->GetCookieString(
          &response_cookies));
      EXPECT_STREQ(expired_cookies, response_cookies);
    }
  }
  
  }  // namespace net_instaweb
  