import { TypedEmitter } from 'tiny-typed-emitter'; import ExerciseRunningEvents from '../../types/Dojo/ExerciseRunningEvents.js'; import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError.js'; import path from 'node:path'; import ClientsSharedConfig from '../../config/ClientsSharedConfig.js'; import Toolbox from '../../../shared/helpers/Toolbox.js'; import * as fs from 'fs-extra'; import ExerciseResultsFile from '../../../shared/types/Dojo/ExerciseResultsFile.js'; import JSON5 from 'json5'; import Json5FileValidator from '../../../shared/helpers/Json5FileValidator.js'; class ExerciseResultsSanitizerAndValidator { readonly events: TypedEmitter<ExerciseRunningEvents> = new TypedEmitter<ExerciseRunningEvents>(); public exerciseResults: ExerciseResultsFile = {}; private resultsFilePath: string = ''; private readonly folderResultsDojo: string; private readonly folderResultsExercise: string; private readonly containerExitCode: number; constructor(folderResultsDojo: string, folderResultsExercise: string, containerExitCode: number) { this.folderResultsDojo = folderResultsDojo; this.folderResultsExercise = folderResultsExercise; this.containerExitCode = containerExitCode; } private 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(resultFilePath: string): Promise<boolean> { // Results file schema validation { this.events.emit('step', 'VALIDATE_RESULTS_FILE', 'Validating results file schema'); const validationResults = Json5FileValidator.validateFile(ExerciseResultsFile, resultFilePath); if ( !validationResults.isValid ) { this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', `Results file is not valid. Here are the errors :\n${ validationResults.error }`, true); this.events.emit('finished', false, ExerciseCheckerError.EXERCISE_RESULTS_FILE_SCHEMA_NOT_VALID); return false; } this.exerciseResults = validationResults.content ?? {}; this.events.emit('endStep', 'VALIDATE_RESULTS_FILE', 'Results file is valid', false); } // Results file content sanitization 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; } run() { void (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 = true; if ( fs.existsSync(resultsFileOriginPath) ) { this.events.emit('endStep', 'CHECK_RESULTS_FILE_EXIST', 'Results file found', false); result = await this.resultsFileProvided(resultsFileOriginPath); 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); this.resultsFileSanitization(); } if ( result ) { fs.writeFileSync(this.resultsFilePath, JSON5.stringify(this.exerciseResults, { space: 4 }), 'utf8'); this.events.emit('finished', true, 0); } })(); } } export default ExerciseResultsSanitizerAndValidator;