Browse Source

CLDC-3244: Replace postcodes_io gem to improve error handling

pull/2634/head
Rachael Booth 2 years ago
parent
commit
b9672c08c6
  1. 3
      Gemfile
  2. 6
      Gemfile.lock
  3. 21
      app/services/postcode_service.rb
  4. 61
      spec/services/postcode_service_spec.rb

3
Gemfile

@ -38,7 +38,6 @@ gem "devise_two_factor_authentication"
# UK postcode parsing and validation
gem "uk_postcode"
# Get rich data from postcode lookups. Wraps postcodes.io
gem "postcodes_io"
# Use Ruby objects to build reusable markup. A React inspired evolution of the presenter pattern
gem "view_component", "~> 3.9"
# Use the AWS S3 SDK as storage mechanism
@ -111,3 +110,5 @@ end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "cssbundling-rails"
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
gem "excon", "~> 0.111.0"

6
Gemfile.lock

@ -183,7 +183,7 @@ GEM
et-orbi (1.2.11)
tzinfo
event_stream_parser (1.0.0)
excon (0.109.0)
excon (0.111.0)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
@ -302,8 +302,6 @@ GEM
racc
pg (1.5.5)
possessive (1.0.1)
postcodes_io (0.4.0)
excon (~> 0.39)
propshaft (0.8.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
@ -533,6 +531,7 @@ DEPENDENCIES
devise_two_factor_authentication
dotenv-rails
erb_lint
excon (~> 0.111.0)
factory_bot_rails
faker
govuk-components (~> 5.1)
@ -549,7 +548,6 @@ DEPENDENCIES
parallel_tests
pg (~> 1.1)
possessive
postcodes_io
propshaft
pry-byebug
puma (~> 5.6)

21
app/services/postcode_service.rb

@ -1,26 +1,23 @@
class PostcodeService
def initialize
@pio = Postcodes::IO.new
end
def lookup(postcode)
# Avoid network calls when postcode is invalid
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
result = nil
begin
# URI encoding only supports ASCII characters
ascii_postcode = self.class.clean(postcode)
Timeout.timeout(30) { postcode_lookup = @pio.lookup(ascii_postcode) }
rescue Timeout::Error
Rails.logger.warn("Postcodes.io lookup timed out")
end
if postcode_lookup && postcode_lookup.info.present?
{
location_code: postcode_lookup.codes["admin_district"],
location_admin_district: postcode_lookup&.admin_district,
response = Excon.get("https://api.postcodes.io/postcodes/#{ascii_postcode}", idempotent: true, timeout: 30, expects: [200])
parsed_response = JSON.parse(response.body)
result = {
location_code: parsed_response["result"]["codes"]["admin_district"],
location_admin_district: parsed_response["result"]["admin_district"],
}
rescue Excon::Error => e
Rails.logger.warn("An error occurred with the postcode lookup request: #{e} #{e.response.body}")
end
result
end
def self.clean(postcode)

61
spec/services/postcode_service_spec.rb

@ -1,9 +1,64 @@
require "rails_helper"
describe PostcodeService do
let(:postcode) { "s r81LS\u00A0" }
let(:service) { described_class.new }
it "returns clean postcode" do
expect(described_class.clean(postcode)).to eq "SR81LS"
describe "clean" do
let(:postcode) { "s r81LS\u00A0" }
it "returns clean postcode" do
expect(described_class.clean(postcode)).to eq "SR81LS"
end
end
describe "lookup" do
before do
Excon.defaults[:mock] = true
Excon.defaults[:stubs] = :local
end
context "when the request returns a success response" do
before do
Excon.stub({}, { body: '{"result": { "admin_district": "District", "codes": { "admin_district": "123" } } }', status: 200 })
end
it "returns the admin district and admin district code" do
result = service.lookup("A00 0AA")
expect(result[:location_code]).to eq("123")
expect(result[:location_admin_district]).to eq("District")
end
end
context "when the request returns a not found response" do
before do
Excon.stub({}, { status: 404 })
end
it "returns nil" do
result = service.lookup("A00 0AA")
expect(result).to be_nil
end
it "logs the error at warning level" do
expect(Rails.logger).to receive(:warn).with(match "404 Not Found")
service.lookup("A00 0AA")
end
end
context "when the request returns an error response" do
before do
Excon.stub({}, { body: "This is an error message that is not valid json", status: 500 })
end
it "returns nil" do
result = service.lookup("A00 0AA")
expect(result).to be_nil
end
it "logs the error at warning level" do
expect(Rails.logger).to receive(:warn).with(match "This is an error message that is not valid json")
service.lookup("A00 0AA")
end
end
end
end

Loading…
Cancel
Save