blob: 69723c74fbea6bff20e6502ee48d8fc854dc6903 [file] [log] [blame]
/*
* 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