@unito/integration-sdk 2.3.0 → 2.3.2
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/dist/src/index.cjs +23 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/middlewares/credentials.d.ts +17 -1
- package/dist/src/resources/logger.d.ts +3 -1
- package/dist/src/resources/logger.js +7 -1
- package/dist/src/resources/provider.js +15 -0
- package/dist/test/resources/logger.test.js +19 -1
- package/dist/test/resources/provider.test.js +19 -7
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/middlewares/credentials.ts +17 -1
- package/src/resources/logger.ts +10 -2
- package/src/resources/provider.ts +21 -0
- package/test/resources/logger.test.ts +23 -1
- package/test/resources/provider.test.ts +27 -7
package/dist/src/index.cjs
CHANGED
|
@@ -67,8 +67,10 @@ const LOGMETA_BLACKLIST = [
|
|
|
67
67
|
* Logger class that can be configured with metadata add creation and when logging to add additional context to your logs.
|
|
68
68
|
*/
|
|
69
69
|
class Logger {
|
|
70
|
+
isDisabled;
|
|
70
71
|
metadata;
|
|
71
|
-
constructor(metadata = {}) {
|
|
72
|
+
constructor(metadata = {}, isDisabled = false) {
|
|
73
|
+
this.isDisabled = isDisabled;
|
|
72
74
|
this.metadata = structuredClone(metadata);
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
@@ -142,6 +144,9 @@ class Logger {
|
|
|
142
144
|
this.metadata = {};
|
|
143
145
|
}
|
|
144
146
|
send(logLevel, message, metadata) {
|
|
147
|
+
if (this.isDisabled) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
145
150
|
// We need to provide the date to Datadog. Otherwise, the date is set to when they receive the log.
|
|
146
151
|
const date = Date.now();
|
|
147
152
|
if (message.length > MAX_LOG_MESSAGE_SIZE) {
|
|
@@ -188,6 +193,7 @@ class Logger {
|
|
|
188
193
|
return prunedMetadata;
|
|
189
194
|
}
|
|
190
195
|
}
|
|
196
|
+
const NULL_LOGGER = new Logger({}, true);
|
|
191
197
|
|
|
192
198
|
/**
|
|
193
199
|
* The Cache class provides caching capabilities that can be used across your integration.
|
|
@@ -1409,6 +1415,7 @@ class Provider {
|
|
|
1409
1415
|
}
|
|
1410
1416
|
}
|
|
1411
1417
|
const callToProvider = async () => {
|
|
1418
|
+
const beforeRequestTimestamp = process.hrtime.bigint();
|
|
1412
1419
|
let response;
|
|
1413
1420
|
try {
|
|
1414
1421
|
response = await fetch(absoluteUrl, {
|
|
@@ -1430,6 +1437,20 @@ class Provider {
|
|
|
1430
1437
|
}
|
|
1431
1438
|
throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate', options);
|
|
1432
1439
|
}
|
|
1440
|
+
const afterRequestTimestamp = process.hrtime.bigint();
|
|
1441
|
+
const requestDurationInNS = Number(afterRequestTimestamp - beforeRequestTimestamp);
|
|
1442
|
+
const requestDurationInMs = (requestDurationInNS / 1_000_000) | 0;
|
|
1443
|
+
options.logger.info(`Connector API Request ${options.method} ${absoluteUrl} ${response.status} - ${requestDurationInMs} ms`, {
|
|
1444
|
+
duration: requestDurationInNS,
|
|
1445
|
+
http: {
|
|
1446
|
+
method: options.method,
|
|
1447
|
+
status_code: response.status,
|
|
1448
|
+
content_type: headers['Content-Type'],
|
|
1449
|
+
url_details: {
|
|
1450
|
+
path: absoluteUrl,
|
|
1451
|
+
},
|
|
1452
|
+
},
|
|
1453
|
+
});
|
|
1433
1454
|
if (response.status >= 400) {
|
|
1434
1455
|
const textResult = await response.text();
|
|
1435
1456
|
throw this.handleError(response.status, textResult, options);
|
|
@@ -1511,5 +1532,6 @@ exports.Cache = Cache;
|
|
|
1511
1532
|
exports.Handler = Handler;
|
|
1512
1533
|
exports.HttpErrors = httpErrors;
|
|
1513
1534
|
exports.Integration = Integration;
|
|
1535
|
+
exports.NULL_LOGGER = NULL_LOGGER;
|
|
1514
1536
|
exports.Provider = Provider;
|
|
1515
1537
|
exports.getApplicableFilters = getApplicableFilters;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export type { Filter } from './middlewares/filters.js';
|
|
|
9
9
|
export * as HttpErrors from './httpErrors.js';
|
|
10
10
|
export { getApplicableFilters } from './helpers.js';
|
|
11
11
|
export * from './resources/context.js';
|
|
12
|
-
export { type default as Logger } from './resources/logger.js';
|
|
12
|
+
export { type default as Logger, NULL_LOGGER } from './resources/logger.js';
|
package/dist/src/index.js
CHANGED
|
@@ -7,4 +7,5 @@ export { Provider, } from './resources/provider.js';
|
|
|
7
7
|
export * as HttpErrors from './httpErrors.js';
|
|
8
8
|
export { getApplicableFilters } from './helpers.js';
|
|
9
9
|
export * from './resources/context.js';
|
|
10
|
+
export { NULL_LOGGER } from './resources/logger.js';
|
|
10
11
|
/* c8 ignore stop */
|
|
@@ -6,9 +6,25 @@ declare global {
|
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* The credentials object passed to every handler function.
|
|
11
|
+
*
|
|
12
|
+
* It contains the parsed credentials payload associated with the request through the X-Unito-Credentials header.
|
|
13
|
+
*/
|
|
9
14
|
export type Credentials = {
|
|
15
|
+
/**
|
|
16
|
+
* The access token for the provider.
|
|
17
|
+
*/
|
|
10
18
|
accessToken?: string;
|
|
11
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The id of the unito credential record.
|
|
21
|
+
*
|
|
22
|
+
* This is not available on the initial call to /me before the creation of the credential.
|
|
23
|
+
*/
|
|
24
|
+
unitoCredentialId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The id of the unito user record.
|
|
27
|
+
*/
|
|
12
28
|
unitoUserId?: string;
|
|
13
29
|
[keys: string]: unknown;
|
|
14
30
|
};
|
|
@@ -10,8 +10,9 @@ type ForbidenMetadataKey = 'message';
|
|
|
10
10
|
* Logger class that can be configured with metadata add creation and when logging to add additional context to your logs.
|
|
11
11
|
*/
|
|
12
12
|
export default class Logger {
|
|
13
|
+
private isDisabled;
|
|
13
14
|
private metadata;
|
|
14
|
-
constructor(metadata?: Metadata);
|
|
15
|
+
constructor(metadata?: Metadata, isDisabled?: boolean);
|
|
15
16
|
/**
|
|
16
17
|
* Logs a message with the 'log' log level.
|
|
17
18
|
* @param message The message to be logged.
|
|
@@ -68,4 +69,5 @@ export default class Logger {
|
|
|
68
69
|
private static snakifyKeys;
|
|
69
70
|
private static pruneSensitiveMetadata;
|
|
70
71
|
}
|
|
72
|
+
export declare const NULL_LOGGER: Logger;
|
|
71
73
|
export {};
|
|
@@ -39,8 +39,10 @@ const LOGMETA_BLACKLIST = [
|
|
|
39
39
|
* Logger class that can be configured with metadata add creation and when logging to add additional context to your logs.
|
|
40
40
|
*/
|
|
41
41
|
export default class Logger {
|
|
42
|
+
isDisabled;
|
|
42
43
|
metadata;
|
|
43
|
-
constructor(metadata = {}) {
|
|
44
|
+
constructor(metadata = {}, isDisabled = false) {
|
|
45
|
+
this.isDisabled = isDisabled;
|
|
44
46
|
this.metadata = structuredClone(metadata);
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -114,6 +116,9 @@ export default class Logger {
|
|
|
114
116
|
this.metadata = {};
|
|
115
117
|
}
|
|
116
118
|
send(logLevel, message, metadata) {
|
|
119
|
+
if (this.isDisabled) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
117
122
|
// We need to provide the date to Datadog. Otherwise, the date is set to when they receive the log.
|
|
118
123
|
const date = Date.now();
|
|
119
124
|
if (message.length > MAX_LOG_MESSAGE_SIZE) {
|
|
@@ -160,3 +165,4 @@ export default class Logger {
|
|
|
160
165
|
return prunedMetadata;
|
|
161
166
|
}
|
|
162
167
|
}
|
|
168
|
+
export const NULL_LOGGER = new Logger({}, true);
|
|
@@ -284,6 +284,7 @@ export class Provider {
|
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
const callToProvider = async () => {
|
|
287
|
+
const beforeRequestTimestamp = process.hrtime.bigint();
|
|
287
288
|
let response;
|
|
288
289
|
try {
|
|
289
290
|
response = await fetch(absoluteUrl, {
|
|
@@ -305,6 +306,20 @@ export class Provider {
|
|
|
305
306
|
}
|
|
306
307
|
throw this.handleError(500, 'Unexpected error while calling the provider - this is not normal, investigate', options);
|
|
307
308
|
}
|
|
309
|
+
const afterRequestTimestamp = process.hrtime.bigint();
|
|
310
|
+
const requestDurationInNS = Number(afterRequestTimestamp - beforeRequestTimestamp);
|
|
311
|
+
const requestDurationInMs = (requestDurationInNS / 1_000_000) | 0;
|
|
312
|
+
options.logger.info(`Connector API Request ${options.method} ${absoluteUrl} ${response.status} - ${requestDurationInMs} ms`, {
|
|
313
|
+
duration: requestDurationInNS,
|
|
314
|
+
http: {
|
|
315
|
+
method: options.method,
|
|
316
|
+
status_code: response.status,
|
|
317
|
+
content_type: headers['Content-Type'],
|
|
318
|
+
url_details: {
|
|
319
|
+
path: absoluteUrl,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
308
323
|
if (response.status >= 400) {
|
|
309
324
|
const textResult = await response.text();
|
|
310
325
|
throw this.handleError(response.status, textResult, options);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
|
-
import { default as Logger } from '../../src/resources/logger.js';
|
|
3
|
+
import { default as Logger, NULL_LOGGER } from '../../src/resources/logger.js';
|
|
4
4
|
describe('Logger', () => {
|
|
5
5
|
it('metadata', () => {
|
|
6
6
|
let metadata = { correlation_id: 'test' };
|
|
@@ -140,4 +140,22 @@ describe('Logger', () => {
|
|
|
140
140
|
assert.deepEqual(actual['http'], { method: 'GET', status_code: 200, jwt: '[REDACTED]' });
|
|
141
141
|
assert.deepEqual(actual['user']['contact'], { email: '[REDACTED]', first_name: '[REDACTED]' });
|
|
142
142
|
});
|
|
143
|
+
it(`NULL_LOGGER should not log`, testContext => {
|
|
144
|
+
const logger = NULL_LOGGER;
|
|
145
|
+
const logSpy = testContext.mock.method(global.console, 'log', () => { });
|
|
146
|
+
const errorSpy = testContext.mock.method(global.console, 'error', () => { });
|
|
147
|
+
const warnSpy = testContext.mock.method(global.console, 'warn', () => { });
|
|
148
|
+
const infoSpy = testContext.mock.method(global.console, 'info', () => { });
|
|
149
|
+
const debugSpy = testContext.mock.method(global.console, 'debug', () => { });
|
|
150
|
+
logger.log('test');
|
|
151
|
+
logger.info('test');
|
|
152
|
+
logger.warn('test');
|
|
153
|
+
logger.error('test');
|
|
154
|
+
logger.debug('test');
|
|
155
|
+
assert.strictEqual(logSpy.mock.calls.length, 0);
|
|
156
|
+
assert.strictEqual(errorSpy.mock.calls.length, 0);
|
|
157
|
+
assert.strictEqual(warnSpy.mock.calls.length, 0);
|
|
158
|
+
assert.strictEqual(infoSpy.mock.calls.length, 0);
|
|
159
|
+
assert.strictEqual(debugSpy.mock.calls.length, 0);
|
|
160
|
+
});
|
|
143
161
|
});
|
|
@@ -50,7 +50,7 @@ describe('Provider', () => {
|
|
|
50
50
|
]);
|
|
51
51
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
52
52
|
});
|
|
53
|
-
it('
|
|
53
|
+
it('accepts text/html type response', async (context) => {
|
|
54
54
|
const response = new Response('', {
|
|
55
55
|
status: 200,
|
|
56
56
|
headers: { 'Content-Type': 'text/html; charset=UTF-8' },
|
|
@@ -79,7 +79,7 @@ describe('Provider', () => {
|
|
|
79
79
|
]);
|
|
80
80
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: '' });
|
|
81
81
|
});
|
|
82
|
-
it('
|
|
82
|
+
it('accepts application/schema+json type response', async (context) => {
|
|
83
83
|
const response = new Response('{"data": "value"}', {
|
|
84
84
|
status: 200,
|
|
85
85
|
headers: { 'Content-Type': 'application/schema+json; charset=UTF-8' },
|
|
@@ -108,7 +108,7 @@ describe('Provider', () => {
|
|
|
108
108
|
]);
|
|
109
109
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
110
110
|
});
|
|
111
|
-
it('
|
|
111
|
+
it('accepts application/swagger+json type response', async (context) => {
|
|
112
112
|
const response = new Response('{"data": "value"}', {
|
|
113
113
|
status: 200,
|
|
114
114
|
headers: { 'Content-Type': 'application/swagger+json; charset=UTF-8' },
|
|
@@ -137,7 +137,7 @@ describe('Provider', () => {
|
|
|
137
137
|
]);
|
|
138
138
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
139
139
|
});
|
|
140
|
-
it('
|
|
140
|
+
it('accepts application/vnd.oracle.resource+json type response', async (context) => {
|
|
141
141
|
const response = new Response('{"data": "value"}', {
|
|
142
142
|
status: 200,
|
|
143
143
|
headers: { 'Content-Type': 'application/vnd.oracle.resource+json; type=collection; charset=UTF-8' },
|
|
@@ -166,7 +166,7 @@ describe('Provider', () => {
|
|
|
166
166
|
]);
|
|
167
167
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
168
168
|
});
|
|
169
|
-
it('
|
|
169
|
+
it('returns the raw response body if specified', async (context) => {
|
|
170
170
|
const response = new Response(`IMAGINE A HUGE PAYLOAD`, {
|
|
171
171
|
status: 200,
|
|
172
172
|
headers: { 'Content-Type': 'image/png' },
|
|
@@ -244,7 +244,7 @@ describe('Provider', () => {
|
|
|
244
244
|
]);
|
|
245
245
|
assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
|
|
246
246
|
});
|
|
247
|
-
it('
|
|
247
|
+
it('accepts an array as body for post request', async (context) => {
|
|
248
248
|
const response = new Response('{"data": "value"}', {
|
|
249
249
|
status: 201,
|
|
250
250
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -485,7 +485,7 @@ describe('Provider', () => {
|
|
|
485
485
|
assert.ok(error instanceof HttpErrors.HttpError);
|
|
486
486
|
assert.equal(error.message, 'Weird provider behavior');
|
|
487
487
|
});
|
|
488
|
-
it('
|
|
488
|
+
it('contains the credential in the custom error handler', async (context) => {
|
|
489
489
|
const provider = new Provider({
|
|
490
490
|
prepareRequest: requestOptions => {
|
|
491
491
|
return {
|
|
@@ -761,4 +761,16 @@ describe('Provider', () => {
|
|
|
761
761
|
assert.ok(error instanceof HttpErrors.RateLimitExceededError);
|
|
762
762
|
assert.equal(error.message, 'response body');
|
|
763
763
|
});
|
|
764
|
+
it('logs provider requests', async (context) => {
|
|
765
|
+
const response = new Response(undefined, { status: 201 });
|
|
766
|
+
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
767
|
+
const loggerStub = context.mock.method(logger, 'info');
|
|
768
|
+
await provider.get('/endpoint/123', {
|
|
769
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
770
|
+
signal: new AbortController().signal,
|
|
771
|
+
logger: logger,
|
|
772
|
+
});
|
|
773
|
+
assert.equal(loggerStub.mock.callCount(), 1);
|
|
774
|
+
assert.match(String(loggerStub.mock.calls[0]?.arguments[0]), /Connector API Request GET www.myApi.com\/endpoint\/123 201 - \d+ ms/);
|
|
775
|
+
});
|
|
764
776
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/errors.ts","../src/handler.ts","../src/helpers.ts","../src/httpErrors.ts","../src/index.ts","../src/integration.ts","../src/middlewares/correlationId.ts","../src/middlewares/credentials.ts","../src/middlewares/errors.ts","../src/middlewares/filters.ts","../src/middlewares/finish.ts","../src/middlewares/health.ts","../src/middlewares/logger.ts","../src/middlewares/notFound.ts","../src/middlewares/relations.ts","../src/middlewares/search.ts","../src/middlewares/secrets.ts","../src/middlewares/selects.ts","../src/middlewares/signal.ts","../src/middlewares/start.ts","../src/resources/cache.ts","../src/resources/context.ts","../src/resources/logger.ts","../src/resources/provider.ts","../test/errors.test.ts","../test/handler.test.ts","../test/helpers.test.ts","../test/integration.test.ts","../test/middlewares/correlationId.test.ts","../test/middlewares/credentials.test.ts","../test/middlewares/errors.test.ts","../test/middlewares/filters.test.ts","../test/middlewares/finish.test.ts","../test/middlewares/health.test.ts","../test/middlewares/logger.test.ts","../test/middlewares/notFound.test.ts","../test/middlewares/relations.test.ts","../test/middlewares/search.test.ts","../test/middlewares/secrets.test.ts","../test/middlewares/selects.test.ts","../test/middlewares/signal.test.ts","../test/middlewares/start.test.ts","../test/resources/cache.test.ts","../test/resources/logger.test.ts","../test/resources/provider.test.ts"],"version":"5.8.3"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -15,5 +15,5 @@ export type { Filter } from './middlewares/filters.js';
|
|
|
15
15
|
export * as HttpErrors from './httpErrors.js';
|
|
16
16
|
export { getApplicableFilters } from './helpers.js';
|
|
17
17
|
export * from './resources/context.js';
|
|
18
|
-
export { type default as Logger } from './resources/logger.js';
|
|
18
|
+
export { type default as Logger, NULL_LOGGER } from './resources/logger.js';
|
|
19
19
|
/* c8 ignore stop */
|
|
@@ -10,9 +10,25 @@ declare global {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The credentials object passed to every handler function.
|
|
15
|
+
*
|
|
16
|
+
* It contains the parsed credentials payload associated with the request through the X-Unito-Credentials header.
|
|
17
|
+
*/
|
|
13
18
|
export type Credentials = {
|
|
19
|
+
/**
|
|
20
|
+
* The access token for the provider.
|
|
21
|
+
*/
|
|
14
22
|
accessToken?: string;
|
|
15
|
-
|
|
23
|
+
/**
|
|
24
|
+
* The id of the unito credential record.
|
|
25
|
+
*
|
|
26
|
+
* This is not available on the initial call to /me before the creation of the credential.
|
|
27
|
+
*/
|
|
28
|
+
unitoCredentialId?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The id of the unito user record.
|
|
31
|
+
*/
|
|
16
32
|
unitoUserId?: string;
|
|
17
33
|
[keys: string]: unknown;
|
|
18
34
|
};
|
package/src/resources/logger.ts
CHANGED
|
@@ -50,8 +50,10 @@ const LOGMETA_BLACKLIST = [
|
|
|
50
50
|
*/
|
|
51
51
|
export default class Logger {
|
|
52
52
|
private metadata: Metadata;
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
constructor(
|
|
54
|
+
metadata: Metadata = {},
|
|
55
|
+
private isDisabled: boolean = false,
|
|
56
|
+
) {
|
|
55
57
|
this.metadata = structuredClone(metadata);
|
|
56
58
|
}
|
|
57
59
|
|
|
@@ -138,6 +140,10 @@ export default class Logger {
|
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
private send(logLevel: LogLevel, message: string, metadata?: Metadata): void {
|
|
143
|
+
if (this.isDisabled) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
141
147
|
// We need to provide the date to Datadog. Otherwise, the date is set to when they receive the log.
|
|
142
148
|
const date = Date.now();
|
|
143
149
|
|
|
@@ -191,3 +197,5 @@ export default class Logger {
|
|
|
191
197
|
return prunedMetadata;
|
|
192
198
|
}
|
|
193
199
|
}
|
|
200
|
+
|
|
201
|
+
export const NULL_LOGGER = new Logger({}, true);
|
|
@@ -388,6 +388,8 @@ export class Provider {
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
const callToProvider = async (): Promise<Response<T>> => {
|
|
391
|
+
const beforeRequestTimestamp = process.hrtime.bigint();
|
|
392
|
+
|
|
391
393
|
let response: globalThis.Response;
|
|
392
394
|
|
|
393
395
|
try {
|
|
@@ -420,6 +422,25 @@ export class Provider {
|
|
|
420
422
|
);
|
|
421
423
|
}
|
|
422
424
|
|
|
425
|
+
const afterRequestTimestamp = process.hrtime.bigint();
|
|
426
|
+
const requestDurationInNS = Number(afterRequestTimestamp - beforeRequestTimestamp);
|
|
427
|
+
const requestDurationInMs = (requestDurationInNS / 1_000_000) | 0;
|
|
428
|
+
|
|
429
|
+
options.logger.info(
|
|
430
|
+
`Connector API Request ${options.method} ${absoluteUrl} ${response.status} - ${requestDurationInMs} ms`,
|
|
431
|
+
{
|
|
432
|
+
duration: requestDurationInNS,
|
|
433
|
+
http: {
|
|
434
|
+
method: options.method,
|
|
435
|
+
status_code: response.status,
|
|
436
|
+
content_type: headers['Content-Type'],
|
|
437
|
+
url_details: {
|
|
438
|
+
path: absoluteUrl,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
);
|
|
443
|
+
|
|
423
444
|
if (response.status >= 400) {
|
|
424
445
|
const textResult = await response.text();
|
|
425
446
|
throw this.handleError(response.status, textResult, options);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import { describe, it } from 'node:test';
|
|
3
|
-
import { default as Logger, Metadata } from '../../src/resources/logger.js';
|
|
3
|
+
import { default as Logger, Metadata, NULL_LOGGER } from '../../src/resources/logger.js';
|
|
4
4
|
|
|
5
5
|
describe('Logger', () => {
|
|
6
6
|
it('metadata', () => {
|
|
@@ -168,4 +168,26 @@ describe('Logger', () => {
|
|
|
168
168
|
assert.deepEqual(actual['http'], { method: 'GET', status_code: 200, jwt: '[REDACTED]' });
|
|
169
169
|
assert.deepEqual(actual['user']['contact'], { email: '[REDACTED]', first_name: '[REDACTED]' });
|
|
170
170
|
});
|
|
171
|
+
|
|
172
|
+
it(`NULL_LOGGER should not log`, testContext => {
|
|
173
|
+
const logger = NULL_LOGGER;
|
|
174
|
+
|
|
175
|
+
const logSpy = testContext.mock.method(global.console, 'log', () => {});
|
|
176
|
+
const errorSpy = testContext.mock.method(global.console, 'error', () => {});
|
|
177
|
+
const warnSpy = testContext.mock.method(global.console, 'warn', () => {});
|
|
178
|
+
const infoSpy = testContext.mock.method(global.console, 'info', () => {});
|
|
179
|
+
const debugSpy = testContext.mock.method(global.console, 'debug', () => {});
|
|
180
|
+
|
|
181
|
+
logger.log('test');
|
|
182
|
+
logger.info('test');
|
|
183
|
+
logger.warn('test');
|
|
184
|
+
logger.error('test');
|
|
185
|
+
logger.debug('test');
|
|
186
|
+
|
|
187
|
+
assert.strictEqual(logSpy.mock.calls.length, 0);
|
|
188
|
+
assert.strictEqual(errorSpy.mock.calls.length, 0);
|
|
189
|
+
assert.strictEqual(warnSpy.mock.calls.length, 0);
|
|
190
|
+
assert.strictEqual(infoSpy.mock.calls.length, 0);
|
|
191
|
+
assert.strictEqual(debugSpy.mock.calls.length, 0);
|
|
192
|
+
});
|
|
171
193
|
});
|
|
@@ -59,7 +59,7 @@ describe('Provider', () => {
|
|
|
59
59
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
it('
|
|
62
|
+
it('accepts text/html type response', async context => {
|
|
63
63
|
const response = new Response('', {
|
|
64
64
|
status: 200,
|
|
65
65
|
headers: { 'Content-Type': 'text/html; charset=UTF-8' },
|
|
@@ -94,7 +94,7 @@ describe('Provider', () => {
|
|
|
94
94
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: '' });
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
it('
|
|
97
|
+
it('accepts application/schema+json type response', async context => {
|
|
98
98
|
const response = new Response('{"data": "value"}', {
|
|
99
99
|
status: 200,
|
|
100
100
|
headers: { 'Content-Type': 'application/schema+json; charset=UTF-8' },
|
|
@@ -129,7 +129,7 @@ describe('Provider', () => {
|
|
|
129
129
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
it('
|
|
132
|
+
it('accepts application/swagger+json type response', async context => {
|
|
133
133
|
const response = new Response('{"data": "value"}', {
|
|
134
134
|
status: 200,
|
|
135
135
|
headers: { 'Content-Type': 'application/swagger+json; charset=UTF-8' },
|
|
@@ -164,7 +164,7 @@ describe('Provider', () => {
|
|
|
164
164
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
-
it('
|
|
167
|
+
it('accepts application/vnd.oracle.resource+json type response', async context => {
|
|
168
168
|
const response = new Response('{"data": "value"}', {
|
|
169
169
|
status: 200,
|
|
170
170
|
headers: { 'Content-Type': 'application/vnd.oracle.resource+json; type=collection; charset=UTF-8' },
|
|
@@ -199,7 +199,7 @@ describe('Provider', () => {
|
|
|
199
199
|
assert.deepEqual(actualResponse, { status: 200, headers: response.headers, body: { data: 'value' } });
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
-
it('
|
|
202
|
+
it('returns the raw response body if specified', async context => {
|
|
203
203
|
const response = new Response(`IMAGINE A HUGE PAYLOAD`, {
|
|
204
204
|
status: 200,
|
|
205
205
|
headers: { 'Content-Type': 'image/png' },
|
|
@@ -293,7 +293,7 @@ describe('Provider', () => {
|
|
|
293
293
|
assert.deepEqual(actualResponse, { status: 201, headers: response.headers, body: { data: 'value' } });
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
-
it('
|
|
296
|
+
it('accepts an array as body for post request', async context => {
|
|
297
297
|
const response = new Response('{"data": "value"}', {
|
|
298
298
|
status: 201,
|
|
299
299
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -580,7 +580,7 @@ describe('Provider', () => {
|
|
|
580
580
|
assert.equal(error.message, 'Weird provider behavior');
|
|
581
581
|
});
|
|
582
582
|
|
|
583
|
-
it('
|
|
583
|
+
it('contains the credential in the custom error handler', async context => {
|
|
584
584
|
const provider = new Provider({
|
|
585
585
|
prepareRequest: requestOptions => {
|
|
586
586
|
return {
|
|
@@ -911,4 +911,24 @@ describe('Provider', () => {
|
|
|
911
911
|
assert.ok(error instanceof HttpErrors.RateLimitExceededError);
|
|
912
912
|
assert.equal(error.message, 'response body');
|
|
913
913
|
});
|
|
914
|
+
|
|
915
|
+
it('logs provider requests', async context => {
|
|
916
|
+
const response = new Response(undefined, { status: 201 });
|
|
917
|
+
|
|
918
|
+
context.mock.method(global, 'fetch', () => Promise.resolve(response));
|
|
919
|
+
|
|
920
|
+
const loggerStub = context.mock.method(logger, 'info');
|
|
921
|
+
|
|
922
|
+
await provider.get('/endpoint/123', {
|
|
923
|
+
credentials: { apiKey: 'apikey#1111', unitoCredentialId: '123' },
|
|
924
|
+
signal: new AbortController().signal,
|
|
925
|
+
logger: logger,
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
assert.equal(loggerStub.mock.callCount(), 1);
|
|
929
|
+
assert.match(
|
|
930
|
+
String(loggerStub.mock.calls[0]?.arguments[0]),
|
|
931
|
+
/Connector API Request GET www.myApi.com\/endpoint\/123 201 - \d+ ms/,
|
|
932
|
+
);
|
|
933
|
+
});
|
|
914
934
|
});
|