From 2b574d9a21ba172a72280ccf909a816407f24623 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           | 69 ++++++++++++-----
 helpers/Dojo/ClientsSharedAssignmentHelper.ts | 39 +++++++++-
 models/Assignment.ts                          | 74 ++++++++++++++++++-
 3 files changed, 159 insertions(+), 23 deletions(-)

diff --git a/helpers/Dojo/AssignmentValidator.ts b/helpers/Dojo/AssignmentValidator.ts
index 6ecec2c..e5e86c5 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';
-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 { 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);
@@ -81,7 +83,7 @@ class AssignmentValidator {
         (async () => {
             let dockerComposeFile: DojoDockerCompose;
             let assignmentFile: AssignmentFile;
-
+            let assignment: Assignment;
 
             /*
              //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 1: Check requirements
@@ -118,11 +120,32 @@ class AssignmentValidator {
                 this.endStep('All requirements are satisfied', false);
             }
 
+            /*
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: Check assignment
+             -   Check if assignment exists in backend
+             */
+            {
+                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 {
+                    assignment = resp;
+                }
+                this.endSubStep('Assignment exists', false);
+
+                this.endStep('Assignment exists and is valid', false);
+            }
+
 
             /*
-             //////////////////////////////////////////////////////////////////////////////////////////////////////////// Step 2: dojo_assignment.json file validation
+             //////////////////////////////////////////////////////////////////////////////////////////////////////////// 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)
              */
             {
                 this.newStep('ASSIGNMENT_FILE_VALIDATION', 'Please wait while we are validating dojo_assignment.json file...');
@@ -159,13 +182,23 @@ 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(assignment.language) && 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
              */
@@ -218,7 +251,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
              */
@@ -241,7 +274,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)
              */
             {
diff --git a/helpers/Dojo/ClientsSharedAssignmentHelper.ts b/helpers/Dojo/ClientsSharedAssignmentHelper.ts
index c47d2b4..7bb900d 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';
-import AssignmentValidator from './AssignmentValidator';
+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 {
@@ -21,6 +28,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 c56f425..5b67023 100644
--- a/models/Assignment.ts
+++ b/models/Assignment.ts
@@ -11,6 +11,8 @@ interface Assignment {
     gitlabLastInfo: GitlabRepository;
     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