Skip to content
Snippets Groups Projects
ExerciseResultsSanitizerAndValidator.ts 5.09 KiB
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;