postcodeinfo-client-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.ruby-gemset +2 -0
- data/.ruby-version +1 -0
- data/.simplecov +6 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +97 -0
- data/LICENSE.MD +19 -0
- data/README.md +150 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/lib/postcodeinfo-client-ruby.rb +3 -0
- data/lib/postcodeinfo/client.rb +129 -0
- data/lib/postcodeinfo/exceptions.rb +16 -0
- data/lib/postcodeinfo/postcode.rb +65 -0
- data/postcodeinfo-client-ruby.gemspec +80 -0
- data/spec/client_spec.rb +289 -0
- data/spec/postcode_spec.rb +225 -0
- data/spec/spec_helper.rb +22 -0
- metadata +181 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module PostcodeInfo
|
2
|
+
class APIExceptionWrapper < Exception
|
3
|
+
attr_accessor :original_exception, :response
|
4
|
+
|
5
|
+
def initialize(original_exception, response)
|
6
|
+
self.original_exception = original_exception
|
7
|
+
self.response = response
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ServiceUnavailable < APIExceptionWrapper; end
|
12
|
+
class ServerError < APIExceptionWrapper; end
|
13
|
+
class UnparseableResponse < APIExceptionWrapper; end
|
14
|
+
class InvalidAuthToken < APIExceptionWrapper; end
|
15
|
+
class UnrecognisedPostcode < APIExceptionWrapper; end
|
16
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module PostcodeInfo
|
2
|
+
class Postcode
|
3
|
+
# these attributes are set on construction
|
4
|
+
attr_reader :as_given, :normalised
|
5
|
+
attr_accessor :client
|
6
|
+
# these attributes require lookups
|
7
|
+
# attr_reader :latitude, :longitude, :addresses, :local_authority
|
8
|
+
|
9
|
+
def initialize(given_postcode, client)
|
10
|
+
@as_given = given_postcode
|
11
|
+
@normalised = self.class.normalise(@as_given)
|
12
|
+
@client = client
|
13
|
+
@info = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.normalise(postcode)
|
17
|
+
postcode.downcase.gsub(/[^a-z0-9]/, '')
|
18
|
+
end
|
19
|
+
|
20
|
+
def latitude
|
21
|
+
lookup_info! if @info.nil?
|
22
|
+
@latitude
|
23
|
+
end
|
24
|
+
|
25
|
+
def longitude
|
26
|
+
lookup_info! if @info.nil?
|
27
|
+
@longitude
|
28
|
+
end
|
29
|
+
|
30
|
+
def local_authority
|
31
|
+
lookup_info! if @info.nil?
|
32
|
+
@local_authority
|
33
|
+
end
|
34
|
+
|
35
|
+
def addresses
|
36
|
+
@addresses ||= @client.addresses(@normalised)
|
37
|
+
end
|
38
|
+
alias_method :lookup_addresses!, :addresses
|
39
|
+
|
40
|
+
def valid?
|
41
|
+
@valid ||= @client.valid?(@normalised)
|
42
|
+
end
|
43
|
+
|
44
|
+
def lookup_info!
|
45
|
+
@info = @client.info(@normalised)
|
46
|
+
parse_info!
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def set_coordinates!(info)
|
53
|
+
if info[:centre]
|
54
|
+
@latitude = info[:centre][:latitude]
|
55
|
+
@longitude = info[:centre][:longitude]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_info!
|
60
|
+
set_coordinates!(@info)
|
61
|
+
@local_authority = @info[:local_authority]
|
62
|
+
@valid = true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: postcodeinfo-client-ruby 0.1.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "postcodeinfo-client-ruby"
|
9
|
+
s.version = "0.1.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Al Davidson"]
|
14
|
+
s.date = "2015-05-29"
|
15
|
+
s.description = "Provides a convenient interface for looking up postcodes in an instance of the MoJ postcodeinfo API (https://://github.com/ministryofjustice/postcodeinfo).\n Lets you check if a postcode is valid, and ookup:\n * all addresses with that postcode\n * the latitude/longitude of its centre point\n * the name & GSS code of the local authority under which it resides."
|
16
|
+
s.email = "alistair.davidson@digital.justice.gov.uk"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.MD",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
".ruby-gemset",
|
25
|
+
".ruby-version",
|
26
|
+
".simplecov",
|
27
|
+
"Gemfile",
|
28
|
+
"Gemfile.lock",
|
29
|
+
"LICENSE.MD",
|
30
|
+
"README.md",
|
31
|
+
"Rakefile",
|
32
|
+
"VERSION",
|
33
|
+
"lib/postcodeinfo-client-ruby.rb",
|
34
|
+
"lib/postcodeinfo/client.rb",
|
35
|
+
"lib/postcodeinfo/exceptions.rb",
|
36
|
+
"lib/postcodeinfo/postcode.rb",
|
37
|
+
"postcodeinfo-client-ruby.gemspec",
|
38
|
+
"spec/client_spec.rb",
|
39
|
+
"spec/postcode_spec.rb",
|
40
|
+
"spec/spec_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = "https://github.com/ministryofjustice/postcodeinfo-client-ruby"
|
43
|
+
s.licenses = ["Open Government License"]
|
44
|
+
s.rubygems_version = "2.4.3"
|
45
|
+
s.summary = "Client for postcodeinfo API (https://://github.com/ministryofjustice/postcodeinfo)"
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
s.specification_version = 4
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_runtime_dependency(%q<json>, [">= 0"])
|
52
|
+
s.add_runtime_dependency(%q<rest-client>, [">= 0"])
|
53
|
+
s.add_development_dependency(%q<byebug>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<rspec>, [">= 3.2.0"])
|
55
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
58
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<json>, [">= 0"])
|
61
|
+
s.add_dependency(%q<rest-client>, [">= 0"])
|
62
|
+
s.add_dependency(%q<byebug>, [">= 0"])
|
63
|
+
s.add_dependency(%q<rspec>, [">= 3.2.0"])
|
64
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
65
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
66
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
67
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<json>, [">= 0"])
|
71
|
+
s.add_dependency(%q<rest-client>, [">= 0"])
|
72
|
+
s.add_dependency(%q<byebug>, [">= 0"])
|
73
|
+
s.add_dependency(%q<rspec>, [">= 3.2.0"])
|
74
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
75
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
76
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
77
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PostcodeInfo::Client do
|
4
|
+
let(:args){ {auth_token: '12345'} }
|
5
|
+
let(:client){ PostcodeInfo::Client.new(args) }
|
6
|
+
let(:postcode){ 'SN15NB' }
|
7
|
+
|
8
|
+
|
9
|
+
describe 'constructing a new client' do
|
10
|
+
context 'with no :auth_token' do
|
11
|
+
let(:args){ {} }
|
12
|
+
it 'does not raise an ArgumentError' do
|
13
|
+
expect{ described_class.new }.to_not raise_error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
context 'with an :auth_token' do
|
17
|
+
let(:args){ {auth_token: '12345'} }
|
18
|
+
let(:client){ PostcodeInfo::Client.new(args) }
|
19
|
+
|
20
|
+
it 'stores the auth_token in an instance variable' do
|
21
|
+
expect( client.auth_token ).to eq('12345')
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'and an :api_url' do
|
25
|
+
before{ args[:api_url] = 'http://my/ur.l' }
|
26
|
+
|
27
|
+
it 'parses the :api_url' do
|
28
|
+
expect( URI ).to receive(:parse).with('http://my/ur.l')
|
29
|
+
client
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'and no :api_url' do
|
34
|
+
let(:mock_urls){
|
35
|
+
{
|
36
|
+
my_env: 'http://my/env/url',
|
37
|
+
foo: 'https://foo/'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
before do
|
41
|
+
allow(described_class).to receive(:api_urls).and_return( mock_urls )
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'but an :env' do
|
45
|
+
before{ args[:env] = 'my_env' }
|
46
|
+
|
47
|
+
it 'parses the looked-up :api_url' do
|
48
|
+
expect( URI ).to receive(:parse).with('http://my/env/url')
|
49
|
+
client
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'and no :env' do
|
54
|
+
context 'when ENV["RAILS_ENV"] is present' do
|
55
|
+
before do
|
56
|
+
allow(ENV).to receive(:[]).with('RAILS_ENV').and_return( 'foo' )
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'parses the looked-up :api_url' do
|
60
|
+
expect( URI ).to receive(:parse).with('https://foo/')
|
61
|
+
client
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'setting the api_url' do
|
70
|
+
let(:new_url){ 'https://my.dom.ain/' }
|
71
|
+
|
72
|
+
it 'stores the parsed URI as @api_uri' do
|
73
|
+
expect{ client.api_url=new_url }.to change(client, :api_uri).to( URI.parse(new_url) )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'getting the api_url' do
|
78
|
+
it 'returns a string' do
|
79
|
+
expect(client.api_url).to be_a(String)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'lookup_postcode' do
|
84
|
+
let(:mock_constructed_postcode){ double(PostcodeInfo::Postcode).as_null_object }
|
85
|
+
before do
|
86
|
+
allow(PostcodeInfo::Postcode).to receive(:new).and_return(mock_constructed_postcode)
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'given a valid postcode' do
|
90
|
+
|
91
|
+
it 'returns a PostcodeInfo::Postcode' do
|
92
|
+
expect(client.lookup_postcode(postcode)).to eq(mock_constructed_postcode)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'looks-up the addresses' do
|
96
|
+
expect(mock_constructed_postcode).to receive(:lookup_addresses!)
|
97
|
+
allow(mock_constructed_postcode).to receive(:lookup_info!)
|
98
|
+
client.lookup_postcode(postcode)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'looks-up the info' do
|
102
|
+
allow(mock_constructed_postcode).to receive(:lookup_addresses!)
|
103
|
+
expect(mock_constructed_postcode).to receive(:lookup_info!)
|
104
|
+
client.lookup_postcode(postcode)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when the response raises a RestClient::ResourceNotFound exception' do
|
109
|
+
before do
|
110
|
+
allow(mock_constructed_postcode).to receive(:lookup_info!).and_raise(RestClient::ResourceNotFound)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'raises PostcodeInfo::UnrecognizedPostcode' do
|
114
|
+
expect{client.lookup_postcode(postcode)}.to raise_error( PostcodeInfo::UnrecognisedPostcode )
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'valid?' do
|
120
|
+
let(:mock_response){ double('response').as_null_object }
|
121
|
+
before do
|
122
|
+
allow(client).to receive(:make_request).with('/postcodes/SN15NB').and_return(mock_response)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'makes a request to /postcodes/(given_postcode)' do
|
126
|
+
expect(client).to receive(:make_request).with('/postcodes/SN15NB').and_return(mock_response)
|
127
|
+
client.valid?(postcode)
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when the response has code 200' do
|
131
|
+
let(:mock_response){ double('response', code: 200) }
|
132
|
+
|
133
|
+
it 'returns true' do
|
134
|
+
expect(client.valid?(postcode)).to eq(true)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'when the response has code 404' do
|
139
|
+
let(:mock_response){ double('response', code: 404) }
|
140
|
+
|
141
|
+
it 'returns false' do
|
142
|
+
expect(client.valid?(postcode)).to eq(false)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
context 'when the response raises a RestClient::ResourceNotFound exception' do
|
148
|
+
before do
|
149
|
+
allow(client).to receive(:make_request).and_raise(RestClient::ResourceNotFound)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'returns false' do
|
153
|
+
expect(client.valid?(postcode)).to eq(false)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'when the response has code 4XX' do
|
158
|
+
let(:mock_response){ double('response', code: 418) }
|
159
|
+
|
160
|
+
it 'returns nil' do
|
161
|
+
expect(client.valid?(postcode)).to be_nil
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'when the response has code 5XX' do
|
166
|
+
let(:mock_response){ double('response', code: 500) }
|
167
|
+
|
168
|
+
it 'returns nil' do
|
169
|
+
expect(client.valid?(postcode)).to be_nil
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe 'addresses' do
|
175
|
+
let(:mock_response){ double('response').as_null_object }
|
176
|
+
|
177
|
+
before do
|
178
|
+
allow(client).to receive(:handle_response).and_return('handled response')
|
179
|
+
allow(client).to receive(:make_request).with('/addresses/?postcode=SN15NB').and_return(mock_response)
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'makes a request to /addresses/?postcode=(given_postcode)' do
|
183
|
+
expect(client).to receive(:make_request).with('/addresses/?postcode=SN15NB').and_return(mock_response)
|
184
|
+
client.addresses(postcode)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'handles the response' do
|
188
|
+
expect(client).to receive(:handle_response).with(mock_response)
|
189
|
+
client.addresses(postcode)
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'returns the handled response' do
|
193
|
+
expect(client.addresses(postcode)).to eq('handled response')
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'info' do
|
198
|
+
let(:mock_response){ double('response').as_null_object }
|
199
|
+
|
200
|
+
before do
|
201
|
+
allow(client).to receive(:handle_response).and_return('handled response')
|
202
|
+
allow(client).to receive(:make_request).with('/postcodes/SN15NB').and_return(mock_response)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'makes a request to /postcodes/(given_postcode)' do
|
206
|
+
expect(client).to receive(:make_request).with('/postcodes/SN15NB').and_return(mock_response)
|
207
|
+
client.info(postcode)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'handles the response' do
|
211
|
+
expect(client).to receive(:handle_response).with(mock_response)
|
212
|
+
client.info(postcode)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'returns the handled response' do
|
216
|
+
expect(client.info(postcode)).to eq('handled response')
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe 'make_request' do
|
221
|
+
before do
|
222
|
+
client.api_uri = URI.parse("http://my.api/")
|
223
|
+
allow(RestClient::Request).to receive(:execute).and_return( 'response' )
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'makes a get request to the given endpoint' do
|
227
|
+
expect(RestClient::Request).to receive(:execute).with(hash_including(method: :get, url: 'http://my.api/my/endpoint')).and_return('response')
|
228
|
+
client.make_request('/my/endpoint')
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'sends the auth_token as an authorization header' do
|
232
|
+
expect(RestClient::Request).to receive(:execute).with(hash_including(headers: { 'Authorization' => 'Token 12345' }))
|
233
|
+
client.make_request('/my/endpoint')
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'returns the response' do
|
237
|
+
expect( client.make_request('endpoint') ).to eq('response')
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'when the get raises a RestClient::Unauthorized exception' do
|
241
|
+
before do
|
242
|
+
allow(RestClient::Request).to receive(:execute).and_raise( RestClient::Unauthorized.new('some error msg') )
|
243
|
+
end
|
244
|
+
|
245
|
+
it 're-raises a PostcodeInfo::InvalidAuthToken' do
|
246
|
+
expect{ client.make_request('endpoint') }.to raise_error( PostcodeInfo::InvalidAuthToken )
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'when the get raises a RestClient::InternalServerError exception' do
|
251
|
+
before do
|
252
|
+
allow(RestClient::Request).to receive(:execute).and_raise( RestClient::InternalServerError.new('some error msg') )
|
253
|
+
end
|
254
|
+
|
255
|
+
it 're-raises a PostcodeInfo::ServerError' do
|
256
|
+
expect{ client.make_request('endpoint') }.to raise_error( PostcodeInfo::ServerError )
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
context 'when the get raises a SocketError exception' do
|
261
|
+
before do
|
262
|
+
allow(RestClient::Request).to receive(:execute).and_raise( SocketError.new('some error msg') )
|
263
|
+
end
|
264
|
+
|
265
|
+
it 're-raises a PostcodeInfo::ServiceUnavailable' do
|
266
|
+
expect{ client.make_request('endpoint') }.to raise_error( PostcodeInfo::ServiceUnavailable )
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context 'when the get raises any other exception' do
|
271
|
+
before do
|
272
|
+
allow(RestClient::Request).to receive(:execute).and_raise( ArgumentError.new('some error msg') )
|
273
|
+
end
|
274
|
+
|
275
|
+
it 're-raises the original exception' do
|
276
|
+
expect{ client.make_request('endpoint') }.to raise_error( ArgumentError )
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe 'handle_response' do
|
282
|
+
let(:mock_response){ double('response', body: 'body') }
|
283
|
+
|
284
|
+
it 'parses the body as JSON, and symbolizes the keys' do
|
285
|
+
expect(JSON).to receive(:parse).with('body', symbolize_names: true).and_return('parsed')
|
286
|
+
client.send(:handle_response, mock_response)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|