cachebar 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []