require File.expand_path(File.dirname(__FILE__) + '/spec_helper') describe "Rack::Gist" do before(:all) do @gist_id = 348301 end def pbody(body) ''.tap do |b| body.each { |part| b << part.to_s } # For giggles end end def app(headers) lambda do |env| body = File.read(File.join(File.dirname(__FILE__), "body-#{env['PATH_INFO'].gsub(/[^\w]/, '')}.html")) rescue '' status = 404 if body.empty? [status || 200, headers, [body]] end end def mock_env(path = '/full') Rack::MockRequest.env_for(path) end def middleware(options = {}, headers = { 'Content-Type' => 'text/html' }) Rack::Gist.new(app(headers), options) end it 'should pass Rack::Lint' do status, headers, body = Rack::Lint.new(middleware).call(mock_env) status, headers, body = Rack::Lint.new(middleware).call(mock_env('/multiple')) status, headers, body = Rack::Lint.new(middleware).call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) end it 'should rewrite gist embed tags for full gists' do middleware.tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should have_html_tag('p').with('id' => "rack-gist-#{@gist_id}", 'gist-id' => @gist_id, 'class' => 'rack-gist') end end it 'should rewrite gist embed tags for full gists when content_type includes other things' do middleware({}, { 'Content-Type' => 'text/html; charset=utf-8' }).tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html; charset=utf-8' pbody(body).should have_html_tag('p').with('id' => "rack-gist-#{@gist_id}", 'gist-id' => @gist_id, 'class' => 'rack-gist') end end it 'should rewrite gist embed tags for partial gists' do middleware.tap do |a| status, headers, body = a.call(mock_env('/partial')) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should have_html_tag('p').with('id' => "rack-gist-#{@gist_id}", 'gist-id' => @gist_id, 'class' => 'rack-gist', 'rack-gist-file' => 'example.pig') end end it 'should not include the github css file if no gists are present' do middleware.tap do |a| status, headers, body = a.call(mock_env('/none')) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should_not have_html_tag('link').with('rel' => 'stylesheet', 'href' => 'http://gist.github.com/stylesheets/gist/embed.css') end end it 'should include the github css file once' do middleware.tap do |a| status, headers, body = a.call(mock_env('/multiple')) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should have_html_tag('link').with('rel' => 'stylesheet', 'href' => 'https://gist.github.com/stylesheets/gist/embed.css') end end it 'should include jquery by default' do middleware.tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should have_html_tag('script[@src*="jquery"]') end end it 'should not include jquery if the option is passed to disable it' do middleware(:jquery => false).tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should_not have_html_tag('script[@src*="jquery"]') end end it 'should include required jquery helper' do middleware(:jquery => false).tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should have_html_tag('script').containing('CDATA') end end it "shouldn't include the jquery helper if no gist is present" do middleware.tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html' pbody(body).should_not have_html_tag('script') end end it 'should update Content-Length' do middleware.tap do |a| status, headers, body = a.call(mock_env) status.should == 200 headers['Content-Type'].should == 'text/html' headers['Content-Length'].should == '1144' end end it 'should proxy/serve single gists' do middleware.tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) status.should == 200 headers['Content-Type'].should == 'application/javascript' pbody(body).should == %q{$('#rack-gist-%s[rack-gist-file="example.pig"]').replaceWith('
\n \n \n \n \n\n
\n
\n \n \n \n
REGISTER com.darkhax.blog.pig.jar;<\/div>
DEFINE Parser com.darkhax.blog.pig.LogParser();<\/div>

<\/div>
logs = LOAD \'apache.log.bz2\' USING TextLoader AS (line: chararray);<\/div>
log_events = FOREACH logs GENERATE FLATTEN(Parser(line));<\/div>

<\/div>
by_action = GROUP log_events BY action;<\/div>
counts = FOREACH by_action GENERATE group, COUNT(log_events);<\/div>
STORE counts INTO \'count_summary\';<\/div><\/pre><\/div>\n \n <\/div>\n\n
\n view raw<\/a>\n example.pig<\/a>\n This Gist<\/a> brought to you by GitHub<\/a>.\n <\/div>\n <\/div>\n \n \n \n<\/div>\n')} % @gist_id end end it 'should proxy/server multiple gists' do middleware.tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}.js")) status.should == 200 headers['Content-Type'].should == 'application/javascript' pbody(body).should == %q{$('#rack-gist-%s').replaceWith('
\n \n \n \n \n\n
\n
\n \n \n \n
REGISTER com.darkhax.blog.pig.jar;<\/div>
DEFINE Parser com.darkhax.blog.pig.LogParser();<\/div>

<\/div>
logs = LOAD \'apache.log.bz2\' USING TextLoader AS (line: chararray);<\/div>
log_events = FOREACH logs GENERATE FLATTEN(Parser(line));<\/div>

<\/div>
by_action = GROUP log_events BY action;<\/div>
counts = FOREACH by_action GENERATE group, COUNT(log_events);<\/div>
STORE counts INTO \'count_summary\';<\/div><\/pre><\/div>\n \n <\/div>\n\n
\n view raw<\/a>\n example.pig<\/a>\n This Gist<\/a> brought to you by GitHub<\/a>.\n <\/div>\n <\/div>\n \n \n\n
\n
\n \n \n \n
package<\/span> com<\/span>.<\/span>codebaby<\/span>.<\/span>monitor<\/span>.<\/span>pig<\/span>;<\/span><\/div>

<\/div>
import<\/span> java.io.IOException<\/span>;<\/span><\/div>

<\/div>
import<\/span> org.apache.pig.EvalFunc<\/span>;<\/span><\/div>
import<\/span> org.apache.pig.data.DataType<\/span>;<\/span><\/div>
import<\/span> org.apache.pig.data.Tuple<\/span>;<\/span><\/div>
import<\/span> org.apache.pig.data.TupleFactory<\/span>;<\/span><\/div>
import<\/span> org.apache.pig.impl.logicalLayer.schema.Schema<\/span>;<\/span><\/div>

<\/div>
// Inherit from EvalFunc<Tuple> to implement a EvalFunc that returns a Tuple<\/span><\/div>
public<\/span> class<\/span> LogParser<\/span> extends<\/span> EvalFunc<\/span><<\/span>Tuple<\/span>><\/span> {<\/span><\/div>

<\/div>
    // The main method in question. Gets run for every 'thing' that gets sent to<\/span><\/div>
    // this UDF<\/span><\/div>
    public<\/span> Tuple<\/span> exec<\/span>(<\/span>Tuple<\/span> input<\/span>)<\/span> throws<\/span> IOException<\/span> {<\/span><\/div>
        if<\/span> (<\/span>null<\/span> ==<\/span> input<\/span> ||<\/span> input<\/span>.<\/span>size<\/span>()<\/span> !=<\/span> 1<\/span>)<\/span> {<\/span><\/div>
            return<\/span> null<\/span>;<\/span><\/div>
        }<\/span><\/div>
        <\/div>
        String<\/span> line<\/span> =<\/span> (<\/span>String<\/span>)<\/span> input<\/span>.<\/span>get<\/span>(<\/span>0<\/span>);<\/span><\/div>
        try<\/span> {<\/span><\/div>
            // In Soviet Russia, factory builds you!<\/span><\/div>
            TupleFactory<\/span> tf<\/span> =<\/span> TupleFactory<\/span>.<\/span>getInstance<\/span>();<\/span><\/div>
            Tuple<\/span> t<\/span> =<\/span> tf<\/span>.<\/span>newTuple<\/span>();<\/span><\/div>

<\/div>
            t<\/span>.<\/span>append<\/span>(<\/span>getHttpMethod<\/span>());<\/span><\/div>
            t<\/span>.<\/span>append<\/span>(<\/span>getIP<\/span>());<\/span><\/div>
            t<\/span>.<\/span>append<\/span>(<\/span>getDate<\/span>());<\/span><\/div>

<\/div>
            // The tuple we are returning now has 3 elements, all strings.<\/span><\/div>
            // In order, they are the HTTP method, the IP address, and the date.<\/span><\/div>

<\/div>
            return<\/span> t<\/span>;<\/span><\/div>
        }<\/span> catch<\/span> (<\/span>Exception<\/span> e<\/span>)<\/span> {<\/span><\/div>
            // Any problems? Just return null and this one doesn't get<\/span><\/div>
            // 'generated' by pig<\/span><\/div>
            return<\/span> null<\/span>;<\/span><\/div>
        }<\/span><\/div>
    }<\/span><\/div>

<\/div>
    public<\/span> Schema<\/span> outputSchema<\/span>(<\/span>Schema<\/span> input<\/span>)<\/span> {<\/span><\/div>
        try<\/span> {<\/span><\/div>
            Schema<\/span> s<\/span> =<\/span> new<\/span> Schema<\/span>();<\/span><\/div>

<\/div>
            s<\/span>.<\/span>add<\/span>(<\/span>new<\/span> Schema<\/span>.<\/span>FieldSchema<\/span>(<\/span>\"action\"<\/span>,<\/span> DataType<\/span>.<\/span>CHARARRAY<\/span>));<\/span><\/div>
            s<\/span>.<\/span>add<\/span>(<\/span>new<\/span> Schema<\/span>.<\/span>FieldSchema<\/span>(<\/span>\"ip\"<\/span>,<\/span> DataType<\/span>.<\/span>CHARARRAY<\/span>));<\/span><\/div>
            s<\/span>.<\/span>add<\/span>(<\/span>new<\/span> Schema<\/span>.<\/span>FieldSchema<\/span>(<\/span>\"date\"<\/span>,<\/span> DataType<\/span>.<\/span>CHARARRAY<\/span>));<\/span><\/div>

<\/div>
            return<\/span> s<\/span>;<\/span><\/div>
        }<\/span> catch<\/span> (<\/span>Exception<\/span> e<\/span>)<\/span> {<\/span><\/div>
            // Any problems? Just return null...there probably won't be any<\/span><\/div>
            // problems though.<\/span><\/div>
            return<\/span> null<\/span>;<\/span><\/div>
        }<\/span><\/div>
    }<\/span><\/div>

<\/div>
    public<\/span> String<\/span> getHttpMethod<\/span>()<\/span> {<\/span><\/div>
        return<\/span> \"\"<\/span>;<\/span><\/div>
    }<\/span><\/div>

<\/div>
    public<\/span> String<\/span> getIP<\/span>()<\/span> {<\/span><\/div>
        return<\/span> \"\"<\/span>;<\/span><\/div>
    }<\/span><\/div>

<\/div>
    public<\/span> String<\/span> getDate<\/span>()<\/span> {<\/span><\/div>
        return<\/span> \"\"<\/span>;<\/span><\/div>
    }<\/span><\/div>
}<\/span><\/div>

<\/div><\/pre><\/div>\n \n <\/div>\n\n
\n view raw<\/a>\n LogParser.java<\/a>\n This Gist<\/a> brought to you by GitHub<\/a>.\n <\/div>\n <\/div>\n \n \n<\/div>\n')} % @gist_id end end it 'should cache things if given a cache' do cache = ActiveSupport::Cache::MemoryStore.new middleware(:cache => cache).tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) status.should == 200 headers['Content-Type'].should == 'application/javascript' RestClient.should_not_receive(:get) cache.should_receive(:fetch).once.with("rack-gist:#{@gist_id}:example.pig", :expires_in => 3600).and_return(pbody(body)) # 1.hour status, headers, body2 = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) status.should == 200 headers['Content-Type'].should == 'application/javascript' pbody(body).should == pbody(body2) end end it 'should cache things for a different time if given cache_time' do cache = ActiveSupport::Cache::MemoryStore.new middleware(:cache => cache, :cache_time => 60).tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) status.should == 200 headers['Content-Type'].should == 'application/javascript' RestClient.should_not_receive(:get) cache.should_receive(:fetch).once.with("rack-gist:#{@gist_id}:example.pig", :expires_in => 60).and_return(pbody(body)) # 1.hour status, headers, body2 = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) status.should == 200 headers['Content-Type'].should == 'application/javascript' pbody(body).should == pbody(body2) end end it 'should not explode if not given a gist' do middleware.tap do |a| status, headers, body = a.call(mock_env('/gist.github.com')) status.should == 404 end end it 'should set http caching headers by default' do middleware.tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) headers['Vary'].should == 'Accept-Encoding' headers['Cache-Control'].should == 'public, must-revalidate, max-age=3600' # 1.hour end end it 'should be able to configure cache time' do middleware(:http_cache_time => 60).tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) headers['Vary'].should == 'Accept-Encoding' headers['Cache-Control'].should == 'public, must-revalidate, max-age=60' # 1.hour end end it 'should be able to disable caching by setting http_cache_time to nil' do middleware(:http_cache_time => nil).tap do |a| status, headers, body = a.call(mock_env("/gist.github.com/#{@gist_id}/example.pig.js")) headers['Vary'].should == 'Accept-Encoding' headers['Cache-Control'].should be_nil end end it 'should encode to the correct content type' do middleware.tap do |a| status, headers, body = a.call(mock_env) pbody(body).should match('utf-8') end middleware(:encoding => 'US-ASCII').tap do |a| status, headers, body = a.call(mock_env) pbody(body).should match('US-ASCII') end end end