Skip to content

Commit a2157f3

Browse files
authored
Merge pull request #6636 from NHSDigital/ordnance-survey-api
Add wrapper for Ordnance Survey Places API
2 parents 70e6b43 + cb4f517 commit a2157f3

3 files changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module OrdnanceSurvey
4+
class PlacesAPI
5+
def self.find(...) = new.find(...)
6+
7+
def initialize
8+
@api_key = Settings.ordnance_survey.api_key
9+
@base_url = "https://api.os.uk"
10+
end
11+
12+
def find(query)
13+
params = { query:, format: "json" }
14+
response = connection.get("/search/places/v1/find", params)
15+
response.body.deep_transform_keys(&:downcase).deep_symbolize_keys
16+
end
17+
18+
private
19+
20+
def connection
21+
@connection ||=
22+
Faraday.new(url: @base_url) do |f|
23+
f.request :url_encoded
24+
f.headers["Key"] = @api_key
25+
f.response :logger if Rails.env.development?
26+
f.response :json
27+
f.response :raise_error
28+
end
29+
end
30+
end
31+
end

lib/tasks/smoke.rake

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@ namespace :smoke do
2222
type: "gp_practice"
2323
)
2424
end
25+
26+
desc "Test the integration with the OS Places API by looking up a known location."
27+
task os_places_api: :environment do
28+
response = OrdnanceSurvey::PlacesAPI.find("The Shard, London")
29+
puts "Found #{response[:results].count} results"
30+
end
2531
end
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# frozen_string_literal: true
2+
3+
describe OrdnanceSurvey::PlacesAPI do
4+
let(:api_key) { "test_api_key" }
5+
6+
before { Settings.ordnance_survey.api_key = api_key }
7+
8+
after { Settings.reload! }
9+
10+
describe "#find" do
11+
subject(:find) { described_class.find(query, **options) }
12+
13+
let(:query) { "1 High Street, London" }
14+
let(:options) { {} }
15+
let(:api_url) { "https://api.os.uk" }
16+
17+
context "when the request is successful" do
18+
let(:response_body) do
19+
{
20+
header: {
21+
uri:
22+
"#{api_url}/search/places/v1/find?query=1+High+Street%2C+London&format=json",
23+
query: "1 High Street, London",
24+
offset: 0,
25+
total_results: 1,
26+
format: "json",
27+
dataset: "DPA",
28+
lr: "EN,GB",
29+
max_results: 100,
30+
epoch: "111",
31+
output_srs: "EPSG:27700"
32+
},
33+
results: [
34+
{
35+
DPA: {
36+
UPRN: "1000000000",
37+
UDPRN: "12345",
38+
ADDRESSBASE: "GB12345678",
39+
ADDRESSBASE_POSTCODE: "SW1A 1AA",
40+
BUILDING_NAME: "Test Building",
41+
BUILDING_NUMBER: "1",
42+
SUB_BUILDING_NAME: "Flat 1",
43+
THOROUGHFARE_NAME: "High Street",
44+
THOROUGHFARE_DESCRIPTOR: "",
45+
POSTTOWN: "London",
46+
POSTCODE: "SW1A 1AA",
47+
POSTCODE_TYPE: "L",
48+
LATITUDE: 51.5074,
49+
LONGITUDE: -0.1278,
50+
X_COORDINATE: 530_000.0,
51+
Y_COORDINATE: 180_000.0,
52+
EASTING: 530_000,
53+
NORTHING: 180_000,
54+
COUNTRY: "England"
55+
},
56+
ADDRESS: "1, High Street, London, SW1A 1AA",
57+
BUILDING_NUMBER: "1",
58+
THOROUGHFARE: "High Street",
59+
LOCALITY: "",
60+
TOWN: "London",
61+
POSTCODE: "SW1A 1AA",
62+
COUNTY: "Greater London",
63+
COUNTRY: "England",
64+
UPRN: "1000000000",
65+
MATCH: 1.0,
66+
MATCH_DESCRIPTION: "EXACT",
67+
DISTANCE: 0.0
68+
}
69+
]
70+
}.to_json
71+
end
72+
73+
before do
74+
stub_request(:get, "#{api_url}/search/places/v1/find").with(
75+
query: hash_including(query:, format: "json"),
76+
headers: {
77+
"Key" => api_key
78+
}
79+
).to_return(
80+
status: 200,
81+
body: response_body,
82+
headers: {
83+
"Content-Type" => "application/json"
84+
}
85+
)
86+
end
87+
88+
it "returns parsed results" do
89+
response = find
90+
91+
expect(response[:results].length).to eq(1)
92+
expect(response[:results][0][:postcode]).to eq("SW1A 1AA")
93+
expect(response[:results][0][:building_number]).to eq("1")
94+
expect(response[:results][0][:thoroughfare]).to eq("High Street")
95+
expect(response[:results][0][:town]).to eq("London")
96+
expect(response[:results][0][:country]).to eq("England")
97+
expect(response[:results][0][:uprn]).to eq("1000000000")
98+
expect(response[:results][0][:match]).to eq(1.0)
99+
expect(response[:results][0][:match_description]).to eq("EXACT")
100+
end
101+
end
102+
103+
context "when the request is invalid" do
104+
before do
105+
stub_request(:get, "#{api_url}/search/places/v1/find").with(
106+
query: hash_including(query:, format: "json"),
107+
headers: {
108+
"Key" => api_key
109+
}
110+
).to_return(
111+
status: 400,
112+
body: { error: "Invalid query" }.to_json,
113+
headers: {
114+
}
115+
)
116+
end
117+
118+
it "raises an error" do
119+
expect { find }.to raise_error(Faraday::BadRequestError)
120+
end
121+
end
122+
123+
context "when rate limit is exceeded" do
124+
before do
125+
stub_request(:get, "#{api_url}/search/places/v1/find").with(
126+
query: hash_including(query:, format: "json"),
127+
headers: {
128+
"Key" => api_key
129+
}
130+
).to_return(status: 429, body: "Rate limit exceeded", headers: {})
131+
end
132+
133+
it "raises an error" do
134+
expect { find }.to raise_error(Faraday::ClientError)
135+
end
136+
end
137+
138+
context "when there is an unexpected error" do
139+
before do
140+
stub_request(:get, "#{api_url}/search/places/v1/find").with(
141+
query: hash_including(query:, format: "json"),
142+
headers: {
143+
"Key" => api_key
144+
}
145+
).to_return(status: 500, body: "Internal Server Error", headers: {})
146+
end
147+
148+
it "raises an error" do
149+
expect { find }.to raise_error(Faraday::ServerError)
150+
end
151+
end
152+
end
153+
end

0 commit comments

Comments
 (0)