text: // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  // Tests exercising the Chrome Plugin API.
  
  #include "base/file_util.h"
  #include "base/message_loop_proxy.h"
  #include "base/path_service.h"
  #include "base/string_util.h"
  #include "chrome/browser/browser_thread.h"
  #include "chrome/browser/chrome_plugin_host.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/common/chrome_plugin_lib.h"
  #include "chrome/common/net/url_request_context_getter.h"
  #include "chrome/test/chrome_plugin/test_chrome_plugin.h"
  #include "net/base/io_buffer.h"
  #include "net/http/http_response_headers.h"
  #include "net/url_request/url_request_test_job.h"
  #include "net/url_request/url_request_unittest.h"
  #include "net/test/test_server.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace {
  
  const FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data");
  const char kPluginFilename[] = "test_chrome_plugin.dll";
  const int kResponseBufferSize = 4096;
  
  class TestURLRequestContextGetter : public URLRequestContextGetter {
   public:
    virtual URLRequestContext* GetURLRequestContext() {
      if (!context_)
        context_ = new TestURLRequestContext();
      return context_;
    }
    virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const {
      return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
    }
  
   private:
    ~TestURLRequestContextGetter() {}
    scoped_refptr<URLRequestContext> context_;
  };
  
  class ChromePluginTest : public testing::Test,
                           public net::URLRequest::Delegate {
   public:
    ChromePluginTest()
        : io_thread_(BrowserThread::IO, &message_loop_),
          request_(NULL),
          response_buffer_(new net::IOBuffer(kResponseBufferSize)),
          plugin_(NULL),
          expected_payload_(NULL),
          request_context_getter_(new TestURLRequestContextGetter()) {
      test_funcs_.test_make_request = NULL;
    }
  
    // Loads/unloads the chrome test plugin.
    void LoadPlugin();
    void UnloadPlugin();
  
    // Runs the test and expects the given payload as a response.  If expectation
    // is NULL, the request is expected to fail.
    void RunTest(const GURL& url, const TestResponsePayload* expected_payload);
  
    // net::URLRequest::Delegate implementations
    virtual void OnResponseStarted(net::URLRequest* request);
    virtual void OnReadCompleted(net::URLRequest* request, int bytes_read);
  
    // Helper called when the net::URLRequest is done.
    void OnURLRequestComplete();
  
    // testing::Test
    virtual void SetUp() {
      LoadPlugin();
      net::URLRequest::RegisterProtocolFactory("test",
                                               &URLRequestTestJob::Factory);
  
      // We need to setup a default request context in order to issue HTTP
      // requests.
      DCHECK(!Profile::GetDefaultRequestContext());
      Profile::set_default_request_context(request_context_getter_.get());
    }
    virtual void TearDown() {
      UnloadPlugin();
      net::URLRequest::RegisterProtocolFactory("test", NULL);
  
      Profile::set_default_request_context(NULL);
  
      // Clear the request before flushing the message loop since killing the
      // request can result in the generation of more tasks.
      request_.reset();
  
      // Flush the message loop to make Purify happy.
      message_loop_.RunAllPending();
    }
   protected:
    MessageLoopForIO message_loop_;
    BrowserThread io_thread_;
  
    // Note: we use net::URLRequest (instead of URLFetcher) because this allows
    // the request to be intercepted.
    scoped_ptr<net::URLRequest> request_;
    scoped_refptr<net::IOBuffer> response_buffer_;
    std::string response_data_;
  
    ChromePluginLib* plugin_;
    TestFuncParams::PluginFuncs test_funcs_;
    const TestResponsePayload* expected_payload_;
    scoped_refptr<URLRequestContextGetter> request_context_getter_;
  };
  
  static void STDCALL CPT_Complete(CPRequest* request, bool success,
                                   const std::string& raw_headers,
                                   const std::string& body) {
    GURL url(request->url);
    if (url == GURL(kChromeTestPluginPayloads[0].url)) {
      // This URL should fail, because the plugin should not have intercepted it.
      EXPECT_FALSE(success);
      MessageLoop::current()->Quit();
      return;
    }
  
    scoped_refptr<net::HttpResponseHeaders> headers(
        new net::HttpResponseHeaders(raw_headers));
    EXPECT_TRUE(success);
    EXPECT_EQ(200, headers->response_code());
  
    if (url == URLRequestTestJob::test_url_1()) {
      EXPECT_EQ(URLRequestTestJob::test_data_1(), body);
    } else if (url == URLRequestTestJob::test_url_2()) {
      EXPECT_EQ(URLRequestTestJob::test_data_2(), body);
    } else if (url.spec().find("echo") != std::string::npos) {
      EXPECT_EQ(kChromeTestPluginPostData, body);
    }
  
    MessageLoop::current()->Quit();
  }
  
  static void STDCALL CPT_InvokeLater(TestFuncParams::CallbackFunc callback,
                                      void* callback_data, int delay_ms) {
    MessageLoop::current()->PostDelayedTask(FROM_HERE,
        NewRunnableFunction(callback, callback_data), delay_ms);
  }
  
  void ChromePluginTest::LoadPlugin() {
    FilePath path;
    PathService::Get(base::DIR_EXE, &path);
    path = path.AppendASCII(kPluginFilename);
    plugin_ = ChromePluginLib::Create(path, GetCPBrowserFuncsForBrowser());
    ASSERT_TRUE(plugin_);
  
    // Exchange test APIs with the plugin.
    TestFuncParams params;
    params.bfuncs.test_complete = CPT_Complete;
    params.bfuncs.invoke_later = CPT_InvokeLater;
    EXPECT_EQ(CPERR_SUCCESS, plugin_->CP_Test(&params));
    test_funcs_ = params.pfuncs;
  }
  
  void ChromePluginTest::UnloadPlugin() {
    ChromePluginLib::UnloadAllPlugins();
    plugin_ = NULL;
  }
  
  void ChromePluginTest::RunTest(const GURL& url,
                                 const TestResponsePayload* expected_payload) {
    expected_payload_ = expected_payload;
  
    response_data_.clear();
    request_.reset(new net::URLRequest(url, this));
    request_->set_context(new TestURLRequestContext());
    request_->Start();
  
    MessageLoop::current()->Run();
  }
  
  void ChromePluginTest::OnResponseStarted(net::URLRequest* request) {
    DCHECK(request == request_);
  
    int bytes_read = 0;
    if (request_->status().is_success())
      request_->Read(response_buffer_, kResponseBufferSize, &bytes_read);
    OnReadCompleted(request_.get(), bytes_read);
  }
  
  void ChromePluginTest::OnReadCompleted(net::URLRequest* request,
                                         int bytes_read) {
    DCHECK(request == request_);
  
    do {
      if (!request_->status().is_success() || bytes_read <= 0)
        break;
      response_data_.append(response_buffer_->data(), bytes_read);
    } while (request_->Read(response_buffer_, kResponseBufferSize, &bytes_read));
  
    if (!request_->status().is_io_pending()) {
      OnURLRequestComplete();
    }
  }
  
  void ChromePluginTest::OnURLRequestComplete() {
    if (expected_payload_) {
      EXPECT_TRUE(request_->status().is_success());
  
      EXPECT_EQ(expected_payload_->status, request_->GetResponseCode());
      if (expected_payload_->mime_type) {
        std::string mime_type;
        EXPECT_TRUE(request_->response_headers()->GetMimeType(&mime_type));
        EXPECT_EQ(expected_payload_->mime_type, mime_type);
      }
      if (expected_payload_->body) {
        EXPECT_EQ(expected_payload_->body, response_data_);
      }
    } else {
      EXPECT_FALSE(request_->status().is_success());
    }
  
    MessageLoop::current()->Quit();
    // If MessageLoop::current() != main_loop_, it will be shut down when the
    // main loop returns and this thread subsequently goes out of scope.
  }
  
  // Tests that the plugin can intercept URLs.
  TEST_F(ChromePluginTest, DoesIntercept) {
    for (size_t i = 0; i < arraysize(kChromeTestPluginPayloads); ++i) {
      RunTest(GURL(kChromeTestPluginPayloads[i].url),
              &kChromeTestPluginPayloads[i]);
    }
  }
  
  // Tests that non-intercepted URLs are handled normally.
  TEST_F(ChromePluginTest, DoesNotIntercept) {
    TestResponsePayload about_blank = {
      "about:blank",
      false,
      -1,
      NULL,
      ""
    };
    RunTest(GURL(about_blank.url), &about_blank);
  }
  
  // Tests that unloading the plugin correctly unregisters URL interception.
  TEST_F(ChromePluginTest, UnregisterIntercept) {
    UnloadPlugin();
  
    RunTest(GURL(kChromeTestPluginPayloads[0].url), NULL);
  }
  
  static void ProcessAllPendingMessages() {
    while (URLRequestTestJob::ProcessOnePendingMessage());
  }
  
  // Tests that the plugin can issue a GET request and receives the data when
  // it comes back synchronously.
  TEST_F(ChromePluginTest, CanMakeGETRequestSync) {
    // test_url_1 has a synchronous response
    EXPECT_EQ(CPERR_SUCCESS, test_funcs_.test_make_request(
        "GET", URLRequestTestJob::test_url_1()));
  
    // Note: we must add this task after we make the request, so that
    // URLRequestTestJob's StartAsync task is added and run first.
    MessageLoop::current()->PostTask(FROM_HERE,
        NewRunnableFunction(&ProcessAllPendingMessages));
    MessageLoop::current()->Run();
  }
  
  // Tests that the plugin can issue a GET request and receives the data when
  // it comes back asynchronously.
  TEST_F(ChromePluginTest, CanMakeGETRequestAsync) {
    // test_url_2 has an asynchronous response
    EXPECT_EQ(CPERR_SUCCESS, test_funcs_.test_make_request(
          "GET", URLRequestTestJob::test_url_2()));
  
    // Note: we must add this task after we make the request, so that
    // URLRequestTestJob's StartAsync task is added and run first.
    MessageLoop::current()->PostTask(FROM_HERE,
        NewRunnableFunction(&ProcessAllPendingMessages));
    MessageLoop::current()->Run();
  }
  
  // Tests that the plugin can issue a POST request.
  TEST_F(ChromePluginTest, CanMakePOSTRequest) {
    net::TestServer test_server(net::TestServer::TYPE_HTTP, FilePath(kDocRoot));
    ASSERT_TRUE(test_server.Start());
  
    GURL url = test_server.GetURL("echo");
  
    EXPECT_EQ(CPERR_SUCCESS, test_funcs_.test_make_request("POST", url));
  
    // Note: we must add this task after we make the request, so that
    // URLRequestTestJob's StartAsync task is added and run first.
    MessageLoop::current()->PostTask(FROM_HERE,
        NewRunnableFunction(&ProcessAllPendingMessages));
    MessageLoop::current()->Run();
  }
  
  // Tests that the plugin does not intercept its own requests.
  TEST_F(ChromePluginTest, DoesNotInterceptOwnRequest) {
    const TestResponsePayload& payload = kChromeTestPluginPayloads[0];
  
    EXPECT_EQ(CPERR_SUCCESS, test_funcs_.test_make_request(
        "GET", GURL(payload.url)));
  
    MessageLoop::current()->Run();
  }
  
  }  // namespace
  