emissions-api-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CODE_OF_CONDUCT.md +76 -0
- package/CONTRIBUTING.md +68 -0
- package/LICENSE +201 -0
- package/MAINTAINERS.md +10 -0
- package/README.md +89 -0
- package/dist/Client.js +227 -0
- package/dist/Constants.js +19 -0
- package/dist/api/Calculation.js +41 -0
- package/dist/api/Factor.js +79 -0
- package/dist/api/FactorSets.js +24 -0
- package/dist/api/Factors.js +98 -0
- package/dist/api/Fugitive.js +40 -0
- package/dist/api/FugitiveEmission.js +40 -0
- package/dist/api/GenericCalculation.js +41 -0
- package/dist/api/Location.js +41 -0
- package/dist/api/LocationEmission.js +41 -0
- package/dist/api/Mobile.js +41 -0
- package/dist/api/MobileEmission.js +41 -0
- package/dist/api/Stationary.js +41 -0
- package/dist/api/StationaryEmission.js +41 -0
- package/dist/api/TransportationAndDistribution.js +40 -0
- package/dist/api/TransportationDistributionEmission.js +40 -0
- package/dist/coverage/clover.xml +225 -0
- package/dist/coverage/coverage-final.json +19 -0
- package/dist/coverage/lcov-report/base.css +224 -0
- package/dist/coverage/lcov-report/block-navigation.js +87 -0
- package/dist/coverage/lcov-report/favicon.png +0 -0
- package/dist/coverage/lcov-report/index.html +161 -0
- package/dist/coverage/lcov-report/prettify.css +1 -0
- package/dist/coverage/lcov-report/prettify.js +2 -0
- package/dist/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/dist/coverage/lcov-report/sorter.js +210 -0
- package/dist/coverage/lcov-report/src/Client.ts.html +838 -0
- package/dist/coverage/lcov-report/src/Constants.ts.html +130 -0
- package/dist/coverage/lcov-report/src/api/Calculation.ts.html +217 -0
- package/dist/coverage/lcov-report/src/api/Factor.ts.html +343 -0
- package/dist/coverage/lcov-report/src/api/FactorSets.ts.html +154 -0
- package/dist/coverage/lcov-report/src/api/Factors.ts.html +403 -0
- package/dist/coverage/lcov-report/src/api/Fugitive.ts.html +214 -0
- package/dist/coverage/lcov-report/src/api/FugitiveEmission.ts.html +214 -0
- package/dist/coverage/lcov-report/src/api/GenericCalculation.ts.html +217 -0
- package/dist/coverage/lcov-report/src/api/Location.ts.html +214 -0
- package/dist/coverage/lcov-report/src/api/LocationEmission.ts.html +214 -0
- package/dist/coverage/lcov-report/src/api/Mobile.ts.html +220 -0
- package/dist/coverage/lcov-report/src/api/MobileEmission.ts.html +220 -0
- package/dist/coverage/lcov-report/src/api/Stationary.ts.html +220 -0
- package/dist/coverage/lcov-report/src/api/StationaryEmission.ts.html +220 -0
- package/dist/coverage/lcov-report/src/api/TransportationAndDistribution.ts.html +211 -0
- package/dist/coverage/lcov-report/src/api/TransportationDistributionEmission.ts.html +211 -0
- package/dist/coverage/lcov-report/src/api/index.html +221 -0
- package/dist/coverage/lcov-report/src/index.html +161 -0
- package/dist/coverage/lcov-report/src/request.ts.html +235 -0
- package/dist/coverage/lcov-report/src/utils.ts.html +139 -0
- package/dist/coverage/lcov-report/test/index.html +116 -0
- package/dist/coverage/lcov-report/test/mocks/CommonRequest.ts.html +142 -0
- package/dist/coverage/lcov-report/test/mocks/FactorRequest.ts.html +133 -0
- package/dist/coverage/lcov-report/test/mocks/GenericCalculationRequest.ts.html +142 -0
- package/dist/coverage/lcov-report/test/mocks/LocationRequest.ts.html +139 -0
- package/dist/coverage/lcov-report/test/mocks/SearchRequest.ts.html +142 -0
- package/dist/coverage/lcov-report/test/mocks/index.html +176 -0
- package/dist/coverage/lcov-report/test/testUtils.ts.html +97 -0
- package/dist/coverage/lcov.info +404 -0
- package/dist/index.js +46 -0
- package/dist/interfaces/Api.js +2 -0
- package/dist/interfaces/Config.js +2 -0
- package/dist/interfaces/common.js +2 -0
- package/dist/request.js +51 -0
- package/dist/types/Client.d.ts +146 -0
- package/dist/types/Constants.d.ts +16 -0
- package/dist/types/api/Calculation.d.ts +28 -0
- package/dist/types/api/Factor.d.ts +57 -0
- package/dist/types/api/FactorSets.d.ts +11 -0
- package/dist/types/api/Factors.d.ts +68 -0
- package/dist/types/api/Fugitive.d.ts +27 -0
- package/dist/types/api/FugitiveEmission.d.ts +27 -0
- package/dist/types/api/GenericCalculation.d.ts +28 -0
- package/dist/types/api/Location.d.ts +28 -0
- package/dist/types/api/LocationEmission.d.ts +28 -0
- package/dist/types/api/Mobile.d.ts +28 -0
- package/dist/types/api/MobileEmission.d.ts +28 -0
- package/dist/types/api/Stationary.d.ts +28 -0
- package/dist/types/api/StationaryEmission.d.ts +28 -0
- package/dist/types/api/TransportationAndDistribution.d.ts +27 -0
- package/dist/types/api/TransportationDistributionEmission.d.ts +27 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/interfaces/Api.d.ts +286 -0
- package/dist/types/interfaces/Config.d.ts +66 -0
- package/dist/types/interfaces/common.d.ts +152 -0
- package/dist/types/request.d.ts +25 -0
- package/dist/types/utils.d.ts +14 -0
- package/dist/utils.js +23 -0
- package/jest.config.js +9 -0
- package/package.json +50 -0
- package/src/Client.ts +252 -0
- package/src/Constants.ts +16 -0
- package/src/api/Calculation.ts +44 -0
- package/src/api/Factor.ts +86 -0
- package/src/api/FactorSets.ts +24 -0
- package/src/api/Fugitive.ts +43 -0
- package/src/api/Location.ts +43 -0
- package/src/api/Mobile.ts +45 -0
- package/src/api/Stationary.ts +45 -0
- package/src/api/TransportationAndDistribution.ts +42 -0
- package/src/index.ts +10 -0
- package/src/interfaces/Api.ts +300 -0
- package/src/interfaces/Config.ts +78 -0
- package/src/interfaces/common.ts +174 -0
- package/src/request.ts +50 -0
- package/src/utils.ts +19 -0
- package/test/apiTest.test.ts +168 -0
- package/test/client.test.ts +82 -0
- package/test/mocks/CommonRequest.ts +19 -0
- package/test/mocks/FactorRequest.ts +16 -0
- package/test/mocks/GenericCalculationRequest.ts +19 -0
- package/test/mocks/LocationRequest.ts +19 -0
- package/test/mocks/SearchRequest.ts +19 -0
- package/test/request.test.ts +111 -0
- package/test/testUtils.ts +5 -0
- package/test/tokenRefresh.test.ts +52 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import * as requestModule from "../src/request";
|
|
2
|
+
import * as locationApi from "../src/api/Location";
|
|
3
|
+
import * as fugitiveApi from "../src/api/Fugitive";
|
|
4
|
+
import * as mobileApi from "../src/api/Mobile";
|
|
5
|
+
import * as stationaryApi from "../src/api/Stationary";
|
|
6
|
+
import * as GenericCalculation from "../src/api/Calculation";
|
|
7
|
+
import * as TransportationDistributionApi from "../src/api/TransportationAndDistribution";
|
|
8
|
+
import * as Factors from "../src/api/Factor";
|
|
9
|
+
import * as FactorSets from "../src/api/FactorSets";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
LOCATION_API_PATH,
|
|
13
|
+
FUGITIVE_API_PATH,
|
|
14
|
+
STATIONARY_API_PATH,
|
|
15
|
+
MOBILE_API_PATH,
|
|
16
|
+
GENERIC_CALCULATION_API_PATH,
|
|
17
|
+
TRANSPORTATION_AND_DISTRIBUTION_API_PATH,
|
|
18
|
+
FACTOR_API_PATH,
|
|
19
|
+
FACTOR_SET_API_PATH,
|
|
20
|
+
SEARCH_API_PATH,
|
|
21
|
+
} from "../src/Constants";
|
|
22
|
+
import locationPayload from "./mocks/LocationRequest";
|
|
23
|
+
import commonpayload from "./mocks/CommonRequest";
|
|
24
|
+
import { Client } from "../src/Client";
|
|
25
|
+
import GenericCalculationPayload from "./mocks/GenericCalculationRequest";
|
|
26
|
+
import FactorPayload from "./mocks/FactorRequest";
|
|
27
|
+
import SearchPayload from "./mocks/SearchRequest";
|
|
28
|
+
|
|
29
|
+
type ApiTestCase = {
|
|
30
|
+
name: string;
|
|
31
|
+
func: (payload?: any) => Promise<string>;
|
|
32
|
+
path: string;
|
|
33
|
+
payload?: any;
|
|
34
|
+
pathParams?: string | string[];
|
|
35
|
+
method: "GET" | "POST";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const mockResp = "mock-success-response";
|
|
39
|
+
|
|
40
|
+
const testCases: ApiTestCase[] = [
|
|
41
|
+
{
|
|
42
|
+
name: "Location API",
|
|
43
|
+
func: locationApi.calculate,
|
|
44
|
+
path: LOCATION_API_PATH,
|
|
45
|
+
payload: locationPayload,
|
|
46
|
+
method: "POST",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "Fugitive API",
|
|
50
|
+
func: fugitiveApi.calculate,
|
|
51
|
+
path: FUGITIVE_API_PATH,
|
|
52
|
+
payload: commonpayload,
|
|
53
|
+
method: "POST",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "Mobile API",
|
|
57
|
+
func: mobileApi.calculate,
|
|
58
|
+
path: MOBILE_API_PATH,
|
|
59
|
+
payload: commonpayload,
|
|
60
|
+
method: "POST",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "Stationary API",
|
|
64
|
+
func: stationaryApi.calculate,
|
|
65
|
+
path: STATIONARY_API_PATH,
|
|
66
|
+
payload: commonpayload,
|
|
67
|
+
method: "POST",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "GenericCalculation API",
|
|
71
|
+
func: GenericCalculation.calculate,
|
|
72
|
+
path: GENERIC_CALCULATION_API_PATH,
|
|
73
|
+
payload: GenericCalculationPayload,
|
|
74
|
+
method: "POST",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "Factor API",
|
|
78
|
+
func: Factors.retrieveFactor,
|
|
79
|
+
path: FACTOR_API_PATH,
|
|
80
|
+
payload: FactorPayload,
|
|
81
|
+
method: "POST",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "Transportation and Distribution API",
|
|
85
|
+
func: TransportationDistributionApi.calculate,
|
|
86
|
+
path: TRANSPORTATION_AND_DISTRIBUTION_API_PATH,
|
|
87
|
+
payload: GenericCalculationPayload,
|
|
88
|
+
method: "POST",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "FactorSet API",
|
|
92
|
+
func: FactorSets.get,
|
|
93
|
+
path: FACTOR_SET_API_PATH,
|
|
94
|
+
method: "GET",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "Search API",
|
|
98
|
+
func: Factors.search,
|
|
99
|
+
path: SEARCH_API_PATH,
|
|
100
|
+
payload: SearchPayload,
|
|
101
|
+
method: "POST",
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
describe("API Test calculate functions", () => {
|
|
106
|
+
let spy: jest.SpyInstance;
|
|
107
|
+
let tokenSpy: jest.SpyInstance;
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
jest.clearAllMocks();
|
|
110
|
+
tokenSpy = jest
|
|
111
|
+
.spyOn(Client as any, "requestToken")
|
|
112
|
+
.mockResolvedValue(
|
|
113
|
+
"eyJpzGciOiJIUzI1NlIsInR5cCI6IkplVCJ9." +
|
|
114
|
+
Buffer.from(
|
|
115
|
+
JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 3600 })
|
|
116
|
+
).toString("base64") +
|
|
117
|
+
".signature"
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
Client.getClient({
|
|
121
|
+
apiKey: "mock-api-key",
|
|
122
|
+
clientId: "mock-client-id",
|
|
123
|
+
orgId: "mock-org-id",
|
|
124
|
+
});
|
|
125
|
+
spy = jest
|
|
126
|
+
.spyOn(requestModule, "makeApiRequest")
|
|
127
|
+
.mockResolvedValue(mockResp);
|
|
128
|
+
});
|
|
129
|
+
afterEach(() => {
|
|
130
|
+
tokenSpy.mockRestore();
|
|
131
|
+
spy.mockRestore();
|
|
132
|
+
});
|
|
133
|
+
describe.each(testCases)(
|
|
134
|
+
"$name",
|
|
135
|
+
({ func, path, payload, pathParams, method }) => {
|
|
136
|
+
it("Should call makeApiRequest with API url", async () => {
|
|
137
|
+
let result;
|
|
138
|
+
if (method === "POST") {
|
|
139
|
+
result = await func(payload);
|
|
140
|
+
} else {
|
|
141
|
+
result =
|
|
142
|
+
pathParams !== undefined
|
|
143
|
+
? await func(pathParams)
|
|
144
|
+
: await func();
|
|
145
|
+
}
|
|
146
|
+
const clientDomain = Client.getInstance().getDomain();
|
|
147
|
+
|
|
148
|
+
let UrlWithParams = `${clientDomain}${path}`;
|
|
149
|
+
if (method === "GET" && pathParams) {
|
|
150
|
+
UrlWithParams = `${clientDomain}${path}/${
|
|
151
|
+
Array.isArray(pathParams) ? pathParams.join("/") : pathParams
|
|
152
|
+
}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const expectedUrl =
|
|
156
|
+
method === "GET" ? UrlWithParams : `${clientDomain}${path}`;
|
|
157
|
+
|
|
158
|
+
const expectedRequest: any = {
|
|
159
|
+
method,
|
|
160
|
+
url: expectedUrl,
|
|
161
|
+
};
|
|
162
|
+
if (method === "POST") expectedRequest.data = payload;
|
|
163
|
+
expect(spy).toHaveBeenCalledWith(expectedRequest);
|
|
164
|
+
expect(result).toBe(mockResp);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Client } from "../src/Client";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import MockAdapter from "axios-mock-adapter";
|
|
4
|
+
import { TOKEN_GENERATION_API } from "../src/Constants";
|
|
5
|
+
import { ClientConfig } from "../src/interfaces/Config";
|
|
6
|
+
|
|
7
|
+
const mock = new MockAdapter(axios);
|
|
8
|
+
|
|
9
|
+
describe("Client initialization and Header Authorization", () => {
|
|
10
|
+
const base64Header = Buffer.from(
|
|
11
|
+
JSON.stringify({ alg: "HS256", typ: "JWT" })
|
|
12
|
+
).toString("base64url");
|
|
13
|
+
const base64Payload = Buffer.from(
|
|
14
|
+
JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 600 })
|
|
15
|
+
).toString("base64url");
|
|
16
|
+
const mockToken = `${base64Header}.${base64Payload}.Signature`;
|
|
17
|
+
|
|
18
|
+
const config = {
|
|
19
|
+
apiKey: " somerandomkey",
|
|
20
|
+
clientId: "emptyclient",
|
|
21
|
+
orgId: "OrgId",
|
|
22
|
+
};
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
mock.reset();
|
|
25
|
+
mock.onGet(TOKEN_GENERATION_API).reply(200, mockToken);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should initialize client and return auth header", async () => {
|
|
29
|
+
await Client.getClient(config);
|
|
30
|
+
const instance = Client.getInstance();
|
|
31
|
+
expect(instance.getAuthHeader()).toEqual({
|
|
32
|
+
Authorization: `Bearer ${mockToken}`,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("Should throw if getInstance called before initiating client", () => {
|
|
37
|
+
Client["instance"] = null;
|
|
38
|
+
expect(() => Client.getInstance()).toThrow(
|
|
39
|
+
"Client is not initialized. Call Client.getClient() first."
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("Should throw validation error for missing dependent config", async () => {
|
|
44
|
+
await expect(
|
|
45
|
+
Client.getClient({ host: "custom-host", clientId: "clientId" })
|
|
46
|
+
).rejects.toThrow(
|
|
47
|
+
'If custom "host" is provided, "authUrl" must also be provided.'
|
|
48
|
+
);
|
|
49
|
+
await expect(
|
|
50
|
+
Client.getClient({ token: "custom token" } as unknown as ClientConfig)
|
|
51
|
+
).rejects.toThrow(
|
|
52
|
+
'If token is provided directly, "clientId" must also be provided.'
|
|
53
|
+
);
|
|
54
|
+
await expect(
|
|
55
|
+
Client.getClient({ apiKey: "abc" } as unknown as ClientConfig)
|
|
56
|
+
).rejects.toThrow(
|
|
57
|
+
'If apiKey is provided , "clientId" and "OrgId" must also be provided.'
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
it("should not call refresh token if userProvidedToken is set and set token to userProvided", async () => {
|
|
61
|
+
const base64Header = Buffer.from(
|
|
62
|
+
JSON.stringify({ alg: "HS256", typ: "JWT" })
|
|
63
|
+
).toString("base64url");
|
|
64
|
+
const base64Payload = Buffer.from(
|
|
65
|
+
JSON.stringify({ exp: Math.floor(Date.now() / 1000) + 600 })
|
|
66
|
+
).toString("base64url");
|
|
67
|
+
const mockToken = `${base64Header}.${base64Payload}.Signature`;
|
|
68
|
+
|
|
69
|
+
const userToken = mockToken;
|
|
70
|
+
const spy = jest.spyOn(Client as any, "requestToken");
|
|
71
|
+
await Client.getClient({
|
|
72
|
+
token: userToken,
|
|
73
|
+
clientId: "sample id",
|
|
74
|
+
} as any);
|
|
75
|
+
const instance = Client.getInstance();
|
|
76
|
+
await instance.refreshToken();
|
|
77
|
+
expect(instance.getAuthHeader()).toEqual({
|
|
78
|
+
Authorization: `Bearer ${userToken}`,
|
|
79
|
+
});
|
|
80
|
+
expect(spy).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CommonRequest } from '../../src/interfaces/Api';
|
|
2
|
+
|
|
3
|
+
const commonPayload: CommonRequest = {
|
|
4
|
+
location: {
|
|
5
|
+
country: 'usa',
|
|
6
|
+
stateProvince: 'new york',
|
|
7
|
+
},
|
|
8
|
+
time: {
|
|
9
|
+
date: '2025-06-10',
|
|
10
|
+
},
|
|
11
|
+
activity: {
|
|
12
|
+
value: 10,
|
|
13
|
+
unit: 'unit',
|
|
14
|
+
type: 'type',
|
|
15
|
+
},
|
|
16
|
+
includeDetails: false,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default commonPayload;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FactorRequest } from "../../src/interfaces/Api";
|
|
2
|
+
|
|
3
|
+
const FactorPayload: FactorRequest = {
|
|
4
|
+
location: {
|
|
5
|
+
"country": "GBR"
|
|
6
|
+
},
|
|
7
|
+
time: {
|
|
8
|
+
date: "2025-06-10",
|
|
9
|
+
},
|
|
10
|
+
activity: {
|
|
11
|
+
"type":"electricity",
|
|
12
|
+
"unit": "kwh"
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default FactorPayload;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CalculationRequest } from "../../src/interfaces/Api";
|
|
2
|
+
|
|
3
|
+
const GenericCalculationPayload: CalculationRequest = {
|
|
4
|
+
location: {
|
|
5
|
+
country: "usa",
|
|
6
|
+
},
|
|
7
|
+
time: {
|
|
8
|
+
date: "2025-06-10",
|
|
9
|
+
},
|
|
10
|
+
activity: {
|
|
11
|
+
type: "electricity:default factor",
|
|
12
|
+
value: 0.1,
|
|
13
|
+
unit: "kwh",
|
|
14
|
+
|
|
15
|
+
},
|
|
16
|
+
includeDetails: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default GenericCalculationPayload;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LocationRequest } from '../../src/interfaces/Api';
|
|
2
|
+
|
|
3
|
+
const locationPayload: LocationRequest = {
|
|
4
|
+
location: {
|
|
5
|
+
country: 'usa',
|
|
6
|
+
stateProvince: 'new york',
|
|
7
|
+
},
|
|
8
|
+
time: {
|
|
9
|
+
date: '2025-06-10',
|
|
10
|
+
},
|
|
11
|
+
activity: {
|
|
12
|
+
value: 100, // Required
|
|
13
|
+
type: 'electricity', // Optional
|
|
14
|
+
unit: 'kWh', // Optional
|
|
15
|
+
},
|
|
16
|
+
includeDetails: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default locationPayload;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { SearchRequest } from "../../src/interfaces/Api";
|
|
2
|
+
|
|
3
|
+
const SearchPayload: SearchRequest = {
|
|
4
|
+
location: {
|
|
5
|
+
"country": "usa"
|
|
6
|
+
},
|
|
7
|
+
time: {
|
|
8
|
+
date: "2025-06-10",
|
|
9
|
+
},
|
|
10
|
+
activity: {
|
|
11
|
+
"search": "electricity"
|
|
12
|
+
},
|
|
13
|
+
pagination:{
|
|
14
|
+
"page": 10,
|
|
15
|
+
"size": 1
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default SearchPayload;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import axios, { Method } from "axios";
|
|
2
|
+
import { makeApiRequest } from "../src/request";
|
|
3
|
+
import { Client } from "../src/Client";
|
|
4
|
+
import { RequestConfig } from "../src/interfaces/Config";
|
|
5
|
+
import { get } from "http";
|
|
6
|
+
import { CLIENT_SOURCE_HEADER } from "../src/Constants";
|
|
7
|
+
|
|
8
|
+
jest.mock("axios");
|
|
9
|
+
|
|
10
|
+
describe("makeApiRequest", () => {
|
|
11
|
+
const mockRefreshToken = jest.fn();
|
|
12
|
+
const mockGetAuthHeader = jest.fn();
|
|
13
|
+
const mockGetClientId = jest.fn();
|
|
14
|
+
const mockGetClientSource = jest.fn();
|
|
15
|
+
const mockAxiosRequest = axios.request as jest.Mock;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
jest.clearAllMocks();
|
|
19
|
+
|
|
20
|
+
const mockClient = {
|
|
21
|
+
refreshToken: mockRefreshToken,
|
|
22
|
+
getAuthHeader: mockGetAuthHeader,
|
|
23
|
+
getClientId: mockGetClientId,
|
|
24
|
+
getClientSource : mockGetClientSource
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
mockGetAuthHeader.mockReturnValue({ Authorization: "Bearer mock-token" });
|
|
28
|
+
mockGetClientId.mockReturnValue("mock-client-id");
|
|
29
|
+
mockGetClientSource.mockReturnValue("excel");
|
|
30
|
+
|
|
31
|
+
jest.spyOn(Client, "getInstance").mockReturnValue(mockClient as any);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should make a successful API call with full config", async () => {
|
|
35
|
+
const expectedResponse = { data: { success: true } };
|
|
36
|
+
mockAxiosRequest.mockResolvedValue(expectedResponse);
|
|
37
|
+
|
|
38
|
+
const config: RequestConfig = {
|
|
39
|
+
method: "POST" as Method,
|
|
40
|
+
url: "/test-route",
|
|
41
|
+
data: { key: "value" },
|
|
42
|
+
params: { id: 1 },
|
|
43
|
+
headers: { custom: "header" },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const result = await makeApiRequest<typeof expectedResponse.data>(config);
|
|
47
|
+
|
|
48
|
+
expect(mockRefreshToken).toHaveBeenCalled();
|
|
49
|
+
expect(mockGetAuthHeader).toHaveBeenCalled();
|
|
50
|
+
expect(mockGetClientId).toHaveBeenCalled();
|
|
51
|
+
expect(mockGetClientSource).toHaveBeenCalled();
|
|
52
|
+
|
|
53
|
+
expect(mockAxiosRequest).toHaveBeenCalledWith({
|
|
54
|
+
method: "POST",
|
|
55
|
+
url: "/test-route",
|
|
56
|
+
data: { key: "value" },
|
|
57
|
+
params: { id: 1 },
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: "Bearer mock-token",
|
|
60
|
+
custom: "header",
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"X-IBM-Client-Id": "ghgemissions-mock-client-id",
|
|
63
|
+
[CLIENT_SOURCE_HEADER]: "excel"
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(result).toEqual(expectedResponse.data);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should make a request with minimal config", async () => {
|
|
71
|
+
const expectedResponse = { data: { message: "ok" } };
|
|
72
|
+
mockAxiosRequest.mockResolvedValue(expectedResponse);
|
|
73
|
+
|
|
74
|
+
const config: RequestConfig = {
|
|
75
|
+
method: "GET" as Method,
|
|
76
|
+
url: "/test-route",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const result = await makeApiRequest<typeof expectedResponse.data>(config);
|
|
80
|
+
|
|
81
|
+
expect(mockAxiosRequest).toHaveBeenCalledWith({
|
|
82
|
+
method: "GET",
|
|
83
|
+
url: "/test-route",
|
|
84
|
+
data: undefined,
|
|
85
|
+
params: undefined,
|
|
86
|
+
headers: {
|
|
87
|
+
Authorization: "Bearer mock-token",
|
|
88
|
+
"Content-Type": "application/json",
|
|
89
|
+
"X-IBM-Client-Id": "ghgemissions-mock-client-id",
|
|
90
|
+
[CLIENT_SOURCE_HEADER]: "excel"
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result).toEqual(expectedResponse.data);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should throw an error if axios request fails", async () => {
|
|
98
|
+
const error = new Error("Network failure");
|
|
99
|
+
mockAxiosRequest.mockRejectedValue(error);
|
|
100
|
+
|
|
101
|
+
const config: RequestConfig = {
|
|
102
|
+
method: "GET" as Method,
|
|
103
|
+
url: "/fail",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await expect(makeApiRequest(config)).rejects.toThrow("Network failure");
|
|
107
|
+
|
|
108
|
+
expect(mockRefreshToken).toHaveBeenCalled();
|
|
109
|
+
expect(mockAxiosRequest).toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export function generateMockjwt(expiryInSecsFromNow : number): string {
|
|
2
|
+
const header = Buffer.from(JSON.stringify({alg: 'HS256', typ: 'JWT'})).toString('base64url');
|
|
3
|
+
const payload = Buffer.from(JSON.stringify({exp: Math.floor(Date.now()/1000) + expiryInSecsFromNow})).toString('base64url');
|
|
4
|
+
return `${header}.${payload}.signature`;
|
|
5
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Client } from "../src/Client";
|
|
2
|
+
import * as utils from "../src/utils";
|
|
3
|
+
import mockAxios from "axios";
|
|
4
|
+
import MockAdapter from "axios-mock-adapter";
|
|
5
|
+
import { generateMockjwt } from "./testUtils";
|
|
6
|
+
import { TOKEN_GENERATION_API } from "../src/Constants";
|
|
7
|
+
|
|
8
|
+
const mock = new MockAdapter(mockAxios);
|
|
9
|
+
describe("Token refreshing", () => {
|
|
10
|
+
const config = { apiKey: "abc", clientId: "234", orgId:"123" };
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
mock.reset();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should not refresh if token is still valid", async () => {
|
|
17
|
+
const freshToken = generateMockjwt(600);
|
|
18
|
+
mock.onGet(TOKEN_GENERATION_API).reply(200, { freshToken });
|
|
19
|
+
|
|
20
|
+
jest
|
|
21
|
+
.spyOn(utils, "findExpiryTime")
|
|
22
|
+
.mockReturnValue(Math.floor(Date.now() / 1000) + 600);
|
|
23
|
+
|
|
24
|
+
await Client.getClient(config);
|
|
25
|
+
const instance = Client.getInstance();
|
|
26
|
+
|
|
27
|
+
const spy = jest.spyOn(Client as any, "requestToken");
|
|
28
|
+
|
|
29
|
+
await instance.refreshToken();
|
|
30
|
+
expect(spy).not.toHaveBeenCalled();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should not refresh if token is still valid", async () => {
|
|
34
|
+
const shortLiveToken = generateMockjwt(1);
|
|
35
|
+
const refreshToken = generateMockjwt(600);
|
|
36
|
+
mock
|
|
37
|
+
.onGet(TOKEN_GENERATION_API)
|
|
38
|
+
.reply(200, { refreshToken });
|
|
39
|
+
|
|
40
|
+
jest
|
|
41
|
+
.spyOn(utils, "findExpiryTime")
|
|
42
|
+
.mockReturnValue(Math.floor(Date.now() / 1000) + 1);
|
|
43
|
+
|
|
44
|
+
await Client.getClient(config);
|
|
45
|
+
const instance = Client.getInstance();
|
|
46
|
+
|
|
47
|
+
const spy = jest.spyOn(Client as any, "requestToken");
|
|
48
|
+
|
|
49
|
+
await instance.refreshToken();
|
|
50
|
+
expect(spy).not.toHaveBeenCalledTimes(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2019",
|
|
4
|
+
"module": "CommonJS",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationDir": "./dist/types",
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src"],
|
|
13
|
+
"exclude": ["test", "**/*.test.ts"]
|
|
14
|
+
|
|
15
|
+
}
|