@squiz/optimization-utils 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.
Files changed (108) hide show
  1. package/dist/cloudflare/CloudflareKVHttpService.d.ts +11 -0
  2. package/dist/cloudflare/CloudflareKVHttpService.js +19 -0
  3. package/dist/cloudflare/CloudflareKVHttpService.js.map +1 -0
  4. package/dist/cloudflare/ImplCloudflareKVHttpService.d.ts +22 -0
  5. package/dist/cloudflare/ImplCloudflareKVHttpService.js +96 -0
  6. package/dist/cloudflare/ImplCloudflareKVHttpService.js.map +1 -0
  7. package/dist/config/ConfigurationLoader.d.ts +23 -0
  8. package/dist/config/ConfigurationLoader.js +99 -0
  9. package/dist/config/ConfigurationLoader.js.map +1 -0
  10. package/dist/date/DateManipulator.d.ts +6 -0
  11. package/dist/date/DateManipulator.js +21 -0
  12. package/dist/date/DateManipulator.js.map +1 -0
  13. package/dist/event/AggregateRoot.d.ts +4 -0
  14. package/dist/event/AggregateRoot.js +3 -0
  15. package/dist/event/AggregateRoot.js.map +1 -0
  16. package/dist/event/DomainEvent.d.ts +13 -0
  17. package/dist/event/DomainEvent.js +28 -0
  18. package/dist/event/DomainEvent.js.map +1 -0
  19. package/dist/event/DynamoDBEventMapper.d.ts +36 -0
  20. package/dist/event/DynamoDBEventMapper.js +55 -0
  21. package/dist/event/DynamoDBEventMapper.js.map +1 -0
  22. package/dist/event/EventHandler.d.ts +14 -0
  23. package/dist/event/EventHandler.js +43 -0
  24. package/dist/event/EventHandler.js.map +1 -0
  25. package/dist/exception/DomainException.d.ts +18 -0
  26. package/dist/exception/DomainException.js +41 -0
  27. package/dist/exception/DomainException.js.map +1 -0
  28. package/dist/httpClient/FetchHttpClient.d.ts +7 -0
  29. package/dist/httpClient/FetchHttpClient.js +86 -0
  30. package/dist/httpClient/FetchHttpClient.js.map +1 -0
  31. package/dist/httpClient/HttpClient.d.ts +25 -0
  32. package/dist/httpClient/HttpClient.js +45 -0
  33. package/dist/httpClient/HttpClient.js.map +1 -0
  34. package/dist/httpClient/HttpRequestBuilder.d.ts +22 -0
  35. package/dist/httpClient/HttpRequestBuilder.js +126 -0
  36. package/dist/httpClient/HttpRequestBuilder.js.map +1 -0
  37. package/dist/logger/Logger.d.ts +10 -0
  38. package/dist/logger/Logger.js +30 -0
  39. package/dist/logger/Logger.js.map +1 -0
  40. package/dist/logger/LoggerMessage.d.ts +27 -0
  41. package/dist/logger/LoggerMessage.js +94 -0
  42. package/dist/logger/LoggerMessage.js.map +1 -0
  43. package/dist/logger/RemoteLogger.d.ts +30 -0
  44. package/dist/logger/RemoteLogger.js +35 -0
  45. package/dist/logger/RemoteLogger.js.map +1 -0
  46. package/dist/logger/SquizRemoteLogger.d.ts +53 -0
  47. package/dist/logger/SquizRemoteLogger.js +128 -0
  48. package/dist/logger/SquizRemoteLogger.js.map +1 -0
  49. package/dist/package.d.ts +23 -0
  50. package/dist/package.js +40 -0
  51. package/dist/package.js.map +1 -0
  52. package/dist/scheduler/EventBridgeScheduler.d.ts +21 -0
  53. package/dist/scheduler/EventBridgeScheduler.js +131 -0
  54. package/dist/scheduler/EventBridgeScheduler.js.map +1 -0
  55. package/dist/scheduler/Scheduler.d.ts +21 -0
  56. package/dist/scheduler/Scheduler.js +17 -0
  57. package/dist/scheduler/Scheduler.js.map +1 -0
  58. package/dist/testing/mock.d.ts +12 -0
  59. package/dist/testing/mock.js +51 -0
  60. package/dist/testing/mock.js.map +1 -0
  61. package/dist/typesUtils/DynamoDB.d.ts +4 -0
  62. package/dist/typesUtils/DynamoDB.js +3 -0
  63. package/dist/typesUtils/DynamoDB.js.map +1 -0
  64. package/dist/typesUtils/utilities.d.ts +9 -0
  65. package/dist/typesUtils/utilities.js +3 -0
  66. package/dist/typesUtils/utilities.js.map +1 -0
  67. package/dist/validation/handleValidation.d.ts +2 -0
  68. package/dist/validation/handleValidation.js +11 -0
  69. package/dist/validation/handleValidation.js.map +1 -0
  70. package/dist/valueObject/TenantId.d.ts +10 -0
  71. package/dist/valueObject/TenantId.js +23 -0
  72. package/dist/valueObject/TenantId.js.map +1 -0
  73. package/package.json +26 -0
  74. package/src/cloudflare/CloudflareKVHttpService.ts +20 -0
  75. package/src/cloudflare/ImplCloudflareKVHttpService.ts +128 -0
  76. package/src/cloudflare/__tests__/ImplCloudflareKVHttpService.spec.ts +178 -0
  77. package/src/config/ConfigurationLoader.ts +72 -0
  78. package/src/config/__tests__/ConfigurationLoader.spec.ts +62 -0
  79. package/src/date/DateManipulator.ts +29 -0
  80. package/src/date/__tests__/DateManipulator.spec.ts +64 -0
  81. package/src/event/AggregateRoot.ts +5 -0
  82. package/src/event/DomainEvent.ts +52 -0
  83. package/src/event/DynamoDBEventMapper.ts +72 -0
  84. package/src/event/EventHandler.ts +57 -0
  85. package/src/event/__tests__/DynamoDBEventMapper.spec.ts +113 -0
  86. package/src/exception/DomainException.ts +34 -0
  87. package/src/httpClient/FetchHttpClient.ts +92 -0
  88. package/src/httpClient/HttpClient.ts +46 -0
  89. package/src/httpClient/HttpRequestBuilder.ts +120 -0
  90. package/src/httpClient/__tests__/FetchHttpClient.spec.ts +146 -0
  91. package/src/httpClient/__tests__/HttpClient.spec.ts +52 -0
  92. package/src/httpClient/__tests__/httpRequestBuilder.spec.ts +75 -0
  93. package/src/logger/Logger.ts +40 -0
  94. package/src/logger/LoggerMessage.ts +151 -0
  95. package/src/logger/RemoteLogger.ts +32 -0
  96. package/src/logger/SquizRemoteLogger.ts +157 -0
  97. package/src/logger/__tests__/LoggerMessage.spec.ts +133 -0
  98. package/src/logger/__tests__/SquizRemoteLogger.spec.ts +185 -0
  99. package/src/package.ts +23 -0
  100. package/src/scheduler/EventBridgeScheduler.ts +177 -0
  101. package/src/scheduler/Scheduler.ts +32 -0
  102. package/src/scheduler/__tests__/EventBridgeScheduler.spec.ts +310 -0
  103. package/src/testing/mock.ts +62 -0
  104. package/src/typesUtils/DynamoDB.ts +17 -0
  105. package/src/typesUtils/utilities.ts +11 -0
  106. package/src/validation/handleValidation.ts +13 -0
  107. package/src/valueObject/TenantId.ts +27 -0
  108. package/tsconfig.json +13 -0
@@ -0,0 +1,75 @@
1
+ import { Container } from 'inversify';
2
+ import { faker } from '@faker-js/faker';
3
+ import {
4
+ HttpClient,
5
+ HttpMethod,
6
+ HttpRequestOptions,
7
+ HttpResponse,
8
+ } from '../HttpClient';
9
+ import { HttpRequestBuilderFactory } from '../HttpRequestBuilder';
10
+ import { createHttpClientMock } from '../../testing/mock';
11
+
12
+ describe('HttpRequestBuilder', () => {
13
+ let httpClient: HttpClient;
14
+ let httpRequestBuilderFactory: HttpRequestBuilderFactory;
15
+
16
+ beforeEach(() => {
17
+ const container = new Container();
18
+
19
+ container.bind(HttpClient).toConstantValue(createHttpClientMock());
20
+ container.bind(HttpRequestBuilderFactory).to(HttpRequestBuilderFactory);
21
+
22
+ httpClient = container.get(HttpClient);
23
+ httpRequestBuilderFactory = container.get(HttpRequestBuilderFactory);
24
+ });
25
+
26
+ it('should compose all params and call HttpClient from builder', async () => {
27
+ const expectedResponse = new HttpResponse({
28
+ statusCode: 200,
29
+ body: undefined,
30
+ });
31
+ const params = {
32
+ url: faker.internet.url(),
33
+ method: faker.helpers.arrayElement(Object.values(HttpMethod)),
34
+ headers: {
35
+ example: faker.word.words(),
36
+ },
37
+ body: {
38
+ exampleBody: faker.word.words(),
39
+ },
40
+ handleHttpErrorStatusCodes: [401, 403, 404],
41
+ };
42
+ const bearerToken = faker.word.words();
43
+ const apiKey = faker.word.words();
44
+
45
+ jest
46
+ .spyOn(httpClient, 'sendRequest')
47
+ .mockResolvedValueOnce(expectedResponse);
48
+
49
+ const response = await httpRequestBuilderFactory
50
+ .create()
51
+ .url(params.url)
52
+ .method(params.method)
53
+ .body(params.body)
54
+ .headers(params.headers)
55
+ .applicationJson()
56
+ .authorizationByBearer(bearerToken)
57
+ .authorizationByXApiKey(apiKey)
58
+ .handleErrorStatusCode(params.handleHttpErrorStatusCodes[0])
59
+ .handleErrorStatusCode(params.handleHttpErrorStatusCodes[1])
60
+ .handleNotFound()
61
+ .sendRequest();
62
+
63
+ expect(response).toEqual(expectedResponse);
64
+ expect(httpClient.sendRequest).toHaveBeenCalledWith({
65
+ ...params,
66
+ headers: expect.objectContaining({
67
+ ...params.headers,
68
+ Authorization: `Bearer ${bearerToken}`,
69
+ 'x-api-key': apiKey,
70
+ 'Content-Type': 'application/json',
71
+ }),
72
+ handleHttpErrorStatusCodes: params.handleHttpErrorStatusCodes,
73
+ } as HttpRequestOptions);
74
+ });
75
+ });
@@ -0,0 +1,40 @@
1
+ import {
2
+ createLogger as createWinstonLogger,
3
+ format,
4
+ LeveledLogMethod,
5
+ Logform,
6
+ LogMethod,
7
+ transports,
8
+ } from 'winston';
9
+
10
+ export abstract class Logger {
11
+ abstract log: LogMethod;
12
+ abstract error: LeveledLogMethod;
13
+ abstract warn: LeveledLogMethod;
14
+ abstract info: LeveledLogMethod;
15
+ abstract debug: LeveledLogMethod;
16
+ abstract verbose: LeveledLogMethod;
17
+ }
18
+
19
+ const lambdaFormat = (): Logform.Format => {
20
+ return format.printf(({ ...attrs }) => {
21
+ // https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime
22
+ return JSON.stringify({
23
+ type: 'app',
24
+ env: process.env.ENV,
25
+ name: process.env.AWS_LAMBDA_FUNCTION_NAME,
26
+ version: process.env.AWS_LAMBDA_FUNCTION_VERSION,
27
+ region: process.env.AWS_REGION,
28
+ trace: process.env._X_AMZN_TRACE_ID,
29
+ ...attrs,
30
+ });
31
+ });
32
+ };
33
+
34
+ export function createLogger(): Logger {
35
+ return createWinstonLogger({
36
+ level: process.env.LOGGING_LEVEL || 'debug',
37
+ format: format.combine(format.timestamp(), format.splat(), lambdaFormat()),
38
+ transports: [new transports.Console()],
39
+ });
40
+ }
@@ -0,0 +1,151 @@
1
+ import { randomUUID } from 'crypto';
2
+
3
+ type ValueOf = {
4
+ valueOf(): string;
5
+ };
6
+
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ const normalizeCaller = <T extends abstract new (...args: any) => any>(
9
+ stringOrClassInstance: string | InstanceType<T>,
10
+ ): string =>
11
+ typeof stringOrClassInstance === 'string'
12
+ ? stringOrClassInstance
13
+ : stringOrClassInstance.constructor.name;
14
+
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ export function createLogMessage<T extends abstract new (...args: any) => any>(
17
+ ...metadata: Array<string | InstanceType<T>>
18
+ ): (message?: string) => string {
19
+ const context = randomUUID();
20
+
21
+ const formattedMetadata = [...metadata, `Context: ${context}`]
22
+ .filter((data) => !!data)
23
+ .map((data) => `[${normalizeCaller(data)}]`)
24
+ .join('');
25
+
26
+ return (message) => `${formattedMetadata} ${message ?? ''}`;
27
+ }
28
+
29
+ export const TENANT_ID_METADATA = (
30
+ tenantId: string | ValueOf,
31
+ ): {
32
+ tenantId: string;
33
+ } => ({
34
+ tenantId: typeof tenantId === 'string' ? tenantId : tenantId.valueOf(),
35
+ });
36
+
37
+ export const PROCESSABLE_LOG_METADATA = { processable: true } as const;
38
+
39
+ export type LogMessage = (
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ message: string | any,
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ ) => [string | any, Record<string, unknown>];
44
+ export type CreateLog = {
45
+ attachMetadata: (metadata: Record<string, unknown>) => CreateLog;
46
+ attachEventName: (eventName: string) => CreateLog;
47
+ attachCorrelationId: (id: string) => CreateLog;
48
+ attachTenantId: (tenantId: string | ValueOf) => CreateLog;
49
+ attachExperimentId: (experimentId: string | ValueOf) => CreateLog;
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ attachCaller: <T extends abstract new (...args: any) => any>(
52
+ caller: InstanceType<T> | string,
53
+ ) => CreateLog;
54
+ isProcessable: () => CreateLog;
55
+ getMetadata: () => Record<string, unknown>;
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ create: <T extends abstract new (...args: any) => any>(
58
+ caller?: InstanceType<T> | string,
59
+ ) => LogMessage;
60
+ };
61
+
62
+ export function createLog(opts?: {
63
+ message?: string;
64
+ metadata?: Record<string, string | boolean | unknown>;
65
+ }): CreateLog {
66
+ opts = {
67
+ metadata: {
68
+ context: randomUUID(),
69
+ ...opts?.metadata,
70
+ },
71
+ message: opts?.message,
72
+ };
73
+
74
+ const attachMetadata = (
75
+ metadata: Record<string, string | boolean | unknown>,
76
+ ): CreateLog => {
77
+ return createLog({
78
+ ...(opts && opts),
79
+ metadata: {
80
+ ...opts?.metadata,
81
+ ...metadata,
82
+ },
83
+ });
84
+ };
85
+
86
+ const attachTenantId = (tenantId: string | ValueOf): CreateLog => {
87
+ return attachMetadata(TENANT_ID_METADATA(tenantId));
88
+ };
89
+
90
+ const attachExperimentId = (experimentId: string | ValueOf): CreateLog => {
91
+ return attachMetadata({
92
+ experimentId:
93
+ typeof experimentId === 'string'
94
+ ? experimentId
95
+ : experimentId.valueOf(),
96
+ });
97
+ };
98
+
99
+ const attachEventName = (eventName: string): CreateLog => {
100
+ return attachMetadata({
101
+ eventName,
102
+ });
103
+ };
104
+
105
+ const attachCorrelationId = (id: string): CreateLog => {
106
+ return attachMetadata({
107
+ correlationId: id,
108
+ });
109
+ };
110
+
111
+ const isProcessable = (): CreateLog => {
112
+ return attachMetadata(PROCESSABLE_LOG_METADATA);
113
+ };
114
+
115
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
116
+ const attachCaller = <T extends abstract new (...args: any) => any>(
117
+ caller: T | string,
118
+ ): CreateLog => {
119
+ return attachMetadata({
120
+ caller: normalizeCaller(caller),
121
+ });
122
+ };
123
+
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ const create = <T extends abstract new (...args: any) => any>(
126
+ caller?: T | string,
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ ): ((message: string | any) => [string | any, Record<string, unknown>]) => {
129
+ const metadata = {
130
+ ...opts?.metadata,
131
+ ...(caller && {
132
+ caller: normalizeCaller(caller),
133
+ }),
134
+ };
135
+
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ return (message: string | any) => [message, metadata];
138
+ };
139
+
140
+ return {
141
+ attachMetadata,
142
+ attachTenantId,
143
+ attachExperimentId,
144
+ attachEventName,
145
+ attachCorrelationId,
146
+ isProcessable,
147
+ attachCaller,
148
+ getMetadata: () => opts?.metadata ?? {},
149
+ create,
150
+ };
151
+ }
@@ -0,0 +1,32 @@
1
+ import { TenantId } from './../valueObject/TenantId';
2
+ import { injectable } from 'inversify';
3
+ import { z } from 'zod';
4
+
5
+ export enum RemoteLogLevel {
6
+ INFO = 'info',
7
+ WARN = 'warn',
8
+ ERROR = 'error',
9
+ }
10
+
11
+ export const REMOTE_LOG_SCHEMA = z.object({
12
+ tenantId: z.string().transform((v) => new TenantId(v)),
13
+ message: z.string(),
14
+ timestamp: z
15
+ .string()
16
+ .datetime()
17
+ .transform((v) => new Date(v))
18
+ .optional(),
19
+ level: z.nativeEnum(RemoteLogLevel),
20
+ });
21
+ export type RemoteLog = z.infer<typeof REMOTE_LOG_SCHEMA>;
22
+
23
+ @injectable()
24
+ export abstract class RemoteLogger {
25
+ abstract info(log: Omit<RemoteLog, 'level'>): Promise<void>;
26
+
27
+ abstract warn(log: Omit<RemoteLog, 'level'>): Promise<void>;
28
+
29
+ abstract error(log: Omit<RemoteLog, 'level'>): Promise<void>;
30
+
31
+ abstract log(logs: ReadonlyArray<RemoteLog>): Promise<void>;
32
+ }
@@ -0,0 +1,157 @@
1
+ import { z } from 'zod';
2
+ import { RemoteLog, RemoteLogLevel, RemoteLogger } from './RemoteLogger';
3
+ import { TenantId } from './../valueObject/TenantId';
4
+ import { injectable } from 'inversify';
5
+ import { Logger } from './Logger';
6
+ import { HttpRequestBuilderFactory } from './../httpClient/HttpRequestBuilder';
7
+ import { createLogMessage } from './LoggerMessage';
8
+ import { HttpMethod } from './../httpClient/HttpClient';
9
+
10
+ export type SquizRemoteLoggerServiceConfig = {
11
+ loggerServiceUrl: URL;
12
+ createLogsPath: string;
13
+ apiKey: string;
14
+ serviceName: string;
15
+ };
16
+ export type SquizRemoteLoggerServiceConfigProvider =
17
+ () => Promise<SquizRemoteLoggerServiceConfig>;
18
+
19
+ const CREATE_LOGS_REQUEST_BODY_DTO_SCHEMA = z
20
+ .object({
21
+ level: z.enum(['INFO', 'WARNING', 'ERROR']),
22
+ service: z.string(),
23
+ timestamp: z.string().datetime(),
24
+ host: z.string().optional(),
25
+ userid: z.string().optional(),
26
+ message: z.string().optional(),
27
+ tags: z.string().optional(),
28
+ traceid: z.string().optional(),
29
+ })
30
+ .array();
31
+
32
+ export type CreateLogsRequestBodyDto = z.infer<
33
+ typeof CREATE_LOGS_REQUEST_BODY_DTO_SCHEMA
34
+ >;
35
+
36
+ @injectable()
37
+ export class SquizRemoteLogger implements RemoteLogger {
38
+ constructor(
39
+ private readonly config: SquizRemoteLoggerServiceConfigProvider,
40
+ private readonly logger: Logger,
41
+ private readonly httpRequestBuilderFactory: HttpRequestBuilderFactory,
42
+ ) {}
43
+
44
+ async info(log: Omit<RemoteLog, 'level'>): Promise<void> {
45
+ await this.callApi(log.tenantId, [
46
+ await this.mapRemoteToRequestBody({
47
+ ...log,
48
+ level: RemoteLogLevel.INFO,
49
+ }),
50
+ ]);
51
+ }
52
+
53
+ async warn(log: Omit<RemoteLog, 'level'>): Promise<void> {
54
+ await this.callApi(log.tenantId, [
55
+ await this.mapRemoteToRequestBody({
56
+ ...log,
57
+ level: RemoteLogLevel.WARN,
58
+ }),
59
+ ]);
60
+ }
61
+
62
+ async error(log: Omit<RemoteLog, 'level'>): Promise<void> {
63
+ await this.callApi(log.tenantId, [
64
+ await this.mapRemoteToRequestBody({
65
+ ...log,
66
+ level: RemoteLogLevel.ERROR,
67
+ }),
68
+ ]);
69
+ }
70
+
71
+ async log(logs: ReadonlyArray<RemoteLog>): Promise<void> {
72
+ const logsGrouped = await logs.reduce(
73
+ async (acc, log) => {
74
+ const resolvedAcc = await acc;
75
+ const tenantLogs = resolvedAcc[log.tenantId.valueOf()];
76
+ const mappedLog = await this.mapRemoteToRequestBody(log);
77
+ const mappedLogs: CreateLogsRequestBodyDto = tenantLogs
78
+ ? [...tenantLogs, mappedLog]
79
+ : [mappedLog];
80
+
81
+ return {
82
+ ...resolvedAcc,
83
+ [log.tenantId.valueOf()]: mappedLogs,
84
+ };
85
+ },
86
+ Promise.resolve({}) as Promise<Record<string, CreateLogsRequestBodyDto>>,
87
+ );
88
+
89
+ const entries = Object.entries(logsGrouped);
90
+ const promises = entries.map(async ([tenantId, logs]) => {
91
+ await this.callApi(new TenantId(tenantId), logs);
92
+ });
93
+
94
+ await Promise.all(promises);
95
+ }
96
+
97
+ private async mapRemoteToRequestBody({
98
+ tenantId,
99
+ ...log
100
+ }: RemoteLog): Promise<CreateLogsRequestBodyDto[number]> {
101
+ const config = await this.config();
102
+ const logLevels = new Map<
103
+ RemoteLogLevel,
104
+ CreateLogsRequestBodyDto[number]['level']
105
+ >([
106
+ [RemoteLogLevel.INFO, 'INFO'],
107
+ [RemoteLogLevel.WARN, 'WARNING'],
108
+ [RemoteLogLevel.ERROR, 'ERROR'],
109
+ ]);
110
+
111
+ return {
112
+ ...log,
113
+ timestamp: log.timestamp
114
+ ? log.timestamp.toISOString()
115
+ : new Date().toISOString(),
116
+ level: logLevels.get(
117
+ log.level,
118
+ ) as CreateLogsRequestBodyDto[number]['level'],
119
+ service: config.serviceName,
120
+ };
121
+ }
122
+
123
+ private async callApi(
124
+ tenantId: TenantId,
125
+ logs: CreateLogsRequestBodyDto,
126
+ ): Promise<void> {
127
+ if (!logs.length) {
128
+ return;
129
+ }
130
+
131
+ const logMessage = createLogMessage(
132
+ this,
133
+ `tenantId: ${tenantId.valueOf()}`,
134
+ );
135
+
136
+ this.logger.debug(logMessage(`started receiving config`));
137
+ const config = await this.config();
138
+
139
+ this.logger.debug(logMessage(`finished receiving config`));
140
+
141
+ const url = new URL(
142
+ config.createLogsPath + `/${tenantId.valueOf()}`,
143
+ config.loggerServiceUrl,
144
+ );
145
+
146
+ this.logger.debug(logMessage(`started request to: ${url.toString()}`));
147
+ await this.httpRequestBuilderFactory
148
+ .create()
149
+ .url(url)
150
+ .method(HttpMethod.POST)
151
+ .applicationJson()
152
+ .authorizationByXApiKey(config.apiKey)
153
+ .body(logs)
154
+ .sendRequest();
155
+ this.logger.debug(logMessage(`finished request to: ${url.toString()}`));
156
+ }
157
+ }
@@ -0,0 +1,133 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { createLog, createLogMessage } from '../LoggerMessage';
3
+ import { TenantId } from '../../valueObject/TenantId';
4
+
5
+ jest.mock('crypto');
6
+
7
+ describe('createLogMessage', () => {
8
+ class ExampleClass {}
9
+
10
+ const mockUUID = (uuid: string): jest.Mock =>
11
+ (randomUUID as jest.Mock).mockReturnValueOnce(uuid);
12
+
13
+ beforeEach(() => {
14
+ jest.resetAllMocks();
15
+ });
16
+
17
+ it('should create a log message with the instance of class', () => {
18
+ mockUUID('some-uuid');
19
+
20
+ const logMessage = createLogMessage(new ExampleClass());
21
+
22
+ expect(logMessage('example message')).toBe(
23
+ '[ExampleClass][Context: some-uuid] example message',
24
+ );
25
+ });
26
+
27
+ it('should create a log message with given metadata', () => {
28
+ mockUUID('some-uuid');
29
+
30
+ const logMessage = createLogMessage(
31
+ new ExampleClass(),
32
+ 'secondMetadata',
33
+ 'thirdMetadata',
34
+ );
35
+
36
+ expect(logMessage('example message')).toBe(
37
+ '[ExampleClass][secondMetadata][thirdMetadata][Context: some-uuid] example message',
38
+ );
39
+ });
40
+
41
+ it('should create a log message without additional metadata', () => {
42
+ mockUUID('some-uuid');
43
+
44
+ const logMessage = createLogMessage();
45
+
46
+ expect(logMessage('example message')).toBe(
47
+ '[Context: some-uuid] example message',
48
+ );
49
+ });
50
+ });
51
+
52
+ describe('createLog', () => {
53
+ const mockUUID: jest.Mock<
54
+ ReturnType<typeof randomUUID>,
55
+ Parameters<typeof randomUUID>
56
+ > = randomUUID as jest.Mock;
57
+
58
+ beforeEach(() => {
59
+ mockUUID.mockReset();
60
+ });
61
+
62
+ it('should create a log message with attached tenant id as metadata', () => {
63
+ const logMessage = createLog().attachTenantId(new TenantId('1')).create();
64
+
65
+ const [, metadata] = logMessage('test');
66
+
67
+ expect(metadata).toEqual({ tenantId: '1' });
68
+ });
69
+
70
+ it('should create a log message with attached processable = true as metadata', () => {
71
+ const logMessage = createLog().isProcessable().create();
72
+
73
+ const [, metadata] = logMessage('test');
74
+
75
+ expect(metadata).toEqual({ processable: true });
76
+ });
77
+
78
+ it('should create a log message with given message', () => {
79
+ const logMessage = createLog().isProcessable().create();
80
+
81
+ const [message] = logMessage('test');
82
+
83
+ expect(message).toContain('test');
84
+ });
85
+
86
+ it('should create a log message with message and caller', () => {
87
+ class ExampleClass {}
88
+
89
+ const logMessage = createLog().isProcessable().create(new ExampleClass());
90
+
91
+ const [message] = logMessage('test');
92
+
93
+ expect(message).toContain('test');
94
+ });
95
+
96
+ it('should combine metadata', () => {
97
+ mockUUID.mockReturnValue('70c1d16a-c15f-462b-844b-c2cd379f425f');
98
+
99
+ const logMessage = createLog()
100
+ .attachTenantId(new TenantId('1'))
101
+ .isProcessable()
102
+ .attachMetadata({ some: 'metadata' })
103
+ .create('SomeCaller');
104
+
105
+ const [, metadata] = logMessage('test');
106
+
107
+ expect(metadata).toEqual({
108
+ tenantId: '1',
109
+ processable: true,
110
+ some: 'metadata',
111
+ context: '70c1d16a-c15f-462b-844b-c2cd379f425f',
112
+ caller: 'SomeCaller',
113
+ });
114
+ });
115
+
116
+ it('should take the context from the base createLog function', () => {
117
+ mockUUID
118
+ .mockReturnValueOnce('70c1d16a-c15f-462b-844b-c2cd379f425f')
119
+ .mockReturnValue('16ed5709-8e7f-4fcd-a4e4-73dd01a66914');
120
+
121
+ const baseLogger = createLog();
122
+
123
+ const logMessage = createLog({
124
+ metadata: baseLogger.getMetadata(),
125
+ }).create();
126
+
127
+ const [, metadata] = logMessage('test');
128
+
129
+ expect(metadata).toEqual({
130
+ context: '70c1d16a-c15f-462b-844b-c2cd379f425f',
131
+ });
132
+ });
133
+ });