Skip to content

Commit 024f567

Browse files
authored
Merge pull request #16 from ideal-postcodes/configure_agent
Configure agent
2 parents fa8e280 + c8f9307 commit 024f567

File tree

5 files changed

+165
-19
lines changed

5 files changed

+165
-19
lines changed

README.md

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ Our JavaScript client implements a common interface which is implemented at [`@i
3838

3939
### Configuration & Usage
4040

41+
- [Install](#install)
42+
- [Instantiate](#instantiate) and [Use](#use) client
43+
- [Catch Errors](#catch-errors)
44+
- [Configure Agent](#configure-agent)
45+
- [Proxy HTTP Requests](#proxy-requests)
46+
4147
#### Install
4248

4349
```bash
@@ -78,26 +84,49 @@ try {
7884
}
7985
```
8086

87+
#### Configure HTTP Agent
88+
89+
`core-node` uses [got](https://github.com/sindresorhus/got) as its underlying HTTP client. The Ideal Postcodes API client can also be optionally configured with a [got](https://github.com/sindresorhus/got) options object which is fed to [got](https://github.com/sindresorhus/got) on every request.
90+
91+
Be aware this options object will overwrite any existing [got](https://github.com/sindresorhus/got) HTTP request parameters.
92+
93+
```javascript
94+
const client = new Client({ api_key: "iddqd" }, {
95+
cache: new Map, // Instantiate a cache: https://github.com/sindresorhus/got#cache-1
96+
hooks: { // Hook into HTTP responses: https://github.com/sindresorhus/got#hooksafterresponse
97+
afterResponse: response => {
98+
log(response);
99+
return response;
100+
}
101+
},
102+
});
103+
```
104+
105+
#### Proxy HTTP Requests
106+
107+
You can [proxy requests](https://github.com/sindresorhus/got#proxies) by configuring the underlying [got](https://github.com/sindresorhus/got) HTTP client.
108+
109+
```javascript
110+
const tunnel = require("tunnel");
111+
112+
const client = new Client(config, {
113+
agent: tunnel.httpOverHttp({
114+
proxy: {
115+
host: "localhost"
116+
}
117+
})
118+
});
119+
```
120+
81121
---
82122

83123
### Quickstart
84124

85125
The client exposes a number of simple methods to get at the most common tasks when interacting with the API. Below is a (incomplete) list of commonly used methods.
86126

87-
- [Links](#links)
88-
- [Other JavaScript Clients](#other-javascript-clients)
89-
- [Documentation](#documentation)
90-
- [Configuration & Usage](#configuration--usage)
91-
- [Install](#install)
92-
- [Instantiate](#instantiate)
93-
- [Use](#use)
94-
- [Catch Errors](#catch-errors)
95-
- [Quickstart](#quickstart)
96-
- [Lookup a Postcode](#lookup-a-postcode)
97-
- [Search for an Address](#search-for-an-address)
98-
- [Search for an Address by UDPRN](#search-for-an-address-by-udprn)
99-
- [Test](#test)
100-
- [Licence](#licence)
127+
- [Lookup a Postcode](#lookup-a-postcode)
128+
- [Search for an Address](#search-for-an-address)
129+
- [Search for an Address by UDPRN](#search-for-an-address-by-udprn)
101130

102131
For a complete list of client methods, including low level resource methods, please see the [core-interface documentation](https://core-interface.ideal-postcodes.dev/#documentation)
103132

lib/agent.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import got, { GotInstance, Response } from "got";
1+
import got, { GotInstance, Response, GotJSONOptions } from "got";
22
import {
33
Agent as IAgent,
44
HttpRequest,
@@ -15,6 +15,14 @@ interface StringMap {
1515
[key: string]: string;
1616
}
1717

18+
/**
19+
* GotConfig
20+
*
21+
* An optional configuration object which is passed to the underlying got http
22+
* client
23+
*/
24+
export type GotConfig = Partial<GotJSONOptions>;
25+
1826
// Converts a Got header object to one that can be used by the client
1927
export const toHeader = (gotHeaders: GotHeaders): StringMap => {
2028
return Object.keys(gotHeaders).reduce(
@@ -55,9 +63,11 @@ const handleError = (error: Error): Promise<never> => {
5563

5664
export class Agent implements IAgent {
5765
public got: GotInstance;
66+
public gotConfig: GotConfig;
5867

59-
constructor() {
68+
constructor(gotConfig: GotConfig = {}) {
6069
this.got = got;
70+
this.gotConfig = gotConfig;
6171
}
6272

6373
private requestWithBody(httpRequest: HttpRequest): Promise<HttpResponse> {
@@ -70,6 +80,7 @@ export class Agent implements IAgent {
7080
throwHttpErrors: false,
7181
body,
7282
timeout,
83+
...this.gotConfig,
7384
})
7485
.then(response => toHttpResponse(httpRequest, response))
7586
.catch(handleError);
@@ -84,6 +95,7 @@ export class Agent implements IAgent {
8495
timeout,
8596
throwHttpErrors: false,
8697
json: true,
98+
...this.gotConfig,
8799
})
88100
.then(response => toHttpResponse(httpRequest, response))
89101
.catch(handleError);

lib/client.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
TIMEOUT,
88
STRICT_AUTHORISATION,
99
} from "@ideal-postcodes/core-interface";
10-
import { Agent } from "./agent";
10+
import { Agent, GotConfig } from "./agent";
1111

1212
const userAgent = `IdealPostcodes ideal-postcodes/core-node`;
1313

@@ -16,8 +16,16 @@ interface Config extends Partial<CoreConfig> {
1616
}
1717

1818
export class Client extends CoreInterface {
19-
constructor(config: Config) {
20-
const agent = new Agent();
19+
/**
20+
* Client constructor extends CoreInterface by also accepting an optional got
21+
* configuration object as the second argument.
22+
*
23+
* got is the underlying HTTP client that powers core-node. Be careful when
24+
* configuring gotConfig so as not to manually override critical request
25+
* attributes like method, query, header, etc.
26+
*/
27+
constructor(config: Config, gotConfig: GotConfig = {}) {
28+
const agent = new Agent(gotConfig);
2129
const header = { "User-Agent": userAgent };
2230
const tls = config.tls === undefined ? TLS : config.tls;
2331
const baseUrl = config.baseUrl === undefined ? API_URL : config.baseUrl;

test/agent.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ describe("Agent", () => {
1313
agent = new Agent();
1414
});
1515

16+
describe("Agent class", () => {
17+
it("allows for optional got configuration", () => {
18+
const a = new Agent();
19+
assert.deepEqual(a.gotConfig, {});
20+
});
21+
22+
it("assigns GOT config", () => {
23+
const retry = 2;
24+
const a = new Agent({ retry });
25+
assert.deepEqual({ retry }, a.gotConfig);
26+
});
27+
});
28+
1629
describe("toHeader", () => {
1730
it("coerces a Got header object into an object of strings", () => {
1831
const gotHeader = {
@@ -129,6 +142,81 @@ describe("Agent", () => {
129142
timeout: 1000,
130143
} as any);
131144
});
145+
146+
describe("GOT Configuration", () => {
147+
const timeout = 2000;
148+
const retry = 2;
149+
150+
beforeEach(() => {
151+
agent = new Agent({ timeout, retry });
152+
});
153+
154+
it("overrides HTTP configuration for GET requests", async () => {
155+
const method: HttpVerb = "GET";
156+
const query = { foo: "bar" };
157+
const header = { baz: "quux" };
158+
const url = "http://www.foo.com/";
159+
const SUCCESS = 200;
160+
161+
const response: unknown = {
162+
statusCode: SUCCESS,
163+
headers: header,
164+
body: Buffer.from("{}"),
165+
url,
166+
};
167+
168+
const stub = sinon
169+
.stub(agent, "got")
170+
.resolves(response as Response<Buffer>);
171+
172+
await agent.http({ method, timeout: 1000, url, header, query });
173+
174+
sinon.assert.calledOnce(stub);
175+
sinon.assert.calledWithExactly(stub, url, {
176+
method,
177+
headers: header,
178+
throwHttpErrors: false,
179+
query,
180+
json: true,
181+
timeout,
182+
retry,
183+
} as any);
184+
});
185+
186+
it("overrides HTTP configuration for POST requests", async () => {
187+
const method: HttpVerb = "POST";
188+
const query = { foo: "bar" };
189+
const header = { baz: "quux" };
190+
const url = "http://www.foo.com/";
191+
const SUCCESS = 200;
192+
const body = { foo: "bar" };
193+
194+
const response: unknown = {
195+
statusCode: SUCCESS,
196+
headers: header,
197+
body: Buffer.from("{}"),
198+
url,
199+
};
200+
201+
const stub = sinon
202+
.stub(agent, "got")
203+
.resolves(response as Response<Buffer>);
204+
205+
await agent.http({ body, method, timeout: 1000, url, header, query });
206+
207+
sinon.assert.calledOnce(stub);
208+
sinon.assert.calledWithExactly(stub, url, {
209+
method,
210+
throwHttpErrors: false,
211+
json: true,
212+
body,
213+
headers: header,
214+
query,
215+
retry,
216+
timeout,
217+
} as any);
218+
});
219+
});
132220
});
133221

134222
describe("Error handling", () => {

test/client.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe("Client", () => {
2525
assert.equal(client.strictAuthorisation, STRICT_AUTHORISATION);
2626
assert.equal(client.timeout, TIMEOUT);
2727
});
28+
2829
it("allows default config values to be overwritten", () => {
2930
const options = {
3031
tls: false,
@@ -44,9 +45,17 @@ describe("Client", () => {
4445
options.strictAuthorisation
4546
);
4647
assert.equal(customClient.timeout, options.timeout);
48+
assert.deepEqual((customClient.agent as any).gotConfig, {});
4749
});
50+
4851
it("assigns user agent header", () => {
4952
assert.match(client.header["User-Agent"], /Core-Node/i);
5053
});
54+
55+
it("assigns got config", () => {
56+
const retry = 2;
57+
const customClient = new Client({ api_key }, { retry });
58+
assert.deepEqual((customClient.agent as any).gotConfig, { retry });
59+
});
5160
});
5261
});

0 commit comments

Comments
 (0)