postcodeinfo-client-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|