reevoomark-ruby-api 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []