| /* |
| * Copyright 2010 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: sligocki@google.com (Shawn Ligocki) |
| |
| #include "net/instaweb/http/public/mock_url_fetcher.h" |
| |
| #include "net/instaweb/http/public/async_fetch.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/url_async_fetcher.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/google_message_handler.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/mock_timer.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/base/time_util.h" |
| #include "pagespeed/kernel/base/timer.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/util/platform.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| // Class for encapsulating objects needed to execute fetches. |
| class MockFetchContainer { |
| public: |
| MockFetchContainer(UrlAsyncFetcher* fetcher, ThreadSystem* thread_system) |
| : fetcher_(fetcher), |
| fetch_(RequestContext::NewTestRequestContext(thread_system), |
| &response_body_), |
| thread_system_(thread_system) { |
| fetch_.set_request_headers(&request_headers_); |
| fetch_.set_response_headers(&response_headers_); |
| } |
| |
| bool Fetch(const GoogleString& url) { |
| fetcher_->Fetch(url, &handler_, &fetch_); |
| EXPECT_TRUE(fetch_.done()); |
| return fetch_.success(); |
| } |
| |
| UrlAsyncFetcher* fetcher_; |
| GoogleString response_body_; |
| RequestHeaders request_headers_; |
| ResponseHeaders response_headers_; |
| StringAsyncFetch fetch_; |
| GoogleMessageHandler handler_; |
| ThreadSystem* thread_system_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockFetchContainer); |
| }; |
| |
| class MockUrlFetcherTest : public ::testing::Test { |
| protected: |
| MockUrlFetcherTest() : thread_system_(Platform::CreateThreadSystem()) { |
| fetcher_.set_fail_on_unexpected(false); |
| } |
| |
| void TestResponse(const GoogleString& url, |
| const ResponseHeaders& expected_header, |
| const GoogleString& expected_body) { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| EXPECT_EQ(expected_header.ToString(), fetch.response_headers_.ToString()); |
| EXPECT_EQ(expected_body, fetch.response_body_); |
| } |
| |
| void TestFetchFail(const GoogleString& url) { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| EXPECT_FALSE(fetch.Fetch(url)); |
| } |
| |
| MockUrlFetcher fetcher_; |
| scoped_ptr<ThreadSystem> thread_system_; |
| }; |
| |
| TEST_F(MockUrlFetcherTest, GetsCorrectMappedResponse) { |
| const char url1[] = "http://www.example.com/successs.html"; |
| ResponseHeaders header1; |
| header1.set_first_line(1, 1, 200, "OK"); |
| const char body1[] = "This website loaded :)"; |
| |
| const char url2[] = "http://www.example.com/failure.html"; |
| ResponseHeaders header2; |
| header2.set_first_line(1, 1, 404, "Not Found"); |
| const char body2[] = "File Not Found :("; |
| |
| // We can't fetch the URLs before they're set. |
| // Note: this does not crash because we are using NullMessageHandler. |
| TestFetchFail(url1); |
| TestFetchFail(url2); |
| |
| // Set the responses. |
| fetcher_.SetResponse(url1, header1, body1); |
| fetcher_.SetResponse(url2, header2, body2); |
| |
| // Now we can fetch the correct URLs |
| TestResponse(url1, header1, body1); |
| TestResponse(url2, header2, body2); |
| |
| // Check that we can fetch the same URL multiple times. |
| TestResponse(url1, header1, body1); |
| |
| |
| // Check that fetches fail after disabling the fetcher_. |
| fetcher_.Disable(); |
| TestFetchFail(url1); |
| // And then work again when re-enabled. |
| fetcher_.Enable(); |
| TestResponse(url1, header1, body1); |
| |
| |
| // Change the response. Test both editability and memory management. |
| fetcher_.SetResponse(url1, header2, body2); |
| |
| // Check that we get the new response. |
| TestResponse(url1, header2, body2); |
| |
| // Change it back. |
| fetcher_.SetResponse(url1, header1, body1); |
| TestResponse(url1, header1, body1); |
| } |
| |
| TEST_F(MockUrlFetcherTest, ConditionalFetchTest) { |
| const char url[] = "http://www.example.com/successs.html"; |
| ResponseHeaders header; |
| header.set_first_line(1, 1, 200, "OK"); |
| // TODO(sligocki): Perhaps set Last-Modified header... |
| const char body[] = "This website loaded :)"; |
| |
| int64 old_time = 1000; |
| int64 last_modified_time = 2000; |
| int64 new_time = 3000; |
| GoogleString old_time_string, now_string, new_time_string; |
| ASSERT_TRUE(ConvertTimeToString(old_time, &old_time_string)); |
| ASSERT_TRUE(ConvertTimeToString(last_modified_time, &now_string)); |
| ASSERT_TRUE(ConvertTimeToString(new_time, &new_time_string)); |
| |
| fetcher_.SetConditionalResponse(url, last_modified_time, "", header, body); |
| |
| // Response is normal to an un-conditional GET. |
| TestResponse(url, header, body); |
| |
| // Also normal for conditional GET with old time. |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| fetch.request_headers_.Add(HttpAttributes::kIfModifiedSince, |
| old_time_string); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| EXPECT_EQ(header.ToString(), fetch.response_headers_.ToString()); |
| EXPECT_EQ(body, fetch.response_body_); |
| } |
| |
| // But conditional GET with current time gets 304 Not Modified response. |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| fetch.request_headers_.Add(HttpAttributes::kIfModifiedSince, |
| now_string); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| EXPECT_EQ(HttpStatus::kNotModified, fetch.response_headers_.status_code()); |
| } |
| |
| // Future time also gets 304 Not Modified. |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| fetch.request_headers_.Add(HttpAttributes::kIfModifiedSince, |
| new_time_string); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| EXPECT_EQ(HttpStatus::kNotModified, fetch.response_headers_.status_code()); |
| } |
| } |
| |
| TEST_F(MockUrlFetcherTest, ConditionalFetchWithEtagsTest) { |
| const char url[] = "http://www.example.com/successs.html"; |
| const char etag[] = "etag"; |
| ResponseHeaders header; |
| header.set_first_line(1, 1, 200, "OK"); |
| const char body[] = "This website loaded :)"; |
| |
| fetcher_.SetConditionalResponse(url, -1, etag, header, body); |
| |
| // Response is normal to an un-conditional GET. |
| TestResponse(url, header, body); |
| |
| // Also normal for conditional GET with wrong etag. |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| fetch.request_headers_.Add(HttpAttributes::kIfNoneMatch, "blah"); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| EXPECT_EQ(header.ToString(), fetch.response_headers_.ToString()); |
| EXPECT_EQ(body, fetch.response_body_); |
| } |
| |
| // But conditional GET with correct etag gets 304 Not Modified response. |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| fetch.request_headers_.Add(HttpAttributes::kIfNoneMatch, etag); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| EXPECT_EQ(HttpStatus::kNotModified, fetch.response_headers_.status_code()); |
| } |
| } |
| |
| TEST_F(MockUrlFetcherTest, UpdateHeaderDates) { |
| MockTimer timer(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms); |
| fetcher_.set_timer(&timer); |
| fetcher_.set_update_date_headers(true); |
| |
| const char url[] = "http://www.example.com/foo.css"; |
| const char body[] = "resource body"; |
| ResponseHeaders header; |
| header.set_first_line(1, 1, 200, "OK"); |
| header.SetLastModified(MockTimer::kApr_5_2010_ms - 2 * Timer::kDayMs); |
| const int64 ttl_ms = 5 * Timer::kMinuteMs; |
| header.SetDateAndCaching(timer.NowMs(), ttl_ms); |
| |
| fetcher_.SetResponse(url, header, body); |
| |
| // Fetch it at current time. |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| // Check that response header's expiration time is set correctly. |
| EXPECT_EQ(timer.NowMs() + ttl_ms, |
| fetch.response_headers_.CacheExpirationTimeMs()); |
| } |
| |
| // Fetch it at current time. |
| timer.AdvanceMs(1 * Timer::kYearMs); // Arbitrary time > 5min (max-age). |
| { |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| EXPECT_TRUE(fetch.Fetch(url)); |
| // Check that response header's expiration time is set correctly. |
| EXPECT_EQ(timer.NowMs() + ttl_ms, |
| fetch.response_headers_.CacheExpirationTimeMs()); |
| } |
| } |
| |
| TEST_F(MockUrlFetcherTest, FailAfterBody) { |
| const char kUrl[] = "http://www.example.com/foo.css"; |
| ResponseHeaders response_headers; |
| response_headers.SetStatusAndReason(HttpStatus::kOK); |
| RequestHeaders request_headres; |
| fetcher_.SetResponse(kUrl, response_headers, "hello"); |
| fetcher_.SetResponseFailure(kUrl); |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| EXPECT_FALSE(fetch.Fetch(kUrl)); |
| EXPECT_EQ("hello", fetch.response_body_); |
| } |
| |
| TEST_F(MockUrlFetcherTest, ErrorMessage) { |
| const char kError[] = "404 Sad Robot"; |
| fetcher_.set_fail_on_unexpected(false); |
| fetcher_.set_error_message(kError); |
| RequestHeaders request_headres; |
| MockFetchContainer fetch(&fetcher_, thread_system_.get()); |
| EXPECT_FALSE(fetch.Fetch("http://example.com/404")); |
| EXPECT_EQ(kError, fetch.response_body_); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |