From c9096766407d3c0e83b5bba3cbe47577234fa8cd Mon Sep 17 00:00:00 2001 From: Stephan Krusche Date: Tue, 21 May 2024 23:54:06 +0200 Subject: [PATCH] Development: Use object mapper and streams to improve and simplify the code (#8372) --- .../tum/in/www1/artemis/domain/Imprint.java | 15 -- .../in/www1/artemis/domain/LegalDocument.java | 40 ----- .../www1/artemis/domain/PrivacyStatement.java | 15 -- .../artemis/service/LegalDocumentService.java | 48 +++--- .../JavaClassDiff.java | 5 +- .../JavaClassDiffSerializer.java | 160 +++++++++--------- .../OracleGenerator.java | 110 ++++++------ .../SerializerUtil.java | 47 +++-- .../web/rest/admin/AdminImprintResource.java | 6 +- .../admin/AdminPrivacyStatementResource.java | 6 +- .../www1/artemis/web/rest/dto/ImprintDTO.java | 14 ++ .../artemis/web/rest/dto/LegalDocument.java | 16 ++ .../web/rest/dto/PrivacyStatementDTO.java | 14 ++ .../web/rest/open/PublicImprintResource.java | 4 +- .../open/PublicPrivacyStatementResource.java | 4 +- .../app/core/legal/imprint.component.ts | 2 +- .../app/core/legal/privacy.component.ts | 2 +- .../app/entities/legal-document.model.ts | 2 +- .../ImprintResourceIntegrationTest.java | 95 +++++------ ...ivacyStatementResourceIntegrationTest.java | 96 +++++------ .../architecture/ArchitectureTest.java | 10 +- .../modeling/ModelingExerciseUtilService.java | 18 +- .../ModelingSubmissionIntegrationTest.java | 6 +- ...AndResultGitlabJenkinsIntegrationTest.java | 56 +++--- .../ProgrammingSubmissionConstants.java | 2 - 25 files changed, 380 insertions(+), 413 deletions(-) delete mode 100644 src/main/java/de/tum/in/www1/artemis/domain/Imprint.java delete mode 100644 src/main/java/de/tum/in/www1/artemis/domain/LegalDocument.java delete mode 100644 src/main/java/de/tum/in/www1/artemis/domain/PrivacyStatement.java create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/ImprintDTO.java create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/LegalDocument.java create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/PrivacyStatementDTO.java diff --git a/src/main/java/de/tum/in/www1/artemis/domain/Imprint.java b/src/main/java/de/tum/in/www1/artemis/domain/Imprint.java deleted file mode 100644 index 907526a491cd..000000000000 --- a/src/main/java/de/tum/in/www1/artemis/domain/Imprint.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.tum.in.www1.artemis.domain; - -import de.tum.in.www1.artemis.domain.enumeration.Language; -import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; - -public class Imprint extends LegalDocument { - - public Imprint(Language language) { - super(LegalDocumentType.IMPRINT, language); - } - - public Imprint(String imprintText, Language language) { - super(LegalDocumentType.IMPRINT, imprintText, language); - } -} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/LegalDocument.java b/src/main/java/de/tum/in/www1/artemis/domain/LegalDocument.java deleted file mode 100644 index 86b24624b8ed..000000000000 --- a/src/main/java/de/tum/in/www1/artemis/domain/LegalDocument.java +++ /dev/null @@ -1,40 +0,0 @@ -package de.tum.in.www1.artemis.domain; - -import de.tum.in.www1.artemis.domain.enumeration.Language; -import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; - -public class LegalDocument { - - private final LegalDocumentType type; - - private String text; - - private final Language language; - - public LegalDocument(LegalDocumentType type, Language language) { - this.type = type; - this.language = language; - } - - public LegalDocument(LegalDocumentType type, String text, Language language) { - this.type = type; - this.text = text; - this.language = language; - } - - public void setText(String text) { - this.text = text; - } - - public LegalDocumentType getType() { - return type; - } - - public String getText() { - return text; - } - - public Language getLanguage() { - return language; - } -} diff --git a/src/main/java/de/tum/in/www1/artemis/domain/PrivacyStatement.java b/src/main/java/de/tum/in/www1/artemis/domain/PrivacyStatement.java deleted file mode 100644 index 23cdb995db20..000000000000 --- a/src/main/java/de/tum/in/www1/artemis/domain/PrivacyStatement.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.tum.in.www1.artemis.domain; - -import de.tum.in.www1.artemis.domain.enumeration.Language; -import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; - -public class PrivacyStatement extends LegalDocument { - - public PrivacyStatement(Language language) { - super(LegalDocumentType.PRIVACY_STATEMENT, language); - } - - public PrivacyStatement(String privacyStatementText, Language language) { - super(LegalDocumentType.PRIVACY_STATEMENT, privacyStatementText, language); - } -} diff --git a/src/main/java/de/tum/in/www1/artemis/service/LegalDocumentService.java b/src/main/java/de/tum/in/www1/artemis/service/LegalDocumentService.java index 725316ac00ea..83cb2a6b78a3 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/LegalDocumentService.java +++ b/src/main/java/de/tum/in/www1/artemis/service/LegalDocumentService.java @@ -15,11 +15,11 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import de.tum.in.www1.artemis.domain.Imprint; -import de.tum.in.www1.artemis.domain.LegalDocument; -import de.tum.in.www1.artemis.domain.PrivacyStatement; import de.tum.in.www1.artemis.domain.enumeration.Language; import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; +import de.tum.in.www1.artemis.web.rest.dto.ImprintDTO; +import de.tum.in.www1.artemis.web.rest.dto.LegalDocument; +import de.tum.in.www1.artemis.web.rest.dto.PrivacyStatementDTO; import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException; import de.tum.in.www1.artemis.web.rest.errors.InternalServerErrorException; @@ -43,8 +43,8 @@ public class LegalDocumentService { * @param language the language of the privacy statement * @return the privacy statement that should be updated */ - public PrivacyStatement getPrivacyStatementForUpdate(Language language) { - return (PrivacyStatement) getLegalDocumentForUpdate(language, LegalDocumentType.PRIVACY_STATEMENT); + public PrivacyStatementDTO getPrivacyStatementForUpdate(Language language) { + return (PrivacyStatementDTO) getLegalDocumentForUpdate(language, LegalDocumentType.PRIVACY_STATEMENT); } /** @@ -53,8 +53,8 @@ public PrivacyStatement getPrivacyStatementForUpdate(Language language) { * @param language the language of the imprint * @return the imprint that should be updated */ - public Imprint getImprintForUpdate(Language language) { - return (Imprint) getLegalDocumentForUpdate(language, LegalDocumentType.IMPRINT); + public ImprintDTO getImprintForUpdate(Language language) { + return (ImprintDTO) getLegalDocumentForUpdate(language, LegalDocumentType.IMPRINT); } /** @@ -63,8 +63,8 @@ public Imprint getImprintForUpdate(Language language) { * @param language the language of the imprint * @return the imprint to view */ - public Imprint getImprint(Language language) { - return (Imprint) getLegalDocument(language, LegalDocumentType.IMPRINT); + public ImprintDTO getImprint(Language language) { + return (ImprintDTO) getLegalDocument(language, LegalDocumentType.IMPRINT); } /** @@ -73,8 +73,8 @@ public Imprint getImprint(Language language) { * @param language the language of the privacy statement * @return the privacy statement to view */ - public PrivacyStatement getPrivacyStatement(Language language) { - return (PrivacyStatement) getLegalDocument(language, LegalDocumentType.PRIVACY_STATEMENT); + public PrivacyStatementDTO getPrivacyStatement(Language language) { + return (PrivacyStatementDTO) getLegalDocument(language, LegalDocumentType.PRIVACY_STATEMENT); } /** @@ -83,8 +83,8 @@ public PrivacyStatement getPrivacyStatement(Language language) { * @param imprint the imprint to update with the new content * @return the updated imprint */ - public Imprint updateImprint(Imprint imprint) { - return (Imprint) updateLegalDocument(imprint); + public ImprintDTO updateImprint(ImprintDTO imprint) { + return (ImprintDTO) updateLegalDocument(imprint); } /** @@ -93,8 +93,8 @@ public Imprint updateImprint(Imprint imprint) { * @param privacyStatement the privacy statement to update with the new content * @return the updated privacy statement */ - public PrivacyStatement updatePrivacyStatement(PrivacyStatement privacyStatement) { - return (PrivacyStatement) updateLegalDocument(privacyStatement); + public PrivacyStatementDTO updatePrivacyStatement(PrivacyStatementDTO privacyStatement) { + return (PrivacyStatementDTO) updateLegalDocument(privacyStatement); } /** @@ -108,8 +108,8 @@ public PrivacyStatement updatePrivacyStatement(PrivacyStatement privacyStatement private LegalDocument getLegalDocumentForUpdate(Language language, LegalDocumentType type) { if (getLegalDocumentPathIfExists(language, type).isEmpty()) { return switch (type) { - case PRIVACY_STATEMENT -> new PrivacyStatement("", language); - case IMPRINT -> new Imprint("", language); + case PRIVACY_STATEMENT -> new PrivacyStatementDTO("", language); + case IMPRINT -> new ImprintDTO("", language); }; } @@ -145,27 +145,27 @@ private LegalDocument readLegalDocument(Language language, LegalDocumentType typ legalDocumentText = Files.readString(getLegalDocumentPath(language, type)); } catch (IOException e) { - log.error("Could not read {} file for language {}:{}", type, language, e); + log.error("Could not read {} file for language {}:{}", type, language, e.getMessage(), e); throw new InternalServerErrorException("Could not read " + type + " file for language " + language); } - return type == LegalDocumentType.PRIVACY_STATEMENT ? new PrivacyStatement(legalDocumentText, language) : new Imprint(legalDocumentText, language); + return type == LegalDocumentType.PRIVACY_STATEMENT ? new PrivacyStatementDTO(legalDocumentText, language) : new ImprintDTO(legalDocumentText, language); } protected LegalDocument updateLegalDocument(LegalDocument legalDocument) { - if (legalDocument.getText().isBlank()) { - throw new BadRequestAlertException("Legal document text cannot be empty", legalDocument.getType().name(), "emptyLegalDocument"); + if (legalDocument.text().isBlank()) { + throw new BadRequestAlertException("Legal document text cannot be empty", legalDocument.type().name(), "emptyLegalDocument"); } try { // If the directory, doesn't exist, we need to create the directory first, otherwise writeString fails. if (!Files.exists(legalDocumentsBasePath)) { Files.createDirectories(legalDocumentsBasePath); } - FileUtils.writeStringToFile(getLegalDocumentPath(legalDocument.getLanguage(), legalDocument.getType()).toFile(), legalDocument.getText(), StandardCharsets.UTF_8); + FileUtils.writeStringToFile(getLegalDocumentPath(legalDocument.language(), legalDocument.type()).toFile(), legalDocument.text(), StandardCharsets.UTF_8); return legalDocument; } catch (IOException e) { - log.error("Could not update {} file for language {}: {} ", legalDocument.getType(), legalDocument.getLanguage(), e); - throw new InternalServerErrorException("Could not update " + legalDocument.getType() + " file for language " + legalDocument.getLanguage()); + log.error("Could not update {} file for language {}: {} ", legalDocument.type(), legalDocument.language(), e.getMessage(), e); + throw new InternalServerErrorException("Could not update " + legalDocument.type() + " file for language " + legalDocument.language()); } } diff --git a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiff.java b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiff.java index adb8b7d4f30e..4cb112c8ffa5 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiff.java +++ b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiff.java @@ -1,6 +1,7 @@ package de.tum.in.www1.artemis.service.util.structureoraclegenerator; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.function.Function; @@ -333,7 +334,7 @@ else if (solutionList == null || templateList == null) { if (solutionList.size() != templateList.size()) { return false; } - return solutionList.containsAll(templateList); + return new HashSet<>(solutionList).containsAll(templateList); } /** @@ -361,7 +362,7 @@ private static boolean parameterTypesAreEqual(JavaExecutable solutionExecutable, } // Otherwise, check if the list of the parameters of the solution executable contains all the parameters in the template executable. - return solutionParams.containsAll(templateParams); + return new HashSet<>(solutionParams).containsAll(templateParams); } /** diff --git a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiffSerializer.java b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiffSerializer.java index d53bd1035a7e..4e62d4f67432 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiffSerializer.java +++ b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/JavaClassDiffSerializer.java @@ -2,8 +2,9 @@ import java.util.HashSet; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.thoughtworks.qdox.model.JavaAnnotatedElement; import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaClass; @@ -19,6 +20,8 @@ class JavaClassDiffSerializer { private final JavaClassDiff javaClassDiff; + private static final ObjectMapper mapper = new ObjectMapper(); + JavaClassDiffSerializer(JavaClassDiff javaClassDiff) { this.javaClassDiff = javaClassDiff; } @@ -36,77 +39,76 @@ class JavaClassDiffSerializer { * * @return The JSON object consisting of JSON objects representation for the wanted hierarchy properties of a type defined in the types diff. */ - JsonObject serializeClassProperties() { - JsonObject classJSON = new JsonObject(); + ObjectNode serializeClassProperties() { + ObjectNode classJSON = mapper.createObjectNode(); - classJSON.addProperty("name", javaClassDiff.getName()); - classJSON.addProperty("package", javaClassDiff.getPackageName()); + // Directly setting values based on class properties + classJSON.put("name", javaClassDiff.getName()); + classJSON.put("package", javaClassDiff.getPackageName()); + classJSON.put("isInterface", javaClassDiff.isInterfaceDifferent); + classJSON.put("isEnum", javaClassDiff.isEnumDifferent); + classJSON.put("isAbstract", javaClassDiff.isAbstractDifferent); - if (javaClassDiff.isInterfaceDifferent) { - classJSON.addProperty("isInterface", true); - } - if (javaClassDiff.isEnumDifferent) { - classJSON.addProperty("isEnum", true); - } - if (javaClassDiff.isAbstractDifferent) { - classJSON.addProperty("isAbstract", true); - } + // Adding superclass name if present if (!javaClassDiff.superClassNameDiff.isEmpty()) { - classJSON.addProperty("superclass", javaClassDiff.superClassNameDiff); + classJSON.put("superclass", javaClassDiff.superClassNameDiff); } - if (!javaClassDiff.superInterfacesDiff.isEmpty()) { - JsonArray superInterfaces = new JsonArray(); - for (JavaClass superInterface : javaClassDiff.superInterfacesDiff) { - superInterfaces.add(superInterface.getSimpleName()); - } - classJSON.add("interfaces", superInterfaces); + // Serializing interfaces + if (!javaClassDiff.superInterfacesDiff.isEmpty()) { + classJSON.set("interfaces", serializeSuperInterfaces(javaClassDiff.superInterfacesDiff)); } - if (!javaClassDiff.annotationsDiff.isEmpty()) { - JsonArray annotations = new JsonArray(); - for (JavaAnnotation annotation : javaClassDiff.annotationsDiff) { - annotations.add(annotation.getType().getSimpleName()); - } - classJSON.add("annotations", annotations); + // Serializing annotations + if (!javaClassDiff.annotationsDiff.isEmpty()) { + classJSON.set("annotations", serializeAnnotations(javaClassDiff.annotationsDiff)); } + return classJSON; } + private ArrayNode serializeSuperInterfaces(Iterable superInterfaces) { + ArrayNode superInterfacesNode = mapper.createArrayNode(); + superInterfaces.forEach(superInterface -> superInterfacesNode.add(superInterface.getSimpleName())); + return superInterfacesNode; + } + + private ArrayNode serializeAnnotations(Iterable annotations) { + ArrayNode annotationsNode = mapper.createArrayNode(); + annotations.forEach(annotation -> annotationsNode.add(annotation.getType().getSimpleName())); + return annotationsNode; + } + /** * This method is used to serialize the attributes of a class into a JSON array containing the following information for each attribute defined in the classes packed into a * JSON object: - Name - Modifiers (if any) - Type * * @return The JSON array consisting of JSON objects representation for each attribute defined in the classes diff. */ - JsonArray serializeAttributes() { - JsonArray attributesJSON = new JsonArray(); - - for (JavaField attribute : javaClassDiff.attributesDiff) { - if (isElementToIgnore(attribute)) { - continue; - } - JsonObject attributeJSON = SerializerUtil.createJsonObject(attribute.getName(), new HashSet<>(attribute.getModifiers()), attribute, attribute.getAnnotations()); - attributeJSON.addProperty("type", attribute.getType().getValue()); - attributesJSON.add(attributeJSON); - } + ArrayNode serializeAttributes() { + ArrayNode attributesJSON = mapper.createArrayNode(); + + javaClassDiff.attributesDiff.stream().filter(attribute -> !JavaClassDiffSerializer.isElementToIgnore(attribute)) + .forEach(attribute -> attributesJSON.add(createAttributeJson(attribute))); return attributesJSON; } + private ObjectNode createAttributeJson(JavaField attribute) { + ObjectNode attributeJSON = SerializerUtil.createJsonObject(attribute.getName(), new HashSet<>(attribute.getModifiers()), attribute, attribute.getAnnotations()); + attributeJSON.put("type", attribute.getType().getValue()); + return attributeJSON; + } + /** * This method is used to serialize the enums of a class into a JSON array containing each enum value: * * @return The JSON array consisting of JSON objects representation for each enum defined in the classes diff. */ - JsonArray serializeEnums() { - JsonArray enumsJSON = new JsonArray(); + ArrayNode serializeEnums() { + ArrayNode enumsJSON = mapper.createArrayNode(); - for (JavaField javaEnum : javaClassDiff.enumsDiff) { - if (!isElementToIgnore(javaEnum)) { - enumsJSON.add(javaEnum.getName()); - } - } + javaClassDiff.enumsDiff.stream().filter(enumField -> !isElementToIgnore(enumField)).forEach(enumField -> enumsJSON.add(enumField.getName())); return enumsJSON; } @@ -117,52 +119,50 @@ JsonArray serializeEnums() { * * @return The JSON array consisting of JSON objects representation for each constructor defined in the classes diff. */ - JsonArray serializeConstructors() { - JsonArray constructorsJSON = new JsonArray(); - - for (JavaConstructor constructor : javaClassDiff.constructorsDiff) { - if (isElementToIgnore(constructor)) { - continue; - } - JsonObject constructorJSON = new JsonObject(); - - if (!constructor.getModifiers().isEmpty()) { - constructorJSON.add("modifiers", SerializerUtil.serializeModifiers(new HashSet<>(constructor.getModifiers()), constructor)); - } - if (!constructor.getParameters().isEmpty()) { - constructorJSON.add("parameters", SerializerUtil.serializeParameters(constructor.getParameters())); - } - if (!constructor.getAnnotations().isEmpty()) { - constructorJSON.add("annotations", SerializerUtil.serializeAnnotations(constructor.getAnnotations())); - } - constructorsJSON.add(constructorJSON); - } + ArrayNode serializeConstructors() { + ArrayNode constructorsJSON = mapper.createArrayNode(); + + javaClassDiff.constructorsDiff.stream().filter(constructor -> !isElementToIgnore(constructor)) + .forEach(constructor -> constructorsJSON.add(serializeConstructor(constructor))); + return constructorsJSON; } + private ObjectNode serializeConstructor(JavaConstructor constructor) { + ObjectNode constructorJSON = mapper.createObjectNode(); + + // No need to check for isEmpty before adding; let the serializer utility methods decide how to handle empty collections + constructorJSON.set("modifiers", SerializerUtil.serializeModifiers(new HashSet<>(constructor.getModifiers()), constructor)); + constructorJSON.set("parameters", SerializerUtil.serializeParameters(constructor.getParameters())); + constructorJSON.set("annotations", SerializerUtil.serializeAnnotations(constructor.getAnnotations())); + + return constructorJSON; + } + /** * This method is used to serialize the methods of a type into a JSON array containing the following information for each method defined in the classes packed into a JSON * object: - Name - Modifiers (if any) - Parameter types (if any) - Return type * * @return The JSON array consisting of JSON objects representation for each method defined in the types diff. */ - JsonArray serializeMethods() { - JsonArray methodsJSON = new JsonArray(); - - for (JavaMethod method : javaClassDiff.methodsDiff) { - if (isElementToIgnore(method)) { - continue; - } - JsonObject methodJSON = SerializerUtil.createJsonObject(method.getName(), new HashSet<>(method.getModifiers()), method, method.getAnnotations()); - if (!method.getParameters().isEmpty()) { - methodJSON.add("parameters", SerializerUtil.serializeParameters(method.getParameters())); - } - methodJSON.addProperty("returnType", method.getReturnType().getValue()); - methodsJSON.add(methodJSON); - } + ArrayNode serializeMethods() { + ArrayNode methodsJSON = mapper.createArrayNode(); + + javaClassDiff.methodsDiff.stream().filter(method -> !isElementToIgnore(method)).forEach(method -> methodsJSON.add(createMethodJson(method))); + return methodsJSON; } + private ObjectNode createMethodJson(JavaMethod method) { + ObjectNode methodJSON = SerializerUtil.createJsonObject(method.getName(), new HashSet<>(method.getModifiers()), method, method.getAnnotations()); + + // No need to check for isEmpty before adding; let the serializer utility methods decide how to handle empty collections + methodJSON.set("parameters", SerializerUtil.serializeParameters(method.getParameters())); + methodJSON.put("returnType", method.getReturnType().getValue()); + + return methodJSON; + } + public static boolean isElementToIgnore(JavaAnnotatedElement element) { return element.getTagByName("oracleIgnore") != null; } diff --git a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/OracleGenerator.java b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/OracleGenerator.java index 8a37e2f0591b..42476a9e8db6 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/OracleGenerator.java +++ b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/OracleGenerator.java @@ -8,15 +8,20 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.thoughtworks.qdox.JavaProjectBuilder; import com.thoughtworks.qdox.model.JavaClass; +import com.thoughtworks.qdox.model.JavaType; import de.tum.in.www1.artemis.web.rest.errors.InternalServerErrorException; @@ -25,7 +30,7 @@ * It is used to automatically generate the structure oracle with the solution of the exercise as the system model and the template as the test model. * The oracle is saved in the form of a JSON file in test.json. * The structure oracle is used in the structural tests and contains information on the expected structural elements that the student has to implement. - * The generator uses the qdox framework. + * The generator uses the qdox framework (qdox). * It extracts first the needed elements by doing a so-called diff of each element e.g. the difference between the solution of an exercise and its template. * The generator uses separate data structures that contain the elements of these diffs and then creates JSON representations of them. *

@@ -59,6 +64,8 @@ public class OracleGenerator { private static final Logger log = LoggerFactory.getLogger(OracleGenerator.class); + private static final ObjectMapper mapper = new ObjectMapper(); + /** * This method generates the structure oracle by scanning the Java projects contained in the paths passed as arguments. * @@ -69,50 +76,40 @@ public class OracleGenerator { public static String generateStructureOracleJSON(Path solutionProjectPath, Path templateProjectPath) { log.debug("Generating the Oracle for the following projects:\nSolution project: {}\nTemplate project: {}\n", solutionProjectPath, templateProjectPath); - // Initialize the empty string. - JsonArray structureOracleJSON = new JsonArray(); - - // Generate the pairs of the types found in the solution project with the corresponding one from the template project. Map solutionToTemplateMapping = generateSolutionToTemplateMapping(solutionProjectPath, templateProjectPath); + ArrayNode structureOracleJSON = mapper.createArrayNode(); - // Loop over each pair of types and create the diff data structures and the JSON representation afterwards for each. - // If the types, classes or enums are equal, then ignore and continue with the next pair - for (var entry : solutionToTemplateMapping.entrySet()) { - JsonObject diffJSON = new JsonObject(); - JavaClass solutionType = entry.getKey(); - JavaClass templateType = entry.getValue(); - - // Initialize the types diff containing various properties as well as methods. - JavaClassDiff javaClassDiff = new JavaClassDiff(solutionType, templateType); - if (javaClassDiff.classesAreEqual() || JavaClassDiffSerializer.isElementToIgnore(solutionType)) { - continue; - } - - // If we are dealing with interfaces, the types diff already has all the information we need - // So we do not need to do anything more - JavaClassDiffSerializer serializer = new JavaClassDiffSerializer(javaClassDiff); - diffJSON.add("class", serializer.serializeClassProperties()); - if (!javaClassDiff.methodsDiff.isEmpty()) { - diffJSON.add("methods", serializer.serializeMethods()); - } + solutionToTemplateMapping.entrySet().stream().map(entry -> generateDiffJSON(entry.getKey(), entry.getValue())).filter(Optional::isPresent).map(Optional::get) + .forEach(structureOracleJSON::add); - if (!javaClassDiff.attributesDiff.isEmpty()) { - diffJSON.add("attributes", serializer.serializeAttributes()); - } + return prettyPrint(structureOracleJSON); + } - if (!javaClassDiff.enumsDiff.isEmpty()) { - diffJSON.add("enumValues", serializer.serializeEnums()); - } + private static Optional generateDiffJSON(JavaClass solutionType, JavaClass templateType) { + JavaClassDiff javaClassDiff = new JavaClassDiff(solutionType, templateType); + if (javaClassDiff.classesAreEqual() || JavaClassDiffSerializer.isElementToIgnore(solutionType)) { + return Optional.empty(); + } - if (!javaClassDiff.constructorsDiff.isEmpty()) { - diffJSON.add("constructors", serializer.serializeConstructors()); - } + ObjectNode diffJSON = mapper.createObjectNode(); + JavaClassDiffSerializer serializer = new JavaClassDiffSerializer(javaClassDiff); + diffJSON.set("class", serializer.serializeClassProperties()); - log.debug("Generated JSON for '{}'.", solutionType.getCanonicalName()); - structureOracleJSON.add(diffJSON); + if (!javaClassDiff.methodsDiff.isEmpty()) { + diffJSON.set("methods", serializer.serializeMethods()); + } + if (!javaClassDiff.attributesDiff.isEmpty()) { + diffJSON.set("attributes", serializer.serializeAttributes()); + } + if (!javaClassDiff.enumsDiff.isEmpty()) { + diffJSON.set("enumValues", serializer.serializeEnums()); + } + if (!javaClassDiff.constructorsDiff.isEmpty()) { + diffJSON.set("constructors", serializer.serializeConstructors()); } - return prettyPrint(structureOracleJSON); + log.debug("Generated JSON for '{}'.", solutionType.getCanonicalName()); + return Optional.of(diffJSON); } /** @@ -121,8 +118,15 @@ public static String generateStructureOracleJSON(Path solutionProjectPath, Path * @param jsonArray The JSON array that needs to get pretty printed. * @return The pretty printed JSON array in its string representation. */ - private static String prettyPrint(JsonArray jsonArray) { - return new GsonBuilder().setPrettyPrinting().create().toJson(jsonArray); + private static String prettyPrint(ArrayNode jsonArray) { + try { + return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonArray); + } + catch (JsonProcessingException e) { + var error = "Error pretty printing JSON"; + log.error(error, e); + throw new InternalServerErrorException(error); + } } /** @@ -140,22 +144,22 @@ private static Map generateSolutionToTemplateMapping(Path List solutionFiles = retrieveJavaSourceFiles(solutionProjectPath); log.debug("Template Java Files {}", templateFiles); log.debug("Solution Java Files {}", solutionFiles); + var templateClasses = getClassesFromFiles(templateFiles); var solutionClasses = getClassesFromFiles(solutionFiles); - Map solutionToTemplateMapping = new HashMap<>(); + // Convert template classes into a map for quick lookup + Map templateClassMap = templateClasses.stream() + .collect(Collectors.toMap(JavaType::getCanonicalName, Function.identity(), (existing, replacement) -> existing)); // In case of name + // conflicts, keep + // the existing + // Map each solution class to its counterpart in the template + Map solutionToTemplateMapping = new HashMap<>(); for (JavaClass solutionClass : solutionClasses) { - // Put an empty template class as a default placeholder. - solutionToTemplateMapping.put(solutionClass, null); - - for (JavaClass templateClass : templateClasses) { - if (solutionClass.getSimpleName().equals(templateClass.getSimpleName()) && templateClass.getPackageName().equals(solutionClass.getPackageName())) { - // If a template class with the same name and package gets found, then replace the empty template with the real one. - solutionToTemplateMapping.put(solutionClass, templateClass); - break; - } - } + String qualifiedName = solutionClass.getCanonicalName(); + JavaClass templateClass = templateClassMap.get(qualifiedName); + solutionToTemplateMapping.put(solutionClass, templateClass); } return solutionToTemplateMapping; diff --git a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/SerializerUtil.java b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/SerializerUtil.java index 6caadbd11ee9..4f1d4db58f37 100644 --- a/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/SerializerUtil.java +++ b/src/main/java/de/tum/in/www1/artemis/service/util/structureoraclegenerator/SerializerUtil.java @@ -3,8 +3,9 @@ import java.util.List; import java.util.Set; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaField; import com.thoughtworks.qdox.model.JavaMember; @@ -17,6 +18,8 @@ */ class SerializerUtil { + private static final ObjectMapper mapper = new ObjectMapper(); + /** * This method is used to serialize the string representations of each modifier into a JSON array. * @@ -24,31 +27,26 @@ class SerializerUtil { * @param javaMember The model of the {@link java.lang.reflect.Member} for which all modifiers should get serialized * @return The JSON array containing the string representations of the modifiers. */ - static JsonArray serializeModifiers(Set modifiers, JavaMember javaMember) { - JsonArray modifiersArray = new JsonArray(); + static ArrayNode serializeModifiers(Set modifiers, JavaMember javaMember) { + ArrayNode modifiersArray = mapper.createArrayNode(); + // Check if the class is an interface and adjust modifiers accordingly if (javaMember.getDeclaringClass().isInterface()) { - // Add some additional modifiers that are not reported by the qdox framework if (javaMember instanceof JavaMethod method) { if (method.isDefault() || method.isStatic()) { - // default and static interface methods are always public modifiers.add("public"); } else if (!method.isPrivate()) { - // "normal" interface methods are always public and abstract modifiers.add("public"); modifiers.add("abstract"); } } else if (javaMember instanceof JavaField) { - // interface attributes are always public, static and final modifiers.add("public"); modifiers.add("static"); modifiers.add("final"); } } - for (String modifier : modifiers) { - modifiersArray.add(modifier); - } + modifiers.forEach(modifiersArray::add); return modifiersArray; } @@ -58,12 +56,10 @@ else if (javaMember instanceof JavaField) { * @param annotations The annotations of the java member (e.g. Override, Inject, etc.) * @return The JSON array containing the string representations of the modifiers. */ - static JsonArray serializeAnnotations(List annotations) { + static ArrayNode serializeAnnotations(List annotations) { filterAnnotations(annotations); - JsonArray annotationsArray = new JsonArray(); - for (JavaAnnotation annotation : annotations) { - annotationsArray.add(annotation.getType().getSimpleName()); - } + ArrayNode annotationsArray = mapper.createArrayNode(); + annotations.forEach(annotation -> annotationsArray.add(annotation.getType().getSimpleName())); return annotationsArray; } @@ -83,12 +79,9 @@ private static void filterAnnotations(List annotations) { * @param parameters A collection of modifiers that needs to get serialized. * @return The JSON array containing the string representations of the parameter types. */ - static JsonArray serializeParameters(List parameters) { - JsonArray parametersArray = new JsonArray(); - for (JavaParameter parameter : parameters) { - parametersArray.add(parameter.getType().getValue()); - } - + static ArrayNode serializeParameters(List parameters) { + ArrayNode parametersArray = mapper.createArrayNode(); + parameters.forEach(parameter -> parametersArray.add(parameter.getType().getValue())); return parametersArray; } @@ -102,12 +95,12 @@ static JsonArray serializeParameters(List parameters) { * @return A new JSON object containing all serialized modifiers under the {@code "modifiers"} key and the name of * the object under the {@code "name"} key */ - static JsonObject createJsonObject(String name, Set modifiers, JavaMember javaMember, List annotations) { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("name", name); - jsonObject.add("modifiers", serializeModifiers(modifiers, javaMember)); + static ObjectNode createJsonObject(String name, Set modifiers, JavaMember javaMember, List annotations) { + ObjectNode jsonObject = mapper.createObjectNode(); + jsonObject.put("name", name); + jsonObject.set("modifiers", serializeModifiers(modifiers, javaMember)); if (!annotations.isEmpty()) { - jsonObject.add("annotations", serializeAnnotations(annotations)); + jsonObject.set("annotations", serializeAnnotations(annotations)); } return jsonObject; } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminImprintResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminImprintResource.java index c7fe2aa2e857..bc1a4c3bcdf3 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminImprintResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminImprintResource.java @@ -13,10 +13,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import de.tum.in.www1.artemis.domain.Imprint; import de.tum.in.www1.artemis.domain.enumeration.Language; import de.tum.in.www1.artemis.security.annotations.EnforceAdmin; import de.tum.in.www1.artemis.service.LegalDocumentService; +import de.tum.in.www1.artemis.web.rest.dto.ImprintDTO; /** * REST controller for editing the imprint as an admin. @@ -41,7 +41,7 @@ public AdminImprintResource(LegalDocumentService legalDocumentService) { */ @GetMapping("imprint-for-update") @EnforceAdmin - public ResponseEntity getImprintForUpdate(@RequestParam("language") String language) { + public ResponseEntity getImprintForUpdate(@RequestParam("language") String language) { if (!Language.isValidShortName(language)) { throw new BadRequestException("Language not supported"); } @@ -56,7 +56,7 @@ public ResponseEntity getImprintForUpdate(@RequestParam("language") Str */ @PutMapping("imprint") @EnforceAdmin - public ResponseEntity updateImprint(@RequestBody Imprint imprint) { + public ResponseEntity updateImprint(@RequestBody ImprintDTO imprint) { return ResponseEntity.ok(legalDocumentService.updateImprint(imprint)); } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminPrivacyStatementResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminPrivacyStatementResource.java index d7108cb06fac..67e1d582fade 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminPrivacyStatementResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/admin/AdminPrivacyStatementResource.java @@ -13,10 +13,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import de.tum.in.www1.artemis.domain.PrivacyStatement; import de.tum.in.www1.artemis.domain.enumeration.Language; import de.tum.in.www1.artemis.security.annotations.EnforceAdmin; import de.tum.in.www1.artemis.service.LegalDocumentService; +import de.tum.in.www1.artemis.web.rest.dto.PrivacyStatementDTO; /** * REST controller for editing the Privacy Statement as an admin. @@ -41,7 +41,7 @@ public AdminPrivacyStatementResource(LegalDocumentService legalDocumentService) */ @EnforceAdmin @GetMapping("privacy-statement-for-update") - public ResponseEntity getPrivacyStatementForUpdate(@RequestParam("language") String language) { + public ResponseEntity getPrivacyStatementForUpdate(@RequestParam("language") String language) { if (!Language.isValidShortName(language)) { throw new BadRequestException("Language not supported"); } @@ -56,7 +56,7 @@ public ResponseEntity getPrivacyStatementForUpdate(@RequestPar */ @EnforceAdmin @PutMapping("privacy-statement") - public ResponseEntity updatePrivacyStatement(@RequestBody PrivacyStatement privacyStatement) { + public ResponseEntity updatePrivacyStatement(@RequestBody PrivacyStatementDTO privacyStatement) { return ResponseEntity.ok(legalDocumentService.updatePrivacyStatement(privacyStatement)); } } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/ImprintDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/ImprintDTO.java new file mode 100644 index 000000000000..5ce6639a2f5d --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/ImprintDTO.java @@ -0,0 +1,14 @@ +package de.tum.in.www1.artemis.web.rest.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.enumeration.Language; +import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record ImprintDTO(LegalDocumentType type, String text, Language language) implements LegalDocument { + + public ImprintDTO(String text, Language language) { + this(LegalDocumentType.IMPRINT, text, language); + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/LegalDocument.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/LegalDocument.java new file mode 100644 index 000000000000..9fc42a921a26 --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/LegalDocument.java @@ -0,0 +1,16 @@ +package de.tum.in.www1.artemis.web.rest.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.enumeration.Language; +import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public interface LegalDocument { + + LegalDocumentType type(); + + String text(); + + Language language(); +} diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/PrivacyStatementDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/PrivacyStatementDTO.java new file mode 100644 index 000000000000..f7068706872e --- /dev/null +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/PrivacyStatementDTO.java @@ -0,0 +1,14 @@ +package de.tum.in.www1.artemis.web.rest.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import de.tum.in.www1.artemis.domain.enumeration.Language; +import de.tum.in.www1.artemis.domain.enumeration.LegalDocumentType; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record PrivacyStatementDTO(LegalDocumentType type, String text, Language language) implements LegalDocument { + + public PrivacyStatementDTO(String text, Language language) { + this(LegalDocumentType.PRIVACY_STATEMENT, text, language); + } +} diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicImprintResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicImprintResource.java index 7879d1c2bdd0..c0c5ba722e7d 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicImprintResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicImprintResource.java @@ -11,10 +11,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import de.tum.in.www1.artemis.domain.Imprint; import de.tum.in.www1.artemis.domain.enumeration.Language; import de.tum.in.www1.artemis.security.annotations.EnforceNothing; import de.tum.in.www1.artemis.service.LegalDocumentService; +import de.tum.in.www1.artemis.web.rest.dto.ImprintDTO; /** * REST controller for retrieving the imprint. @@ -39,7 +39,7 @@ public PublicImprintResource(LegalDocumentService legalDocumentService) { */ @GetMapping("imprint") @EnforceNothing - public ResponseEntity getImprint(@RequestParam("language") String language) { + public ResponseEntity getImprint(@RequestParam("language") String language) { if (!Language.isValidShortName(language)) { throw new BadRequestException("Language not supported"); } diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicPrivacyStatementResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicPrivacyStatementResource.java index 2d6001b58a0c..a3da13b8618e 100644 --- a/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicPrivacyStatementResource.java +++ b/src/main/java/de/tum/in/www1/artemis/web/rest/open/PublicPrivacyStatementResource.java @@ -11,10 +11,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import de.tum.in.www1.artemis.domain.PrivacyStatement; import de.tum.in.www1.artemis.domain.enumeration.Language; import de.tum.in.www1.artemis.security.annotations.EnforceNothing; import de.tum.in.www1.artemis.service.LegalDocumentService; +import de.tum.in.www1.artemis.web.rest.dto.PrivacyStatementDTO; /** * REST controller for retrieving the Privacy Statement. @@ -39,7 +39,7 @@ public PublicPrivacyStatementResource(LegalDocumentService legalDocumentService) */ @EnforceNothing @GetMapping("privacy-statement") - public ResponseEntity getPrivacyStatement(@RequestParam("language") String language) { + public ResponseEntity getPrivacyStatement(@RequestParam("language") String language) { if (!Language.isValidShortName(language)) { throw new BadRequestException("Language not supported"); } diff --git a/src/main/webapp/app/core/legal/imprint.component.ts b/src/main/webapp/app/core/legal/imprint.component.ts index e29ca5b34d35..c7b1ca5e2ff6 100644 --- a/src/main/webapp/app/core/legal/imprint.component.ts +++ b/src/main/webapp/app/core/legal/imprint.component.ts @@ -10,7 +10,7 @@ import { LegalDocumentService } from 'app/shared/service/legal-document.service' template: `

`, }) export class ImprintComponent implements AfterViewInit, OnInit, OnDestroy { - imprint: string; + imprint?: string; private languageChangeSubscription?: Subscription; constructor( diff --git a/src/main/webapp/app/core/legal/privacy.component.ts b/src/main/webapp/app/core/legal/privacy.component.ts index a1855578dd9e..95a9b73eec9c 100644 --- a/src/main/webapp/app/core/legal/privacy.component.ts +++ b/src/main/webapp/app/core/legal/privacy.component.ts @@ -16,7 +16,7 @@ import { LegalDocumentLanguage } from 'app/entities/legal-document.model'; `, }) export class PrivacyComponent implements AfterViewInit, OnInit, OnDestroy { - privacyStatement: string; + privacyStatement?: string; private languageChangeSubscription?: Subscription; isAuthenticated: boolean; diff --git a/src/main/webapp/app/entities/legal-document.model.ts b/src/main/webapp/app/entities/legal-document.model.ts index 4e6ef8a1fb1f..65c91ef51b26 100644 --- a/src/main/webapp/app/entities/legal-document.model.ts +++ b/src/main/webapp/app/entities/legal-document.model.ts @@ -19,7 +19,7 @@ export enum LegalDocumentType { */ export class LegalDocument { language: LegalDocumentLanguage; - text: string; + text?: string; type: LegalDocumentType; constructor(type: LegalDocumentType, language: LegalDocumentLanguage) { this.type = type; diff --git a/src/test/java/de/tum/in/www1/artemis/ImprintResourceIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/ImprintResourceIntegrationTest.java index afd7a77f2576..7126cb9b2bd0 100644 --- a/src/test/java/de/tum/in/www1/artemis/ImprintResourceIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/ImprintResourceIntegrationTest.java @@ -11,6 +11,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; @@ -20,10 +22,10 @@ import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.databind.ObjectMapper; -import de.tum.in.www1.artemis.domain.Imprint; import de.tum.in.www1.artemis.domain.enumeration.Language; +import de.tum.in.www1.artemis.web.rest.dto.ImprintDTO; class ImprintResourceIntegrationTest extends AbstractSpringIntegrationIndependentTest { @@ -31,7 +33,7 @@ class ImprintResourceIntegrationTest extends AbstractSpringIntegrationIndependen @Test void testGetImprint_unsupportedLanguageBadRequest() throws Exception { - request.get("/api/public/imprint?language=fr", HttpStatus.BAD_REQUEST, Imprint.class); + request.get("/api/public/imprint?language=fr", HttpStatus.BAD_REQUEST, ImprintDTO.class); } @Test @@ -39,7 +41,7 @@ void testGetImprint_cannotReadFileInternalServerError() throws Exception { try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(true); mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenThrow(new IOException()); - request.get("/api/public/imprint?language=de", HttpStatus.INTERNAL_SERVER_ERROR, Imprint.class); + request.get("/api/public/imprint?language=de", HttpStatus.INTERNAL_SERVER_ERROR, ImprintDTO.class); } } @@ -49,7 +51,7 @@ void testGetImprintForUpdate_cannotReadFileInternalServerError() throws Exceptio try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(true); mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenThrow(new IOException()); - request.get("/api/admin/imprint-for-update?language=de", HttpStatus.INTERNAL_SERVER_ERROR, Imprint.class); + request.get("/api/admin/imprint-for-update?language=de", HttpStatus.INTERNAL_SERVER_ERROR, ImprintDTO.class); } } @@ -60,30 +62,30 @@ void testUpdatePrivacyStatement_cannotWriteFileInternalServerError() throws Exce mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(true); mockedFileUtils.when(() -> FileUtils.writeStringToFile(argThat(file -> file.toString().contains("_de")), anyString(), eq(StandardCharsets.UTF_8))) .thenThrow(new IOException()); - request.putWithResponseBody("/api/admin/imprint", new Imprint("text", Language.GERMAN), Imprint.class, HttpStatus.INTERNAL_SERVER_ERROR); + request.putWithResponseBody("/api/admin/imprint", new ImprintDTO("text", Language.GERMAN), ImprintDTO.class, HttpStatus.INTERNAL_SERVER_ERROR); } } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdatePrivacyStatement_directoryDoesntExist_createsDirectoryAndSavesFile() throws Exception { - Imprint response; + ImprintDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class); MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(false); - response = request.putWithResponseBody("/api/admin/imprint", new Imprint("updatedText", Language.GERMAN), Imprint.class, HttpStatus.OK); + response = request.putWithResponseBody("/api/admin/imprint", new ImprintDTO("updatedText", Language.GERMAN), ImprintDTO.class, HttpStatus.OK); mockedFiles.verify(() -> Files.createDirectories(any())); mockedFileUtils.verify(() -> FileUtils.writeStringToFile(argThat(file -> file.toString().contains("_de")), anyString(), eq(StandardCharsets.UTF_8))); } - assertThat(response.getText()).isEqualTo("updatedText"); - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isEqualTo("updatedText"); + assertThat(response.language()).isEqualTo(Language.GERMAN); } // no mock user as anonymous access should be allowed @ParameterizedTest @EnumSource(value = Language.class, names = { "GERMAN", "ENGLISH" }) void testGetImprintReturnsOtherLanguageIfFirstLanguageNotFound(Language language) throws Exception { - Imprint response; + ImprintDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { if ("de".equals(language.getShortName())) { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(false); @@ -96,15 +98,15 @@ void testGetImprintReturnsOtherLanguageIfFirstLanguageNotFound(Language language mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenReturn("Impressum"); } - response = request.get("/api/public/imprint?language=" + language.getShortName(), HttpStatus.OK, Imprint.class); + response = request.get("/api/public/imprint?language=" + language.getShortName(), HttpStatus.OK, ImprintDTO.class); } if ("de".equals(language.getShortName())) { - assertThat(response.getLanguage()).isEqualTo(Language.ENGLISH); - assertThat(response.getText()).isEqualTo("Imprint"); + assertThat(response.language()).isEqualTo(Language.ENGLISH); + assertThat(response.text()).isEqualTo("Imprint"); } else { - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); - assertThat(response.getText()).isEqualTo("Impressum"); + assertThat(response.language()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isEqualTo("Impressum"); } } @@ -114,38 +116,38 @@ void testGetImprint_noLanguageFound_badRequest() throws Exception { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(false); mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_en")))).thenReturn(false); - request.get("/api/public/imprint?language=de", HttpStatus.BAD_REQUEST, Imprint.class); + request.get("/api/public/imprint?language=de", HttpStatus.BAD_REQUEST, ImprintDTO.class); } } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testGetImprintForUpdate_instructorAccessForbidden() throws Exception { - request.get("/api/admin/imprint-for-update?language=de", HttpStatus.FORBIDDEN, Imprint.class); + request.get("/api/admin/imprint-for-update?language=de", HttpStatus.FORBIDDEN, ImprintDTO.class); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testGetImprintForUpdate_unsupportedLanguageBadRequest() throws Exception { - request.get("/api/admin/imprint-for-update?language=fr", HttpStatus.BAD_REQUEST, Imprint.class); + request.get("/api/admin/imprint-for-update?language=fr", HttpStatus.BAD_REQUEST, ImprintDTO.class); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") - void testGetImprintForUpdateFileDoesntExist_emptyStringSuccess() throws Exception { - Imprint response; + void testGetImprintForUpdateFileDoesntExist() throws Exception { + ImprintDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(false); - response = request.get("/api/admin/imprint-for-update?language=de", HttpStatus.OK, Imprint.class); + response = request.get("/api/admin/imprint-for-update?language=de", HttpStatus.OK, ImprintDTO.class); } - assertThat(response.getText()).isEqualTo(""); - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isNull(); + assertThat(response.language()).isEqualTo(Language.GERMAN); } @ParameterizedTest @EnumSource(value = Language.class, names = { "GERMAN", "ENGLISH" }) void testGetImprintReturnsCorrectFileContent(Language language) throws Exception { - Imprint response; + ImprintDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(true); if (language == Language.ENGLISH) { @@ -154,15 +156,15 @@ void testGetImprintReturnsCorrectFileContent(Language language) throws Exception else { mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenReturn("Impressum"); } - response = request.get("/api/public/imprint?language=" + language.getShortName(), HttpStatus.OK, Imprint.class); + response = request.get("/api/public/imprint?language=" + language.getShortName(), HttpStatus.OK, ImprintDTO.class); } - assertThat(response.getLanguage()).isEqualTo(language); + assertThat(response.language()).isEqualTo(language); if (language == Language.ENGLISH) { - assertThat(response.getText()).isEqualTo("Imprint"); + assertThat(response.text()).isEqualTo("Imprint"); } else { - assertThat(response.getText()).isEqualTo("Impressum"); + assertThat(response.text()).isEqualTo("Impressum"); } } @@ -170,7 +172,7 @@ void testGetImprintReturnsCorrectFileContent(Language language) throws Exception @EnumSource(value = Language.class, names = { "GERMAN", "ENGLISH" }) @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testGetImprintForUpdateReturnsCorrectFileContent(Language language) throws Exception { - Imprint response; + ImprintDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(true); if ("de".equals(language.getShortName())) { @@ -179,54 +181,51 @@ void testGetImprintForUpdateReturnsCorrectFileContent(Language language) throws else { mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_en")))).thenReturn("Imprint"); } - response = request.get("/api/admin/imprint-for-update?language=" + language.getShortName(), HttpStatus.OK, Imprint.class); + response = request.get("/api/admin/imprint-for-update?language=" + language.getShortName(), HttpStatus.OK, ImprintDTO.class); } - assertThat(response.getLanguage()).isEqualTo(language); + assertThat(response.language()).isEqualTo(language); if ("de".equals(language.getShortName())) { - assertThat(response.getText()).isEqualTo("Impressum"); + assertThat(response.text()).isEqualTo("Impressum"); } else { - assertThat(response.getText()).isEqualTo("Imprint"); + assertThat(response.text()).isEqualTo("Imprint"); } } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUpdateImprint_instructorAccessForbidden() throws Exception { - request.put("/api/admin/imprint", new Imprint(Language.GERMAN), HttpStatus.FORBIDDEN); + request.put("/api/admin/imprint", new ImprintDTO("Impressum", Language.GERMAN), HttpStatus.FORBIDDEN); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdateImprint_writesFile_ReturnsUpdatedFileContent() throws Exception { - Imprint response; - Imprint requestBody = new Imprint(Language.GERMAN); - requestBody.setText("Impressum"); + + ImprintDTO requestBody = new ImprintDTO("Impressum", Language.GERMAN); try (MockedStatic mockedFiles = mockStatic(Files.class); MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(true); - response = request.putWithResponseBody("/api/admin/imprint", requestBody, Imprint.class, HttpStatus.OK); + ImprintDTO response = request.putWithResponseBody("/api/admin/imprint", requestBody, ImprintDTO.class, HttpStatus.OK); mockedFileUtils.verify(() -> FileUtils.writeStringToFile(argThat(file -> file.toString().contains("_de")), anyString(), eq(StandardCharsets.UTF_8))); + assertThat(response.language()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isEqualTo("Impressum"); } - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); - assertThat(response.getText()).isEqualTo("Impressum"); - } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdateImprint_unsupportedLanguageBadRequest() throws Exception { - JsonObject body = new JsonObject(); - body.addProperty("text", "test"); - body.addProperty("language", "FRENCH"); - request.put("/api/admin/imprint", body.toString(), HttpStatus.BAD_REQUEST); + Map requestBody = new HashMap<>(); + requestBody.put("text", "test"); + requestBody.put("language", "FRENCH"); + request.put("/api/admin/imprint", new ObjectMapper().writeValueAsString(requestBody), HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdateImprint_blankTextBadRequest() throws Exception { - Imprint requestBody = new Imprint(Language.GERMAN); - requestBody.setText(" "); + ImprintDTO requestBody = new ImprintDTO(" ", Language.GERMAN); request.put("/api/admin/imprint", requestBody, HttpStatus.BAD_REQUEST); } diff --git a/src/test/java/de/tum/in/www1/artemis/PrivacyStatementResourceIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/PrivacyStatementResourceIntegrationTest.java index b691f3eec82a..2581ad81746b 100644 --- a/src/test/java/de/tum/in/www1/artemis/PrivacyStatementResourceIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/PrivacyStatementResourceIntegrationTest.java @@ -11,6 +11,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; @@ -20,10 +22,10 @@ import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.databind.ObjectMapper; -import de.tum.in.www1.artemis.domain.PrivacyStatement; import de.tum.in.www1.artemis.domain.enumeration.Language; +import de.tum.in.www1.artemis.web.rest.dto.PrivacyStatementDTO; class PrivacyStatementResourceIntegrationTest extends AbstractSpringIntegrationIndependentTest { @@ -31,7 +33,7 @@ class PrivacyStatementResourceIntegrationTest extends AbstractSpringIntegrationI @Test void testGetPrivacyStatement_unsupportedLanguageBadRequest() throws Exception { - request.get("/api/public/privacy-statement?language=fr", HttpStatus.BAD_REQUEST, PrivacyStatement.class); + request.get("/api/public/privacy-statement?language=fr", HttpStatus.BAD_REQUEST, PrivacyStatementDTO.class); } @Test @@ -39,7 +41,7 @@ void testGetPrivacyStatement_cannotReadFileInternalServerError() throws Exceptio try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(true); mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenThrow(new IOException()); - request.get("/api/public/privacy-statement?language=de", HttpStatus.INTERNAL_SERVER_ERROR, PrivacyStatement.class); + request.get("/api/public/privacy-statement?language=de", HttpStatus.INTERNAL_SERVER_ERROR, PrivacyStatementDTO.class); } } @@ -50,7 +52,7 @@ void testGetPrivacyStatementForUpdate_cannotReadFileInternalServerError() throws try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(true); mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenThrow(new IOException()); - request.get("/api/admin/privacy-statement-for-update?language=de", HttpStatus.INTERNAL_SERVER_ERROR, PrivacyStatement.class); + request.get("/api/admin/privacy-statement-for-update?language=de", HttpStatus.INTERNAL_SERVER_ERROR, PrivacyStatementDTO.class); } } @@ -62,7 +64,8 @@ void testUpdatePrivacyStatement_cannotWriteFileInternalServerError() throws Exce mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(true); mockedFileUtils.when(() -> FileUtils.writeStringToFile(argThat(file -> file.toString().contains("_de")), anyString(), eq(StandardCharsets.UTF_8))) .thenThrow(new IOException()); - request.putWithResponseBody("/api/admin/privacy-statement", new PrivacyStatement("text", Language.GERMAN), PrivacyStatement.class, HttpStatus.INTERNAL_SERVER_ERROR); + request.putWithResponseBody("/api/admin/privacy-statement", new PrivacyStatementDTO("text", Language.GERMAN), PrivacyStatementDTO.class, + HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -70,16 +73,17 @@ void testUpdatePrivacyStatement_cannotWriteFileInternalServerError() throws Exce @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdatePrivacyStatement_directoryDoesntExist_createsDirectoryAndSavesFile() throws Exception { - PrivacyStatement response; + PrivacyStatementDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class); MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { mockedFiles.when(() -> Files.exists(any(Path.class))).thenReturn(false); - response = request.putWithResponseBody("/api/admin/privacy-statement", new PrivacyStatement("updatedText", Language.GERMAN), PrivacyStatement.class, HttpStatus.OK); + response = request.putWithResponseBody("/api/admin/privacy-statement", new PrivacyStatementDTO("updatedText", Language.GERMAN), PrivacyStatementDTO.class, + HttpStatus.OK); mockedFiles.verify(() -> Files.createDirectories(any())); mockedFileUtils.verify(() -> FileUtils.writeStringToFile(argThat(file -> file.toString().contains("_de")), anyString(), eq(StandardCharsets.UTF_8))); } - assertThat(response.getText()).isEqualTo("updatedText"); - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isEqualTo("updatedText"); + assertThat(response.language()).isEqualTo(Language.GERMAN); } @@ -87,7 +91,7 @@ void testUpdatePrivacyStatement_directoryDoesntExist_createsDirectoryAndSavesFil @ParameterizedTest @EnumSource(value = Language.class, names = { "GERMAN", "ENGLISH" }) void testGetPrivacyStatementReturnsOtherLanguageIfFirstLanguageNotFound(Language language) throws Exception { - PrivacyStatement response; + PrivacyStatementDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { if ("de".equals(language.getShortName())) { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(false); @@ -100,15 +104,15 @@ void testGetPrivacyStatementReturnsOtherLanguageIfFirstLanguageNotFound(Language mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenReturn("Datenschutzerklärung"); } - response = request.get("/api/public/privacy-statement?language=" + language.getShortName(), HttpStatus.OK, PrivacyStatement.class); + response = request.get("/api/public/privacy-statement?language=" + language.getShortName(), HttpStatus.OK, PrivacyStatementDTO.class); } if ("de".equals(language.getShortName())) { - assertThat(response.getLanguage()).isEqualTo(Language.ENGLISH); - assertThat(response.getText()).isEqualTo("Privacy Statement"); + assertThat(response.language()).isEqualTo(Language.ENGLISH); + assertThat(response.text()).isEqualTo("Privacy Statement"); } else { - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); - assertThat(response.getText()).isEqualTo("Datenschutzerklärung"); + assertThat(response.language()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isEqualTo("Datenschutzerklärung"); } } @@ -118,38 +122,38 @@ void testGetPrivacyStatement_noLanguageFound_badRequest() throws Exception { mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_de")))).thenReturn(false); mockedFiles.when(() -> Files.exists(argThat(path -> path.toString().contains("_en")))).thenReturn(false); - request.get("/api/public/privacy-statement?language=de", HttpStatus.BAD_REQUEST, PrivacyStatement.class); + request.get("/api/public/privacy-statement?language=de", HttpStatus.BAD_REQUEST, PrivacyStatementDTO.class); } } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testGetPrivacyStatementForUpdate_instructorAccessForbidden() throws Exception { - request.get("/api/admin/privacy-statement-for-update?language=de", HttpStatus.FORBIDDEN, PrivacyStatement.class); + request.get("/api/admin/privacy-statement-for-update?language=de", HttpStatus.FORBIDDEN, PrivacyStatementDTO.class); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testGetPrivacyStatementForUpdate_unsupportedLanguageBadRequest() throws Exception { - request.get("/api/admin/privacy-statement-for-update?language=fr", HttpStatus.BAD_REQUEST, PrivacyStatement.class); + request.get("/api/admin/privacy-statement-for-update?language=fr", HttpStatus.BAD_REQUEST, PrivacyStatementDTO.class); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") - void testGetPrivacyStatementForUpdateFileDoesntExist_emptyStringSuccess() throws Exception { - PrivacyStatement response; + void testGetPrivacyStatementForUpdateFileDoesntExist() throws Exception { + PrivacyStatementDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(false); - response = request.get("/api/admin/privacy-statement-for-update?language=de", HttpStatus.OK, PrivacyStatement.class); + response = request.get("/api/admin/privacy-statement-for-update?language=de", HttpStatus.OK, PrivacyStatementDTO.class); } - assertThat(response.getText()).isEqualTo(""); - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isNull(); + assertThat(response.language()).isEqualTo(Language.GERMAN); } @ParameterizedTest @EnumSource(value = Language.class, names = { "GERMAN", "ENGLISH" }) void testGetPrivacyStatementReturnsCorrectFileContent(Language language) throws Exception { - PrivacyStatement response; + PrivacyStatementDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(true); if (language == Language.ENGLISH) { @@ -158,15 +162,15 @@ void testGetPrivacyStatementReturnsCorrectFileContent(Language language) throws else { mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_de")))).thenReturn("Datenschutzerklärung"); } - response = request.get("/api/public/privacy-statement?language=" + language.getShortName(), HttpStatus.OK, PrivacyStatement.class); + response = request.get("/api/public/privacy-statement?language=" + language.getShortName(), HttpStatus.OK, PrivacyStatementDTO.class); } - assertThat(response.getLanguage()).isEqualTo(language); + assertThat(response.language()).isEqualTo(language); if (language == Language.ENGLISH) { - assertThat(response.getText()).isEqualTo("Privacy Statement"); + assertThat(response.text()).isEqualTo("Privacy Statement"); } else { - assertThat(response.getText()).isEqualTo("Datenschutzerklärung"); + assertThat(response.text()).isEqualTo("Datenschutzerklärung"); } } @@ -174,7 +178,7 @@ void testGetPrivacyStatementReturnsCorrectFileContent(Language language) throws @EnumSource(value = Language.class, names = { "GERMAN", "ENGLISH" }) @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testGetPrivacyStatementForUpdateReturnsCorrectFileContent(Language language) throws Exception { - PrivacyStatement response; + PrivacyStatementDTO response; try (MockedStatic mockedFiles = mockStatic(Files.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(true); if ("de".equals(language.getShortName())) { @@ -183,58 +187,54 @@ void testGetPrivacyStatementForUpdateReturnsCorrectFileContent(Language language else { mockedFiles.when(() -> Files.readString(argThat(path -> path.toString().contains("_en")))).thenReturn("Privacy Statement"); } - response = request.get("/api/admin/privacy-statement-for-update?language=" + language.getShortName(), HttpStatus.OK, PrivacyStatement.class); + response = request.get("/api/admin/privacy-statement-for-update?language=" + language.getShortName(), HttpStatus.OK, PrivacyStatementDTO.class); } - assertThat(response.getLanguage()).isEqualTo(language); + assertThat(response.language()).isEqualTo(language); if ("de".equals(language.getShortName())) { - assertThat(response.getText()).isEqualTo("Datenschutzerklärung"); + assertThat(response.text()).isEqualTo("Datenschutzerklärung"); } else { - assertThat(response.getText()).isEqualTo("Privacy Statement"); + assertThat(response.text()).isEqualTo("Privacy Statement"); } } @Test @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR") void testUpdatePrivacyStatement_instructorAccessForbidden() throws Exception { - request.put("/api/admin/privacy-statement", new PrivacyStatement(Language.GERMAN), HttpStatus.FORBIDDEN); + request.put("/api/admin/privacy-statement", new PrivacyStatementDTO("", Language.GERMAN), HttpStatus.FORBIDDEN); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdatePrivacyStatement_writesFile_ReturnsUpdatedFileContent() throws Exception { - PrivacyStatement response; - PrivacyStatement requestBody = new PrivacyStatement(Language.GERMAN); - requestBody.setText("Datenschutzerklärung"); + PrivacyStatementDTO requestBody = new PrivacyStatementDTO("Datenschutzerklärung", Language.GERMAN); try (MockedStatic mockedFiles = mockStatic(Files.class); MockedStatic mockedFileUtils = mockStatic(FileUtils.class)) { mockedFiles.when(() -> Files.exists(any())).thenReturn(true); - response = request.putWithResponseBody("/api/admin/privacy-statement", requestBody, PrivacyStatement.class, HttpStatus.OK); + PrivacyStatementDTO response = request.putWithResponseBody("/api/admin/privacy-statement", requestBody, PrivacyStatementDTO.class, HttpStatus.OK); mockedFileUtils.verify(() -> FileUtils.writeStringToFile(argThat(file -> file.toString().contains("_de")), anyString(), eq(StandardCharsets.UTF_8))); // we explicitly check the method calls to ensure createDirectories is not called when the directory exists mockedFiles.verify(() -> Files.exists(any())); mockedFiles.verifyNoMoreInteractions(); + assertThat(response.language()).isEqualTo(Language.GERMAN); + assertThat(response.text()).isEqualTo("Datenschutzerklärung"); } - assertThat(response.getLanguage()).isEqualTo(Language.GERMAN); - assertThat(response.getText()).isEqualTo("Datenschutzerklärung"); - } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdatePrivacyStatement_unsupportedLanguageBadRequest() throws Exception { - JsonObject body = new JsonObject(); - body.addProperty("text", "test"); - body.addProperty("language", "FRENCH"); - request.put("/api/admin/privacy-statement", body.toString(), HttpStatus.BAD_REQUEST); + Map requestBody = new HashMap<>(); + requestBody.put("text", "test"); + requestBody.put("language", "FRENCH"); + request.put("/api/admin/privacy-statement", new ObjectMapper().writeValueAsString(requestBody), HttpStatus.BAD_REQUEST); } @Test @WithMockUser(username = TEST_PREFIX + "admin", roles = "ADMIN") void testUpdatePrivacyStatement_blankTextBadRequest() throws Exception { - PrivacyStatement requestBody = new PrivacyStatement(Language.GERMAN); - requestBody.setText(" "); + PrivacyStatementDTO requestBody = new PrivacyStatementDTO(" ", Language.GERMAN); request.put("/api/admin/privacy-statement", requestBody, HttpStatus.BAD_REQUEST); } diff --git a/src/test/java/de/tum/in/www1/artemis/architecture/ArchitectureTest.java b/src/test/java/de/tum/in/www1/artemis/architecture/ArchitectureTest.java index ee3d238df751..cf592f771b6b 100644 --- a/src/test/java/de/tum/in/www1/artemis/architecture/ArchitectureTest.java +++ b/src/test/java/de/tum/in/www1/artemis/architecture/ArchitectureTest.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -88,6 +89,8 @@ */ class ArchitectureTest extends AbstractArchitectureTest { + private static final Logger log = LoggerFactory.getLogger(ArchitectureTest.class); + @Test void testNoJUnit4() { ArchRule noJUnit4Imports = noClasses().should().dependOnClassesThat().resideInAPackage("org.junit"); @@ -207,8 +210,9 @@ void testGsonExclusion() { // TODO: Replace all uses of gson with Jackson and check that gson is not used any more var gsonUsageRule = noClasses().should().accessClassesThat().resideInAnyPackage("com.google.gson..").because("we use an alternative JSON parsing library."); var result = gsonUsageRule.evaluate(allClasses); + log.info("Current number of Gson usages: {}", result.getFailureReport().getDetails().size()); // TODO: reduce the following number to 0 - assertThat(result.getFailureReport().getDetails()).hasSize(733); + assertThat(result.getFailureReport().getDetails()).hasSizeLessThanOrEqualTo(664); } /** @@ -293,9 +297,7 @@ void testNoRestControllersImported() { @Override public void check(JavaClass item, ConditionEvents events) { item.getDirectDependenciesFromSelf().stream().map(Dependency::getTargetClass).filter(targetClass -> targetClass.isAnnotatedWith(RestController.class)) - .forEach(targetClass -> { - events.add(violated(item, "%s imports the RestController %s".formatted(item.getName(), targetClass.getName()))); - }); + .forEach(targetClass -> events.add(violated(item, "%s imports the RestController %s".formatted(item.getName(), targetClass.getName())))); } }; diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingExerciseUtilService.java b/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingExerciseUtilService.java index 31db8b325d89..1839b879fbd9 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingExerciseUtilService.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingExerciseUtilService.java @@ -1,6 +1,5 @@ package de.tum.in.www1.artemis.exercise.modeling; -import static com.google.gson.JsonParser.parseString; import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; @@ -14,7 +13,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import de.tum.in.www1.artemis.course.CourseFactory; import de.tum.in.www1.artemis.domain.Course; @@ -340,7 +341,7 @@ public Submission addModelingSubmissionWithFinishedResultAndAssessor(ModelingExe * @param path The path to the file that contains the submission's model * @param login The login of the user the submission belongs to * @return The created ModelingSubmission - * @throws Exception If the file can't be read + * @throws IOException If the file can't be read */ public ModelingSubmission addModelingSubmissionFromResources(ModelingExercise exercise, String path, String login) throws IOException { String model = TestResourceUtils.loadFileFromResources(path); @@ -356,7 +357,7 @@ public ModelingSubmission addModelingSubmissionFromResources(ModelingExercise ex * @param submissionId The id of the ModelingSubmission * @param sentModel The model that should have been stored */ - public void checkModelingSubmissionCorrectlyStored(Long submissionId, String sentModel) { + public void checkModelingSubmissionCorrectlyStored(Long submissionId, String sentModel) throws JsonProcessingException { Optional modelingSubmission = modelingSubmissionRepo.findById(submissionId); assertThat(modelingSubmission).as("submission correctly stored").isPresent(); checkModelsAreEqual(modelingSubmission.orElseThrow().getModel(), sentModel); @@ -368,10 +369,11 @@ public void checkModelingSubmissionCorrectlyStored(Long submissionId, String sen * @param storedModel The model that has been stored * @param sentModel The model that should have been stored */ - public void checkModelsAreEqual(String storedModel, String sentModel) { - JsonObject sentModelObject = parseString(sentModel).getAsJsonObject(); - JsonObject storedModelObject = parseString(storedModel).getAsJsonObject(); - assertThat(storedModelObject).as("model correctly stored").isEqualTo(sentModelObject); + public void checkModelsAreEqual(String storedModel, String sentModel) throws JsonProcessingException { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode sentModelNode = objectMapper.readTree(sentModel); + JsonNode storedModelNode = objectMapper.readTree(storedModel); + assertThat(storedModelNode).as("model correctly stored").isEqualTo(sentModelNode); } /** diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingSubmissionIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingSubmissionIntegrationTest.java index 30eda851c49a..8428292ffdda 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingSubmissionIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/modeling/ModelingSubmissionIntegrationTest.java @@ -168,7 +168,7 @@ void initTestCase() throws Exception { unsubmittedSubmission = generateUnsubmittedSubmission(); Course course2 = textExerciseUtilService.addCourseWithOneReleasedTextExercise(); - textExercise = (TextExercise) new ArrayList<>(course2.getExercises()).get(0); + textExercise = (TextExercise) new ArrayList<>(course2.getExercises()).getFirst(); // Add users that are not in the course userUtilService.createAndSaveUser(TEST_PREFIX + "student4"); @@ -770,7 +770,7 @@ void getModelingResult_BeforeExamPublishDate_Forbidden() throws Exception { exam.setPublishResultsDate(ZonedDateTime.now().plusHours(3)); // creating exercise - ExerciseGroup exerciseGroup = exam.getExerciseGroups().get(0); + ExerciseGroup exerciseGroup = exam.getExerciseGroups().getFirst(); ModelingExercise modelingExercise = ModelingExerciseFactory.generateModelingExerciseForExam(DiagramType.ActivityDiagram, exerciseGroup); exerciseGroup.addExercise(modelingExercise); @@ -794,7 +794,7 @@ void getModelingResult_testExam() throws Exception { exam.setEndDate(ZonedDateTime.now().minusHours(1)); exam.setVisibleDate(ZonedDateTime.now().minusHours(3)); - ExerciseGroup exerciseGroup = exam.getExerciseGroups().get(0); + ExerciseGroup exerciseGroup = exam.getExerciseGroups().getFirst(); ModelingExercise modelingExercise = ModelingExerciseFactory.generateModelingExerciseForExam(DiagramType.ActivityDiagram, exerciseGroup); exerciseGroup.addExercise(modelingExercise); exerciseGroupRepository.save(exerciseGroup); diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java index a89cd7f38615..079826795e24 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java @@ -23,9 +23,9 @@ import org.springframework.http.HttpStatus; import org.springframework.security.test.context.support.WithMockUser; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.google.gson.JsonParser; import de.tum.in.www1.artemis.AbstractSpringIntegrationJenkinsGitlabTest; import de.tum.in.www1.artemis.domain.BuildLogEntry; @@ -148,13 +148,7 @@ void shouldExtractBuildLogAnalytics_noSca() throws Exception { var participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, userLogin); programmingExerciseUtilService.createProgrammingSubmission(participation, false); - List logs = new ArrayList<>(); - logs.add("[2021-05-10T14:58:30.000Z] Agents is getting prepared"); - logs.add("[2021-05-10T15:00:00.000Z] docker exec"); // Job started - logs.add("[2021-05-10T15:00:05.000Z] Scanning for projects..."); // Build & test started - logs.add("[2021-05-10T15:00:10.000Z] Dependency 1 Downloaded from"); - logs.add("[2021-05-10T15:00:15.000Z] Total time: Some time"); // Build & test finished - logs.add("[2021-05-10T15:00:20.000Z] Everything finished"); // Job finished + final var logs = getLogs("[2021-05-10T15:00:10.000Z] Dependency 1 Downloaded from", "[2021-05-10T15:00:20.000Z] Everything finished"); var notification = createJenkinsNewResultNotification(exercise.getProjectKey(), userLogin, ProgrammingLanguage.JAVA, List.of(), logs, null, new ArrayList<>()); postResult(notification, HttpStatus.OK); @@ -168,6 +162,17 @@ void shouldExtractBuildLogAnalytics_noSca() throws Exception { assertThat(statistics.dependenciesDownloadedCount()).isEqualTo(1); } + private static List getLogs(String dependencyDownloaded, String jobFinished) { + List logs = new ArrayList<>(); + logs.add("[2021-05-10T14:58:30.000Z] Agents is getting prepared"); + logs.add("[2021-05-10T15:00:00.000Z] docker exec"); // Job started + logs.add("[2021-05-10T15:00:05.000Z] Scanning for projects..."); // Build & test started + logs.add(dependencyDownloaded); + logs.add("[2021-05-10T15:00:15.000Z] Total time: Some time"); // Build & test finished + logs.add(jobFinished); // Job finished + return logs; + } + @Test @WithMockUser(username = TEST_PREFIX + "student1", roles = "USER") void shouldExtractBuildLogAnalytics_noSca_gradle() throws Exception { @@ -208,13 +213,7 @@ void shouldExtractBuildLogAnalytics_sca() throws Exception { var participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, userLogin); programmingExerciseUtilService.createProgrammingSubmission(participation, false); - List logs = new ArrayList<>(); - logs.add("[2021-05-10T14:58:30.000Z] Agents is getting prepared"); - logs.add("[2021-05-10T15:00:00.000Z] docker exec"); // Job started - logs.add("[2021-05-10T15:00:05.000Z] Scanning for projects..."); // Build & test started - logs.add("[2021-05-10T15:00:20.000Z] Dependency 1 Downloaded from"); - logs.add("[2021-05-10T15:00:15.000Z] Total time: Some time"); // Build & test finished - logs.add("[2021-05-10T15:00:16.000Z] Scanning for projects..."); // SCA started + final var logs = getLogs("[2021-05-10T15:00:20.000Z] Dependency 1 Downloaded from", "[2021-05-10T15:00:16.000Z] Scanning for projects..."); logs.add("[2021-05-10T15:00:20.000Z] Dependency 2 Downloaded from"); logs.add("[2021-05-10T15:00:27.000Z] Total time: Some time"); // SCA finished logs.add("[2021-05-10T15:00:30.000Z] Everything finished"); // Job finished @@ -241,13 +240,7 @@ void shouldExtractBuildLogAnalytics_unsupportedProgrammingLanguage() throws Exce var participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, userLogin); programmingExerciseUtilService.createProgrammingSubmission(participation, false); - List logs = new ArrayList<>(); - logs.add("[2021-05-10T14:58:30.000Z] Agents is getting prepared"); - logs.add("[2021-05-10T15:00:00.000Z] docker exec"); // Job started - logs.add("[2021-05-10T15:00:05.000Z] Scanning for projects..."); // Build & test started - logs.add("[2021-05-10T15:00:20.000Z] Dependency 1 Downloaded from"); - logs.add("[2021-05-10T15:00:15.000Z] Total time: Some time"); // Build & test finished - logs.add("[2021-05-10T15:00:20.000Z] Everything finished"); // Job finished + final var logs = getLogs("[2021-05-10T15:00:20.000Z] Dependency 1 Downloaded from", "[2021-05-10T15:00:20.000Z] Everything finished"); var notification = createJenkinsNewResultNotification(exercise.getProjectKey(), userLogin, ProgrammingLanguage.PYTHON, List.of(), logs, null, new ArrayList<>()); postResult(notification, HttpStatus.OK); @@ -296,9 +289,10 @@ void shouldReturnBadRequestWhenPlanKeyDoesntExist(ProgrammingLanguage programmin void shouldSetSubmissionDateForBuildCorrectlyIfOnlyOnePushIsReceived() throws Exception { testService.setUp_shouldSetSubmissionDateForBuildCorrectlyIfOnlyOnePushIsReceived(TEST_PREFIX); String userLogin = TEST_PREFIX + "student1"; - var pushJSON = new JsonParser().parse(GITLAB_PUSH_EVENT_REQUEST).getAsJsonObject(); - var firstCommitHash = pushJSON.get("before").getAsString(); - var secondCommitHash = pushJSON.get("after").getAsString(); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(GITLAB_PUSH_EVENT_REQUEST); + String firstCommitHash = rootNode.get("before").asText(); + String secondCommitHash = rootNode.get("after").asText(); var firstCommitDate = ZonedDateTime.now().minusSeconds(60); var secondCommitDate = ZonedDateTime.now().minusSeconds(30); @@ -397,13 +391,13 @@ void shouldCreateGradleFeedback() throws Exception { var longErrorMessage = new TestCaseDetailMessageDTO("abc\nmultiline\nfeedback"); var testCase = new TestCaseDTO("test1", "Class", 0d, new ArrayList<>(), List.of(longErrorMessage), new ArrayList<>()); - notification.getResults().get(0).testCases().set(0, testCase); + notification.getResults().getFirst().testCases().set(0, testCase); postResult(notification, HttpStatus.OK); var result = resultRepository.findFirstWithFeedbacksByParticipationIdOrderByCompletionDateDescElseThrow(participation.getId()); // Jenkins Setup -> Gradle Feedback is not duplicated and should be kept like this - assertThat(result.getFeedbacks().get(0).getDetailText()).isEqualTo("abc\nmultiline\nfeedback"); + assertThat(result.getFeedbacks().getFirst().getDetailText()).isEqualTo("abc\nmultiline\nfeedback"); } private Result assertBuildError(Long participationId, String userLogin) throws Exception { @@ -411,7 +405,7 @@ private Result assertBuildError(Long participationId, String userLogin) throws E // Assert that result is linked to the participation var results = resultRepository.findAllByParticipationIdOrderByCompletionDateDesc(participationId); assertThat(results).hasSize(1); - var result = results.get(0); + var result = results.getFirst(); assertThat(result.isSuccessful()).isFalse(); assertThat(result.getScore()).isZero(); @@ -435,14 +429,14 @@ private Result assertBuildError(Long participationId, String userLogin) throws E /** * This is the simulated request from the VCS to Artemis on a new commit. */ - private ProgrammingSubmission postSubmission(Long participationId, HttpStatus expectedStatus) throws Exception { - return testService.postSubmission(participationId, expectedStatus, GITLAB_PUSH_EVENT_REQUEST); + private void postSubmission(Long participationId, HttpStatus expectedStatus) throws Exception { + testService.postSubmission(participationId, expectedStatus, GITLAB_PUSH_EVENT_REQUEST); } private void assertNoNewSubmissions(long participationId, ProgrammingSubmission existingSubmission) { var updatedSubmissions = submissionRepository.findAllByParticipationIdWithResults(participationId); assertThat(updatedSubmissions).hasSize(1); - assertThat(updatedSubmissions.get(0).getId()).isEqualTo(existingSubmission.getId()); + assertThat(updatedSubmissions.getFirst().getId()).isEqualTo(existingSubmission.getId()); } private void postResult(TestResultsDTO requestBodyMap, HttpStatus status) throws Exception { diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionConstants.java b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionConstants.java index 92ea645b30c2..5069428053cc 100644 --- a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionConstants.java +++ b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingSubmissionConstants.java @@ -8,8 +8,6 @@ public final class ProgrammingSubmissionConstants { - public static final String TEST_COMMIT = "a6250b6f03c3ae8fa8fb8fdf6bb1dc1c4cc57bad"; - public static final String GITLAB_PUSH_EVENT_REQUEST; public static final String GITLAB_PUSH_EVENT_REQUEST_WITHOUT_COMMIT;