cachebar 1.1.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.
@@ -0,0 +1,70 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'mocha'
12
+ require 'shoulda'
13
+ require 'vcr'
14
+ require 'active_support/testing/assertions'
15
+
16
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
17
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
18
+ require 'cachebar'
19
+ require 'twitter_api'
20
+ require 'redis-namespace'
21
+
22
+ VCR.config do |c|
23
+ c.cassette_library_dir = 'test/fixtures/vcr_cassettes'
24
+ c.stub_with :webmock
25
+ c.default_cassette_options = { :record => :none }
26
+ c.allow_http_connections_when_no_cassette = false
27
+ end
28
+
29
+ HTTParty::HTTPCache.perform_caching = true
30
+
31
+ class Test::Unit::TestCase
32
+ include ActiveSupport::Testing::Assertions
33
+
34
+ def turn_off_caching
35
+ HTTParty::HTTPCache.perform_caching = false
36
+ end
37
+
38
+ def turn_on_caching
39
+ HTTParty::HTTPCache.perform_caching = true
40
+ end
41
+
42
+ def fixture_file(name)
43
+ File.read(File.join(File.dirname(__FILE__), 'fixtures', name))
44
+ end
45
+
46
+ def mock_response_in_backup(hash)
47
+ @redis.stubs(:exists).with("api-cache:twitter:#{hash}").returns(false)
48
+ @redis.stubs(:exists).with("api-cache:twitter").returns(true)
49
+ @redis.stubs(:hexists).with("api-cache:twitter", hash).returns(true)
50
+ @redis.expects(:hget).with('api-cache:twitter', hash).returns(@response)
51
+ @redis.expects(:set).with("api-cache:twitter:#{hash}", @response).returns(true)
52
+ @redis.expects(:expire).with("api-cache:twitter:#{hash}", 300).returns(true)
53
+ end
54
+
55
+ def mock_no_response_in_backup(hash)
56
+ @redis.stubs(:exists).with("api-cache:twitter:#{hash}").returns(false)
57
+ @redis.stubs(:exists).with("api-cache:twitter").returns(true)
58
+ @redis.stubs(:hexists).with("api-cache:twitter", hash).returns(false)
59
+ end
60
+
61
+ def mock_bad_response_from_server
62
+ http_response = mock
63
+ http_response.expects(:is_a?).with(Net::HTTPSuccess).returns(false)
64
+ HTTParty::Response.any_instance.expects(:response).at_least_once.returns(http_response)
65
+ end
66
+
67
+ def mock_http_exception
68
+ HTTParty::Request.any_instance.expects(:perform_without_caching).raises(Net::HTTPError.new("ERROR MESSAGE", stub('response')))
69
+ end
70
+ end
@@ -0,0 +1,235 @@
1
+ require 'helper'
2
+
3
+ class TestCacheBar < Test::Unit::TestCase
4
+ context 'CacheBar' do
5
+
6
+ context 'mocking redis' do
7
+ setup do
8
+ @redis = mock
9
+ HTTParty::HTTPCache.redis = @redis
10
+ end
11
+
12
+ context 'with caching on' do
13
+ context 'with a good response' do
14
+ setup do
15
+ @uri_hash = '007a3a7aa28b11ef362040283e114f55'
16
+ @response = fixture_file('user_timeline.json')
17
+ VCR.insert_cassette('good_response')
18
+ end
19
+
20
+ teardown do
21
+ VCR.eject_cassette
22
+ end
23
+
24
+ should "set key with expiration" do
25
+ @redis.stubs(:exists).returns(false)
26
+ @redis.expects(:set).with("api-cache:twitter:#{@uri_hash}", @response).returns(true)
27
+ @redis.expects(:expire).with("api-cache:twitter:#{@uri_hash}", is_a(Integer)).returns(true)
28
+ @redis.stubs(:hset).returns(true)
29
+ TwitterAPI.user_timeline('viget')
30
+ end
31
+
32
+ should "store a backup" do
33
+ @redis.stubs(:exists).returns(false)
34
+ @redis.stubs(:set).returns(true)
35
+ @redis.stubs(:expire).returns(true)
36
+ @redis.expects(:hset).with('api-cache:twitter', @uri_hash, @response).returns(true)
37
+ TwitterAPI.user_timeline('viget')
38
+ end
39
+
40
+ should 'return expected result to PIM class' do
41
+ @redis.stubs(:exists).returns(false)
42
+ @redis.stubs(:set).returns(true)
43
+ @redis.stubs(:expire).returns(true)
44
+ @redis.stubs(:hset).returns(true)
45
+ tweets = TwitterAPI.user_timeline('viget')
46
+ assert_kind_of Array, tweets.parsed_response
47
+ assert tweets.parsed_response.present?
48
+ end
49
+
50
+ context 'and stored in cache' do
51
+ should 'retrieve response from cache correctly' do
52
+ @redis.stubs(:exists).returns(true)
53
+ @redis.expects(:get).with("api-cache:twitter:#{@uri_hash}").returns(@response)
54
+ tweets = TwitterAPI.user_timeline('viget')
55
+ assert_kind_of Array, tweets.parsed_response
56
+ assert tweets.parsed_response.present?
57
+ end
58
+ end
59
+ end
60
+
61
+ context 'with timeout' do
62
+ setup do
63
+ @uri_hash = '007a3a7aa28b11ef362040283e114f55'
64
+ @response = fixture_file('user_timeline.json')
65
+ HTTParty::Request.any_instance.expects(:timeout).raises(Timeout::Error)
66
+ end
67
+
68
+ should 'retrieve from backup if exists' do
69
+ mock_response_in_backup(@uri_hash)
70
+ response = TwitterAPI.user_timeline('viget')
71
+ assert_kind_of Array, response.parsed_response
72
+ assert response.parsed_response.present?
73
+ end
74
+
75
+ should 'raise NoResponseError error if no response in backup' do
76
+ mock_no_response_in_backup(@uri_hash)
77
+ assert_raise HTTParty::HTTPCache::NoResponseError do
78
+ TwitterAPI.user_timeline('viget')
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'with unparsable response' do
84
+ setup do
85
+ VCR.insert_cassette('unparsable')
86
+ @uri_hash = '9780998e3ec445e5c0e56ca0bf10248c'
87
+ @response = fixture_file('user_timeline.json')
88
+ end
89
+
90
+ should 'retrieve from backup if exists' do
91
+ mock_response_in_backup(@uri_hash)
92
+ response = TwitterAPI.bogus_resource
93
+ assert_kind_of Array, response.parsed_response
94
+ assert response.parsed_response.present?
95
+ end
96
+
97
+ should 'raise NoResponseError error if no response in backup' do
98
+ mock_no_response_in_backup(@uri_hash)
99
+ assert_raise HTTParty::HTTPCache::NoResponseError do
100
+ TwitterAPI.bogus_resource
101
+ end
102
+ end
103
+
104
+ teardown do
105
+ VCR.eject_cassette
106
+ end
107
+ end
108
+
109
+ context 'with bad response' do
110
+ setup do
111
+ VCR.insert_cassette('bad_response')
112
+ @uri_hash = '01cedaf99cbf9f7e585ac4fa034a4fd5'
113
+ @response = fixture_file('user_timeline.json')
114
+ end
115
+
116
+ should 'retrieve from backup if exists' do
117
+ mock_response_in_backup(@uri_hash)
118
+ response = TwitterAPI.bogus_timeline('viget')
119
+ assert_kind_of Array, response.parsed_response
120
+ assert response.parsed_response.present?
121
+ end
122
+
123
+ should 'return bad response' do
124
+ mock_no_response_in_backup(@uri_hash)
125
+ response = TwitterAPI.bogus_timeline('viget')
126
+ assert_equal 401, response.code
127
+ assert !response.response.is_a?(Net::HTTPSuccess)
128
+ end
129
+
130
+ teardown do
131
+ VCR.eject_cassette
132
+ end
133
+ end
134
+
135
+ context 'with bad response exception' do
136
+ setup do
137
+ @uri_hash = '9780998e3ec445e5c0e56ca0bf10248c'
138
+ @response = fixture_file('user_timeline.json')
139
+ end
140
+
141
+ should 'retrieve from backup if exists' do
142
+ mock_response_in_backup(@uri_hash)
143
+ mock_http_exception
144
+ response = TwitterAPI.bogus_resource
145
+ assert_kind_of Array, response.parsed_response
146
+ assert response.parsed_response.present?
147
+ end
148
+
149
+ should 'raise NoResponseError error if no response in backup' do
150
+ mock_no_response_in_backup(@uri_hash)
151
+ mock_http_exception
152
+ assert_raise HTTParty::HTTPCache::NoResponseError do
153
+ TwitterAPI.bogus_resource
154
+ end
155
+ end
156
+ end
157
+
158
+ context 'with a post' do
159
+ setup do
160
+ VCR.insert_cassette('status_update_post')
161
+ end
162
+
163
+ should 'never try to cache' do
164
+ @redis.expects(:exists).never
165
+ @redis.expects(:set).never
166
+ @redis.expects(:expires).never
167
+ @redis.expects(:hset).never
168
+ @redis.expects(:get).never
169
+ TwitterAPI.update_status('viget', 'My new status.')
170
+ end
171
+
172
+ teardown do
173
+ VCR.eject_cassette
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'with caching off' do
179
+ setup do
180
+ turn_off_caching
181
+ @redis.expects(:exists).never
182
+ @uri_hash = '007a3a7aa28b11ef362040283e114f55'
183
+ @response = fixture_file('user_timeline.json')
184
+ VCR.insert_cassette('good_response')
185
+ end
186
+
187
+ should 'just return the response' do
188
+ tweets = TwitterAPI.user_timeline('viget')
189
+ assert_kind_of Array, tweets.parsed_response
190
+ assert tweets.parsed_response.present?
191
+ end
192
+
193
+ teardown do
194
+ VCR.eject_cassette
195
+ turn_on_caching
196
+ end
197
+ end
198
+ end
199
+
200
+ context 'connecting to redis' do
201
+ setup do
202
+ VCR.insert_cassette('good_response')
203
+
204
+ redis = Redis.new(:host => 'localhost', :port => 6379,
205
+ :thread_safe => true, :db => '3')
206
+ @redis = Redis::Namespace.new('httpcache', :redis => redis)
207
+ HTTParty::HTTPCache.redis = @redis
208
+
209
+ @redis.keys("api-cache*").each do |key|
210
+ @redis.del(key)
211
+ end
212
+ end
213
+
214
+ should "store its response in the cache" do
215
+ assert_difference "@redis.keys('api-cache:twitter:*').count", 1 do
216
+ TwitterAPI.user_timeline('viget')
217
+ end
218
+ end
219
+
220
+ should "store a backup of its response" do
221
+ assert_difference "@redis.hkeys('api-cache:twitter').count", 1 do
222
+ TwitterAPI.user_timeline('viget')
223
+ end
224
+ end
225
+
226
+ teardown do
227
+ VCR.eject_cassette
228
+ @redis.keys("api-cache*").each do |key|
229
+ @redis.del(key)
230
+ end
231
+ end
232
+ end
233
+
234
+ end
235
+ end
@@ -0,0 +1,30 @@
1
+ module TwitterAPI
2
+ include HTTParty
3
+ base_uri 'https://api.twitter.com/1'
4
+ format :json
5
+
6
+ caches_api_responses :key_name => "twitter", :expire_in => 3600
7
+
8
+ class << self
9
+ def user_timeline(username)
10
+ get '/statuses/user_timeline.json', :query => {:screen_name => username}
11
+ end
12
+
13
+ def bogus_timeline(username)
14
+ get '/statuses/bogus_timeline.json', :query => {:screen_name => username}
15
+ end
16
+
17
+ def bogus_resource
18
+ get '/user_timeline.json'
19
+ end
20
+
21
+ def update_status(username, new_status)
22
+ # this is just for testing as this isn't really complient with API
23
+ post '/statuses/update.json', :query => {
24
+ :screen_name => username,
25
+ :status => new_status
26
+ }
27
+ end
28
+ end
29
+
30
+ end
metadata ADDED
@@ -0,0 +1,277 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cachebar
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Landau
9
+ - David Eisinger
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-04-23 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: redis
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: redis-namespace
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: httparty
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.3
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: 0.8.3
63
+ - !ruby/object:Gem::Dependency
64
+ name: activesupport
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: shoulda
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: bundler
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: '1.0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: jeweler
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ~>
117
+ - !ruby/object:Gem::Version
118
+ version: '1.6'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ~>
125
+ - !ruby/object:Gem::Version
126
+ version: '1.6'
127
+ - !ruby/object:Gem::Dependency
128
+ name: rcov
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: webmock
145
+ requirement: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ! '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: vcr
161
+ requirement: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ - !ruby/object:Gem::Dependency
176
+ name: mocha
177
+ requirement: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ type: :development
184
+ prerelease: false
185
+ version_requirements: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ - !ruby/object:Gem::Dependency
192
+ name: rake
193
+ requirement: !ruby/object:Gem::Requirement
194
+ none: false
195
+ requirements:
196
+ - - ~>
197
+ - !ruby/object:Gem::Version
198
+ version: 0.8.7
199
+ type: :development
200
+ prerelease: false
201
+ version_requirements: !ruby/object:Gem::Requirement
202
+ none: false
203
+ requirements:
204
+ - - ~>
205
+ - !ruby/object:Gem::Version
206
+ version: 0.8.7
207
+ - !ruby/object:Gem::Dependency
208
+ name: SystemTimer
209
+ requirement: !ruby/object:Gem::Requirement
210
+ none: false
211
+ requirements:
212
+ - - ! '>='
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ none: false
219
+ requirements:
220
+ - - ! '>='
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ description: A simple API caching layer built on top of HTTParty and Redis
224
+ email: brian.landau@viget.com
225
+ executables: []
226
+ extensions: []
227
+ extra_rdoc_files:
228
+ - LICENSE.txt
229
+ - README.md
230
+ files:
231
+ - .document
232
+ - CacheBar.gemspec
233
+ - Gemfile
234
+ - HISTORY
235
+ - LICENSE.txt
236
+ - README.md
237
+ - Rakefile
238
+ - VERSION
239
+ - lib/cachebar.rb
240
+ - lib/httparty/httpcache.rb
241
+ - test/fixtures/user_timeline.json
242
+ - test/fixtures/vcr_cassettes/bad_response.yml
243
+ - test/fixtures/vcr_cassettes/good_response.yml
244
+ - test/fixtures/vcr_cassettes/status_update_post.yml
245
+ - test/fixtures/vcr_cassettes/unparsable.yml
246
+ - test/helper.rb
247
+ - test/test_cachebar.rb
248
+ - test/twitter_api.rb
249
+ homepage: http://github.com/vigetlabs/cachebar
250
+ licenses:
251
+ - MIT
252
+ post_install_message:
253
+ rdoc_options: []
254
+ require_paths:
255
+ - lib
256
+ required_ruby_version: !ruby/object:Gem::Requirement
257
+ none: false
258
+ requirements:
259
+ - - ! '>='
260
+ - !ruby/object:Gem::Version
261
+ version: '0'
262
+ segments:
263
+ - 0
264
+ hash: -4078844821069531998
265
+ required_rubygems_version: !ruby/object:Gem::Requirement
266
+ none: false
267
+ requirements:
268
+ - - ! '>='
269
+ - !ruby/object:Gem::Version
270
+ version: '0'
271
+ requirements: []
272
+ rubyforge_project:
273
+ rubygems_version: 1.8.21
274
+ signing_key:
275
+ specification_version: 3
276
+ summary: A simple API caching layer built on top of HTTParty and Redis
277
+ test_files: []