Skip to content

Commit 14227c3

Browse files
committed
feat(Client#lookupUmprn): Implement UMPRN lookup
- Small refactor - Add documentation
1 parent e709853 commit 14227c3

File tree

3 files changed

+268
-38
lines changed

3 files changed

+268
-38
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ The client exposes a number of simple methods to get at the most common tasks wh
7575

7676
- [Lookup a Postcode](#lookup-a-postcode)
7777
- [Search for an Address](#search-for-an-address)
78+
- [Search for an Address by UDPRN](#search-for-an-address-by-udprn)
79+
- [Search for an Address by UMPRN](#search-for-an-address-by-umprn)
7880

7981
#### Lookup a Postcode
8082

@@ -114,6 +116,48 @@ client.lookupAddress({ query }).then(addresses => {
114116

115117
Method options [outlined in the docs](https://core-interface.ideal-postcodes.dev/interfaces/lookupaddressoptions.html)
116118

119+
#### Search for an Address by UDPRN
120+
121+
Return address for a given `udprn`
122+
123+
Invalid UDPRN will return `null`
124+
125+
```javascript
126+
const udprn = 23747771;
127+
128+
client.lookupUdprn({ udprn }).then(address => {
129+
console.log(address);
130+
{
131+
postcode: "SW1A 2AA",
132+
line_1: "Prime Minister & First Lord Of The Treasury",
133+
// ...etc...
134+
}
135+
});
136+
```
137+
138+
Method options [outlined in the docs](https://core-interface.ideal-postcodes.dev/interfaces/lookupudprnoptions.html)
139+
140+
#### Search for an Address by UMPRN
141+
142+
Return address for a given `umprn`
143+
144+
Invalid UMPRN will return `null`
145+
146+
```javascript
147+
const umprn = 50906066;
148+
149+
client.lookupUmprn({ umprn }).then(address => {
150+
console.log(address);
151+
{
152+
postcode: "CV4 7AL",
153+
line_1: "Room 1, Block 1 Arthur Vick",
154+
// ...etc...
155+
}
156+
});
157+
```
158+
159+
Method options [outlined in the docs](https://core-interface.ideal-postcodes.dev/interfaces/lookupumprnoptions.html)
160+
117161
---
118162

119163
#### Resource Methods

lib/client.ts

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import {
66
HttpOptions,
77
Paginateable,
88
} from "./types";
9-
import { IdpcPostcodeNotFoundError, IdpcUdprnNotFoundError } from "./error";
9+
import {
10+
IdpcPostcodeNotFoundError,
11+
IdpcUmprnNotFoundError,
12+
IdpcUdprnNotFoundError,
13+
} from "./error";
1014
import { Address } from "@ideal-postcodes/api-typings";
1115
import {
1216
appendAuthorization,
@@ -15,7 +19,12 @@ import {
1519
appendFilter,
1620
appendTags,
1721
} from "./util";
18-
import { Request } from "./resources/resource";
22+
import { Request as BaseRequest } from "./resources/resource";
23+
24+
interface Request extends BaseRequest {
25+
query: StringMap;
26+
header: StringMap;
27+
}
1928

2029
type Protocol = "http" | "https";
2130

@@ -82,11 +91,25 @@ import {
8291
UmprnResource,
8392
} from "./resources/umprn";
8493

85-
interface LookupPostcodeOptions
94+
interface LookupIdOptions
95+
extends Authenticable,
96+
Filterable,
97+
Taggable,
98+
HttpOptions {}
99+
100+
interface LookupAddressOptions
86101
extends Authenticable,
87102
Filterable,
88103
Taggable,
104+
Paginateable,
89105
HttpOptions {
106+
/**
107+
* Query for address
108+
*/
109+
query: string;
110+
}
111+
112+
interface LookupPostcodeOptions extends LookupIdOptions {
90113
/**
91114
* Postcode to query for. Space and case insensitive
92115
*/
@@ -99,27 +122,18 @@ interface LookupPostcodeOptions
99122
page?: number;
100123
}
101124

102-
interface LookupAddressOptions
103-
extends Authenticable,
104-
Filterable,
105-
Taggable,
106-
Paginateable,
107-
HttpOptions {
125+
interface LookupUdprnOptions extends LookupIdOptions {
108126
/**
109-
* Query for address
127+
* UDPRN to query for
110128
*/
111-
query: string;
129+
udprn: number;
112130
}
113131

114-
interface LookupUdprnOptions
115-
extends Authenticable,
116-
Filterable,
117-
Taggable,
118-
HttpOptions {
132+
interface LookupUmprnOptions extends LookupIdOptions {
119133
/**
120-
* UDPRN to query for
134+
* UMPRN to query for
121135
*/
122-
udprn: number;
136+
umprn: number;
123137
}
124138

125139
export class Client {
@@ -200,19 +214,10 @@ export class Client {
200214
* [API Documentation for /postcodes](https://ideal-postcodes.co.uk/documentation/postcodes#postcode)
201215
*/
202216
lookupPostcode(options: LookupPostcodeOptions): Promise<Address[]> {
203-
const header: StringMap = {};
204-
const query: StringMap = {};
205-
206-
appendAuthorization({ client: this, header, options });
207-
appendIp({ header, options });
208-
appendFilter({ query, options });
209-
appendTags({ query, options });
217+
const queryOptions = this.toAddressIdQuery(options);
210218

211219
const { page } = options;
212-
if (page !== undefined) query.page = page.toString();
213-
214-
const queryOptions: Request = { header, query };
215-
if (options.timeout !== undefined) queryOptions.timeout = options.timeout;
220+
if (page !== undefined) queryOptions.query.page = page.toString();
216221

217222
return this.postcodes
218223
.retrieve(options.postcode, queryOptions)
@@ -249,15 +254,15 @@ export class Client {
249254
}
250255

251256
/**
252-
* lookupUdprn
253-
*
254-
* Search for an address given a UDPRN
257+
* toAddressIdQuery
255258
*
256-
* Invalid UDPRN returns `null`
257-
*
258-
* [API Documentation for /udprn](https://ideal-postcodes.co.uk/documentation/udprn)
259+
* Generates a request object. Bundles together commonly used header/query extractions:
260+
* - Authorization (api_key, licensee, user_token)
261+
* - Source IP forwarding
262+
* - Result filtering
263+
* - Tagging
259264
*/
260-
lookupUdprn(options: LookupUdprnOptions): Promise<Address | null> {
265+
private toAddressIdQuery(options: LookupIdOptions): Request {
261266
const header: StringMap = {};
262267
const query: StringMap = {};
263268

@@ -266,9 +271,23 @@ export class Client {
266271
appendFilter({ query, options });
267272
appendTags({ query, options });
268273

269-
const queryOptions: Request = { header, query };
270-
if (options.timeout !== undefined) queryOptions.timeout = options.timeout;
274+
const request: Request = { header, query };
275+
if (options.timeout !== undefined) request.timeout = options.timeout;
276+
277+
return request;
278+
}
271279

280+
/**
281+
* lookupUdprn
282+
*
283+
* Search for an address given a UDPRN
284+
*
285+
* Invalid UDPRN returns `null`
286+
*
287+
* [API Documentation for /udprn](https://ideal-postcodes.co.uk/documentation/udprn)
288+
*/
289+
lookupUdprn(options: LookupUdprnOptions): Promise<Address | null> {
290+
const queryOptions = this.toAddressIdQuery(options);
272291
return this.udprn
273292
.retrieve(options.udprn.toString(), queryOptions)
274293
.then(response => response.body.result)
@@ -277,4 +296,24 @@ export class Client {
277296
throw error;
278297
});
279298
}
299+
300+
/**
301+
* lookupUdprn
302+
*
303+
* Search for an address given a UDPRN
304+
*
305+
* Invalid UDPRN returns `null`
306+
*
307+
* [API Documentation for /udprn](https://ideal-postcodes.co.uk/documentation/udprn)
308+
*/
309+
lookupUmprn(options: LookupUmprnOptions): Promise<Address | null> {
310+
const queryOptions = this.toAddressIdQuery(options);
311+
return this.umprn
312+
.retrieve(options.umprn.toString(), queryOptions)
313+
.then(response => response.body.result)
314+
.catch(error => {
315+
if (error instanceof IdpcUmprnNotFoundError) return null;
316+
throw error;
317+
});
318+
}
280319
}

test/client.lookupumprn.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { assert } from "chai";
2+
import * as sinon from "sinon";
3+
import { Client } from "../lib/client";
4+
import { newConfig } from "./helper/index";
5+
import { AddressKeys } from "../lib/types";
6+
import { HttpRequest } from "../lib/agent";
7+
import { umprn as fixtures, errors } from "@ideal-postcodes/api-fixtures";
8+
import { toResponse } from "./helper/index";
9+
import { IdpcInvalidKeyError } from "../lib/error";
10+
11+
describe("Client", () => {
12+
afterEach(() => sinon.restore());
13+
14+
describe("lookupUmprn", () => {
15+
const client = new Client(newConfig());
16+
17+
it("returns an address", done => {
18+
const expectedRequest: HttpRequest = {
19+
method: "GET",
20+
timeout: client.timeout,
21+
query: {},
22+
header: {
23+
...Client.defaults.header,
24+
Authorization: `IDEALPOSTCODES api_key="${client.api_key}"`,
25+
},
26+
url: "https://api.ideal-postcodes.co.uk/v1/umprn/1",
27+
};
28+
29+
const stub = sinon
30+
.stub(client.agent, "http")
31+
.resolves(toResponse(fixtures.success, expectedRequest));
32+
33+
const umprn = 1;
34+
35+
client
36+
.lookupUmprn({ umprn })
37+
.then(addresses => {
38+
assert.deepEqual(fixtures.success.body.result, addresses);
39+
sinon.assert.calledOnce(stub);
40+
sinon.assert.calledWithExactly(stub, expectedRequest);
41+
done();
42+
})
43+
.catch(error => done(error));
44+
});
45+
46+
it("accepts arguments laid out in HTTP API", done => {
47+
const sourceIp = "8.8.8.8";
48+
const filter: AddressKeys[] = ["line_1", "postcode"];
49+
const tags = ["foo", "bar"];
50+
const timeout = 3000;
51+
const licensee = "quux";
52+
const api_key = "fooo";
53+
const umprn = 1;
54+
55+
const expectedRequest: HttpRequest = {
56+
method: "GET",
57+
timeout,
58+
query: {
59+
filter: "line_1,postcode",
60+
tags: "foo,bar",
61+
},
62+
header: {
63+
...Client.defaults.header,
64+
Authorization: `IDEALPOSTCODES api_key="${api_key}" licensee="${licensee}"`,
65+
"IDPC-Source-IP": sourceIp,
66+
},
67+
url: "https://api.ideal-postcodes.co.uk/v1/umprn/1",
68+
};
69+
70+
const stub = sinon
71+
.stub(client.agent, "http")
72+
.resolves(toResponse(fixtures.success, expectedRequest));
73+
74+
client
75+
.lookupUmprn({
76+
umprn,
77+
licensee,
78+
api_key,
79+
sourceIp,
80+
filter,
81+
tags,
82+
timeout,
83+
})
84+
.then(address => {
85+
assert.deepEqual(fixtures.success.body.result, address);
86+
sinon.assert.calledOnce(stub);
87+
sinon.assert.calledWithExactly(stub, expectedRequest);
88+
done();
89+
})
90+
.catch(error => done(error));
91+
});
92+
93+
it("returns null if umprn not found", done => {
94+
const expectedRequest: HttpRequest = {
95+
method: "GET",
96+
timeout: client.timeout,
97+
query: {},
98+
header: {
99+
...Client.defaults.header,
100+
Authorization: `IDEALPOSTCODES api_key="${client.api_key}"`,
101+
},
102+
url: "https://api.ideal-postcodes.co.uk/v1/umprn/-1",
103+
};
104+
105+
sinon
106+
.stub(client.agent, "http")
107+
.resolves(toResponse(fixtures.notFound, expectedRequest));
108+
109+
const umprn = -1;
110+
111+
client
112+
.lookupUmprn({ umprn })
113+
.then(address => {
114+
assert.isNull(address);
115+
done();
116+
})
117+
.catch(error => done(error));
118+
});
119+
120+
it("`catches` for all other errors", done => {
121+
const expectedRequest: HttpRequest = {
122+
method: "GET",
123+
timeout: client.timeout,
124+
query: {},
125+
header: {
126+
...Client.defaults.header,
127+
Authorization: `IDEALPOSTCODES api_key="${client.api_key}"`,
128+
},
129+
url: "https://api.ideal-postcodes.co.uk/v1/umprn/1",
130+
};
131+
132+
sinon
133+
.stub(client.agent, "http")
134+
.resolves(toResponse(errors.invalidKey, expectedRequest));
135+
136+
const umprn = 1;
137+
138+
client
139+
.lookupUmprn({ umprn })
140+
.then(() => done(new Error("This test should throw")))
141+
.catch(error => {
142+
assert.instanceOf(error, IdpcInvalidKeyError);
143+
done();
144+
});
145+
});
146+
});
147+
});

0 commit comments

Comments
 (0)