From 2566232efaec507e4c78cddbbc36dc6e5d1780fd Mon Sep 17 00:00:00 2001 From: Cedric Evers <12080057+CeEv@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:58:07 +0200 Subject: [PATCH] BC-7082 Add domain error handler (#4904) To allow passing errors to our domain workflow a domain error handler is outsource from the error pipline. --- apps/server/src/core/core.module.ts | 2 +- .../error/domain/domainErrorHandler.spec.ts | 205 ++++++++++++++++++ .../core/error/domain/domainErrorHandler.ts | 29 +++ apps/server/src/core/error/domain/index.ts | 1 + apps/server/src/core/error/error.module.ts | 3 + .../error/filter/global-error.filter.spec.ts | 198 ++++++++++------- .../core/error/filter/global-error.filter.ts | 86 ++++---- apps/server/src/core/error/index.ts | 3 +- apps/server/src/core/index.ts | 3 +- apps/server/src/core/logger/index.ts | 1 + apps/server/src/core/logger/logging.utils.ts | 2 + .../uc/files-storage-copy.uc.spec.ts | 5 + .../uc/files-storage-delete.uc.spec.ts | 5 + .../files-storage-download-preview.uc.spec.ts | 5 + .../uc/files-storage-download.uc.spec.ts | 5 + .../uc/files-storage-get.uc.spec.ts | 5 + .../uc/files-storage-restore.uc.spec.ts | 5 + .../uc/files-storage-update.uc.spec.ts | 5 + .../uc/files-storage-upload.uc.spec.ts | 5 + .../files-storage/uc/files-storage.uc.ts | 6 +- 20 files changed, 455 insertions(+), 124 deletions(-) create mode 100644 apps/server/src/core/error/domain/domainErrorHandler.spec.ts create mode 100644 apps/server/src/core/error/domain/domainErrorHandler.ts create mode 100644 apps/server/src/core/error/domain/index.ts diff --git a/apps/server/src/core/core.module.ts b/apps/server/src/core/core.module.ts index a8399351c24..7f62bc47a4a 100644 --- a/apps/server/src/core/core.module.ts +++ b/apps/server/src/core/core.module.ts @@ -10,6 +10,6 @@ import { ValidationModule } from './validation'; */ @Module({ imports: [LoggerModule, ErrorModule, ValidationModule, InterceptorModule], - exports: [LoggerModule], + exports: [LoggerModule, ErrorModule], }) export class CoreModule {} diff --git a/apps/server/src/core/error/domain/domainErrorHandler.spec.ts b/apps/server/src/core/error/domain/domainErrorHandler.spec.ts new file mode 100644 index 00000000000..d253593efb5 --- /dev/null +++ b/apps/server/src/core/error/domain/domainErrorHandler.spec.ts @@ -0,0 +1,205 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { BadRequestException, HttpStatus, InternalServerErrorException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { BusinessError } from '@shared/common'; +import { ErrorLogger, ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; +import util from 'util'; +import { ErrorLoggable } from '../loggable/error.loggable'; +import { ErrorUtils } from '../utils'; +import { DomainErrorHandler } from './domainErrorHandler'; + +class SampleLoggableException extends BadRequestException implements Loggable { + constructor(private testData: string) { + super(); + } + + getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { + const message = { + type: 'BAD_REQUEST_EXCEPTION', + stack: this.stack, + data: { + testData: this.testData, + }, + }; + + return message; + } +} + +class SampleLoggableExceptionWithCause extends InternalServerErrorException implements Loggable { + constructor(private readonly testValue: string, error?: unknown) { + super(ErrorUtils.createHttpExceptionOptions(error)); + } + + getLogMessage(): ErrorLogMessage { + const message: ErrorLogMessage = { + type: 'WITH_CAUSE', + stack: this.stack, + data: { + testValue: this.testValue, + }, + }; + + return message; + } +} + +class SampleLoggableFromBusinessException extends BusinessError implements Loggable { + constructor(private readonly testValue: string) { + super( + { + type: 'xyu', + title: 'test_title', + defaultMessage: 'test_defaultMessage', + }, + HttpStatus.INTERNAL_SERVER_ERROR + ); + } + + getLogMessage(): ErrorLogMessage { + const message: ErrorLogMessage = { + type: 'WITH_CAUSE', + stack: this.stack, + data: { + testValue: this.testValue, + }, + }; + + return message; + } +} + +describe('GlobalErrorFilter', () => { + let module: TestingModule; + let domainErrorHandler: DomainErrorHandler; + let logger: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + DomainErrorHandler, + { + provide: ErrorLogger, + useValue: createMock(), + }, + ], + }).compile(); + + domainErrorHandler = module.get(DomainErrorHandler); + logger = module.get(ErrorLogger); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + afterAll(async () => { + await module.close(); + }); + + it('should be defined', () => { + expect(domainErrorHandler).toBeDefined(); + }); + + describe('exec', () => { + describe('when random non error object is passed', () => { + const setup = () => { + const rndObject = { abc: '123' }; + + return { rndObject }; + }; + + it('should call logger with error', () => { + const { rndObject } = setup(); + + domainErrorHandler.exec(rndObject); + + expect(logger.error).toBeCalledWith(expect.any(ErrorLoggable)); + }); + }); + + describe('when error implements Loggable', () => { + const setup = () => { + const error = new SampleLoggableException('test'); + + return { error }; + }; + + it('should call logger with error', () => { + const { error } = setup(); + + domainErrorHandler.exec(error); + + expect(logger.error).toBeCalledWith(error); + }); + }); + + describe('when error is a generic error', () => { + const setup = () => { + const error = new Error('test'); + const loggable = new ErrorLoggable(error); + + return { error, loggable }; + }; + + it('should call logger with ErrorLoggable', () => { + const { error, loggable } = setup(); + + domainErrorHandler.exec(error); + + expect(logger.error).toBeCalledWith(loggable); + }); + }); + + describe('when error is some random object', () => { + const setup = () => { + const randomObject = { foo: 'bar' }; + const error = new Error(util.inspect(randomObject)); + const loggable = new ErrorLoggable(error); + + return { error, loggable }; + }; + + it('should call logger with ErrorLoggable', () => { + const { error, loggable } = setup(); + + domainErrorHandler.exec(error); + + expect(logger.error).toBeCalledWith(loggable); + }); + }); + + describe('when error is loggable exception with cause', () => { + const setup = () => { + const error = new Error('test'); + const loggable = new SampleLoggableExceptionWithCause('test', error); + + return { error, loggable }; + }; + + it('should call logger with ErrorLoggable', () => { + const { loggable } = setup(); + + domainErrorHandler.exec(loggable); + + expect(logger.error).toBeCalledWith(loggable); + }); + }); + + describe('when error is a business exception', () => { + const setup = () => { + const loggable = new SampleLoggableFromBusinessException('test'); + + return { loggable }; + }; + + it('should call logger with ErrorLoggable', () => { + const { loggable } = setup(); + + domainErrorHandler.exec(loggable); + + expect(logger.error).toBeCalledWith(loggable); + }); + }); + }); +}); diff --git a/apps/server/src/core/error/domain/domainErrorHandler.ts b/apps/server/src/core/error/domain/domainErrorHandler.ts new file mode 100644 index 00000000000..e61daf094df --- /dev/null +++ b/apps/server/src/core/error/domain/domainErrorHandler.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import util from 'util'; +import { ErrorLogger, Loggable, LoggingUtils } from '@src/core/logger'; +import { ErrorLoggable } from '../loggable'; + +@Injectable() +export class DomainErrorHandler { + constructor(private readonly logger: ErrorLogger) {} + + public exec(error: unknown): void { + const loggable = this.createErrorLoggable(error); + this.logger.error(loggable); + } + + private createErrorLoggable(error: unknown): Loggable { + let loggable: Loggable; + + if (LoggingUtils.isInstanceOfLoggable(error)) { + loggable = error; + } else if (error instanceof Error) { + loggable = new ErrorLoggable(error); + } else { + const unknownError = new Error(util.inspect(error)); + loggable = new ErrorLoggable(unknownError); + } + + return loggable; + } +} diff --git a/apps/server/src/core/error/domain/index.ts b/apps/server/src/core/error/domain/index.ts new file mode 100644 index 00000000000..8d30603d1d3 --- /dev/null +++ b/apps/server/src/core/error/domain/index.ts @@ -0,0 +1 @@ +export { DomainErrorHandler } from './domainErrorHandler'; diff --git a/apps/server/src/core/error/error.module.ts b/apps/server/src/core/error/error.module.ts index b318673a3c2..c2e811fed43 100644 --- a/apps/server/src/core/error/error.module.ts +++ b/apps/server/src/core/error/error.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { LoggerModule } from '../logger'; import { GlobalErrorFilter } from './filter/global-error.filter'; +import { DomainErrorHandler } from './domain'; /** * Overrides the default global Exception Filter of NestJS provided by @APP_FILTER @@ -13,6 +14,8 @@ import { GlobalErrorFilter } from './filter/global-error.filter'; provide: APP_FILTER, useClass: GlobalErrorFilter, }, + DomainErrorHandler, ], + exports: [DomainErrorHandler], }) export class ErrorModule {} diff --git a/apps/server/src/core/error/filter/global-error.filter.spec.ts b/apps/server/src/core/error/filter/global-error.filter.spec.ts index c45c13e4bff..8773c0bb5f3 100644 --- a/apps/server/src/core/error/filter/global-error.filter.spec.ts +++ b/apps/server/src/core/error/filter/global-error.filter.spec.ts @@ -4,13 +4,13 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { ArgumentsHost, BadRequestException, HttpStatus, InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { BusinessError } from '@shared/common'; -import { ErrorLogger, ErrorLogMessage, Loggable, LogMessage, ValidationErrorLogMessage } from '@src/core/logger'; +import { ErrorLogMessage, Loggable } from '@src/core/logger'; import { Response } from 'express'; -import util from 'util'; +import { WsException } from '@nestjs/websockets'; import { ErrorResponse } from '../dto'; -import { ErrorLoggable } from '../loggable/error.loggable'; import { ErrorUtils } from '../utils'; import { GlobalErrorFilter } from './global-error.filter'; +import { DomainErrorHandler } from '../domain'; class SampleBusinessError extends BusinessError { constructor() { @@ -25,24 +25,6 @@ class SampleBusinessError extends BusinessError { } } -class SampleLoggableException extends BadRequestException implements Loggable { - constructor(private testData: string) { - super(); - } - - getLogMessage(): LogMessage | ErrorLogMessage | ValidationErrorLogMessage { - const message = { - type: 'BAD_REQUEST_EXCEPTION', - stack: this.stack, - data: { - testData: this.testData, - }, - }; - - return message; - } -} - class SampleLoggableExceptionWithCause extends InternalServerErrorException implements Loggable { constructor(private readonly testValue: string, error?: unknown) { super(ErrorUtils.createHttpExceptionOptions(error)); @@ -64,21 +46,21 @@ class SampleLoggableExceptionWithCause extends InternalServerErrorException impl describe('GlobalErrorFilter', () => { let module: TestingModule; let service: GlobalErrorFilter; - let logger: DeepMocked; + let domainErrorHandler: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ providers: [ GlobalErrorFilter, { - provide: ErrorLogger, - useValue: createMock(), + provide: DomainErrorHandler, + useValue: createMock(), }, ], }).compile(); service = module.get(GlobalErrorFilter); - logger = module.get(ErrorLogger); + domainErrorHandler = module.get(DomainErrorHandler); }); afterEach(() => { @@ -94,64 +76,26 @@ describe('GlobalErrorFilter', () => { }); describe('catch', () => { - // tests regarding logging - describe('when error implements Loggable', () => { + describe('when any error is passed as parameter', () => { const setup = () => { - const error = new SampleLoggableException('test'); const argumentsHost = createMock(); + argumentsHost.getType.mockReturnValueOnce('http'); + const error = new Error('test'); return { error, argumentsHost }; }; - it('should call logger with error', () => { + it('should be pass the error to domain error handler', () => { const { error, argumentsHost } = setup(); service.catch(error, argumentsHost); - expect(logger.error).toBeCalledWith(error); - }); - }); - - describe('when error is a generic error', () => { - const setup = () => { - const error = new Error('test'); - const loggable = new ErrorLoggable(error); - const argumentsHost = createMock(); - - return { error, loggable, argumentsHost }; - }; - - it('should call logger with ErrorLoggable', () => { - const { error, loggable, argumentsHost } = setup(); - - service.catch(error, argumentsHost); - - expect(logger.error).toBeCalledWith(loggable); - }); - }); - - describe('when error is some random object', () => { - const setup = () => { - const randomObject = { foo: 'bar' }; - const error = new Error(util.inspect(randomObject)); - const loggable = new ErrorLoggable(error); - const argumentsHost = createMock(); - - return { error, loggable, argumentsHost }; - }; - - it('should call logger with ErrorLoggable', () => { - const { error, loggable, argumentsHost } = setup(); - - service.catch(error, argumentsHost); - - expect(logger.error).toBeCalledWith(loggable); + expect(domainErrorHandler.exec).toBeCalledWith(error); }); }); - // tests regarding response - describe('when context is http', () => { - const setupHttpArgumentsHost = () => { + describe('given context is http', () => { + const mockHttpArgumentsHost = () => { const argumentsHost = createMock(); argumentsHost.getType.mockReturnValueOnce('http'); @@ -160,7 +104,7 @@ describe('GlobalErrorFilter', () => { describe('when error is a FeathersError', () => { const setup = () => { - const argumentsHost = setupHttpArgumentsHost(); + const argumentsHost = mockHttpArgumentsHost(); const error = new NotFound(); const expectedResponse = new ErrorResponse('NOT_FOUND', 'Not Found', 'Error', HttpStatus.NOT_FOUND); @@ -188,7 +132,7 @@ describe('GlobalErrorFilter', () => { describe('when error is a BusinessError', () => { const setup = () => { - const argumentsHost = setupHttpArgumentsHost(); + const argumentsHost = mockHttpArgumentsHost(); const error = new SampleBusinessError(); const expectedResponse = new ErrorResponse( 'SAMPLE_ERROR', @@ -223,7 +167,7 @@ describe('GlobalErrorFilter', () => { describe('when error is a NestHttpException', () => { const setup = () => { - const argumentsHost = setupHttpArgumentsHost(); + const argumentsHost = mockHttpArgumentsHost(); const error = new BadRequestException(); const expectedResponse = new ErrorResponse( 'BAD_REQUEST', @@ -256,7 +200,7 @@ describe('GlobalErrorFilter', () => { describe('when error is a generic error', () => { const setup = () => { - const argumentsHost = setupHttpArgumentsHost(); + const argumentsHost = mockHttpArgumentsHost(); const error = new Error(); const expectedResponse = new ErrorResponse( 'INTERNAL_SERVER_ERROR', @@ -291,7 +235,7 @@ describe('GlobalErrorFilter', () => { describe('when error is some random object', () => { const setup = () => { - const argumentsHost = setupHttpArgumentsHost(); + const argumentsHost = mockHttpArgumentsHost(); const error = { foo: 'bar' }; const expectedResponse = new ErrorResponse( 'INTERNAL_SERVER_ERROR', @@ -335,7 +279,7 @@ describe('GlobalErrorFilter', () => { HttpStatus.INTERNAL_SERVER_ERROR ); - const argumentsHost = setupHttpArgumentsHost(); + const argumentsHost = mockHttpArgumentsHost(); return { error, argumentsHost, expectedResponse }; }; @@ -362,12 +306,105 @@ describe('GlobalErrorFilter', () => { }); }); - describe('when context is rmq', () => { + describe('given context is rmq', () => { + const mockRmqArgumentHost = () => { + const argumentsHost = createMock(); + argumentsHost.getType.mockReturnValueOnce('rmq'); + + return argumentsHost; + }; + describe('when error is unknown error', () => { const setup = () => { - const argumentsHost = createMock(); - argumentsHost.getType.mockReturnValueOnce('rmq'); + const argumentsHost = mockRmqArgumentHost(); + const error = new Error(); + return { error, argumentsHost }; + }; + + it('should return an RpcMessage with the error', () => { + const { error, argumentsHost } = setup(); + + const result = service.catch(error, argumentsHost); + + expect(result).toEqual({ message: undefined, error }); + }); + }); + + describe('when error is a LoggableError', () => { + const setup = () => { + const argumentsHost = mockRmqArgumentHost(); + const causeError = new Error('Cause error'); + const error = new SampleLoggableExceptionWithCause('test', causeError); + + return { error, argumentsHost }; + }; + + it('should return appropriate error', () => { + const { error, argumentsHost } = setup(); + + const result = service.catch(error, argumentsHost); + + expect(result).toEqual({ message: undefined, error }); + }); + }); + }); + + describe('given context is ws', () => { + const mockWsArgumentHost = () => { + const argumentsHost = createMock(); + argumentsHost.getType.mockReturnValueOnce('ws'); + + return argumentsHost; + }; + + describe('when error is unknown error', () => { + const setup = () => { + const argumentsHost = mockWsArgumentHost(); + const error = new Error('test'); + + return { error, argumentsHost }; + }; + + it('should return an RpcMessage with the error', () => { + const { error, argumentsHost } = setup(); + + const result = service.catch(error, argumentsHost); + + expect(result).toEqual(new WsException(error)); + }); + }); + + describe('when error is a LoggableError', () => { + const setup = () => { + const argumentsHost = mockWsArgumentHost(); + const causeError = new Error('Cause error'); + const error = new SampleLoggableExceptionWithCause('test', causeError); + + return { error, argumentsHost }; + }; + + it('should return appropriate error', () => { + const { error, argumentsHost } = setup(); + + const result = service.catch(error, argumentsHost); + + expect(result).toEqual(new WsException(error)); + }); + }); + }); + + describe('given context is rpc', () => { + const mockRpcArgumentHost = () => { + const argumentsHost = createMock(); + argumentsHost.getType.mockReturnValueOnce('rpc'); + + return argumentsHost; + }; + + describe('when error is unknown error', () => { + const setup = () => { + const argumentsHost = mockRpcArgumentHost(); const error = new Error(); return { error, argumentsHost }; @@ -384,10 +421,9 @@ describe('GlobalErrorFilter', () => { describe('when error is a LoggableError', () => { const setup = () => { + const argumentsHost = mockRpcArgumentHost(); const causeError = new Error('Cause error'); const error = new SampleLoggableExceptionWithCause('test', causeError); - const argumentsHost = createMock(); - argumentsHost.getType.mockReturnValueOnce('rmq'); return { error, argumentsHost }; }; diff --git a/apps/server/src/core/error/filter/global-error.filter.ts b/apps/server/src/core/error/filter/global-error.filter.ts index 068e7bcca8a..a658d0115a7 100644 --- a/apps/server/src/core/error/filter/global-error.filter.ts +++ b/apps/server/src/core/error/filter/global-error.filter.ts @@ -1,58 +1,68 @@ -import { IError, RpcMessage } from '@infra/rabbitmq/rpc-message'; +import { IError, RpcMessage } from '@infra/rabbitmq'; import { ArgumentsHost, Catch, ExceptionFilter, HttpException, InternalServerErrorException } from '@nestjs/common'; import { ApiValidationError, BusinessError } from '@shared/common'; -import { ErrorLogger, Loggable } from '@src/core/logger'; -import { LoggingUtils } from '@src/core/logger/logging.utils'; import { Response } from 'express'; import _ from 'lodash'; -import util from 'util'; +import { WsException } from '@nestjs/websockets'; import { ApiValidationErrorResponse, ErrorResponse } from '../dto'; import { FeathersError } from '../interface'; -import { ErrorLoggable } from '../loggable/error.loggable'; import { ErrorUtils } from '../utils'; +import { DomainErrorHandler } from '../domain'; + +// We are receiving rmq instead of rpc and rmq is missing in context type. +// @nestjs/common export type ContextType = 'http' | 'ws' | 'rpc'; +enum UseableContextType { + http = 'http', + rpc = 'rpc', + ws = 'ws', + rmq = 'rmq', +} @Catch() -export class GlobalErrorFilter implements ExceptionFilter { - constructor(private readonly logger: ErrorLogger) {} - - // eslint-disable-next-line consistent-return - catch(error: T, host: ArgumentsHost): void | RpcMessage { - const loggable = this.createErrorLoggable(error); - this.logger.error(loggable); - - const contextType = host.getType<'http' | 'rmq'>(); - - if (contextType === 'http') { - this.sendHttpResponse(error, host); - } - - if (contextType === 'rmq') { - return { message: undefined, error }; +export class GlobalErrorFilter implements ExceptionFilter { + constructor(private readonly domainErrorHandler: DomainErrorHandler) {} + + catch(error: E, host: ArgumentsHost): void | RpcMessage | WsException { + this.domainErrorHandler.exec(error); + + const contextType = host.getType(); + switch (contextType) { + case UseableContextType.http: + return this.sendHttpResponse(error, host); + case UseableContextType.rpc: + case UseableContextType.rmq: + return this.sendRpcResponse(error); + case UseableContextType.ws: + return this.sendWsResponse(error); + default: + return undefined; } } - private createErrorLoggable(error: unknown): Loggable { - let loggable: Loggable; - - if (LoggingUtils.isInstanceOfLoggable(error)) { - loggable = error; - } else if (error instanceof Error) { - loggable = new ErrorLoggable(error); - } else { - const unknownError = new Error(util.inspect(error)); - loggable = new ErrorLoggable(unknownError); - } - - return loggable; - } - - private sendHttpResponse(error: T, host: ArgumentsHost): void { + private sendHttpResponse(error: E, host: ArgumentsHost): void { const errorResponse = this.createErrorResponse(error); const httpArgumentHost = host.switchToHttp(); const response = httpArgumentHost.getResponse(); response.status(errorResponse.code).json(errorResponse); } + private sendRpcResponse(error: E): RpcMessage { + const rpcError = { message: undefined, error }; + + return rpcError; + } + + // // https://docs.nestjs.com/websockets/exception-filters + private sendWsResponse(error: E): WsException { + const wsError = new WsException(error); + + // TODO: Need to implemented an rewrite in correct way + // const wsArgumentHost = host.switchToWs(); + // wsArgumentHost.getClient(); + + return wsError; + } + private createErrorResponse(error: unknown): ErrorResponse { let response: ErrorResponse; @@ -69,7 +79,7 @@ export class GlobalErrorFilter implements Exceptio return response; } - private createErrorResponseForFeathersError(error: FeathersError) { + private createErrorResponseForFeathersError(error: FeathersError): ErrorResponse { const { code, className, name, message } = error; const type = _.snakeCase(className).toUpperCase(); const title = _.startCase(name); diff --git a/apps/server/src/core/error/index.ts b/apps/server/src/core/error/index.ts index ba16c330c6f..da2014c340e 100644 --- a/apps/server/src/core/error/index.ts +++ b/apps/server/src/core/error/index.ts @@ -1 +1,2 @@ -export * from './error.module'; +export { ErrorModule } from './error.module'; +export { DomainErrorHandler } from './domain'; diff --git a/apps/server/src/core/index.ts b/apps/server/src/core/index.ts index f7681446d37..2617f97a6c6 100644 --- a/apps/server/src/core/index.ts +++ b/apps/server/src/core/index.ts @@ -1,2 +1,3 @@ -export * from './core.module'; +export { CoreModule } from './core.module'; +export { DomainErrorHandler } from './error'; export * from './interfaces'; diff --git a/apps/server/src/core/logger/index.ts b/apps/server/src/core/logger/index.ts index 30783b851ba..5948341d8c6 100644 --- a/apps/server/src/core/logger/index.ts +++ b/apps/server/src/core/logger/index.ts @@ -4,3 +4,4 @@ export * from './legacy-logger.service'; export * from './logger'; export * from './error-logger'; export * from './types'; +export * from './logging.utils'; diff --git a/apps/server/src/core/logger/logging.utils.ts b/apps/server/src/core/logger/logging.utils.ts index 012eb939f28..d4c4a741500 100644 --- a/apps/server/src/core/logger/logging.utils.ts +++ b/apps/server/src/core/logger/logging.utils.ts @@ -7,11 +7,13 @@ export class LoggingUtils { const message = loggable.getLogMessage(); const stringifiedMessage = this.stringifyMessage(message); const messageWithContext = { message: stringifiedMessage, context }; + return messageWithContext; } private static stringifyMessage(message: unknown): string { const stringifiedMessage = util.inspect(message).replace(/\n/g, '').replace(/\\n/g, ''); + return stringifiedMessage; } diff --git a/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts index 9dda2df44da..c737f641c3d 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-copy.uc.spec.ts @@ -12,6 +12,7 @@ import { Permission } from '@shared/domain/interface'; import { EntityId } from '@shared/domain/types'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { CopyFileResponseBuilder } from '../mapper'; @@ -83,6 +84,10 @@ describe('FilesStorageUC', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts index 8606fcda798..471e6888334 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-delete.uc.spec.ts @@ -10,6 +10,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { Counted, EntityId } from '@shared/domain/types'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -66,6 +67,10 @@ describe('FilesStorageUC delete methods', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts index 2e2f89ee224..a32e9170c60 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download-preview.uc.spec.ts @@ -9,6 +9,7 @@ import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -52,6 +53,10 @@ describe('FilesStorageUC', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts index 3654fdabe8e..cc81964b8f7 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-download.uc.spec.ts @@ -9,6 +9,7 @@ import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -43,6 +44,10 @@ describe('FilesStorageUC', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts index 708bd1bce2f..78ec33cd4c5 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-get.uc.spec.ts @@ -8,6 +8,7 @@ import { HttpService } from '@nestjs/axios'; import { Test, TestingModule } from '@nestjs/testing'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -46,6 +47,10 @@ describe('FilesStorageUC', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts index 6ee67c75cb6..f8a375b3180 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-restore.uc.spec.ts @@ -9,6 +9,7 @@ import { ForbiddenException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { FileRecordParams, SingleFileParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -61,6 +62,10 @@ describe('FilesStorageUC', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts index f07914a8915..576715c2f2d 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-update.uc.spec.ts @@ -8,6 +8,7 @@ import { HttpService } from '@nestjs/axios'; import { Test, TestingModule } from '@nestjs/testing'; import { fileRecordFactory, setupEntities } from '@shared/testing'; import { LegacyLogger } from '@src/core/logger'; +import { DomainErrorHandler } from '@src/core'; import { RenameFileParams, ScanResultParams, SingleFileParams } from '../controller/dto'; import { FileRecord } from '../entity'; import { FileStorageAuthorizationContext } from '../files-storage.const'; @@ -40,6 +41,10 @@ describe('FilesStorageUC', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts b/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts index 336745140af..03eb4ab2188 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage-upload.uc.spec.ts @@ -15,6 +15,7 @@ import { AxiosRequestConfig, AxiosResponse } from 'axios'; import { Request } from 'express'; import { of } from 'rxjs'; import { Readable } from 'stream'; +import { DomainErrorHandler } from '@src/core'; import { FileRecordParams } from '../controller/dto'; import { FileRecord, FileRecordParentType } from '../entity'; import { ErrorType } from '../error'; @@ -83,6 +84,10 @@ describe('FilesStorageUC upload methods', () => { module = await Test.createTestingModule({ providers: [ FilesStorageUC, + { + provide: DomainErrorHandler, + useValue: createMock(), + }, { provide: S3ClientAdapter, useValue: createMock(), diff --git a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts index 94d2afc02e4..90ca595e9f1 100644 --- a/apps/server/src/modules/files-storage/uc/files-storage.uc.ts +++ b/apps/server/src/modules/files-storage/uc/files-storage.uc.ts @@ -10,6 +10,7 @@ import busboy from 'busboy'; import { Request } from 'express'; import { firstValueFrom } from 'rxjs'; import internal from 'stream'; +import { DomainErrorHandler } from '@src/core'; import { CopyFileParams, CopyFileResponse, @@ -40,7 +41,8 @@ export class FilesStorageUC { private readonly filesStorageService: FilesStorageService, private readonly previewService: PreviewService, // maybe better to pass the request context from controller and avoid em at this place - private readonly em: EntityManager + private readonly em: EntityManager, + private readonly domainErrorHandler: DomainErrorHandler ) { this.logger.setContext(FilesStorageUC.name); } @@ -129,7 +131,7 @@ export class FilesStorageUC { /* istanbul ignore next */ response.data.on('error', (error) => { - throw error; + this.domainErrorHandler.exec(error); }); return response;