From bef7705ef60e85e4a2bf1f9768bb242d18cc4249 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C3=ABl=20Minelli?= <michael@minelli.me>
Date: Tue, 3 Oct 2023 14:20:27 +0200
Subject: [PATCH] ExerciseResultsSanAndVal => Adapt class for optional results
 file

---
 .../ExerciseResultsSanitizerAndValidator.ts   | 110 ++++++++++++++++++
 helpers/Dojo/ExerciseResultsValidation.ts     |  74 ------------
 2 files changed, 110 insertions(+), 74 deletions(-)
 create mode 100644 helpers/Dojo/ExerciseResultsSanitizerAndValidator.ts
 delete mode 100644 helpers/Dojo/ExerciseResultsValidation.ts

diff --git a/helpers/Dojo/ExerciseResultsSanitizerAndValidator.ts b/helpers/Dojo/ExerciseResultsSanitizerAndValidator.ts
new file mode 100644
index 0000000..608e406
--- /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 9bf2691..0000000
--- 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
-- 
GitLab