reevoomark-ruby-api 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ M2NiODc2MzQyMjljMDQxMTFlMzAxNjlkZGVhNmU1ZDFiM2EyZjM4OA==
5
+ data.tar.gz: !binary |-
6
+ MTI5YmQzOWFlZTRiNTUzMzA3YWY0YmM4N2NjYTk4MGJlODI0NTk4NQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ ZjE5N2IxNDJkMTAzMjE2ZWRkNDUzMTUxMDY1MWFiZTNhMjIyMWMyOTRmYTk4
10
+ ZTI4YTRmYjViYTFkMzA3NjM1NDNjZDNmZDA5MTFmMTQ1MWIzZThiZGQxZjg3
11
+ YTRiZTYyZDI1ZDEyNDIwY2JkNjIwNmRlYzQ1N2IyNTUxOTQ3MDQ=
12
+ data.tar.gz: !binary |-
13
+ ZjNhYzhhODZkN2Y5NTYwMTY4Zjc1Y2RmMTg5YmYyM2FkNzk1YTQ1OWMzZjk1
14
+ ZTE3OTE3MTMzYjQzMGM4MDE4ZjMzZDU3NmIzZjg2MWExYzAzNzlkOWEyMzg5
15
+ NWM4ODdhYzE1MDQ4ZjE1NjVmN2E2YjgxYjU5NGYwMGJlZTJjNzE=
data/lib/reevoomark.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'fileutils'
2
+ require 'uri'
3
+ require 'httpclient'
4
+ require 'digest/md5'
5
+
6
+
7
+ # Usage:
8
+ #
9
+ # # Somewhere in you application config, build a client.
10
+ # $reevoomark_client = ReevooMark.create_client(
11
+ # Rails.root.join("tmp/reevoo_cache"),
12
+ # "http://mark.reevoo.com/reevoomark/embeddable_reviews.html"
13
+ # )
14
+ #
15
+ # # In your controller (assuming @entry.sku is your product SKU):
16
+ # @reevoo_reviews = $reevoomark_client.fetch('YOUR TRKREF', @entry.sku)
17
+ #
18
+ # # In your view:
19
+ # <%= @reevoo_reviews.body %>
20
+
21
+ module ReevooMark
22
+ # Legacy API.
23
+ # Creates a new client every time, so considered bad for business.
24
+ def self.new(cache_dir, url, trkref, sku)
25
+ create_client(cache_dir, url).fetch(trkref, sku)
26
+ end
27
+
28
+ # Creates a new client.
29
+ def self.create_client(cache_dir, base_url, options = {})
30
+ cache = ReevooMark::Cache.new(cache_dir)
31
+ fetcher = ReevooMark::Fetcher.new(options[:timeout] || 1)
32
+ ReevooMark::Client.new(cache, fetcher, base_url)
33
+ end
34
+ end
35
+
36
+ require 'reevoomark/version'
37
+ require 'reevoomark/document'
38
+ require 'reevoomark/document/factory'
39
+ require 'reevoomark/client'
40
+ require 'reevoomark/fetcher'
41
+ require 'reevoomark/cache'
42
+ require 'reevoomark/cache/entry'
@@ -0,0 +1,32 @@
1
+ class ReevooMark::Cache
2
+ # Create a new cache repository, storing it's cache in the given dir.
3
+ def initialize(cache_dir)
4
+ FileUtils.mkdir_p(cache_dir) unless File.exist?(cache_dir)
5
+ @cache_dir = cache_dir
6
+ end
7
+
8
+ # Fetch the cache entry, don't worry if it's expired.
9
+ def fetch_expired(remote_url, options = {})
10
+ entry = entry_for(remote_url)
11
+ if entry.exists?
12
+ entry.revalidate_for(options[:revalidate_for])
13
+ entry.document
14
+ end
15
+ end
16
+
17
+ # Fetch an unexpired cached document, or store the result of the block.
18
+ def fetch(remote_url, &fetcher)
19
+ entry = entry_for(remote_url)
20
+ if entry.valid?
21
+ entry.document
22
+ else
23
+ entry.document = fetcher.call
24
+ end
25
+ end
26
+
27
+ protected
28
+ def entry_for(remote_url)
29
+ digest = Digest::MD5.hexdigest(remote_url)
30
+ Entry.new("#{@cache_dir}/#{digest}.cache")
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ class ReevooMark::Cache::Entry
2
+ attr_reader :cache_path
3
+
4
+ def initialize(cache_path)
5
+ @cache_path = Pathname.new(cache_path)
6
+ end
7
+
8
+ def exists?
9
+ @cache_path.exist?
10
+ end
11
+
12
+ def expired?
13
+ document.expired?
14
+ end
15
+
16
+ def valid?
17
+ exists? and not expired?
18
+ end
19
+
20
+ def document
21
+ raise "Loading from cache, where no cache exists is bad." unless exists?
22
+ @document ||= YAML.load(read)
23
+ end
24
+
25
+ def document= doc
26
+ @document = nil # Flush the memoized value
27
+ write doc.to_yaml
28
+ doc
29
+ end
30
+
31
+ def revalidate_for(max_age)
32
+ if exists?
33
+ self.document = document.revalidated_for(max_age)
34
+ end
35
+ end
36
+
37
+ protected
38
+ def m_time
39
+ cache_path.mtime if exists?
40
+ end
41
+
42
+ def write(data)
43
+ cache_path.open('w'){ |f| f.puts data }
44
+ end
45
+
46
+ def read
47
+ cache_path.read if exists?
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ class ReevooMark::Client
2
+ DEFAULT_URL = 'http://mark.reevoo.com/reevoomark/embeddable_reviews.html'
3
+
4
+ def initialize(cache, fetcher, url = DEFAULT_URL)
5
+ @cache, @fetcher, @url = cache, fetcher, url
6
+ end
7
+
8
+ def fetch(trkref, sku)
9
+ remote_url = url_for(trkref, sku)
10
+ @cache.fetch(remote_url){ remote_fetch(remote_url) }
11
+ end
12
+
13
+ protected
14
+
15
+ def remote_fetch(remote_url)
16
+ document = @fetcher.fetch(remote_url)
17
+ if document.status_code < 500
18
+ document
19
+ else
20
+ @cache.fetch_expired(remote_url, :revalidate_for => 300) || document
21
+ end
22
+ end
23
+
24
+ def url_for(trkref, sku)
25
+ sep = (@url =~ /\?/) ? "&" : "?"
26
+ "#{@url}#{sep}sku=#{sku}&retailer=#{trkref}"
27
+ end
28
+
29
+ end
@@ -0,0 +1,90 @@
1
+ class ReevooMark::Document
2
+ attr_reader :time, :status_code, :age, :max_age, :counts
3
+
4
+ def self.from_response(response)
5
+ ReevooMark::Document::Factory.from_response(response)
6
+ end
7
+
8
+ def self.error
9
+ ReevooMark::Document::Factory.new_error_document
10
+ end
11
+
12
+ def initialize(time, max_age, age, status_code, body, counts)
13
+ @time, @max_age, @age = time, max_age, age
14
+ @status_code, @body = status_code, body
15
+ @counts = counts
16
+ end
17
+
18
+ def identity_values
19
+ [current_age(0), @content_values]
20
+ end
21
+
22
+ def content_values
23
+ [@status_code, @body, @counts]
24
+ end
25
+
26
+ def == other
27
+ identity_values == other.identity_values
28
+ end
29
+
30
+ def === other
31
+ content_values == other.content_values
32
+ end
33
+
34
+ def any?
35
+ review_count > 0
36
+ end
37
+
38
+ def review_count
39
+ @counts[:review_count]
40
+ end
41
+
42
+ def offer_count
43
+ @counts[:offer_count]
44
+ end
45
+
46
+ def conversation_count
47
+ @counts[:conversation_count]
48
+ end
49
+
50
+ def best_price
51
+ @counts[:best_price]
52
+ end
53
+
54
+ def is_valid?
55
+ status_code < 500
56
+ end
57
+
58
+ def body
59
+ if is_valid?
60
+ @body
61
+ else
62
+ ""
63
+ end
64
+ end
65
+
66
+ alias render body
67
+
68
+ def expired?(now = nil)
69
+ now ||= Time.now
70
+ max_age < current_age(now)
71
+ end
72
+
73
+ def revalidated_for(max_age)
74
+ ReevooMark::Document.new(
75
+ Time.now.to_i,
76
+ max_age || @max_age,
77
+ 0,
78
+ @status_code,
79
+ @body,
80
+ @counts
81
+ )
82
+ end
83
+
84
+ protected
85
+ def current_age(now = nil)
86
+ now ||= Time.now.to_i
87
+ now.to_i - (time.to_i - age.to_i)
88
+ end
89
+
90
+ end
@@ -0,0 +1,54 @@
1
+ module ReevooMark::Document::Factory
2
+ class HeaderSet < Hash
3
+ def initialize(hash)
4
+ hash.each do |k,v|
5
+ self[k] = v
6
+ end
7
+ end
8
+
9
+ def [] k
10
+ super(k.downcase)
11
+ end
12
+
13
+ def []= k,v
14
+ super(k.downcase, v)
15
+ end
16
+ end
17
+
18
+ HEADER_MAPPING = {
19
+ :review_count => 'X-Reevoo-ReviewCount',
20
+ :offer_count => 'X-Reevoo-OfferCount',
21
+ :conversation_count => 'X-Reevoo-ConversationCount',
22
+ :best_price => 'X-Reevoo-BestPrice'
23
+ }
24
+
25
+ # Factory method for building a document from a HTTP response.
26
+ def self.from_response(response)
27
+ headers = HeaderSet.new(response.headers)
28
+
29
+ counts = HEADER_MAPPING.inject(Hash.new(0)){ |acc, (name, header)|
30
+ acc.merge(name => headers[header].to_i)
31
+ }
32
+
33
+ if cache_header = headers['Cache-Control']
34
+ max_age = cache_header.match("max-age=([0-9]+)")[1].to_i
35
+ else
36
+ max_age = 300
37
+ end
38
+
39
+ age = headers['Age'].to_i
40
+
41
+ ReevooMark::Document.new(
42
+ Time.now,
43
+ max_age,
44
+ age,
45
+ response.status_code,
46
+ response.body,
47
+ counts
48
+ )
49
+ end
50
+
51
+ def self.new_error_document
52
+ ReevooMark::Document.new(Time.now, 300, 0, 599, "", {})
53
+ end
54
+ end
@@ -0,0 +1,43 @@
1
+ class ReevooMark::Fetcher
2
+ FetchError = Class.new(RuntimeError)
3
+
4
+ attr_reader :headers
5
+
6
+ def initialize(timeout)
7
+ @timeout = timeout
8
+ @http_client = HTTPClient.new
9
+ @http_client.connect_timeout = timeout
10
+ @headers = {
11
+ 'User-Agent' => "ReevooMark Ruby Widget/#{ReevooMark::VERSION}",
12
+ 'Referer' => "http://#{Socket::gethostname}"
13
+ }
14
+ end
15
+
16
+ def fetch(remote_url)
17
+ response = fetch_http(remote_url)
18
+ ReevooMark::Document.from_response(response)
19
+ rescue FetchError
20
+ ReevooMark::Document.error
21
+ end
22
+
23
+ protected
24
+
25
+ def log(message)
26
+ if defined? Rails
27
+ Rails.logger.debug message
28
+ else
29
+ STDERR.puts message
30
+ end
31
+ end
32
+
33
+ def fetch_http(remote_url)
34
+ log "ReevooMark::Fetcher: Fetching #{remote_url}"
35
+ Timeout.timeout(@timeout){
36
+ return @http_client.get(remote_url, nil, headers)
37
+ }
38
+ rescue => e
39
+ raise FetchError, "#{e.class} #{e.message}"
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,3 @@
1
+ module ReevooMark
2
+ VERSION="2.0.0"
3
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ReevooMark caching" do
4
+ before do
5
+ stub_request(:get, /.*example.*/).to_return(:body => "test")
6
+ end
7
+
8
+ context 'with an empty cache' do
9
+ it 'saves a valid fetched response to the cache file' do
10
+ ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")
11
+
12
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
13
+ File.open("tmp/cache/#{filename}.cache", 'r').read.should match(/test/)
14
+ end
15
+
16
+ it "saves a 404 response to the cache file" do
17
+ stub_request(:get, /.*example.*/).to_return(:body => "No content found", :status => 404)
18
+ ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")
19
+
20
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
21
+ File.open("tmp/cache/#{filename}.cache", 'r').read.should match(/No content found/)
22
+ end
23
+
24
+ it "saves a 500 response to the cache file" do
25
+ stub_request(:get, /.*example.*/).to_return(:body => "My face is on fire", :status => 500)
26
+ ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")
27
+
28
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
29
+ File.open("tmp/cache/#{filename}.cache", 'r').read.should match(/My face is on fire/)
30
+ end
31
+ end
32
+
33
+ context 'with a valid cache' do
34
+ before do
35
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
36
+ example = ReevooMark::Document.new(
37
+ Time.now.to_i,
38
+ 1,
39
+ 0,
40
+ 200,
41
+ "I'm a cache record.",
42
+ :review_count => 1,
43
+ :offer_count => 2,
44
+ :conversation_count => 3,
45
+ :best_price => 4
46
+ )
47
+
48
+ File.open("tmp/cache/#{filename}.cache", 'w') do |file|
49
+ file << example.to_yaml
50
+ end
51
+ end
52
+
53
+ subject {ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")}
54
+
55
+ it "does NOT make an http request" do
56
+ subject
57
+ WebMock.should_not have_requested(:get, "http://example.com/foo?sku=SKU123&retailer=PNY")
58
+ end
59
+
60
+ it "returns the cached response body" do
61
+ subject.review_count.should == 1
62
+ subject.offer_count.should == 2
63
+ subject.conversation_count.should == 3
64
+ subject.best_price.should == 4
65
+ subject.render.should == "I'm a cache record."
66
+ end
67
+ end
68
+
69
+ context 'with an expired cache' do
70
+ before do
71
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
72
+ example = ReevooMark::Document.new(
73
+ Time.now.to_i - 60*60,
74
+ 1,
75
+ 0,
76
+ 200,
77
+ "I'm a cache record.",
78
+ :review_count => 1,
79
+ :offer_count => 2,
80
+ :conversation_count => 3,
81
+ :best_price => 4
82
+ )
83
+
84
+ File.open("tmp/cache/#{filename}.cache", 'w') do |file|
85
+ file << example.to_yaml
86
+ end
87
+ end
88
+
89
+ subject {ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")}
90
+
91
+ it "makes an http request" do
92
+ subject
93
+ WebMock.should have_requested(:get, "http://example.com/foo?sku=SKU123&retailer=PNY")
94
+ end
95
+
96
+ context "and a functioning server" do
97
+ it "returns the response body" do
98
+ subject.render.should == "test"
99
+ end
100
+ it 'saves the fetched response to the cache file' do
101
+ subject
102
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
103
+ File.open("tmp/cache/#{filename}.cache", 'r').read.should match(/test/)
104
+ end
105
+ end
106
+
107
+ context "and an erroring server" do
108
+
109
+ before do
110
+ stub_request(:get, /.*example.*/).to_return(:body => "My face is on fire", :status => 500)
111
+ end
112
+
113
+ it "returns the cached response body" do
114
+ subject.render.should == "I'm a cache record."
115
+ end
116
+
117
+ it 'does not save the fetched response to the cache file' do
118
+ subject
119
+ filename = Digest::MD5.hexdigest("http://example.com/foo?sku=SKU123&retailer=PNY")
120
+ File.open("tmp/cache/#{filename}.cache", 'r').read.should match(/I'm a cache record/)
121
+ end
122
+ end
123
+
124
+ end
125
+
126
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReevooMark do
4
+
5
+ describe "a new ReevooMark instance" do
6
+ it "requires 4 arguments" do
7
+ lambda { ReevooMark.new }.should raise_exception ArgumentError
8
+ end
9
+
10
+ describe "the http request it makes" do
11
+ it "GETs the url with the trkref and sku" do
12
+ stub_request(:get, "http://example.com/foo?sku=SKU123&retailer=PNY").to_return(:body => "")
13
+ ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")
14
+ WebMock.should have_requested(:get, "http://example.com/foo?sku=SKU123&retailer=PNY")
15
+ end
16
+
17
+ it "copes fine with urls that already have query strings" do
18
+ stub_request(:get, "http://example.com/foo?bar=baz&sku=SKU123&retailer=PNY").to_return(:body => "")
19
+ ReevooMark.new("tmp/cache/", "http://example.com/foo?bar=baz", "PNY", "SKU123")
20
+ WebMock.should have_requested(:get, "http://example.com/foo?bar=baz&sku=SKU123&retailer=PNY")
21
+ end
22
+
23
+ it "passes the correct headers in the request" do
24
+ stub_request(:get, /.*example.*/).to_return(:body => "")
25
+ expected_headers_hash = {
26
+ 'User-Agent' => "ReevooMark Ruby Widget/#{ReevooMark::VERSION}",
27
+ 'Referer' => "http://#{Socket::gethostname}"
28
+ }
29
+
30
+ ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123")
31
+ WebMock.should have_requested(:get, /.*example.*/).with(:headers => expected_headers_hash)
32
+ end
33
+ end
34
+ end
35
+
36
+ context "with a new ReevooMark instance" do
37
+ before do
38
+ stub_request(:get, /.*example.*/).to_return(
39
+ :headers => {
40
+ "X-Reevoo-ReviewCount" => 12,
41
+ "X-Reevoo-OfferCount" => 9,
42
+ "X-Reevoo-ConversationCount" => 165,
43
+ "X-Reevoo-BestPrice" => 19986
44
+ },
45
+ :body => "test"
46
+ )
47
+ end
48
+ subject { ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123") }
49
+
50
+ it "parses the body" do
51
+ subject.render.should == "test"
52
+ end
53
+
54
+ it 'parses the headers' do
55
+ subject.review_count.should == 12
56
+ subject.offer_count.should == 9
57
+ subject.conversation_count.should == 165
58
+ subject.best_price.should == 19986
59
+ end
60
+ end
61
+
62
+ context "with a ReevooMark instance that failed to load due to server error" do
63
+
64
+ before do
65
+ stub_request(:get, /.*example.*/).to_return(:body => "Some sort of server error", :status => 500)
66
+ end
67
+ subject { ReevooMark.new("tmp/cache/", "http://example.com/foo", "PNY", "SKU123") }
68
+
69
+ describe "#render" do
70
+ it "returns a blank string" do
71
+ subject.render.should == ""
72
+ end
73
+ end
74
+
75
+ it 'returns zero for the counts' do
76
+ subject.review_count.should be_zero
77
+ subject.offer_count.should be_zero
78
+ subject.conversation_count.should be_zero
79
+ subject.best_price.should be_zero
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'pry'
5
+ require 'rspec'
6
+ require 'webmock/rspec'
7
+ require 'reevoomark'
8
+
9
+ # Dir["spec/support/**/*.rb"].each { |f| require File.expand_path(f) }
10
+
11
+ RSpec.configure do |config|
12
+ config.before do
13
+ FileUtils.rm Dir.glob('tmp/cache/*')
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+
3
+ describe ReevooMark::Cache do
4
+ subject{ ReevooMark::Cache.new("tmp/cache") }
5
+ let(:valid_doc){
6
+ ReevooMark::Document.new(Time.now.to_i, 100, 0, 200, "I am a document", {})
7
+ }
8
+ let(:expired_doc){
9
+ ReevooMark::Document.new(Time.now.to_i - 101, 100, 0, 200, "I am a document", {})
10
+ }
11
+
12
+ describe "#fetch" do
13
+
14
+ it "fetches valid documents" do
15
+ subject.fetch("foo"){ valid_doc } # Prime cache
16
+ doc = subject.fetch("foo"){ raise "Was not expecting to be run" }
17
+ doc.should == valid_doc
18
+ end
19
+
20
+ it "skips expired documents" do
21
+ subject.fetch("foo"){ expired_doc } # Prime cache
22
+ doc = subject.fetch("foo"){ valid_doc }
23
+ doc.should == valid_doc
24
+ doc = subject.fetch("foo"){ raise "Don't get here" }
25
+ doc.should == valid_doc
26
+ end
27
+
28
+ end
29
+
30
+ describe "fetch_expired" do
31
+ it "returns even an expires document" do
32
+ subject.fetch("foo"){ expired_doc } # Prime cache
33
+ revalidated = subject.fetch_expired("foo", :revalidate_for => 30)
34
+ revalidated.should === expired_doc
35
+ revalidated.should_not be_expired
36
+ end
37
+
38
+ it "returns nil if nothing was found" do
39
+ subject.fetch_expired('bar', :revalidate_for => 30).should be_nil
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ require "spec_helper"
2
+
3
+ describe ReevooMark::Client do
4
+ let(:valid_doc){
5
+ ReevooMark::Document.new(Time.now.to_i, 100, 0, 200, "I am a document", {})
6
+ }
7
+ let(:invalid_doc){
8
+ ReevooMark::Document.new(Time.now.to_i, 100, 0, 500, "I am a ndnsment", {})
9
+ }
10
+
11
+ let(:cache){ double(:cache) }
12
+ let(:fetcher){ double(:fetcher) }
13
+ let(:url){ "http://example.com/foo?bar=bum" }
14
+ subject{
15
+ ReevooMark::Client.new(cache, fetcher, url)
16
+ }
17
+
18
+ describe "#fetch" do
19
+ it "fetches from the cache first" do
20
+ cache.should_receive(:fetch).with(
21
+ "http://example.com/foo?bar=bum&sku=123&retailer=TST"
22
+ )
23
+
24
+ subject.fetch("TST", "123")
25
+ end
26
+
27
+ it "uses the remote_fetcher if the cache misses" do
28
+ def cache.fetch(remote_url, &block)
29
+ yield
30
+ end
31
+
32
+ fetcher.should_receive(:fetch).and_return valid_doc
33
+ subject.fetch("TST", "123").should == valid_doc
34
+ end
35
+
36
+
37
+ it "falls back to an existing cached document if response is an error" do
38
+ def cache.fetch(remote_url, &block)
39
+ yield
40
+ end
41
+ cache.should_receive(:fetch_expired).and_return(valid_doc)
42
+ fetcher.should_receive(:fetch).and_return invalid_doc
43
+ subject.fetch("TST", "123").should == valid_doc
44
+ end
45
+
46
+ it "if all we have is errors, let them eat errors" do
47
+ def cache.fetch(remote_url, &block)
48
+ yield
49
+ end
50
+ cache.should_receive(:fetch_expired).and_return(invalid_doc)
51
+ fetcher.should_receive(:fetch).and_return invalid_doc
52
+ subject.fetch("TST", "123").should == invalid_doc
53
+ end
54
+ end
55
+
56
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ describe ReevooMark::Document do
4
+ describe "#any?" do
5
+ it "returns true iff the review_count > 0" do
6
+ doc = ReevooMark::Document.new(
7
+ time = double(),
8
+ max_age = double(),
9
+ age = double(),
10
+ status_code = double(),
11
+ body = double(),
12
+ counts = {:review_count => 4}
13
+ )
14
+
15
+ doc.any?.should be_true
16
+ end
17
+
18
+ it "returns false iff the review_count <= 0" do
19
+ doc = ReevooMark::Document.new(
20
+ time = double(),
21
+ max_age = double(),
22
+ age = double(),
23
+ status_code = double(),
24
+ body = double(),
25
+ counts = {:review_count => 0}
26
+ )
27
+
28
+ doc.any?.should be_false
29
+ end
30
+ end
31
+
32
+ it "has some attributes it just parrots back" do
33
+ doc = ReevooMark::Document.new(
34
+ time = double(),
35
+ max_age = double(),
36
+ age = double(),
37
+ status_code = double(),
38
+ body = double(),
39
+ counts = double()
40
+ )
41
+
42
+ doc.time.should be time
43
+ doc.max_age.should be max_age
44
+ doc.age.should be age
45
+ doc.status_code.should be status_code
46
+ doc.counts.should be counts
47
+ end
48
+
49
+ describe '#body' do
50
+ it "returns blank if isn't valid" do
51
+ doc = ReevooMark::Document.new(
52
+ time = double(),
53
+ max_age = double(),
54
+ age = double(),
55
+ status_code = 500,
56
+ body = double(),
57
+ counts = double()
58
+ )
59
+
60
+ doc.body.should == ""
61
+ end
62
+
63
+ it "returns the body if it's valid" do
64
+ doc = ReevooMark::Document.new(
65
+ time = double(),
66
+ max_age = double(),
67
+ age = double(),
68
+ status_code = 200,
69
+ body = double(),
70
+ counts = double()
71
+ )
72
+
73
+ doc.body.should == body
74
+
75
+ doc = ReevooMark::Document.new(
76
+ time = double(),
77
+ max_age = double(),
78
+ age = double(),
79
+ status_code = 499,
80
+ body = double(),
81
+ counts = double()
82
+ )
83
+
84
+ doc.render.should == body
85
+ end
86
+
87
+ context "with a documnt" do
88
+ let(:doc){
89
+ ReevooMark::Document.new(
90
+ time = 10,
91
+ max_age = 10,
92
+ age = 5,
93
+ nil, nil, nil
94
+ )
95
+ }
96
+
97
+ describe '#has_expired?' do
98
+ it "is expired after the correct number of secconds" do
99
+ doc.should_not be_expired(1)
100
+ doc.should_not be_expired(6)
101
+ doc.should_not be_expired(11)
102
+ doc.should be_expired(16)
103
+ end
104
+ end
105
+
106
+ describe "#revalidated_for" do
107
+ it "creates a new document, valid for a given ammoiunt of time" do
108
+ doc.revalidated_for(1).should_not be_expired
109
+ doc.revalidated_for(-1).should be_expired
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,36 @@
1
+ require "spec_helper"
2
+
3
+ describe ReevooMark::Fetcher do
4
+ describe "#fetch" do
5
+
6
+ it "responds with a 5xx when the server is slow" do
7
+ fetcher = ReevooMark::Fetcher.new(1)
8
+ Timeout.should_receive(:timeout).and_raise(Timeout::Error)
9
+ document = fetcher.fetch("http://example.com/foo")
10
+ document.status_code.should == 599
11
+ end
12
+
13
+ it "responds with a 5xx when the server is broken" do
14
+ fetcher = ReevooMark::Fetcher.new(1)
15
+ HTTPClient.any_instance.should_receive(:get).and_raise(RuntimeError)
16
+ document = fetcher.fetch("http://example.com/foo")
17
+ document.status_code.should == 599
18
+ end
19
+
20
+ it "responds with a document when all is fine" do
21
+ fetcher = ReevooMark::Fetcher.new(1)
22
+ stub_request(:get, /.*example.*/).to_return(:body => "", :status => 200)
23
+ document = fetcher.fetch("http://example.com/foo")
24
+ document.status_code.should == 200
25
+ end
26
+
27
+ it "responds with a document when there is a 404" do
28
+ fetcher = ReevooMark::Fetcher.new(1)
29
+ stub_request(:get, /.*example.*/).to_return(:body => "foo", :status => 404)
30
+ document = fetcher.fetch("http://example.com/foo")
31
+ document.status_code.should == 404
32
+ document.body.should == "foo"
33
+ end
34
+
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reevoomark-ruby-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Reevoo Developers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2012-06-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httpclient
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Reevoo's ReevooMark & Traffic server-side ruby implementation. This API
28
+ is free to use but requires you to be a Reevoo customer.
29
+ email:
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/reevoomark/cache/entry.rb
35
+ - lib/reevoomark/cache.rb
36
+ - lib/reevoomark/client.rb
37
+ - lib/reevoomark/document/factory.rb
38
+ - lib/reevoomark/document.rb
39
+ - lib/reevoomark/fetcher.rb
40
+ - lib/reevoomark/version.rb
41
+ - lib/reevoomark.rb
42
+ - spec/acceptance/reevoomark_caching_spec.rb
43
+ - spec/acceptance/reevoomark_spec.rb
44
+ - spec/spec_helper.rb
45
+ - spec/unit/cache_spec.rb
46
+ - spec/unit/client_spec.rb
47
+ - spec/unit/document_spec.rb
48
+ - spec/unit/fetcher_spec.rb
49
+ homepage: http://www.reevoo.com
50
+ licenses: []
51
+ metadata: {}
52
+ post_install_message:
53
+ rdoc_options: []
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 2.0.3
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Implement ReevooMark on your ruby-based website.
72
+ test_files: []