From 9483f64a7666966079bc56adb8c4e38f9bfd1e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Courvoisier?= Date: Wed, 4 Dec 2024 09:15:26 +0100 Subject: [PATCH] Make it prettier, make it better? --- src/api/app.module.ts | 6 +- .../repositories/decision.repository.ts | 4 +- .../decision/decision.controller.ts | 17 ++-- src/api/main.ts | 2 +- src/api/usecase/deleteDecision.usecase.ts | 12 +-- src/api/usecase/saveDecision.usecase.spec.ts | 4 +- src/api/usecase/saveDecision.usecase.ts | 3 +- src/batch/batch.module.ts | 7 +- src/batch/batch.service.spec.ts | 62 +++++++------- src/batch/batch.service.ts | 18 ++-- .../infrastructure/files/file.service.spec.ts | 20 ++--- .../infrastructure/files/file.service.ts | 8 +- .../repositories/decisionS3.repository.ts | 4 +- .../security/auth/auth.module.ts | 3 +- .../security/auth/auth.service.spec.ts | 84 +++++++++---------- .../security/auth/auth.service.ts | 61 +++++++------- 16 files changed, 148 insertions(+), 167 deletions(-) diff --git a/src/api/app.module.ts b/src/api/app.module.ts index 6190c07..34f17d5 100644 --- a/src/api/app.module.ts +++ b/src/api/app.module.ts @@ -22,11 +22,9 @@ import { BatchModule } from '../batch/batch.module' configureLoggerModule(), AuthModule, FileModule, - BatchModule, + BatchModule ], controllers: [RedirectController, HealthController, DecisionController], - providers: [ - BucketHealthIndicator, - ] + providers: [BucketHealthIndicator] }) export class AppModule {} diff --git a/src/api/domain/decisions/repositories/decision.repository.ts b/src/api/domain/decisions/repositories/decision.repository.ts index 5b07d7b..abda51e 100644 --- a/src/api/domain/decisions/repositories/decision.repository.ts +++ b/src/api/domain/decisions/repositories/decision.repository.ts @@ -5,9 +5,7 @@ export interface DecisionRepository { jsonS3Key: string ): Promise - deleteDataDecisionIntegre( - jsonS3Key: string - ): Promise + deleteDataDecisionIntegre(jsonS3Key: string): Promise uploadFichierDecisionIntegre( fichierDecisionIntegre: Express.Multer.File | Buffer, diff --git a/src/api/infrastructure/controllers/decision/decision.controller.ts b/src/api/infrastructure/controllers/decision/decision.controller.ts index 4ab781d..c1615ac 100644 --- a/src/api/infrastructure/controllers/decision/decision.controller.ts +++ b/src/api/infrastructure/controllers/decision/decision.controller.ts @@ -98,7 +98,10 @@ export class DecisionController { description: "Une erreur inattendue liée à une dépendance de l'API a été rencontrée. " }) @HttpCode(HttpStatus.NO_CONTENT) - async deleteDecision(@Param('decisionId') decisionId: string, @Req() request: Request) : Promise { + async deleteDecision( + @Param('decisionId') decisionId: string, + @Req() request: Request + ): Promise { const routePath = request.method + ' ' + request.path const decisionUseCase = new DeleteDecisionUsecase(new DecisionS3Repository(this.logger)) const formatLogs: LogsFormat = { @@ -152,21 +155,21 @@ export class DecisionController { @ApiConsumes('multipart/form-data') @ApiBody({ description: - 'Les ressources pour modifier des décisions intègres sont :\n* PUT : envoi d\'une décision intègre', + "Les ressources pour modifier des décisions intègres sont :\n* PUT : envoi d'une décision intègre", type: ReceiveDto }) @ApiCreatedResponse({ description: 'La requête a été acceptée et va être traitée.' }) @ApiBadRequestResponse({ - description: 'La requête n\'est pas correcte' + description: "La requête n'est pas correcte" }) @ApiInternalServerErrorResponse({ - description: 'Une erreur interne s\'est produite' + description: "Une erreur interne s'est produite" }) @ApiUnauthorizedResponse({ - description: 'La requête n\'est pas autorisée' + description: "La requête n'est pas autorisée" }) @ApiServiceUnavailableResponse({ - description: 'Une erreur inattendue liée à une dépendance de l\'API a été rencontrée. ' + description: "Une erreur inattendue liée à une dépendance de l'API a été rencontrée. " }) @HttpCode(HttpStatus.CREATED) @UseInterceptors(FileInterceptor('fichierDecisionIntegre')) @@ -174,7 +177,7 @@ export class DecisionController { @UploadedFile() fichierDecisionIntegre: Express.Multer.File, @Body('texteDecisionIntegre') texteDecisionIntegre: string, @Body('metadonnees', new StringToJsonPipe(), new ValidateDtoPipe()) - metadonneeDto: MetadonneeDto, + metadonneeDto: MetadonneeDto, @Req() request: Request ): Promise { if (!fichierDecisionIntegre || !isPdfFile(fichierDecisionIntegre.mimetype)) { diff --git a/src/api/main.ts b/src/api/main.ts index da158ab..1bb5925 100644 --- a/src/api/main.ts +++ b/src/api/main.ts @@ -187,7 +187,7 @@ db.saveClient({ async function bootstrap() { const appOptions = { - logger: ['log', 'error', 'warn'], + logger: ['log', 'error', 'warn'] } as NestApplicationOptions const app = await NestFactory.create(AppModule, appOptions) diff --git a/src/api/usecase/deleteDecision.usecase.ts b/src/api/usecase/deleteDecision.usecase.ts index 5dd5943..12f975b 100644 --- a/src/api/usecase/deleteDecision.usecase.ts +++ b/src/api/usecase/deleteDecision.usecase.ts @@ -1,18 +1,12 @@ import { DecisionRepository } from '../domain/decisions/repositories/decision.repository' export class DeleteDecisionUsecase { + constructor(private decisionsRepository: DecisionRepository) {} - constructor(private decisionsRepository: DecisionRepository) { - } - - async deleteDecision( - decisionId: string - ): Promise { + async deleteDecision(decisionId: string): Promise { const jsonFileName = `${decisionId}.json` - await this.decisionsRepository.deleteDataDecisionIntegre( - jsonFileName - ) + await this.decisionsRepository.deleteDataDecisionIntegre(jsonFileName) return jsonFileName } diff --git a/src/api/usecase/saveDecision.usecase.spec.ts b/src/api/usecase/saveDecision.usecase.spec.ts index b41a0fa..eb05dd5 100644 --- a/src/api/usecase/saveDecision.usecase.spec.ts +++ b/src/api/usecase/saveDecision.usecase.spec.ts @@ -5,7 +5,6 @@ import { DecisionRepository } from '../domain/decisions/repositories/decision.re import { MetadonneeDto } from '../../shared/infrastructure/dto/metadonnee.dto' import * as fs from 'fs' - const fakeFilename = 'test' jest.mock('uuid', () => ({ v4: () => fakeFilename })) jest.mock('fs', () => ({ @@ -32,8 +31,7 @@ describe('SaveDecision Usecase', () => { path: '' } as unknown as Express.Multer.File const metadonnees = new MockUtils().metadonneeDtoMock as unknown as MetadonneeDto - (fs.writeFileSync as jest.Mock).mockImplementation(() => { - }) + ;(fs.writeFileSync as jest.Mock).mockImplementation(() => {}) await usecase.putDecision(fichierDecisionIntegre, 'test', metadonnees) const requestDto = { diff --git a/src/api/usecase/saveDecision.usecase.ts b/src/api/usecase/saveDecision.usecase.ts index 8bac832..e5b0e27 100644 --- a/src/api/usecase/saveDecision.usecase.ts +++ b/src/api/usecase/saveDecision.usecase.ts @@ -7,8 +7,7 @@ import { CollectDto } from 'src/shared/infrastructure/dto/collect.dto' export class SaveDecisionUsecase { private readonly fileService: FileService = new FileService() - constructor(private decisionsRepository: DecisionRepository) { - } + constructor(private decisionsRepository: DecisionRepository) {} async putDecision( fichierDecisionIntegre: Express.Multer.File, diff --git a/src/batch/batch.module.ts b/src/batch/batch.module.ts index 63750bb..ea1be02 100644 --- a/src/batch/batch.module.ts +++ b/src/batch/batch.module.ts @@ -3,11 +3,8 @@ import { BatchService } from './batch.service' import { ScheduleModule } from '@nestjs/schedule' @Module({ - imports: [ - ScheduleModule.forRoot() - ], + imports: [ScheduleModule.forRoot()], providers: [BatchService], exports: [BatchService] }) -export class BatchModule { -} \ No newline at end of file +export class BatchModule {} diff --git a/src/batch/batch.service.spec.ts b/src/batch/batch.service.spec.ts index 7f53087..47b0e61 100644 --- a/src/batch/batch.service.spec.ts +++ b/src/batch/batch.service.spec.ts @@ -26,8 +26,10 @@ describe('BatchService', () => { let schedulerRegistry: jest.Mocked const mockFolderPath = '/mock-folder' - const mockFileNames = ['05526bff-58be-4f35-b0a9-b3bfbdd9e1ea_file_1.pdf', - 'c5e1783b-9fa4-4147-9b16-90b902060866_file2.pdf'] + const mockFileNames = [ + '05526bff-58be-4f35-b0a9-b3bfbdd9e1ea_file_1.pdf', + 'c5e1783b-9fa4-4147-9b16-90b902060866_file2.pdf' + ] const mockFileData = Buffer.from('mock file data') beforeEach(() => { @@ -47,12 +49,11 @@ describe('BatchService', () => { addCronJob: jest.fn() } as unknown as jest.Mocked + batchService = new BatchService(schedulerRegistry) + ;(batchService as any).decisionsRepository = decisionRepository + ;(batchService as any).logger = logger - batchService = new BatchService(schedulerRegistry); - (batchService as any).decisionsRepository = decisionRepository; - (batchService as any).logger = logger; - - (fs.readdirSync as jest.Mock).mockReturnValue(mockFileNames) + ;(fs.readdirSync as jest.Mock).mockReturnValue(mockFileNames) }) afterEach(() => { @@ -60,7 +61,7 @@ describe('BatchService', () => { }) it('should log an error if reading files fails', async () => { - (fs.readdirSync as jest.Mock).mockImplementation(() => { + ;(fs.readdirSync as jest.Mock).mockImplementation(() => { throw new Error('File read error') }) @@ -74,36 +75,42 @@ describe('BatchService', () => { }) it('should process files in the folder and upload them', async () => { - (path.join as jest.Mock).mockImplementation((folder, file) => `${folder}/${file}`); - (fs.statSync as jest.Mock).mockReturnValue({ isFile: () => true }); - (fs.readFileSync as jest.Mock).mockReturnValue(mockFileData); - (fs.unlinkSync as jest.Mock).mockImplementation(() => { - }) + ;(path.join as jest.Mock).mockImplementation((folder, file) => `${folder}/${file}`) + ;(fs.statSync as jest.Mock).mockReturnValue({ isFile: () => true }) + ;(fs.readFileSync as jest.Mock).mockReturnValue(mockFileData) + ;(fs.unlinkSync as jest.Mock).mockImplementation(() => {}) await batchService.archiveFilesToS3() mockFileNames.forEach((filename) => { - expect(decisionRepository.uploadFichierDecisionIntegre).toHaveBeenCalledTimes(mockFileNames.length) + expect(decisionRepository.uploadFichierDecisionIntegre).toHaveBeenCalledTimes( + mockFileNames.length + ) expect(fs.unlinkSync).toHaveBeenCalledWith(`${mockFolderPath}/${filename}`) }) - expect(logger.log).toHaveBeenCalledWith({ msg: 'Starting scan', operationName: 'archiveFilesToS3' }) - expect(logger.log).toHaveBeenCalledWith({ msg: 'End of scan', operationName: 'archiveFilesToS3' }) + expect(logger.log).toHaveBeenCalledWith({ + msg: 'Starting scan', + operationName: 'archiveFilesToS3' + }) + expect(logger.log).toHaveBeenCalledWith({ + msg: 'End of scan', + operationName: 'archiveFilesToS3' + }) }) it('should skip non-file entries in the folder', async () => { - const mockEntries = ['file1.pdf', 'file2.pdf']; + const mockEntries = ['file1.pdf', 'file2.pdf'] - (fs.readdirSync as jest.Mock).mockReturnValue(mockEntries); - (path.join as jest.Mock).mockImplementation((folder, file) => `${folder}/${file}`); - (fs.statSync as jest.Mock).mockImplementation(() => { + ;(fs.readdirSync as jest.Mock).mockReturnValue(mockEntries) + ;(path.join as jest.Mock).mockImplementation((folder, file) => `${folder}/${file}`) + ;(fs.statSync as jest.Mock).mockImplementation(() => { return { isFile: () => true } - }); - (fs.readFileSync as jest.Mock).mockReturnValue(mockFileData); - (fs.unlinkSync as jest.Mock).mockImplementation(() => { }) + ;(fs.readFileSync as jest.Mock).mockReturnValue(mockFileData) + ;(fs.unlinkSync as jest.Mock).mockImplementation(() => {}) await batchService.archiveFilesToS3() @@ -111,11 +118,11 @@ describe('BatchService', () => { }) it('should throw and log an error if upload fails', async () => { - (path.join as jest.Mock).mockImplementation((folder, file) => `${folder}/${file}`); - (fs.statSync as jest.Mock).mockReturnValue({ isFile: () => true }); - (fs.readFileSync as jest.Mock).mockReturnValue(mockFileData); + ;(path.join as jest.Mock).mockImplementation((folder, file) => `${folder}/${file}`) + ;(fs.statSync as jest.Mock).mockReturnValue({ isFile: () => true }) + ;(fs.readFileSync as jest.Mock).mockReturnValue(mockFileData) - (decisionRepository.uploadFichierDecisionIntegre as jest.Mock).mockImplementation(() => { + ;(decisionRepository.uploadFichierDecisionIntegre as jest.Mock).mockImplementation(() => { throw new Error('Upload error') }) @@ -126,6 +133,5 @@ describe('BatchService', () => { msg: 'Upload error', operationName: 'archiveFilesToS3' }) - }) }) diff --git a/src/batch/batch.service.ts b/src/batch/batch.service.ts index 3081682..1759790 100644 --- a/src/batch/batch.service.ts +++ b/src/batch/batch.service.ts @@ -6,7 +6,6 @@ import { DecisionRepository } from '../api/domain/decisions/repositories/decisio import { DecisionS3Repository } from '../shared/infrastructure/repositories/decisionS3.repository' import { CronJob } from 'cron' - @Injectable() export class BatchService implements OnModuleInit { private readonly folderPath = process.env.AV_PDF_PATH @@ -14,11 +13,14 @@ export class BatchService implements OnModuleInit { private readonly logger: Logger = new Logger(BatchService.name) private readonly decisionsRepository: DecisionRepository = new DecisionS3Repository(this.logger) - constructor(private schedulerRegistry: SchedulerRegistry) { - } + constructor(private schedulerRegistry: SchedulerRegistry) {} onModuleInit() { - this.addCronJob(`archive_files_to_s3`, process.env.S3_ARCHIVE_SCHEDULE, this.archiveFilesToS3.bind(this)) + this.addCronJob( + `archive_files_to_s3`, + process.env.S3_ARCHIVE_SCHEDULE, + this.archiveFilesToS3.bind(this) + ) } async archiveFilesToS3() { @@ -26,7 +28,7 @@ export class BatchService implements OnModuleInit { try { const fileNames = fs.readdirSync(this.folderPath) - fileNames.forEach(filename => { + fileNames.forEach((filename) => { const filePath = path.join(this.folderPath, filename) const stats = fs.statSync(filePath) @@ -47,7 +49,6 @@ export class BatchService implements OnModuleInit { const filePath = path.join(this.folderPath, filename) fs.unlinkSync(filePath) // file deletion }) - } catch (error) { this.logger.error({ operationName: 'archiveFilesToS3', msg: error.message, data: error }) } @@ -70,7 +71,8 @@ export class BatchService implements OnModuleInit { this.schedulerRegistry.addCronJob(name, job) job.start() - this.logger.log(`The cron job ${name} has been added with the following cron expression : ${cronExpression}`) + this.logger.log( + `The cron job ${name} has been added with the following cron expression : ${cronExpression}` + ) } - } diff --git a/src/shared/infrastructure/files/file.service.spec.ts b/src/shared/infrastructure/files/file.service.spec.ts index 1549ba7..7a3aed8 100644 --- a/src/shared/infrastructure/files/file.service.spec.ts +++ b/src/shared/infrastructure/files/file.service.spec.ts @@ -22,9 +22,8 @@ describe('FileService', () => { }) it('should create the upload directory if it does not exist', () => { - (fs.existsSync as jest.Mock).mockReturnValue(false); - (fs.mkdirSync as jest.Mock).mockImplementation(() => { - }) + ;(fs.existsSync as jest.Mock).mockReturnValue(false) + ;(fs.mkdirSync as jest.Mock).mockImplementation(() => {}) new FileService() @@ -32,7 +31,7 @@ describe('FileService', () => { }) it('should not create the upload directory if it already exists', () => { - (fs.existsSync as jest.Mock).mockReturnValue(true) + ;(fs.existsSync as jest.Mock).mockReturnValue(true) new FileService() @@ -41,9 +40,8 @@ describe('FileService', () => { it('should save the file successfully and return the file path and name', () => { const mockFilename = 'uniqueFile.pdf' - const mockFullPath = `${mockUploadPath}/${mockFilename}`; - (fs.writeFileSync as jest.Mock).mockImplementation(() => { - }) + const mockFullPath = `${mockUploadPath}/${mockFilename}` + ;(fs.writeFileSync as jest.Mock).mockImplementation(() => {}) const result = fileService.saveFile(mockFile, mockFilename) @@ -55,13 +53,11 @@ describe('FileService', () => { }) it('should throw an InternalServerErrorException if saving the file fails', () => { - const mockFilename = 'uniqueFile.pdf'; - (fs.writeFileSync as jest.Mock).mockImplementation(() => { + const mockFilename = 'uniqueFile.pdf' + ;(fs.writeFileSync as jest.Mock).mockImplementation(() => { throw new Error('Write failed') }) - expect(() => fileService.saveFile(mockFile, mockFilename)).toThrow( - InternalServerErrorException - ) + expect(() => fileService.saveFile(mockFile, mockFilename)).toThrow(InternalServerErrorException) }) }) diff --git a/src/shared/infrastructure/files/file.service.ts b/src/shared/infrastructure/files/file.service.ts index ef2c680..327501a 100644 --- a/src/shared/infrastructure/files/file.service.ts +++ b/src/shared/infrastructure/files/file.service.ts @@ -26,11 +26,7 @@ export class FileService { path: fullPath } } catch (error) { - throw new InternalServerErrorException( - 'Error saving file' - ) + throw new InternalServerErrorException('Error saving file') } } - - -} \ No newline at end of file +} diff --git a/src/shared/infrastructure/repositories/decisionS3.repository.ts b/src/shared/infrastructure/repositories/decisionS3.repository.ts index bb4ce42..86de54f 100644 --- a/src/shared/infrastructure/repositories/decisionS3.repository.ts +++ b/src/shared/infrastructure/repositories/decisionS3.repository.ts @@ -67,9 +67,7 @@ export class DecisionS3Repository implements DecisionRepository { } } - async deleteDataDecisionIntegre( - jsonS3Key: string - ): Promise { + async deleteDataDecisionIntegre(jsonS3Key: string): Promise { const reqParamsMarkForDeletion = { Body: JSON.stringify({ date: new Date() diff --git a/src/shared/infrastructure/security/auth/auth.module.ts b/src/shared/infrastructure/security/auth/auth.module.ts index 01d28bd..899de85 100644 --- a/src/shared/infrastructure/security/auth/auth.module.ts +++ b/src/shared/infrastructure/security/auth/auth.module.ts @@ -6,5 +6,4 @@ import { AuthService } from './auth.service' providers: [AuthService, JwtAuthGuard], exports: [AuthService] }) -export class AuthModule { -} +export class AuthModule {} diff --git a/src/shared/infrastructure/security/auth/auth.service.spec.ts b/src/shared/infrastructure/security/auth/auth.service.spec.ts index 0f5671b..7303526 100644 --- a/src/shared/infrastructure/security/auth/auth.service.spec.ts +++ b/src/shared/infrastructure/security/auth/auth.service.spec.ts @@ -1,43 +1,41 @@ -import { Test, TestingModule } from '@nestjs/testing' -import { AuthService } from './auth.service' - -describe('AuthService', () => { - let authService: AuthService - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - AuthService - ] - }).compile() - - authService = module.get(AuthService) - }) - - it('should be defined', () => { - expect(authService).toBeDefined() - }) - - describe('validateToken', () => { - it('should call oauthService.validateToken with correct token', async () => { - const token = 'valid-token' - const mockResponse = true // Example response - - // Mock the oauthService.validateToken implementation to return a value - jest.spyOn(authService, 'validateToken').mockResolvedValue(mockResponse) - - const result = await authService.validateToken(token) - - expect(result).toBe(mockResponse) - }) - - it('should handle errors from oauthService.validateToken', async () => { - const token = 'invalid-token' - - // Mock the oauthService.validateToken to throw an error - jest.spyOn(authService, 'validateToken').mockRejectedValue(new Error('Invalid token')) - - await expect(authService.validateToken(token)).rejects.toThrow('Invalid token') - }) - }) -}) +import { Test, TestingModule } from '@nestjs/testing' +import { AuthService } from './auth.service' + +describe('AuthService', () => { + let authService: AuthService + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AuthService] + }).compile() + + authService = module.get(AuthService) + }) + + it('should be defined', () => { + expect(authService).toBeDefined() + }) + + describe('validateToken', () => { + it('should call oauthService.validateToken with correct token', async () => { + const token = 'valid-token' + const mockResponse = true // Example response + + // Mock the oauthService.validateToken implementation to return a value + jest.spyOn(authService, 'validateToken').mockResolvedValue(mockResponse) + + const result = await authService.validateToken(token) + + expect(result).toBe(mockResponse) + }) + + it('should handle errors from oauthService.validateToken', async () => { + const token = 'invalid-token' + + // Mock the oauthService.validateToken to throw an error + jest.spyOn(authService, 'validateToken').mockRejectedValue(new Error('Invalid token')) + + await expect(authService.validateToken(token)).rejects.toThrow('Invalid token') + }) + }) +}) diff --git a/src/shared/infrastructure/security/auth/auth.service.ts b/src/shared/infrastructure/security/auth/auth.service.ts index ba1845f..9ce9e90 100644 --- a/src/shared/infrastructure/security/auth/auth.service.ts +++ b/src/shared/infrastructure/security/auth/auth.service.ts @@ -1,31 +1,30 @@ -import { Injectable } from '@nestjs/common' -import axios from 'axios' - -@Injectable() -export class AuthService { - constructor(/*private readonly oauthService: OauthService*/ ) { - } - - async getToken() { - let config = { - method: 'post', - maxBodyLength: Infinity, - url: process.env.OAUTH_TOKEN_URL, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - data: { - client_id: process.env.OAUTH_CLIENT_ID, - client_secret: process.env.OAUTH_CLIENT_SECRET, - grant_type: 'client_credentials' - } - } - const tokenResponse = await axios.request(config) - return tokenResponse.data.access_token - } - - async validateToken(token: string) { - // return this.oauthService.validateToken(token) - return false - } -} +import { Injectable } from '@nestjs/common' +import axios from 'axios' + +@Injectable() +export class AuthService { + constructor(/*private readonly oauthService: OauthService*/) {} + + async getToken() { + let config = { + method: 'post', + maxBodyLength: Infinity, + url: process.env.OAUTH_TOKEN_URL, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + data: { + client_id: process.env.OAUTH_CLIENT_ID, + client_secret: process.env.OAUTH_CLIENT_SECRET, + grant_type: 'client_credentials' + } + } + const tokenResponse = await axios.request(config) + return tokenResponse.data.access_token + } + + async validateToken(token: string) { + // return this.oauthService.validateToken(token) + return false + } +}