addressfinder 1.5.2 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ require 'ostruct'
2
+
3
+ module AddressFinder
4
+ class AddressAutocomplete
5
+
6
+ attr_reader :results
7
+
8
+ def initialize(params:, http:)
9
+ @http = http
10
+ @country = params.delete(:country) || 'au'
11
+
12
+ @params = params
13
+ @params[:key] ||= config.api_key
14
+ @params[:secret] ||= config.api_secret
15
+ end
16
+
17
+ def perform
18
+ validate_params
19
+ build_request
20
+ execute_request
21
+ build_result
22
+
23
+ self
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :request_uri, :params, :country, :http
29
+ attr_accessor :response_body, :response_status
30
+ attr_writer :results
31
+
32
+ def validate_params
33
+ if country != 'au'
34
+ raise AddressFinder::RequestRejectedError.new("400", "#address_autocomplete only available for Australian addresses")
35
+ end
36
+ end
37
+
38
+ def build_request
39
+ @request_uri = "/api/au/address/autocomplete.json?#{encoded_params}"
40
+ end
41
+
42
+ def encoded_params
43
+ Util.encode_and_join_params(params)
44
+ end
45
+
46
+ def execute_request
47
+ request = Net::HTTP::Get.new(request_uri)
48
+
49
+ response = http.request(request)
50
+
51
+ self.response_body = response.body
52
+ self.response_status = response.code
53
+ end
54
+
55
+ def build_result
56
+ case response_status
57
+ when '200'
58
+ self.results = response_hash['completions'].map do |result_hash|
59
+ Result.new(result_hash)
60
+ end
61
+ else
62
+ raise AddressFinder::RequestRejectedError.new(@response_status, @response_body)
63
+ end
64
+ end
65
+
66
+ def response_hash
67
+ @_response_hash ||= MultiJson.load(response_body)
68
+ end
69
+
70
+ def config
71
+ @_config ||= AddressFinder.configuration
72
+ end
73
+
74
+ class Result < OpenStruct
75
+ end
76
+ end
77
+ end
@@ -1,24 +1,19 @@
1
1
  module AddressFinder
2
2
  class Bulk
3
- def initialize(&block)
3
+ def initialize(http:, &block)
4
4
  @block = block
5
+ @http_config = http
5
6
  end
6
7
 
7
8
  def perform
8
- Net::HTTP.start(config.hostname, config.port, use_ssl: true,
9
- open_timeout: config.timeout,
10
- read_timeout: config.timeout) do |http|
9
+ http_config.start do |http|
11
10
  block.call ClientProxy.new(http: http)
12
11
  end
13
12
  end
14
13
 
15
14
  private
16
15
 
17
- attr_reader :block
18
-
19
- def config
20
- @_config ||= AddressFinder.configuration
21
- end
16
+ attr_reader :block, :http_config
22
17
 
23
18
  class ClientProxy
24
19
  def initialize(http:)
@@ -26,7 +21,11 @@ module AddressFinder
26
21
  end
27
22
 
28
23
  def cleanse(args={})
29
- AddressFinder::Cleanse.new(args.merge(http: http)).perform.result
24
+ AddressFinder::Verification.new(args.merge(http: http)).perform.result
25
+ end
26
+
27
+ def verification(args={})
28
+ AddressFinder::Verification.new(args.merge(http: http)).perform.result
30
29
  end
31
30
 
32
31
  private
@@ -11,11 +11,15 @@ module AddressFinder
11
11
  attr_accessor :timeout
12
12
  attr_accessor :default_country
13
13
  attr_accessor :domain
14
+ attr_accessor :retries
15
+ attr_accessor :retry_delay
14
16
 
15
17
  def initialize
16
18
  self.hostname = 'api.addressfinder.io'
17
19
  self.port = 443
18
20
  self.timeout = 10
21
+ self.retries = 12
22
+ self.retry_delay = 5
19
23
  self.default_country = 'nz'
20
24
  end
21
25
  end
@@ -0,0 +1,55 @@
1
+ require 'net/http'
2
+
3
+ module AddressFinder
4
+ class HTTP
5
+ attr_reader :config
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ @connection_is_bad = false
10
+ end
11
+
12
+ def start(&block)
13
+ net_http.start do
14
+ block.call(self)
15
+ end
16
+ end
17
+
18
+ def request(args)
19
+ retries = 0
20
+ begin
21
+ re_establish_connection if @connection_is_bad
22
+ net_http.request(args)
23
+ rescue Net::ReadTimeout, Net::OpenTimeout, SocketError => error
24
+ if retries < config.retries
25
+ retries += 1
26
+ sleep(config.retry_delay)
27
+ @connection_is_bad = true if net_http.started?
28
+ retry
29
+ else
30
+ raise error
31
+ end
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def re_establish_connection
38
+ @connection_is_bad = false
39
+ net_http.finish
40
+ net_http.start
41
+ end
42
+
43
+ def net_http
44
+ @net_http ||= begin
45
+ http = Net::HTTP.new(config.hostname, config.port, config.proxy_host,
46
+ config.proxy_port, config.proxy_user,
47
+ config.proxy_password)
48
+ http.open_timeout = config.timeout
49
+ http.read_timeout = config.timeout
50
+ http.use_ssl = true
51
+ http
52
+ end
53
+ end
54
+ end
55
+ end
@@ -6,7 +6,14 @@ module AddressFinder
6
6
  end
7
7
 
8
8
  def self.encode_and_join_params(params)
9
- params.map{ |k,v| "#{k}=#{encode(v)}" }.join('&')
9
+ # URI.encode_www_form(params)
10
+ params.map do |k,v|
11
+ if v.is_a? Array
12
+ v.collect {|e| "#{k}[]=#{encode(e)}" }
13
+ else
14
+ "#{k}=#{encode(v)}"
15
+ end
16
+ end.join('&')
10
17
  end
11
18
  end
12
19
  end
@@ -1,17 +1,20 @@
1
1
  require 'ostruct'
2
2
 
3
3
  module AddressFinder
4
- class Cleanse
4
+ class Verification
5
5
 
6
6
  attr_reader :result
7
7
 
8
- def initialize(q:, country: nil, delivered: nil, post_box: nil, rural: nil, region_code: nil, domain: nil, key: nil, secret: nil, http:)
8
+ def initialize(q:, country: nil, delivered: nil, post_box: nil, paf: nil, rural: nil, region_code: nil, state_codes: nil, census: nil, domain: nil, key: nil, secret: nil, http:)
9
9
  @params = {}
10
10
  @params['q'] = q
11
11
  @params['delivered'] = delivered if delivered
12
12
  @params['post_box'] = post_box if post_box
13
+ @params['paf'] = paf if paf
13
14
  @params['rural'] = rural if rural
14
15
  @params['region_code'] = region_code if region_code
16
+ @params['state_codes'] = state_codes if state_codes
17
+ @params['census'] = census if census
15
18
  @params['domain'] = domain || config.domain if (domain || config.domain)
16
19
  @params['format'] = 'json'
17
20
  @params['key'] = key || config.api_key
@@ -35,7 +38,7 @@ module AddressFinder
35
38
  attr_writer :result
36
39
 
37
40
  def build_request
38
- @request_uri = "/api/#{country}/address/cleanse?#{encoded_params}"
41
+ @request_uri = "/api/#{country}/address/verification?#{encoded_params}"
39
42
  end
40
43
 
41
44
  def execute_request
@@ -1,3 +1,3 @@
1
1
  module AddressFinder
2
- VERSION = '1.5.2'
2
+ VERSION = '1.7.1'
3
3
  end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AddressFinder::AddressAutocomplete do
4
+ before do
5
+ AddressFinder.configure do |af|
6
+ af.api_key = 'XXX'
7
+ af.api_secret = 'YYY'
8
+ af.default_country = 'au'
9
+ end
10
+ end
11
+
12
+ describe '#build_request' do
13
+ let(:locator){ AddressFinder::AddressAutocomplete.new(params: args, http: http) }
14
+ let(:http){ AddressFinder::HTTP.new(AddressFinder.configuration) }
15
+
16
+ subject(:request_uri){ locator.send(:build_request) }
17
+
18
+ context 'with minimal arguments' do
19
+ let(:args){ {q: '186 willis'} }
20
+
21
+ it { expect(request_uri).to eq('/api/au/address/autocomplete.json?q=186+willis&key=XXX&secret=YYY') }
22
+ end
23
+
24
+ context 'with more arguments' do
25
+ let(:args){ {q: '186 willis st', au_paf: 1, max: 10} }
26
+
27
+ it { expect(request_uri).to eq('/api/au/address/autocomplete.json?q=186+willis+st&au_paf=1&max=10&key=XXX&secret=YYY') }
28
+ end
29
+
30
+ context 'with a country override' do
31
+ let(:args){ {q: '186 willis st', country: 'au'} }
32
+
33
+ it { expect(request_uri).to eq('/api/au/address/autocomplete.json?q=186+willis+st&key=XXX&secret=YYY') }
34
+ end
35
+
36
+ context 'with a key override' do
37
+ let(:args){ {q: '186 willis st', key: 'AAA'} }
38
+
39
+ it { expect(request_uri).to eq('/api/au/address/autocomplete.json?q=186+willis+st&key=AAA&secret=YYY') }
40
+ end
41
+
42
+ context 'with a secret override' do
43
+ let(:args){ {q: '186 willis st', secret: 'BBB'} }
44
+
45
+ it { expect(request_uri).to eq('/api/au/address/autocomplete.json?q=186+willis+st&secret=BBB&key=XXX') }
46
+ end
47
+ end
48
+
49
+ describe '#validate_params' do
50
+ let(:locator){ AddressFinder::AddressAutocomplete.new(params: args, http: http) }
51
+ let(:http){ AddressFinder::HTTP.new(AddressFinder.configuration) }
52
+
53
+ subject(:validate_params){ locator.send(:validate_params) }
54
+
55
+ context 'with wrong country' do
56
+ let(:args){ {q: '186 willis st', country: 'nz'} }
57
+
58
+ it { expect{validate_params}.to raise_error(AddressFinder::RequestRejectedError) }
59
+ end
60
+
61
+ context 'with correct country' do
62
+ let(:args){ {q: '186 willis st', country: 'au'} }
63
+
64
+ it { expect{validate_params}.not_to raise_error }
65
+ end
66
+ end
67
+
68
+ describe '#build_result' do
69
+ let(:locator){ AddressFinder::AddressSearch.new(params: {q: 'ignored'}, http: nil) }
70
+
71
+ before do
72
+ locator.send('response_body=', body)
73
+ locator.send('response_status=', status)
74
+ locator.send(:build_result)
75
+ end
76
+
77
+ subject(:results){ locator.results }
78
+
79
+ context 'with completions' do
80
+ let(:body){ '{"completions":[{"a":"184 William Jones Drive, Otangarei, Whangarei 0112","pxid":"2-.9.2U.F.F.2I","v":1},{"a":"184 Williams Street, Kaiapoi 7630","pxid":"2-.3.1q.2.4G.4c","v":0},{"a":"184 Willis Street, Te Aro, Wellington 6011","pxid":"2-.F.1W.p.1D.1W","v":0}],"paid":true}' }
81
+ let(:status){ '200' }
82
+
83
+ it { expect(results.size).to eq(3) }
84
+ it { expect(results.first.class).to eq(AddressFinder::AddressSearch::Result) }
85
+ it { expect(results.first.a).to eq("184 William Jones Drive, Otangarei, Whangarei 0112") }
86
+ end
87
+
88
+ context 'with no completions' do
89
+ let(:body){ '{"completions":[],"paid":true}' }
90
+ let(:status){ '200' }
91
+
92
+ it { expect(results).to eq([]) }
93
+ end
94
+ end
95
+ end
@@ -11,7 +11,7 @@ RSpec.describe AddressFinder::AddressInfo do
11
11
 
12
12
  describe '#build_request' do
13
13
  let(:locator){ AddressFinder::AddressInfo.new(params: args, http: http) }
14
- let(:http){ AddressFinder.send(:configure_http) }
14
+ let(:http){ AddressFinder::HTTP.new(AddressFinder.configuration) }
15
15
 
16
16
  subject(:request_uri){ locator.send(:build_request) }
17
17
 
@@ -11,7 +11,7 @@ RSpec.describe AddressFinder::AddressSearch do
11
11
 
12
12
  describe '#build_request' do
13
13
  let(:locator){ AddressFinder::AddressSearch.new(params: args, http: http) }
14
- let(:http){ AddressFinder.send(:configure_http) }
14
+ let(:http){ AddressFinder::HTTP.new(AddressFinder.configuration) }
15
15
 
16
16
  subject(:request_uri){ locator.send(:build_request) }
17
17
 
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe AddressFinder::Bulk do
4
+ before do
5
+ AddressFinder.configure do |af|
6
+ af.api_key = 'XXX'
7
+ af.api_secret = 'YYY'
8
+ af.default_country = 'nz'
9
+ af.timeout = 5
10
+ af.retries = 5
11
+ end
12
+ end
13
+
14
+ describe '#perform' do
15
+ let(:http){ AddressFinder::HTTP.new(AddressFinder.configuration) }
16
+ let(:net_http){ http.send(:net_http) }
17
+
18
+ before do
19
+ WebMock.allow_net_connect!(net_http_connect_on_start: true)
20
+ allow(http).to receive(:sleep)
21
+ end
22
+
23
+ after do
24
+ WebMock.disable_net_connect!
25
+ end
26
+
27
+ context "with 3 requests in the provided block" do
28
+ let(:response){ double(:response, body: %Q({"success": true}), code: "200") }
29
+ let(:block){
30
+ Proc.new do |proxy|
31
+ proxy.verification(q: "1 Willis")
32
+ proxy.verification(q: "2 Willis")
33
+ proxy.verification(q: "3 Willis")
34
+ end
35
+ }
36
+
37
+ it "uses 1 http connection" do
38
+ expect(net_http).to receive(:do_start).once.and_call_original
39
+ expect(net_http).to receive(:transport_request).exactly(3).times.and_return(response)
40
+ expect(net_http).to receive(:do_finish).once.and_call_original
41
+ AddressFinder::Bulk.new(http: http, &block).perform
42
+ end
43
+
44
+ it "re-establishes the http connection and continues where we left off when a Net::OpenTimeout, Net::ReadTimeout or SocketError is raised" do
45
+ expect(http).to receive(:re_establish_connection).exactly(3).times.and_call_original
46
+ expect(net_http).to receive(:do_start).exactly(4).times.and_call_original
47
+ expect(net_http).to receive(:transport_request).once.and_return(response) # 1 Willis (success)
48
+ expect(net_http).to receive(:transport_request).once.and_raise(Net::OpenTimeout) # 2 Willis (error)
49
+ expect(net_http).to receive(:transport_request).once.and_raise(Net::ReadTimeout) # Retry 2 Willis (error)
50
+ expect(net_http).to receive(:transport_request).once.and_raise(SocketError) # Retry 2 Willis (error)
51
+ expect(net_http).to receive(:transport_request).exactly(2).and_return(response) # Retry 2 Willis (success) & 3 Willis (success)
52
+ expect(net_http).to receive(:do_finish).exactly(4).times.and_call_original
53
+ AddressFinder::Bulk.new(http: http, &block).perform
54
+ end
55
+ end
56
+
57
+ context "with the deprecated cleanse method" do
58
+ let(:response){ double(:response, body: %Q({"success": true}), code: "200") }
59
+ let(:block){
60
+ Proc.new do |proxy|
61
+ proxy.cleanse(q: "1 Willis")
62
+ end
63
+ }
64
+
65
+ it "has the same behaviour as the verification method" do
66
+ expect(net_http).to receive(:do_start).once.and_call_original
67
+ expect(net_http).to receive(:transport_request).once.and_return(response)
68
+ expect(net_http).to receive(:do_finish).once.and_call_original
69
+ AddressFinder::Bulk.new(http: http, &block).perform
70
+ end
71
+ end
72
+ end
73
+ end
@@ -11,7 +11,7 @@ RSpec.describe AddressFinder::LocationInfo do
11
11
 
12
12
  describe '#build_request' do
13
13
  let(:locator){ AddressFinder::LocationInfo.new(params: args, http: http) }
14
- let(:http){ AddressFinder.send(:configure_http) }
14
+ let(:http){ AddressFinder::HTTP.new(AddressFinder.configuration) }
15
15
 
16
16
  subject(:request_uri){ locator.send(:build_request) }
17
17