From b294c18e881f1d374d63dda3d17862904493a87e Mon Sep 17 00:00:00 2001
From: Gero Lueckemeyer <gero.lueckemeyer@hft-stuttgart.de>
Date: Fri, 19 Jan 2024 10:06:19 +0100
Subject: [PATCH] refactor & extend url listener

---
 Dockerfile                                    |  98 +++---
 .../dtabackend/rest/v1/task/TaskUpload.java   | 194 ++++++------
 .../utils/CompetencyAssessmentUtil.java       | 188 ++++++------
 .../dtabackend/utils/ExecuteTestUtil.java     | 288 +++++++++---------
 src/main/resources/application.properties     |   2 +-
 5 files changed, 391 insertions(+), 379 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index bfee054..f5ddbb7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,48 +1,50 @@
-# base image to build a JRE
-FROM amazoncorretto:17.0.3-alpine as corretto-jdk
-
-# required for strip-debug to work
-RUN apk add --no-cache binutils
-
-# Build small JRE image
-RUN $JAVA_HOME/bin/jlink \
-         --verbose \
-         --add-modules ALL-MODULE-PATH \
-         --strip-debug \
-         --no-man-pages \
-         --no-header-files \
-         --compress=2 \
-         --output /customjre
-
-# main app image
-FROM alpine:latest
-
-ENV JAVA_HOME=/jre
-ENV PATH="${JAVA_HOME}/bin:${PATH}"
-ENV SPRING_CONFIG_ADDITIONAL_LOCATION "file:/data/config/"
-
-# copy JRE from the base image
-COPY --from=corretto-jdk /customjre $JAVA_HOME
-
-# Add app user
-ARG AUSER=appuser
-ARG AGID=137
-ENV USER=$AUSER
-ENV GID=$AGID
-ARG BUILD_NUMBER=
-RUN addgroup -g $GID -S docker
-RUN adduser --no-create-home -u 1000 -G docker -D $USER
-
-# Prepare environment.
-# Create needed folders
-RUN mkdir /data && \
-    mkdir /data/config && \
-    chown -R $USER /data
-
-VOLUME /data
-
-COPY --chown=1000:$GID target/dta-backend-$BUILD_NUMBER.jar app.jar
-
-USER 1000:$GID
-
-ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
+# base image to build a JRE
+FROM amazoncorretto:17.0.3-alpine as corretto-jdk
+
+# required for strip-debug to work
+RUN apk add --no-cache binutils
+
+# Build small JRE image
+RUN $JAVA_HOME/bin/jlink \
+         --verbose \
+         --add-modules ALL-MODULE-PATH \
+         --strip-debug \
+         --no-man-pages \
+         --no-header-files \
+         --compress=2 \
+         --output /customjre
+
+# main app image
+FROM alpine:latest
+
+ENV JAVA_HOME=/jre
+ENV PATH="${JAVA_HOME}/bin:${PATH}"
+ENV SPRING_CONFIG_ADDITIONAL_LOCATION "file:/data/config/"
+
+# copy JRE from the base image
+COPY --from=corretto-jdk /customjre $JAVA_HOME
+
+# Add app user
+ARG AUSER=appuser
+ARG AGID=137
+ENV USER=$AUSER
+ENV GID=$AGID
+ARG BUILD_NUMBER=
+
+# Create docker group identical to host
+RUN addgroup -g $GID -S docker
+RUN adduser --no-create-home -u 1000 -G docker -D $USER
+
+# Prepare environment.
+# Create needed folders
+RUN mkdir /data && \
+    mkdir /data/config && \
+    chown -R $USER /data
+
+VOLUME /data
+
+COPY --chown=1000:$GID target/dta-backend-$BUILD_NUMBER.jar app.jar
+
+USER 1000:$GID
+
+ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
diff --git a/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java
index 4c4e520..3e21519 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java
@@ -1,97 +1,97 @@
-package de.hftstuttgart.dtabackend.rest.v1.task;
-
-import de.hftstuttgart.dtabackend.utils.*;
-import de.hftstuttgart.dtabackend.models.ResultSummary;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.tika.Tika;
-import org.springframework.core.env.Environment;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.regex.Matcher;
-import jakarta.servlet.annotation.MultipartConfig;
-
-/**
- * Rest controller for everything related to the TASK files
- */
-@RestController
-@RequestMapping("/v1/task")
-@MultipartConfig
-public class TaskUpload {
-    private static final Logger LOG = LogManager.getLogger(TaskUpload.class);
-
-    private final JGitUtil jGitUtil;
-    private final Path testTmpPath;
-    private final ExecuteTestUtil executeTestUtil;
-
-    public TaskUpload(
-        Environment env,
-        JGitUtil jGitUtil,
-        ExecuteTestUtil executeTestUtil
-    ) {
-        this.jGitUtil = jGitUtil;
-        this.executeTestUtil = executeTestUtil;
-
-        // set path of temporary directory on host and inside our container
-        this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir"));
-    }
-
-    @RequestMapping(method = RequestMethod.POST)
-    public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef,
-                                           @RequestParam("assignmentId") String assignmentId
-    ) throws IOException, InterruptedException {
-        LOG.info("submission for testing received");
-
-        LOG.debug("creating new temporary directory");
-        Path workDirectory = Files.createTempDirectory(testTmpPath, "dtt");
-        LOG.debug(String.format("working dir for test is: %s", workDirectory.toAbsolutePath().toString()));
-
-        // define paths for the test, the submission and where the result is to be expected afterwards
-        Path srcPath    = Paths.get(workDirectory.toAbsolutePath().toString(), "src");
-
-        String mimeInfo = new Tika().detect(taskFileRef.getInputStream());
-        switch (mimeInfo) {
-            case "text/plain":
-                LOG.debug("textfile uploaded, searching for dtt config");
-                // find URI in config file
-                Matcher config = RegexUtil.findStudentConfig(taskFileRef.getInputStream());
-
-                LOG.debug("calling repo clone");
-                jGitUtil.cloneRepository(config, srcPath.toAbsolutePath().toString());
-                break;
-
-            case "application/zip":
-                LOG.debug("zip archive uploaded, extracting content as student submission");
-                ArchiveUtil.extractProjectFromZip(taskFileRef.getInputStream(), srcPath.toAbsolutePath());
-                break;
-
-            default:
-                String msg = String.format("couldn't process uploaded file with mime type %s", mimeInfo);
-                LOG.error(msg);
-                throw new RuntimeException(msg);
-        }
-
-        // run test
-        LOG.debug("calling test execution");
-        ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
-
-        if (mimeInfo.equals("text/plain")) {
-            LOG.info("check for provided Ticketsystem information");
-            UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
-        }
-        
-        taskFileRef.getInputStream().close();
-
-        LOG.info("submission tested successfully");
-        return resultSummary;
-    }
-}
+package de.hftstuttgart.dtabackend.rest.v1.task;
+
+import de.hftstuttgart.dtabackend.utils.*;
+import de.hftstuttgart.dtabackend.models.ResultSummary;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.tika.Tika;
+import org.springframework.core.env.Environment;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.regex.Matcher;
+import jakarta.servlet.annotation.MultipartConfig;
+
+/**
+ * Rest controller for everything related to the TASK files
+ */
+@RestController
+@RequestMapping("/v1/task/*")
+@MultipartConfig
+public class TaskUpload {
+    private static final Logger LOG = LogManager.getLogger(TaskUpload.class);
+
+    private final JGitUtil jGitUtil;
+    private final Path testTmpPath;
+    private final ExecuteTestUtil executeTestUtil;
+
+    public TaskUpload(
+        Environment env,
+        JGitUtil jGitUtil,
+        ExecuteTestUtil executeTestUtil
+    ) {
+        this.jGitUtil = jGitUtil;
+        this.executeTestUtil = executeTestUtil;
+
+        // set path of temporary directory on host and inside our container
+        this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir"));
+    }
+
+    @RequestMapping(method = RequestMethod.POST)
+    public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef,
+                                           @RequestParam("assignmentId") String assignmentId
+    ) throws IOException, InterruptedException {
+        LOG.info("submission for testing received");
+
+        LOG.debug("creating new temporary directory");
+        Path workDirectory = Files.createTempDirectory(testTmpPath, "dtt");
+        LOG.debug(String.format("working dir for test is: %s", workDirectory.toAbsolutePath().toString()));
+
+        // define paths for the test, the submission and where the result is to be expected afterwards
+        Path srcPath    = Paths.get(workDirectory.toAbsolutePath().toString(), "src");
+
+        String mimeInfo = new Tika().detect(taskFileRef.getInputStream());
+        switch (mimeInfo) {
+            case "text/plain":
+                LOG.debug("textfile uploaded, searching for dtt config");
+                // find URI in config file
+                Matcher config = RegexUtil.findStudentConfig(taskFileRef.getInputStream());
+
+                LOG.debug("calling repo clone");
+                jGitUtil.cloneRepository(config, srcPath.toAbsolutePath().toString());
+                break;
+
+            case "application/zip":
+                LOG.debug("zip archive uploaded, extracting content as student submission");
+                ArchiveUtil.extractProjectFromZip(taskFileRef.getInputStream(), srcPath.toAbsolutePath());
+                break;
+
+            default:
+                String msg = String.format("couldn't process uploaded file with mime type %s", mimeInfo);
+                LOG.error(msg);
+                throw new RuntimeException(msg);
+        }
+
+        // run test
+        LOG.debug("calling test execution");
+        ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
+
+        if (mimeInfo.equals("text/plain")) {
+            LOG.info("check for provided Ticketsystem information");
+            UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
+        }
+        
+        taskFileRef.getInputStream().close();
+
+        LOG.info("submission tested successfully");
+        return resultSummary;
+    }
+}
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
index bf8b636..40284d0 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
@@ -1,90 +1,98 @@
-package de.hftstuttgart.dtabackend.utils;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-import de.hftstuttgart.dtabackend.models.Result;
-import de.hftstuttgart.dtabackend.models.ResultSummary;
-import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
-
-import java.io.FileNotFoundException;
-
-public class CompetencyAssessmentUtil {
-	private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
-			
-	public static String TEST_COMPETENCY_MANIFEST_FILE_NAME="competency-tests.mft";
-	
-/*	public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException {
-		List<TestCompetencyProfile> testCompetencyProfiles=readTestCompetencyProfiles(Path.of(args[0]), args[1]);
-		sumTestCompetencyProfiles(testCompetencyProfiles);
-		ObjectMapper objectMapper = new ObjectMapper();
-        ResultSummary resultSummary = objectMapper.readValue(
-            new File(Path.of(args[0]).toFile(), args[2]).toURI().toURL(),
-            ResultSummary.class);
-		sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary);
-	}
-*/	
-	public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
-		float[] tcpTotalProfile=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-		for(TestCompetencyProfile currentProfile: testCompetencyProfiles) {
-			tcpTotalProfile=TestCompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
-		}
-		return tcpTotalProfile;
-	}
-	
-	public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) {
-		float[] sumSuccessful=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-		for(Result currentResult: resultSummary.results) {
-			if(currentResult.state==Result.State.SUCCESS.ordinal()) {
-				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
-				currentProfile.testPackageName=(currentResult.packageName!=null)?currentResult.packageName:"";
-				currentProfile.testClassName=(currentResult.className!=null)?currentResult.className:"";
-				currentProfile.testName=(currentResult.name!=null)?currentResult.name:"";
-				int testIndex=testCompetencyProfiles.indexOf(currentProfile);
-				if(testIndex!=-1) {
-					sumSuccessful=TestCompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
-				}
-			}
-		}
-		return sumSuccessful;
-	}
-	
-	public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path testPath, String fileName) {
-		List<TestCompetencyProfile> testCompetencyProfiles=new ArrayList<TestCompetencyProfile>();
-		try {
-			BufferedReader testCompetencyManifest=new BufferedReader(new FileReader(new File(testPath.toFile(), fileName)));
-			String testEntry=testCompetencyManifest.readLine();
-			while(testEntry!=null)
-			{
-				String[] testEntyComponents=testEntry.split(TestCompetencyProfile.COMPETENCY_SEPARATOR);
-				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
-				currentProfile.testPackageName=testEntyComponents[0];
-				currentProfile.testClassName=testEntyComponents[1];
-				currentProfile.testName=testEntyComponents[2];
-				for(int competencyIndex=0; competencyIndex<TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-					currentProfile.competencyAssessments[competencyIndex]=Float.valueOf(testEntyComponents[competencyIndex+3]);
-				}
-				testCompetencyProfiles.add(currentProfile);
-				testEntry=testCompetencyManifest.readLine();
-			}
-			testCompetencyManifest.close();
-			LOG.info("Added "+testCompetencyProfiles.size()+" test competency profiles from test competency manifest. Optional agent functionality enabled.");
-		} catch (FileNotFoundException e) {
-			LOG.info("Test competency manifest file for agent feedback not found. Skipping optional functionality.");
-			testCompetencyProfiles=null;
-		} catch (IOException e) {
-			LOG.info("Test competency manifest file for agent feedback unreadable. Skipping optional functionality.");
-			testCompetencyProfiles=null;
-		} 
-		return testCompetencyProfiles;
-	}
-
-}
+package de.hftstuttgart.dtabackend.utils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.nio.file.Path;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.hftstuttgart.dtabackend.models.Result;
+import de.hftstuttgart.dtabackend.models.ResultSummary;
+import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
+
+import java.io.FileNotFoundException;
+
+public class CompetencyAssessmentUtil {
+	private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
+			
+	public static String TEST_COMPETENCY_MANIFEST_FILE_NAME="competency-tests.mft";
+	
+	public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException {
+		ResultSummary summary=ExecuteTestUtil.generateResult(Path.of(args[0]), Path.of(args[1]));
+        System.out.println(summary.successfulTestCompetencyProfile);
+	}
+	
+	public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
+		float[] tcpTotalProfile=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+		for(TestCompetencyProfile currentProfile: testCompetencyProfiles) {
+			tcpTotalProfile=TestCompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
+		}
+		return tcpTotalProfile;
+	}
+	
+	public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) {
+		float[] sumSuccessful=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+		for(Result currentResult: resultSummary.results) {
+			if(currentResult.state==Result.State.SUCCESS.ordinal()) {
+				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
+				currentProfile.testPackageName=(currentResult.packageName!=null)?currentResult.packageName:"";
+				currentProfile.testClassName=(currentResult.className!=null)?currentResult.className:"";
+				currentProfile.testName=(currentResult.name!=null)?currentResult.name:"";
+				int testIndex=testCompetencyProfiles.indexOf(currentProfile);
+				if(testIndex!=-1) {
+					sumSuccessful=TestCompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
+				}
+			}
+		}
+		return sumSuccessful;
+	}
+	
+	public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path testPath, String fileName) {
+		List<TestCompetencyProfile> testCompetencyProfiles=new ArrayList<TestCompetencyProfile>();
+		try {
+			BufferedReader testCompetencyManifest=new BufferedReader(new FileReader(new File(testPath.toFile(), fileName)));
+			String testEntry=testCompetencyManifest.readLine();
+			while(testEntry!=null)
+			{
+				String[] testEntyComponents=testEntry.split(TestCompetencyProfile.COMPETENCY_SEPARATOR);
+				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
+				currentProfile.testPackageName=testEntyComponents[0];
+				currentProfile.testClassName=testEntyComponents[1];
+				currentProfile.testName=testEntyComponents[2];
+				for(int competencyIndex=0; competencyIndex<TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+					currentProfile.competencyAssessments[competencyIndex]=Float.valueOf(testEntyComponents[competencyIndex+3]);
+				}
+				testCompetencyProfiles.add(currentProfile);
+				testEntry=testCompetencyManifest.readLine();
+			}
+			testCompetencyManifest.close();
+			LOG.info("Added "+testCompetencyProfiles.size()+" test competency profiles from test competency manifest. Optional agent functionality enabled.");
+		} catch (FileNotFoundException e) {
+			LOG.info("Test competency manifest file for agent feedback not found. Skipping optional functionality.");
+			testCompetencyProfiles=null;
+		} catch (IOException e) {
+			LOG.info("Test competency manifest file for agent feedback unreadable. Skipping optional functionality.");
+			testCompetencyProfiles=null;
+		} 
+		return testCompetencyProfiles;
+	}
+
+	public static String packFloats(float[] array) {
+		return IntStream.range(0, array.length)
+	            .mapToObj(i -> String.valueOf(array[i]))
+	            .collect(Collectors.joining(";"));
+	}
+
+}
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 1e496cf..8318f73 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -1,143 +1,145 @@
-package de.hftstuttgart.dtabackend.utils;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.dockerjava.api.model.Bind;
-import com.github.dockerjava.api.model.Volume;
-import de.hftstuttgart.dtabackend.models.ResultSummary;
-import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.springframework.core.env.Environment;
-import org.springframework.stereotype.Component;
-
-import java.io.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-@Component
-public class ExecuteTestUtil  {
-    private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class);
-
-    private final JGitUtil jGitUtil;
-    private final DockerUtil dockerUtil;
-    private final String assignmentBasePath;
-    private final Path testTmpPathHost;
-    private final Path testTmpPath;
-
-    public ExecuteTestUtil(
-        Environment env,
-        JGitUtil jGitUtil,
-        DockerUtil dockerUtil
-    ) {
-        this.jGitUtil = jGitUtil;
-        this.dockerUtil = dockerUtil;
-
-        // set base path for assignments to be stored
-        Path p = Paths.get(
-            env.getProperty("data.dir"),
-            env.getProperty("data.dir.test.folder.name"));
-        this.assignmentBasePath = p.toAbsolutePath().toString();
-
-        // set path of temporary directory on host and inside our container
-        this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
-        this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir"));
-    }
-
-    public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
-
-        // define paths for the test, the submission and where the result is to be expected afterwards
-        Path testPath   = Paths.get(workDirectory.toAbsolutePath().toString(), "/test");
-        Path srcPath    = Paths.get(workDirectory.toAbsolutePath().toString(), "/src");
-        Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/result");
-
-        // clone stored test to tmpdir
-        LOG.debug("copying pre-downloaded unitttest repo");
-        FileUtil.copyFolder(
-            Paths.get(
-                assignmentBasePath,
-                assignmentId
-            ),
-            testPath
-        );
-
-        LOG.debug("copy test config");
-        Files.copy(
-            Paths.get(
-                assignmentBasePath,
-                assignmentId + ".txt"
-            ),
-            Paths.get(
-                workDirectory.toAbsolutePath().toString(),
-                "config.txt"
-            )
-        );
-
-        Files.createDirectory(resultPath);
-
-        LOG.info("reading test config");
-        Matcher config = RegexUtil.findProfessorConfig(
-            new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()));
-
-        // define the paths to mount as Binds from Host to the test-container
-        Path testPathHost = Paths.get(
-            testTmpPathHost.toAbsolutePath().toString(),
-            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
-            testPath.getName(testPath.getNameCount()-1).toString()
-        );
-        Path srcPathHost = Paths.get(
-            testTmpPathHost.toAbsolutePath().toString(),
-            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
-            srcPath.getName(srcPath.getNameCount()-1).toString()
-        );
-        Path resultPathHost = Paths.get(
-            testTmpPathHost.toAbsolutePath().toString(),
-            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
-            resultPath.getName(resultPath.getNameCount()-1).toString()
-        );
-
-        // start test-container with professor given image and bind mounts for test, submission and result
-        dockerUtil.runContainer(
-            config.group(4),
-            new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/data/test")),
-            new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/data/src")),
-            new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/data/result"))
-        );
-
-        // define expected result file
-        File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
-
-        // check if result file is there
-        if (!resultFile.exists() || !resultFile.isFile()) {
-            LOG.error(String.format("Could not find result file in %s", resultFile.getAbsolutePath()));
-            throw new RuntimeException("no resultfile found");
-        }
-
-        LOG.debug("parse results json");
-        ObjectMapper objectMapper = new ObjectMapper();
-        ResultSummary resultSummary = objectMapper.readValue(
-            resultFile.toURI().toURL(),
-            ResultSummary.class);
-        
-        LOG.info("Checking for optional test competency profile information for paedagogical agent functionality...");
-        List<TestCompetencyProfile> testCompetencyProfiles=CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME);
-        if(testCompetencyProfiles!=null) {
-        	LOG.info("Found optional test competency profiles, generating agent profile data...");
-        	resultSummary.overallTestCompetencyProfile=packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles));
-        	resultSummary.successfulTestCompetencyProfile=packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary));
-        }
-        
-        return resultSummary;
-    }
-    
-    private static String packFloats(float[] array) {
-    	return IntStream.range(0, array.length)
-                .mapToObj(i -> String.valueOf(array[i]))
-                .collect(Collectors.joining(";"));
-    }
-}
+package de.hftstuttgart.dtabackend.utils;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.dockerjava.api.model.Bind;
+import com.github.dockerjava.api.model.Volume;
+import de.hftstuttgart.dtabackend.models.ResultSummary;
+import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.regex.Matcher;
+
+@Component
+public class ExecuteTestUtil  {
+    private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class);
+
+    private final JGitUtil jGitUtil;
+    private final DockerUtil dockerUtil;
+    private final String assignmentBasePath;
+    private final Path testTmpPathHost;
+    private final Path testTmpPath;
+
+    public ExecuteTestUtil(
+        Environment env,
+        JGitUtil jGitUtil,
+        DockerUtil dockerUtil
+    ) {
+        this.jGitUtil = jGitUtil;
+        this.dockerUtil = dockerUtil;
+
+        // set base path for assignments to be stored
+        Path p = Paths.get(
+            env.getProperty("data.dir"),
+            env.getProperty("data.dir.test.folder.name"));
+        this.assignmentBasePath = p.toAbsolutePath().toString();
+
+        // set path of temporary directory on host and inside our container
+        this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
+        this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir"));
+    }
+
+    public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
+
+        // define paths for the test, the submission and where the result is to be expected afterwards
+        Path testPath   = Paths.get(workDirectory.toAbsolutePath().toString(), "/test");
+        Path srcPath    = Paths.get(workDirectory.toAbsolutePath().toString(), "/src");
+        Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "/result");
+
+        // clone stored test to tmpdir
+        LOG.debug("copying pre-downloaded unitttest repo");
+        FileUtil.copyFolder(
+            Paths.get(
+                assignmentBasePath,
+                assignmentId
+            ),
+            testPath
+        );
+
+        LOG.debug("copy test config");
+        Files.copy(
+            Paths.get(
+                assignmentBasePath,
+                assignmentId + ".txt"
+            ),
+            Paths.get(
+                workDirectory.toAbsolutePath().toString(),
+                "config.txt"
+            )
+        );
+
+        Files.createDirectory(resultPath);
+
+        LOG.info("reading test config");
+        Matcher config = RegexUtil.findProfessorConfig(
+            new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()));
+
+        // define the paths to mount as Binds from Host to the test-container
+        Path testPathHost = Paths.get(
+            testTmpPathHost.toAbsolutePath().toString(),
+            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
+            testPath.getName(testPath.getNameCount()-1).toString()
+        );
+        Path srcPathHost = Paths.get(
+            testTmpPathHost.toAbsolutePath().toString(),
+            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
+            srcPath.getName(srcPath.getNameCount()-1).toString()
+        );
+        Path resultPathHost = Paths.get(
+            testTmpPathHost.toAbsolutePath().toString(),
+            workDirectory.getName(workDirectory.getNameCount()-1).toString(),
+            resultPath.getName(resultPath.getNameCount()-1).toString()
+        );
+
+        // start test-container with professor given image and bind mounts for test, submission and result
+        dockerUtil.runContainer(
+            config.group(4),
+            new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/data/test")),
+            new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/data/src")),
+            new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/data/result"))
+        );
+
+        ResultSummary resultSummary = generateResult(resultPath, testPathHost);
+        
+        return resultSummary;
+    }
+
+	static ResultSummary generateResult(Path resultPath, Path testPathHost)
+			throws IOException, StreamReadException, DatabindException, MalformedURLException {
+		// define expected result file
+        File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
+
+        // check if result file is there
+        if (!resultFile.exists() || !resultFile.isFile()) {
+            LOG.error(String.format("Could not find result file in %s", resultFile.getAbsolutePath()));
+            throw new RuntimeException("no resultfile found");
+        }
+
+        LOG.debug("parse results json");
+        ObjectMapper objectMapper = new ObjectMapper();
+        ResultSummary resultSummary = objectMapper.readValue(
+            resultFile.toURI().toURL(),
+            ResultSummary.class);
+        LOG.debug("result json returned time "+ resultSummary.timestamp + " with "+resultSummary.results.size()+ " test results.");
+        
+        LOG.info("Checking for optional test competency profile information for paedagogical agent functionality...");
+        List<TestCompetencyProfile> testCompetencyProfiles=CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME);
+        if(testCompetencyProfiles!=null) {
+        	LOG.info("Found optional test competency profiles, generating agent profile data...");
+        	resultSummary.overallTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles));
+        	resultSummary.successfulTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary));
+        }
+		return resultSummary;
+	}
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 06b5301..af9a4d1 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -7,7 +7,7 @@ spring.http.multipart.max-file-size=5Mb
 ###############################################
 
 # Holds the uploaded Zip-Files
-tests.tmp.dir=/tmp/dtt-tests
+tests.tmp.dir=~/dta-tests
 host.tests.tmp.dir=${tests.tmp.dir}
 data.dir=/data
 data.dir.test.folder.name=UnitTests
-- 
GitLab