roverjoe 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/.rvmrc.example +1 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +65 -0
- data/Guardfile +14 -0
- data/README.md +102 -0
- data/Rakefile +7 -0
- data/lib/roverjoe.rb +51 -0
- data/lib/roverjoe/configuration.rb +51 -0
- data/lib/roverjoe/null_logger.rb +11 -0
- data/lib/roverjoe/quota_handler.rb +64 -0
- data/lib/roverjoe/remote_properties_page.rb +45 -0
- data/lib/roverjoe/remote_property.rb +28 -0
- data/lib/roverjoe/request.rb +60 -0
- data/lib/roverjoe/response.rb +64 -0
- data/lib/roverjoe/retry_handler.rb +56 -0
- data/lib/roverjoe/version.rb +3 -0
- data/roverjoe.gemspec +32 -0
- data/spec/fixtures/configurations/consumer_key_only.yml +2 -0
- data/spec/fixtures/configurations/consumer_signature_only.yml +2 -0
- data/spec/fixtures/hostelworld_api/real_propertyinformation_json.txt +1 -0
- data/spec/integration/configuration_spec.rb +35 -0
- data/spec/integration/star_ratings_spec.rb +61 -0
- data/spec/lib/roverjoe/configuration_spec.rb +55 -0
- data/spec/lib/roverjoe/quota_handler_spec.rb +48 -0
- data/spec/lib/roverjoe/remote_properties_page_spec.rb +90 -0
- data/spec/lib/roverjoe/remote_property_spec.rb +25 -0
- data/spec/lib/roverjoe/request_spec.rb +67 -0
- data/spec/lib/roverjoe/response_spec.rb +56 -0
- data/spec/lib/roverjoe/retry_handler_spec.rb +51 -0
- data/spec/lib/roverjoe_spec.rb +93 -0
- data/spec/spec_helper.rb +30 -0
- metadata +179 -0
@@ -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
|
data/roverjoe.gemspec
ADDED
@@ -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 @@
|
|
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
|