roverjoe 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ module RoverJoe
2
+
3
+ class RemoteProperty
4
+
5
+ attr_accessor :api_request_class, :id
6
+
7
+ def initialize( params )
8
+ @api_request_class = params.delete( :api_request_class ) || RoverJoe::Request
9
+ @id = params.delete( :id )
10
+ raise ArgumentError, ":id should not be blank" if @id.nil? or @id == ''
11
+ end
12
+
13
+ def star_rating
14
+ raise RuntimeError, "'starRating' not found in #{response.keys} for RemoteProperty #{id}." unless response.keys.include?( 'starRating' )
15
+ response['starRating']
16
+ end
17
+
18
+ def response
19
+ @response ||= request.execute
20
+ end
21
+
22
+ def request
23
+ @request ||= api_request_class.new( :propertyinformation, :PropertyNumber => self.id )
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,60 @@
1
+ module RoverJoe
2
+
3
+ require 'httparty'
4
+ require 'addressable/uri'
5
+
6
+ class Request
7
+
8
+ attr_accessor :request_type, :request_params, :httparty_response
9
+
10
+ def initialize( request_type, request_params = {} )
11
+ self.request_type, self.request_params = request_type, request_params
12
+ end
13
+
14
+ def execute
15
+ if response.error_status?
16
+ message = "Hostelworld API returned Error(s): #{response.errors.to_json} on request #{request_type.inspect} with parameters #{request_params.inspect}"
17
+ raise PropertyInactiveError, message if response.property_inactive?
18
+ raise RuntimeError, message
19
+ end
20
+ response.result
21
+ end
22
+
23
+ private
24
+
25
+ def response
26
+ @response ||= quota_handler.run do
27
+
28
+ httparty_get = retry_handler.run( request_type, request_params ) do
29
+ HTTParty.get( uri.to_s )
30
+ end
31
+ Response.new( httparty_get.parsed_response, httparty_get )
32
+
33
+ end
34
+ end
35
+
36
+ def uri
37
+ full_params = RoverJoe.configuration.request_params.merge(request_params)
38
+ format = full_params.delete('format')
39
+ uri = Addressable::URI.parse( "#{RoverJoe.configuration.request_uri}#{request_type}.#{format}").tap { |u| u.query_values = full_params}
40
+ end
41
+
42
+ def quota_handler
43
+ self.class.quota_handler
44
+ end
45
+
46
+ def retry_handler
47
+ self.class.retry_handler
48
+ end
49
+
50
+ def self.retry_handler
51
+ @retry_handler ||= RetryHandler.new
52
+ end
53
+
54
+ def self.quota_handler
55
+ @quota_handler ||= QuotaHandler.new
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,64 @@
1
+ module RoverJoe
2
+
3
+ class Response
4
+
5
+ QUOTA_EXCEEDED_ERROR_CODE = '526'
6
+ PROPERTY_INACTIVE_ERROR_CODE = '2027'
7
+
8
+ attr_reader :parsed_response, :httparty_response
9
+
10
+ def initialize( parsed_response, httparty_response )
11
+ raise ArgumentError, "Unable to process response \"#{parsed_response.inspect}\" from the Hostelworld API" unless parsed_response.respond_to?( '[]' )
12
+ @parsed_response = parsed_response
13
+ @httparty_response = httparty_response
14
+ end
15
+
16
+ def calls_remaining
17
+ api['callsremaining'].to_i
18
+ end
19
+
20
+ def error_status?
21
+ unless api.respond_to?(:[])
22
+ RoverJoe.logger.error "RoverJoe::Response#errors: Hostelworld API result is not in expected format. Key 'api' in parsed response is either missing or not itself a hash."
23
+ abort_with_diagnostics
24
+ end
25
+ api['status'] == 'Error'
26
+ end
27
+
28
+
29
+ def abort_with_diagnostics
30
+ RoverJoe.logger.warn "RoverJoe::Response: Hostelworld parsed response : #{parsed_response.inspect[0..600]}"
31
+ RoverJoe.logger.warn "RoverJoe::Response: Hostelworld raw response body : #{httparty_response.body[0..600]}"
32
+ RoverJoe.logger.warn "RoverJoe::Response: Hostelworld raw response headers : #{httparty_response.headers.inspect}"
33
+ raise RuntimeError, "Hostelworld API result is not in expected format, see log for details. Response begins: #{parsed_response.to_s[0..200]}"
34
+ end
35
+
36
+ def result
37
+ @parsed_response['result']
38
+ end
39
+
40
+ def errors
41
+ unless result.respond_to?(:[])
42
+ RoverJoe.logger.error "RoverJoe::Response#errors: Hostelworld API result is not in expected format. Key 'result' in parsed response is either missing or not itself a hash."
43
+ abort_with_diagnostics
44
+ end
45
+ result['errors']
46
+ end
47
+
48
+ def quota_exceeded?
49
+ error_status? && errors.respond_to?(:has_key?) && errors.has_key?(QUOTA_EXCEEDED_ERROR_CODE)
50
+ end
51
+
52
+ def property_inactive?
53
+ error_status? && errors.respond_to?(:has_key?) && errors.has_key?(PROPERTY_INACTIVE_ERROR_CODE)
54
+ end
55
+
56
+ private
57
+
58
+ def api
59
+ @parsed_response['api']
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,56 @@
1
+ module RoverJoe
2
+
3
+ class RetryHandler
4
+
5
+ def run( *request )
6
+
7
+ reset_retry_counters
8
+
9
+ begin
10
+
11
+ yield
12
+
13
+ rescue Errno::ECONNRESET => e
14
+
15
+ log_request_exception( e, *request )
16
+ retry unless excessive_retries?( :conn_reset_peer )
17
+ abort_on_excess( *request )
18
+
19
+ rescue Errno::ETIMEDOUT => e
20
+
21
+ log_request_exception( e, *request )
22
+ retry unless excessive_retries?( :timed_out )
23
+ abort_on_excess( *request )
24
+
25
+ rescue Timeout::Error => e
26
+
27
+ log_request_exception( e, *request )
28
+ retry unless excessive_retries?( :timed_out )
29
+ abort_on_excess( *request )
30
+
31
+ end
32
+
33
+ end
34
+
35
+ def log_request_exception( exception, *request )
36
+ RoverJoe.logger.warn( "Hostelworld API: Exception #{exception.class.name} \"#{exception}\" processing request #{request.inspect}" )
37
+ end
38
+
39
+ def abort_on_excess( *request )
40
+ message = "Hostelworld API: Aborting request after excessive retries processing request #{request.inspect}"
41
+ RoverJoe.logger.error message
42
+ raise ExcessiveRetries, message
43
+ end
44
+
45
+ def excessive_retries?( kind_of_error )
46
+ @retry_counter[ kind_of_error ] += 1
47
+ @retry_counter[ kind_of_error ] > 10
48
+ end
49
+
50
+ def reset_retry_counters
51
+ @retry_counter = Hash.new( 0 )
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,3 @@
1
+ module RoverJoe
2
+ VERSION = "0.0.10"
3
+ end
@@ -0,0 +1,32 @@
1
+ require File.expand_path("../lib/roverjoe/version", __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "roverjoe"
5
+ s.version = RoverJoe::VERSION
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["Andy Roberts"]
8
+ s.email = ["andy.roberts@lonelyplanet.com"]
9
+ s.homepage = "http://github.com/lonelyplanet/roverjoe"
10
+ s.summary = "Ruby wrapper for the Hostelworld API"
11
+ s.description = "Ruby wrapper for the Hostelworld API"
12
+
13
+ s.required_rubygems_version = ">= 1.3.6"
14
+
15
+ # lol - required for validation
16
+ s.rubyforge_project = "roverjoe"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ s.add_dependency 'addressable'
24
+ s.add_dependency 'httparty'
25
+
26
+ s.add_development_dependency 'cucumber'
27
+ s.add_development_dependency 'rspec'
28
+ s.add_development_dependency 'guard'
29
+ s.add_development_dependency 'guard-rspec'
30
+ s.add_development_dependency 'guard-cucumber'
31
+ s.add_development_dependency 'webmock'
32
+ end
@@ -0,0 +1,2 @@
1
+ params:
2
+ consumer_key: 'lonelyplanet.com'
@@ -0,0 +1,2 @@
1
+ params:
2
+ consumer_signature: 'somethingsecret'
@@ -0,0 +1 @@
1
+ {"api":{"status":"Success","callsremaining":58,"version":"1.1.35.2"},"result":{"propertyNumber":"640","propertyName":"Flying Pig Uptown","address1":"Vossiusstraat 46","address2":"","geo":{"latitude":"52.3604797","longitude":"4.8783588"},"city":"Amsterdam","CityNO":"15","country":"Netherlands","CountryNO":"148","description":"As the original Flying Pig hostel, we can't wait to welcome you again! \r\n\r\nWe're located perfectly central in-between the museum and fashion district, the park and Leidseplein, where the best concert-halls, clubs, bars and coffee shops are and most locals go out. You'll get that real Amsterdam feel and are still close enough to the red light district, which is only a 15-minute walk.\r\n\r\nThe Pig has been world-famous for its laidback atmosphere, with a hostel bar that everyone keeps talking about and friendly, helpful staff. We have a guest kitchen and a bar with indoor smoking area where you'll meet lots of people. This lively bar has theme nights, the cheapest beer in town and daily drinks specials! \r\n\r\nEvery bed in our dorms has a reading light, with single and queen-size bunk beds and free individual lockers in the rooms (bring your own padlock or buy one at reception for \u20ac3). There's a shower and toilet in most of the rooms. Our private rooms have a little fridge and TV.\r\n\r\nThere's free breakfast, linen and internet; one price covers all! The Flying Pig Uptown provides a free buffet-style breakfast, free use of our reception safe for small valuables, free luggage storage, Internet, Skype and Wi-Fi, a book exchange, free movies to watch and munchy food for sale! You can even borrow some skates for free.\r\n\r\nThis spring and summer you can enjoy Amsterdam's famous green grass in the beautiful Vondelpark, right outside the Flying Pig Uptown, for drinks, smokes and picnics. Check out the free musical and cultural performances on the park stage or play games with your (new) friends in your bare feet.\r\n\r\nIf you're in for some culture, the Van Gogh museum and the Rijksmuseum (with the 'Nachtwacht') are just around the corner, same as the beautiful Concert Building and the 'I Amsterdam' letters. Do you want to explore the Red Light District, some historic sites or Chinatown? Just take a stroll - everything is within walking distance. Public transport is close by, too.\r\n\r\nBut there's always more: go shopping at one of the local markets (such as the Albert Cuijp in the Pijp area) or the 'Nine Little Streets', drink a beer at the Amsterdam Heineken Museum or have a wild night out in the so-called pop temple the Paradiso, within crawling-home distance. You can borrow some free hostel skates and share a smoke, drink and a blanket with your (newly made) friends on the green grass of the famous Vondelpark. Laugh your head off at one of the comedy clubs on the Leidseplein or visit other local areas of the city.\r\n\r\nAt reception you can buy Pig merchandise and tickets to several events. We have free walking tours, a daily updated Amsterdam agenda, a free detailed map of Amsterdam and discounts on several things to do in Amsterdam. You get 50% off entrance to the Winston Kingdom club with your Flying Pig key card. We also offer a shuttle bus between Amsterdam and the Beach hostel.\r\n\r\nWe're open 24hrs with night security and no curfews or lockout. Access is by keycard.\r\n\r\nPlease note that the Flying Pig has an age restriction; we welcome guests from above 18 to the eternal young at heart! \r\n\r\nPlease read our Things To Note below. We only accept groups of up to 10 people through Hostelworld.","averageRating":86,"propertyType":"HOSTEL","currency":"EUR","conditions":"Please bring this confirmation with you for check-in. \r\nOur office handles all email cancellations (Mon-Fri 9am-5pm). Urgent cancellations or changes for the current weekend must therefore be done by phone directly with the hostel (+31 20 400 41 87) at least 24hrs before arrival date (write down the date and hour of cancelling and the name of the person you spoke to).\r\nNon-urgent cancellations and changes will need to be done by email to info@flyingpig.nl. Please provide the reservation details in your email. Only when you receive a personal reply with the confirmation of your cancellation can you be sure that your cancellation has been processed by us. No-shows will be charged for a single night's accommodation.\r\nThe 10% web deposit is always non-refundable.\r\nWe authorize all credit cards on the day of arrival for the amount of the first night. This is an authorization only and will be released shortly after.\r\nThe Flying Pig has an age restriction; we welcome guests from above 18 to the eternal young at heart!\r\nGroups of more than 10 people must contact us before booking.","directions":"From Schiphol airport to Amsterdam Central Station:\r\nThe easiest way into Amsterdam from Schiphol Airport is by train. Trains between Schiphol and Central Station depart about every 10 minutes between 6am and 1am, and once every hour between 1am and 6am. A one-way ticket will cost you \u20ac3.20 from a vending machine; you'll need some coins though. Trains to Amsterdam Central Station generally depart from platform 1 or 2. \r\n\r\nTo get to the Flying Pig Uptown from Central Station:\r\nTake tram 2 or 5 and get off at the Rijksmuseum (Hobbemastraat), one stop past the famous Leidseplein bar area. Walk back 150m in the direction the tram came from and turn left into the Vossiusstraat; we're at number 46.","email":"headoffice@flyingpig.nl","tel":"+31 20 428 49 34","locatedNear":"Located near Leidseplein (party square), Coffee shops, Van Gogh museum, Rembrandt museum, Stedelijk museum, Albert Cuyp market and Vondel Park.","notes":"It is not allowed to bring your own sleeping bag or pillow. Our beds are ready-made for you. We have a 24hr cancellation policy. Late cancellations and no-shows shall be charged the equivalent of one night's stay. The 10% deposit is not refundable in case of cancellations. We do our best to accommodate you (together with your friends) in the dorm of your choice. However, due to the nature of our business, room arrangements and price may be slightly different. We authorize all credit cards on the day of arrival for the amount of the first night. This is an authorization only and will be released shortly after.","starRating":4,"creationdate":"2000-01-01","shortintro":"As the original Flying Pig hostel, we can't wait to welcome you again! \r\n\r\nWe're located perfectly central in-between the museum and fashion district, the park and Leidseplein, where the best concert-halls, clubs, bars and coffee shops are and most locals go out. You'll get that real Amsterdam feel","signupsource":"","active":"1","province":"","cancellationPolicy":"1","propertyImages":[{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/1.jpg","imageWidth":"333","imageHeight":"333"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/2.jpg","imageWidth":"323","imageHeight":"431"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/3.jpg","imageWidth":"250","imageHeight":"250"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/4.jpg","imageWidth":"444","imageHeight":"296"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/5.jpg","imageWidth":"453","imageHeight":"340"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/6.jpg","imageWidth":"438","imageHeight":"329"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/7.jpg","imageWidth":"414","imageHeight":"311"},{"imageSize":"max","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/8.jpg","imageWidth":"450","imageHeight":"600"},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/1_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/2_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/3_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/4_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/5_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/6_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/7_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"large","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/8_l.jpg","imageWidth":250,"imageHeight":163},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/1_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/2_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/3_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/4_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/5_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/6_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/7_s.jpg","imageWidth":150,"imageHeight":100},{"imageSize":"small","imageURL":"http:\/\/u.hwstatic.com\/propertyimages\/6\/640\/8_s.jpg","imageWidth":150,"imageHeight":100}],"facilities":["24 Hour Reception","24 Hour Security","Air Conditioning","Bar","Bicycle Hire","Breakfast Included","Cafe","Card Phones","Common Room","Free Internet Access","Internet Access","Key Card Access","Kitchen","Linen Included","Lockers","Luggage Storage","Reading Light","Towels for hire"],"checkInTime":{"earliest":0,"latest":23}}}
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe RoverJoe do
4
+ describe 'Configuration integration.' do
5
+
6
+ describe 'from file that sets consumer key only' do
7
+ before(:all) { RoverJoe.configure_from_file( configuration_file_fixture( 'consumer_key_only.yml' ) ) }
8
+
9
+ it 'sets the consumer key to the value from the file' do
10
+ RoverJoe.configuration.request_params['consumer_key'].should == 'lonelyplanet.com'
11
+ RoverJoe.configuration.consumer_key.should == 'lonelyplanet.com'
12
+ end
13
+
14
+ it 'sets the consumer signature to the default' do
15
+ RoverJoe.configuration.request_params['consumer_signature'].should == 'you-forgot-to-configure-consumer-signature'
16
+ RoverJoe.configuration.consumer_signature.should == 'you-forgot-to-configure-consumer-signature'
17
+ end
18
+ end
19
+
20
+ describe 'from file that sets consumer signature only' do
21
+ before(:all) { RoverJoe.configure_from_file( configuration_file_fixture( 'consumer_signature_only.yml' ) ) }
22
+
23
+ it 'sets the consumer signature to the value from the file' do
24
+ RoverJoe.configuration.request_params['consumer_signature'].should == 'somethingsecret'
25
+ RoverJoe.configuration.consumer_signature.should == 'somethingsecret'
26
+ end
27
+
28
+ it 'sets the consumer key to the default' do
29
+ RoverJoe.configuration.request_params['consumer_key'].should == 'you-forgot-to-configure-consumer-key'
30
+ RoverJoe.configuration.consumer_key.should == 'you-forgot-to-configure-consumer-key'
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe RoverJoe do
4
+ describe 'Star ratings integration.' do
5
+ before(:all) do
6
+ RoverJoe.reset_configuration
7
+ RoverJoe.configure do |c|
8
+ c.consumer_key = 'lonelyplanet.com'
9
+ c.consumer_signature = 'asecretsignature'
10
+ end
11
+ end
12
+
13
+ let( :hostel ) { RoverJoe::RemoteProperty.new( :id => 4898797 ) }
14
+
15
+ context 'When API gives' do
16
+ describe 'normal repsonse' do
17
+
18
+ it 'remote property returns a star rating' do
19
+ hostelworld_api_propertyinformation_stub( hostel.id ).
20
+ to_return(:status => 200, :body => response_content, :headers => {content_type: 'application/json'})
21
+ hostel.star_rating.should == 4
22
+ end
23
+ end
24
+
25
+ describe 'quota exceed response, followed by normal repsonse' do
26
+ it 'remote property returns a star rating' do
27
+ quota_response = { 'api' => { 'status' => 'Error', 'callsremaining' => '0'}, 'result' => { 'errors' => { '526' => 'you ran out' } } }.to_json
28
+ hostelworld_api_propertyinformation_stub( hostel.id ).
29
+ to_return( [
30
+ { :status => 200, :body => quota_response, :headers => {content_type: 'application/json'} } ,
31
+ { :status => 200, :body => response_content, :headers => {content_type: 'application/json'} }
32
+ ] )
33
+ hostel.star_rating.should == 4
34
+ end
35
+ end
36
+
37
+ describe 'timeout responses, followed by normal repsonse' do
38
+ it 'remote property returns a star rating' do
39
+ quota_response = { 'api' => { 'status' => 'Error', 'callsremaining' => '0'}, 'result' => { 'errors' => { '526' => 'you ran out' } } }.to_json
40
+ hostelworld_api_propertyinformation_stub( hostel.id ).
41
+ to_raise( Errno::ETIMEDOUT ).then.
42
+ to_raise( TimeoutError ).then.
43
+ to_raise( Timeout::Error ).then.
44
+ to_return( { :status => 200, :body => response_content, :headers => {content_type: 'application/json'} } )
45
+ hostel.star_rating.should == 4
46
+ end
47
+ end
48
+
49
+ describe 'connection reset by peer response, followed by normal repsonse' do
50
+ it 'remote property returns a star rating' do
51
+ quota_response = { 'api' => { 'status' => 'Error', 'callsremaining' => '0'}, 'result' => { 'errors' => { '526' => 'you ran out' } } }.to_json
52
+ hostelworld_api_propertyinformation_stub( hostel.id ).
53
+ to_raise( Errno::ECONNRESET ).then.
54
+ to_return( { :status => 200, :body => response_content, :headers => {content_type: 'application/json'} } )
55
+ hostel.star_rating.should == 4
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ module RoverJoe
4
+ describe Configuration do
5
+
6
+ %w{ logger request_uri consumer_key consumer_signature }.each do |attribute|
7
+ let( :some_value ) { double( 'some value' ).as_null_object }
8
+
9
+ it "retains new values for #{attribute}" do
10
+ subject.send( attribute + '=' , some_value )
11
+ subject.send( attribute ).should === some_value
12
+ end
13
+ end
14
+
15
+ let( :some_request_params ) do
16
+ %w{ consumer_key consumer_signature format Language}.inject({}) { |result,attrib| result[attrib] = double.as_null_object ; result }
17
+ end
18
+
19
+ it 'retains new values for request_params' do
20
+ subject.request_params = some_request_params
21
+ subject.request_params.should === some_request_params
22
+ end
23
+
24
+ %w{ consumer_key consumer_signature }.each do |attribute|
25
+ let( :some_value ) { double( 'some value' ).as_null_object }
26
+
27
+ it "request_params['#{attribute}'] is set by #{attribute}=" do
28
+ subject.send( attribute + '=' , some_value )
29
+ subject.request_params[attribute].should === some_value
30
+ end
31
+ end
32
+
33
+ context 'when initialized' do
34
+
35
+ let (:null_logger) { double( 'null logger' ) }
36
+
37
+ %w{ request_params request_uri consumer_key consumer_signature }.each do |attribute|
38
+ it "has a default #{attribute}" do
39
+ subject.send(attribute).should_not be_nil
40
+ end
41
+ end
42
+ %w{ consumer_signature consumer_key format Language }.each do |key|
43
+ it "has a default request_params['#{key}']" do
44
+ subject.request_params[key].should_not be_nil
45
+ end
46
+ end
47
+ it 'has a default logger set to a new NullLogger' do
48
+ NullLogger.should_receive(:new).and_return( null_logger )
49
+ subject.logger.should === null_logger
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ module RoverJoe
4
+ describe QuotaHandler do
5
+
6
+ describe '#run' do
7
+
8
+ let( :block_result ) { double('block result').as_null_object }
9
+ let( :some_block ) { Proc.new { block_result } }
10
+
11
+ it 'returns the result of the yield' do
12
+ block_result.stub!(:quota_exceeded?).and_return(false)
13
+ subject.run( &some_block ).should == block_result
14
+ end
15
+
16
+ context 'when the quota is exceeded first time, but not the second time' do
17
+ it 'returns the result of the yield' do
18
+ block_result.stub!(:quota_exceeded?).and_return(true,false)
19
+ subject.stub!(:sleep) # we don't want to wait 400 seconds in the test
20
+ subject.run( &some_block ).should == block_result
21
+ end
22
+
23
+ context 'and an after_delay callback is defined' do
24
+ let( :mock_object ) { double( 'mock object ') }
25
+ before(:each) { RoverJoe.configuration.after_delay { |delay| mock_object.mock_method(delay) } }
26
+
27
+ it 'calls the callback with the delay' do
28
+ block_result.stub!(:quota_exceeded?).and_return(true,false)
29
+ mock_object.should_receive(:mock_method).with( instance_of(Float) )
30
+ subject.stub!(:sleep) # we don't want to wait 400 seconds in the test
31
+ subject.run( &some_block ).should == block_result
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'when the quota is exceeded forever' do
37
+ it 'throws an exception' do
38
+ block_result.stub!(:quota_exceeded?).and_return(true)
39
+ subject.stub!(:sleep) # we don't want to wait 400 seconds in the test
40
+ lambda{ subject.run( &some_block ) }.should raise_error
41
+ end
42
+ end
43
+
44
+
45
+ end
46
+
47
+ end
48
+ end