diff --git a/helpers/Dojo/ExerciseResultsSanitizerAndValidator.ts b/helpers/Dojo/ExerciseResultsSanitizerAndValidator.ts new file mode 100644 index 0000000000000000000000000000000000000000..608e406f7d11b3b4178e925265c958b9459d574e --- /dev/null +++ b/helpers/Dojo/ExerciseResultsSanitizerAndValidator.ts @@ -0,0 +1,110 @@ +import { TypedEmitter } from 'tiny-typed-emitter'; +import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents'; +import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError'; +import path from 'node:path'; +import SharedExerciseHelper from '../../../shared/helpers/Dojo/SharedExerciseHelper'; +import ClientsSharedConfig from '../../config/ClientsSharedConfig'; +import Toolbox from '../../../shared/helpers/Toolbox'; +import * as fs from 'fs-extra'; +import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile'; +import JSON5 from 'json5'; + + +class ExerciseResultsSanitizerAndValidator { + readonly events: TypedEmitter<ExerciseRunningEvents> = new TypedEmitter<ExerciseRunningEvents>(); + + public exerciseResults: ExerciseResultsFile = {}; + + private resultsFilePath: string = ''; + + constructor(private folderResultsDojo: string, private folderResultsExercise: string, private containerExitCode: number) { } + + private async resultsFileSanitization() { + this.events.emit('step', 'RESULTS_FILE_SANITIZATION', 'Sanitizing results file'); + + if ( this.exerciseResults.success === undefined ) { + this.exerciseResults.success = this.containerExitCode === 0; + } + + if ( this.exerciseResults.containerExitCode === undefined ) { + this.exerciseResults.containerExitCode = this.containerExitCode; + } + + this.events.emit('endStep', 'RESULTS_FILE_SANITIZATION', 'Results file sanitized', false); + } + + private async resultsFileProvided(): Promise<boolean> { + // Results file schema validation + { + this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema'); + const validationResults = SharedExerciseHelper.validateResultFile(this.resultsFilePath); + if ( !validationResults.isValid ) { + this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ JSON.stringify(validationResults.errors) }`, true); + this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID); + return false; + } + this.exerciseResults = validationResults.results ?? {}; + this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', 'Results file is valid', false); + } + + + // Results file content sanitization + await this.resultsFileSanitization(); + + + // Results folder size + // ATTENTION: This test is at the end because even if it fail the local execution will continue and we need the other test above to be done + { + this.events.emit('step', 'CHECK_SIZE', 'Validating results folder size'); + const resultsFolderSize = await Toolbox.fs.getTotalSize(this.folderResultsExercise); + if ( resultsFolderSize > ClientsSharedConfig.exerciseResultsFolderMaxSizeInBytes ) { + this.events.emit('endStep', 'CHECK_SIZE', `Results folder size is too big (bigger than ${ ClientsSharedConfig.exerciseResultsFolderMaxSizeInBytes / 1000000 } MB)`, true); + this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FOLDER_TOO_BIG); + return false; + } + this.events.emit('endStep', 'CHECK_SIZE', 'Results folder size is in bounds', false); + } + + return true; + } + + private async resultsFileNotProvided(): Promise<boolean> { + await this.resultsFileSanitization(); + return true; + } + + run() { + (async () => { + // Results file existence + this.events.emit('step', 'CHECK_RESULTS_FILE_EXIST', 'Checking if results file exists'); + const resultsFileOriginPath = path.join(this.folderResultsExercise, ClientsSharedConfig.filenames.results); + this.resultsFilePath = path.join(this.folderResultsDojo, ClientsSharedConfig.filenames.results); + + let result: boolean; + if ( fs.existsSync(resultsFileOriginPath) ) { + this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file found', false); + + result = await this.resultsFileProvided(); + + if ( result ) { + fs.rmSync(resultsFileOriginPath, { force: true }); + } else { + fs.moveSync(resultsFileOriginPath, this.resultsFilePath, { overwrite: true }); + } + } else { + this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file not found', false); + + result = await this.resultsFileNotProvided(); + } + + if ( result ) { + fs.writeFileSync(this.resultsFilePath, JSON5.stringify(this.exerciseResults), 'utf8'); + + this.events.emit('finished', true, 0); + } + })(); + } +} + + +export default ExerciseResultsSanitizerAndValidator; \ No newline at end of file diff --git a/helpers/Dojo/ExerciseResultsValidation.ts b/helpers/Dojo/ExerciseResultsValidation.ts deleted file mode 100644 index 9bf2691fa9fd075181ae7ade73ba2768f0a92fbe..0000000000000000000000000000000000000000 --- a/helpers/Dojo/ExerciseResultsValidation.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { TypedEmitter } from 'tiny-typed-emitter'; -import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents'; -import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError'; -import path from 'node:path'; -import SharedExerciseHelper from '../../../shared/helpers/Dojo/SharedExerciseHelper'; -import ClientsSharedConfig from '../../config/ClientsSharedConfig'; -import Toolbox from '../../../shared/helpers/Toolbox'; -import * as fs from 'fs-extra'; -import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile'; - - -class ExerciseResultsValidation { - readonly events: TypedEmitter<ExerciseRunningEvents> = new TypedEmitter<ExerciseRunningEvents>(); - - public exerciseResults: ExerciseResultsFile | undefined = undefined; - - constructor(private folderResultsDojo: string, private folderResultsExercise: string) { } - - run() { - (async () => { - let resultsFilePath: string; - - - // Results file existence - { - this.events.emit('step', 'CHECK_RESULTS_FILE_EXIST', 'Checking if results file exists'); - const resultsFileOriginPath = path.join(this.folderResultsExercise, ClientsSharedConfig.filenames.results); - resultsFilePath = path.join(this.folderResultsDojo, ClientsSharedConfig.filenames.results); - - if ( !fs.existsSync(resultsFileOriginPath) ) { - this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', `Results file not found`, true); - this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_NOT_FOUND); - return; - } - this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file found', false); - - fs.moveSync(resultsFileOriginPath, resultsFilePath, { overwrite: true }); - } - - - // Results file schema validation - { - this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema'); - const validationResults = SharedExerciseHelper.validateResultFile(resultsFilePath); - if ( !validationResults.isValid ) { - this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ JSON.stringify(validationResults.errors) }`, true); - this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID); - return; - } - this.exerciseResults = validationResults.results; - this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', 'Results file is valid', false); - } - - - // Results folder size - // ATTENTION: This test is at the end because even if it fail the local execution will continue and we need the other test above to be done - { - this.events.emit('step', 'CHECK_SIZE', 'Validating results folder size'); - const resultsFolderSize = await Toolbox.fs.getTotalSize(this.folderResultsExercise); - if ( resultsFolderSize > ClientsSharedConfig.exerciseResultsFolderMaxSizeInBytes ) { - this.events.emit('endStep', 'CHECK_SIZE', `Results folder size is too big (bigger than ${ ClientsSharedConfig.exerciseResultsFolderMaxSizeInBytes / 1000000 } MB)`, true); - this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FOLDER_TOO_BIG); - return; - } - this.events.emit('endStep', 'CHECK_SIZE', 'Results folder size is in bounds', false); - } - - this.events.emit('finished', true, 0); - })(); - } -} - - -export default ExerciseResultsValidation; \ No newline at end of file