From b648cbdfa1c7f6035bc9359274baf6fbe13bc135 Mon Sep 17 00:00:00 2001
From: Joel von der Weid <joel.von-der-weid@hesge.ch>
Date: Mon, 25 Mar 2024 13:19:30 +0100
Subject: [PATCH] Add check buildline in assignment check

---
 helpers/Dojo/AssignmentValidator.ts           | 71 +++++++++++++-----
 helpers/Dojo/ClientsSharedAssignmentHelper.ts | 39 +++++++++-
 models/Assignment.ts                          | 74 ++++++++++++++++++-
 3 files changed, 162 insertions(+), 22 deletions(-)

diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts
index 1a92994..1d2743b 100644
--- a/helpers/Dojo/AssignmentValidator.ts
+++ b/helpers/Dojo/AssignmentValidator.ts
@@ -1,16 +1,18 @@
-import { TypedEmitter }          from 'tiny-typed-emitter';
-import AssignmentValidatorEvents from '../../types/Dojo/AssignmentValidatorEvents.js';
-import SharedAssignmentHelper    from '../../../shared/helpers/Dojo/SharedAssignmentHelper.js';
-import path                      from 'node:path';
-import AssignmentCheckerError    from '../../../shared/types/Dojo/AssignmentCheckerError.js';
-import fs                        from 'fs-extra';
-import YAML                      from 'yaml';
-import DojoDockerCompose         from '../../types/Dojo/DojoDockerCompose.js';
-import { exec, spawn }           from 'child_process';
-import AssignmentFile            from '../../../shared/types/Dojo/AssignmentFile.js';
-import ExerciseDockerCompose     from './ExerciseDockerCompose.js';
-import util                      from 'util';
-import ClientsSharedConfig       from '../../config/ClientsSharedConfig';
+import { TypedEmitter }              from 'tiny-typed-emitter';
+import AssignmentValidatorEvents     from '../../types/Dojo/AssignmentValidatorEvents';
+import SharedAssignmentHelper        from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
+import path                          from 'node:path';
+import AssignmentCheckerError        from '../../../shared/types/Dojo/AssignmentCheckerError';
+import fs                            from 'fs-extra';
+import ClientsSharedConfig           from '../../config/ClientsSharedConfig';
+import YAML                          from 'yaml';
+import DojoDockerCompose             from '../../types/Dojo/DojoDockerCompose';
+import { exec, spawn }               from 'child_process';
+import AssignmentFile                from '../../../shared/types/Dojo/AssignmentFile';
+import ExerciseDockerCompose         from './ExerciseDockerCompose';
+import util                          from 'util';
+import Assignment, { Language }      from '../../models/Assignment';
+import ClientsSharedAssignmentHelper from './ClientsSharedAssignmentHelper';
 
 
 const execAsync = util.promisify(exec);
@@ -35,6 +37,7 @@ class AssignmentValidator {
 
     private dockerComposeFile!: DojoDockerCompose;
     private assignmentFile!: AssignmentFile;
+    private assignment!: Assignment;
 
     constructor(folderAssignment: string, doDown: boolean = false) {
         this.folderAssignment = folderAssignment;
@@ -123,9 +126,31 @@ class AssignmentValidator {
     }
 
     /**
-     * Step 2: dojo_assignment.json file validation
+     * Step 2: Check assignment
+     * -    Check if assignment exists in backend
+     * @private
+     */
+    private async checkAssignment() {
+        this.newStep('ASSIGNMENT_CHECKING', 'Please wait while we are checking the assignment...');
+
+        this.newSubStep('ASSIGNMENT_EXISTS', 'Checking if the assignment exists');
+        const resp = await ClientsSharedAssignmentHelper.getAssignmentFromPath(this.folderAssignment);
+        if ( resp == undefined ) {
+            this.emitError(`The assignment doesn't exist. An assignment must be created with "assignment create" before checking it.`, `Assignment doesn't exists`, AssignmentCheckerError.ASSIGNMENT_MISSING);
+            return;
+        } else {
+            this.assignment = resp;
+        }
+        this.endSubStep('Assignment exists', false);
+
+        this.endStep('Assignment exists and is valid', false);
+    }
+
+    /**
+     * Step 3: dojo_assignment.json file validation
      * -   Structure validation
      * -   Immutable files validation (Check if exists and if the given type is correct)
+     * -   Build line validation (for C-derived languages and sonar activated projects)
      * @private
      */
     private dojoAssignmentFileValidation() {
@@ -164,12 +189,22 @@ class AssignmentValidator {
         }
         this.endSubStep('Immutable files are valid', false);
 
+        // Build line validation (only if language is C/CPP/OBJ-C and sonar activated)
+        if ( [ Language.c, Language.cpp, Language.objc ].includes(this.assignment.language) && this.assignment.useSonar ) {
+            this.newSubStep('ASSIGNMENT_FILE_BUILD_LINE_VALIDATION', 'Validating build line');
+            const build = validationResults.content!.buildLine;
+            if ( build == undefined || build.trim() == '' ) {
+                this.emitError(`BuildLine is required for this language`, 'dojo_assignment.json file is invalid', AssignmentCheckerError.BUILD_LINE_MISSING);
+                return;
+            }
+            this.endSubStep('Build line is valid', false);
+        }
 
         this.endStep('dojo_assignment.json file is valid', false);
     }
 
     /**
-     * Step 3: Docker Compose file validation
+     * Step 4: Docker Compose file validation
      * -   Global validation
      * -   Validation of the containers and volumes named in dojo_assignment.json
      * @private
@@ -223,7 +258,7 @@ class AssignmentValidator {
     }
 
     /**
-     * Step 4: Dockerfiles validation
+     * Step 5: Dockerfiles validation
      * -   Check if file exists
      * -   TODO - Dockerfile structure linter - Issue #51 - https://github.com/hadolint/hadolint
      * @private
@@ -246,7 +281,7 @@ class AssignmentValidator {
     }
 
     /**
-     * Step 5: Run
+     * Step 6: Run
      * -   Make a run of the assignment (If the return code is 0, the assignment is not valid because it means that there no need of modification for succeed the exercise)
      * @private
      */
@@ -292,6 +327,8 @@ class AssignmentValidator {
             try {
                 await this.checkRequirements();
 
+                await this.checkAssignment();
+
                 this.dojoAssignmentFileValidation();
 
                 await this.dockerComposeFileValidation();
diff --git a/helpers/Dojo/ClientsSharedAssignmentHelper.ts b/helpers/Dojo/ClientsSharedAssignmentHelper.ts
index 99d956a..b7672f1 100644
--- a/helpers/Dojo/ClientsSharedAssignmentHelper.ts
+++ b/helpers/Dojo/ClientsSharedAssignmentHelper.ts
@@ -1,7 +1,14 @@
-import chalk               from 'chalk';
-import boxen               from 'boxen';
-import Icon                from '../../../shared/types/Icon.js';
-import AssignmentValidator from './AssignmentValidator.js';
+import { existsSync, readFileSync } from 'fs';
+import { join }                     from 'path';
+import chalk                        from 'chalk';
+import boxen                        from 'boxen';
+import Icon                         from '../../../shared/types/Icon';
+import AssignmentValidator          from './AssignmentValidator';
+import Assignment                   from '../../models/Assignment';
+import axios                        from 'axios';
+import DojoBackendResponse          from '../../../shared/types/Dojo/DojoBackendResponse';
+import ApiRoute                     from '../../types/Dojo/ApiRoute';
+import ClientsSharedConfig          from '../../config/ClientsSharedConfig';
 
 
 class ClientsSharedAssignmentHelper {
@@ -22,6 +29,30 @@ class ClientsSharedAssignmentHelper {
             textAlignment : 'left'
         }));
     }
+
+    private async getAssignment(url: string): Promise<Assignment | undefined> {
+        try {
+            return (await axios.get<DojoBackendResponse<Assignment>>(`${ ClientsSharedConfig.apiURL }${ ApiRoute.ASSIGNMENT_GET }`.replace('{{nameOrUrl}}', encodeURIComponent(url)))).data.data;
+        } catch ( error ) {
+            return undefined;
+        }
+    }
+
+    private async extractOriginUrl(content: string): Promise<string> {
+        const regexp = /\[remote "origin"]\r?\n\s*url\s*=\s*(.*)\s*\n/gm;
+        return Array.from(content.matchAll(regexp), m => m[1])[0];
+    }
+
+    async getAssignmentFromPath(path: string): Promise<Assignment | undefined> {
+        const fullPath = join(path, "./.git/config");
+        if (!existsSync(fullPath)) {
+            return undefined;
+        }
+        const content = readFileSync(fullPath, 'utf-8');
+        const url = await this.extractOriginUrl(content);
+
+        return await this.getAssignment(url);
+    }
 }
 
 
diff --git a/models/Assignment.ts b/models/Assignment.ts
index 5d8f03e..9e447ec 100644
--- a/models/Assignment.ts
+++ b/models/Assignment.ts
@@ -11,6 +11,8 @@ interface Assignment {
     gitlabLastInfo: Gitlab.ProjectSchema;
     gitlabLastInfoDate: string;
     published: boolean;
+    useSonar: boolean;
+    language: Language;
 
     staff: Array<User>;
     exercises: Array<Exercise>;
@@ -19,4 +21,74 @@ interface Assignment {
 }
 
 
-export default Assignment;
\ No newline at end of file
+export enum Language {
+    abap = "abap",
+    ada = "ada",
+    asm = "asm",
+    bash = "bash",
+    bqn = "bqn",
+    c = "c",
+    caml = "caml",
+    cloudformation = "cloudformation",
+    cpp = "cpp",
+    csharp = "csharp",
+    css = "css",
+    cuda = "cuda",
+    dart = "dart",
+    delphi = "delphi",
+    docker = "docker",
+    erlang = "erlang",
+    f = "f",
+    fsharp = "fsharp",
+    flex = "flex",
+    fortran = "fortran",
+    futhark = "futhark",
+    go = "go",
+    groovy = "groovy",
+    haskell = "haskell",
+    hepial = "hepial",
+    json = "json",
+    jsp = "jsp",
+    java = "java",
+    js = "js",
+    julia = "julia",
+    kotlin = "kotlin",
+    kubernetes = "kubernetes",
+    latex = "latex",
+    lisp = "lisp",
+    lua = "lua",
+    matlab = "matlab",
+    objc = "objc",
+    ocaml = "ocaml",
+    pascal = "pascal",
+    pearl = "pearl",
+    perl = "perl",
+    php = "php",
+    postscript = "postscript",
+    powershell = "powershell",
+    prolog = "prolog",
+    promela = "promela",
+    python = "python",
+    r = "r",
+    ruby = "ruby",
+    rust = "rust",
+    scala = "scala",
+    sql = "sql",
+    smalltalk = "smalltalk",
+    swift = "swift",
+    terraform = "terraform",
+    text = "text",
+    ts = "ts",
+    tsql = "tsql",
+    typst = "typst",
+    vba = "vba",
+    vbnet = "vbnet",
+    web = "web",
+    xml = "xml",
+    yaml = "yaml",
+
+    other = "other"
+}
+
+
+export default Assignment;
-- 
GitLab