Skip to content

Commit feb973f

Browse files
authored
Merge pull request #40 from companieshouse/feature/SR-129_refresh_token_service
feat: add refresh access token service
2 parents aa37da2 + 10a2264 commit feb973f

File tree

5 files changed

+124
-0
lines changed

5 files changed

+124
-0
lines changed

src/client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { LateFilingPenaltyService } from "./services/lfp";
66
import { BasketService, OrderService, CertificateService, CertifiedCopiesService, MidService } from "./services/order/";
77
import { PaymentService } from "./services/payment/";
88
import CompanyFilingHistoryService from "./services/company-filing-history/service";
9+
import { RefreshTokenService } from "./services/refresh-token";
910

1011
/**
1112
* ApiClient is the class that all service objects hang off.
@@ -22,6 +23,7 @@ export default class ApiClient {
2223
public readonly payment: PaymentService;
2324
public readonly order: OrderService;
2425
public readonly mid : MidService;
26+
public readonly refreshToken: RefreshTokenService;
2527

2628
constructor (readonly apiClient: IHttpClient, readonly accountClient: IHttpClient) {
2729
// services on the api domain using the apiClient
@@ -38,5 +40,6 @@ export default class ApiClient {
3840
this.mid = new MidService(apiClient);
3941
// service on the account/identity domain using the accountClient
4042
// e.g. user profile service can be added here when required
43+
this.refreshToken = new RefreshTokenService(accountClient);
4144
}
4245
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as RefreshTokenService } from "./service";
2+
export * from "./types";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { IHttpClient } from "../../http";
2+
import { RefreshTokenData } from "./types";
3+
import Resource from "../resource";
4+
5+
export default class {
6+
constructor (private readonly client: IHttpClient) {}
7+
8+
public async refresh (refreshToken: string, grantType: string, clientId: string,
9+
clientSecret: string): Promise<Resource<RefreshTokenData>> {
10+
const url: string = `/oauth2/token?grant_type=${grantType}&refresh_token=${refreshToken}&client_id=${clientId}` +
11+
`&client_secret=${clientSecret}`;
12+
13+
const response = await this.client.httpPost(url);
14+
15+
const resource: Resource<RefreshTokenData> = {
16+
httpStatusCode: response.status
17+
};
18+
19+
if (response.error) {
20+
return resource;
21+
}
22+
23+
const body = response.body as RefreshTokenData;
24+
25+
resource.resource = {
26+
expires_in: body.expires_in,
27+
token_type: body.token_type,
28+
access_token: body.access_token
29+
};
30+
31+
return resource;
32+
}
33+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface RefreshTokenData {
2+
expires_in: number;
3+
token_type: string;
4+
access_token: string;
5+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { RequestClient } from "../../../src/http";
2+
import { RefreshTokenService } from "../../../src/services/refresh-token";
3+
import chai from "chai";
4+
import sinon from "sinon";
5+
const expect = chai.expect;
6+
const requestClient = new RequestClient({ baseUrl: "URL-NOT-USED", oauthToken: "TOKEN-NOT-USED" });
7+
8+
describe("refresh token", () => {
9+
beforeEach(() => {
10+
sinon.reset();
11+
sinon.restore();
12+
});
13+
14+
afterEach(done => {
15+
sinon.reset();
16+
sinon.restore();
17+
done();
18+
});
19+
20+
it("returns an error response on failure", async () => {
21+
22+
sinon.stub(requestClient, "httpPost").resolves({
23+
status: 400,
24+
error: "Invalid parameter"
25+
});
26+
27+
const refreshToken: RefreshTokenService = new RefreshTokenService(requestClient);
28+
29+
const data = await refreshToken.refresh("REFRESH_TOKEN", "GRANT_TYPE", "CLIENT_ID",
30+
"CLIENT_SECRET");
31+
32+
expect(data.httpStatusCode).to.be.equal(400);
33+
expect(data.resource).to.be.undefined;
34+
});
35+
36+
it("maps the refresh token data correctly", async () => {
37+
const mockResponseBody = ({
38+
access_token: "string",
39+
token_type: "string",
40+
expires_in: 1
41+
});
42+
43+
sinon.stub(requestClient, "httpPost").resolves({
44+
status: 200,
45+
body: mockResponseBody
46+
});
47+
48+
const refreshToken: RefreshTokenService = new RefreshTokenService(requestClient);
49+
50+
const data = await refreshToken.refresh("REFRESH_TOKEN", "GRANT_TYPE", "CLIENT_ID",
51+
"CLIENT_SECRET");
52+
53+
expect(data.httpStatusCode).to.be.equal(200);
54+
expect(data.resource.access_token).to.be.equal(mockResponseBody.access_token);
55+
expect(data.resource.token_type).to.be.equal(mockResponseBody.token_type);
56+
expect(data.resource.expires_in).to.be.equal(mockResponseBody.expires_in);
57+
});
58+
59+
it("maps the refresh token data correctly when fields are missing", async () => {
60+
const mockResponseBody = ({
61+
access_token: "string",
62+
token_type: undefined,
63+
expires_in: 1
64+
});
65+
66+
sinon.stub(requestClient, "httpPost").resolves({
67+
status: 200,
68+
body: mockResponseBody
69+
});
70+
71+
const refreshToken: RefreshTokenService = new RefreshTokenService(requestClient);
72+
73+
const data = await refreshToken.refresh("REFRESH_TOKEN", "GRANT_TYPE", "CLIENT_ID",
74+
"CLIENT_SECRET");
75+
76+
expect(data.httpStatusCode).to.be.equal(200);
77+
expect(data.resource.access_token).to.be.equal(mockResponseBody.access_token);
78+
expect(data.resource.token_type).to.be.equal(undefined);
79+
expect(data.resource.expires_in).to.be.equal(mockResponseBody.expires_in);
80+
});
81+
});

0 commit comments

Comments
 (0)