| /* |
| * 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: sligocki@google.com (Shawn Ligocki) |
| |
| #include "net/instaweb/rewriter/public/association_transformer.h" |
| |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/rewriter/public/css_url_counter.h" |
| #include "net/instaweb/rewriter/public/resource.h" |
| #include "net/instaweb/rewriter/public/resource_slot.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/null_message_handler.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/string_writer.h" |
| #include "pagespeed/kernel/base/thread_system.h" |
| #include "pagespeed/kernel/http/google_url.h" |
| #include "pagespeed/kernel/util/platform.h" |
| |
| namespace net_instaweb { |
| |
| // Outside of anonymous namespace to support friend declaration. |
| class DummyResource : public Resource { |
| public: |
| DummyResource() : Resource() {} |
| virtual ~DummyResource() {} |
| |
| void set_url(const StringPiece& url) { |
| url_ = url.as_string(); |
| } |
| virtual GoogleString url() const { return url_; } |
| |
| virtual void LoadAndCallback(NotCacheablePolicy not_cacheable_policy, |
| const RequestContextPtr& request_context, |
| AsyncCallback* callback) { |
| callback->Done(false, false); |
| } |
| |
| virtual bool UseHttpCache() const { return false; } |
| |
| private: |
| GoogleString url_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DummyResource); |
| }; |
| |
| namespace { |
| |
| class DummyTransformer : public CssTagScanner::Transformer { |
| public: |
| DummyTransformer() {} |
| virtual ~DummyTransformer() {} |
| |
| virtual TransformStatus Transform(GoogleString* str) { |
| *str = StrCat("Dummy:", *str); |
| return kSuccess; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DummyTransformer); |
| }; |
| |
| } // namespace |
| |
| class AssociationTransformerTest : public ::testing::Test { |
| protected: |
| AssociationTransformerTest() |
| : thread_system_(Platform::CreateThreadSystem()) { |
| RewriteOptions::Initialize(); |
| options_.reset(new RewriteOptions(thread_system_.get())); |
| options_->ComputeSignature(); |
| } |
| |
| ~AssociationTransformerTest() { |
| RewriteOptions::Terminate(); |
| } |
| |
| template <class T> |
| void ExpectValue(const std::map<GoogleString, T>& map, |
| const StringPiece& key, const T& expected_value) { |
| typename std::map<GoogleString, T>::const_iterator iter = |
| map.find(key.as_string()); |
| ASSERT_NE(map.end(), iter) << "map does not have key " << key; |
| EXPECT_EQ(expected_value, iter->second) |
| << "map[\"" << key << "\"] not as expected"; |
| } |
| |
| scoped_ptr<ThreadSystem> thread_system_; |
| scoped_ptr<RewriteOptions> options_; |
| }; |
| |
| TEST_F(AssociationTransformerTest, TransformsCorrectly) { |
| const char css_template[] = |
| "blah fwe.fwei ofe w {{{ " |
| "url('%s') fafwe" |
| "@import '%s';829hqbr23b" |
| "url()" // Empty URLs are left alone. |
| "url(%s)" |
| "url(%s)" |
| "url(%s)"; |
| const GoogleString css_before = StringPrintf( |
| css_template, "image.gif", "before.css", "http://example.com/before.css", |
| "http://other.org/foo.ttf", "data:text/plain,Foobar"); |
| |
| GoogleUrl base_url("http://example.com/"); |
| NullMessageHandler handler; |
| CssUrlCounter url_counter(&base_url, &handler); |
| DummyTransformer backup_trans; |
| AssociationTransformer trans(&base_url, options_.get(), &backup_trans, |
| &handler); |
| |
| // Run first pass. |
| EXPECT_TRUE(url_counter.Count(css_before)); |
| |
| // Check that 1 URL was discovered and absolutified correctly. |
| EXPECT_EQ(4, url_counter.url_counts().size()); |
| ExpectValue(url_counter.url_counts(), "http://example.com/image.gif", 1); |
| ExpectValue(url_counter.url_counts(), "http://example.com/before.css", 2); |
| ExpectValue(url_counter.url_counts(), "http://other.org/foo.ttf", 1); |
| ExpectValue(url_counter.url_counts(), "data:text/plain,Foobar", 1); |
| |
| // Provide URL association. |
| DummyResource* resource = new DummyResource; |
| ResourcePtr resource_ptr(resource); |
| ResourceSlotPtr slot(new AssociationSlot( |
| resource_ptr, trans.map(), "http://example.com/before.css")); |
| resource->set_url("http://example.com/after.css"); |
| slot->Render(); |
| |
| // Check that the association was registered. |
| EXPECT_EQ(1, trans.map()->size()); |
| ExpectValue<GoogleString>(*trans.map(), "http://example.com/before.css", |
| "http://example.com/after.css"); |
| |
| // Run second pass. |
| GoogleString out; |
| StringWriter out_writer(&out); |
| EXPECT_TRUE(CssTagScanner::TransformUrls(css_before, &out_writer, &trans, |
| &handler)); |
| |
| // Check that contents was rewritten correctly. |
| const GoogleString css_after = StringPrintf( |
| css_template, |
| // image.gif did not have an association set, so it was passed to |
| // DummyTransformer. |
| "Dummy:image.gif", |
| // before.css was rewritten in both places to after.css. |
| // The first one stays relative and the second stays absolute. |
| "after.css", |
| "http://example.com/after.css", |
| // Passed through DummyTransformer. |
| "Dummy:http://other.org/foo.ttf", |
| "Dummy:data:text/plain,Foobar"); |
| EXPECT_EQ(css_after, out); |
| } |
| |
| TEST_F(AssociationTransformerTest, FailsOnInvalidUrl) { |
| const char css_before[] = "url(////)"; |
| |
| GoogleUrl base_url("http://example.com/"); |
| DummyTransformer backup_trans; |
| NullMessageHandler handler; |
| AssociationTransformer trans(&base_url, options_.get(), &backup_trans, |
| &handler); |
| |
| // Transform fails because there is an invalid URL. |
| GoogleString out; |
| StringWriter out_writer(&out); |
| EXPECT_FALSE(CssTagScanner::TransformUrls(css_before, &out_writer, &trans, |
| &handler)); |
| } |
| |
| } // namespace net_instaweb |