diff --git a/.idea/runConfigurations/Artemis__Server__LocalVC___Jenkins_.xml b/.idea/runConfigurations/Artemis__Server__LocalVC___Jenkins_.xml
index 2eae7040817e..3af26cd768b8 100644
--- a/.idea/runConfigurations/Artemis__Server__LocalVC___Jenkins_.xml
+++ b/.idea/runConfigurations/Artemis__Server__LocalVC___Jenkins_.xml
@@ -6,7 +6,7 @@
-
+
-
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b26bf64d85c2..be6b0b7d4b5f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -19,6 +19,7 @@ plugins {
id "com.google.cloud.tools.jib" version "3.4.4"
id "com.gorylenko.gradle-git-properties" version "2.4.2"
id "io.spring.dependency-management" version "1.1.7"
+ id "nebula.lint" version "20.5.4"
id "org.liquibase.gradle" version "${liquibase_plugin_version}"
id "org.owasp.dependencycheck" version "11.1.1"
id "org.springframework.boot" version "${spring_boot_version}"
@@ -94,13 +95,13 @@ repositories {
}
configurations.configureEach {
-// exclude group: "org.dom4j", module: "dom4j"
+ exclude group: "org.dom4j", module: "dom4j"
exclude group: "org.xmlpull", module: "pull-parser"
exclude group: "jaxen", module: "jaxen"
exclude group: "xmlpull", module: "xpp3"
exclude group: "xsdlib", module: "xsdlib"
exclude group: "javax.xml.stream", module: "stax-api"
-// exclude group: "javax.xml.bind", module: "jaxb-api"
+ exclude group: "javax.xml.bind", module: "jaxb-api"
exclude group: "org.junit.vintage", module: "junit-vintage-engine"
exclude group: "com.vaadin.external.google", module: "android-json"
@@ -123,27 +124,36 @@ configurations.configureEach {
exclude group: "org.apache.lucene", module: "lucene-core"
exclude group: "org.apache.lucene", module: "lucene-analyzers-common"
exclude group: "com.google.protobuf", module: "protobuf-java"
+
+ exclude group: "org.jasypt", module: "jasypt"
+
+ // required by eureka client, but not used in this project
+ exclude group: "com.thoughtworks.xstream", module: "xstream"
+ // required by JPlag, but not used in this project
+ exclude group: "xerces", module: "xercesImpl"
+ // required by JPlag, but not used in this project
+ exclude group: "xalan", module: "xalan"
+ // required by JPlag, but not used in this project
+ exclude group: "xalan", module: "serializer"
+
+ exclude group: "org.springframework.boot", module: "spring-boot-starter-cache"
+ exclude group: "io.micrometer", module: "micrometer-registry-prometheus"
+ exclude group: "net.logstash.logback", module: "logstash-logback-encoder"
+
+ exclude group: "javax.cache", module: "cache-api"
+ exclude group: "javax.transaction", module: "javax.transaction-api"
+
+ // JPlag depends on those, but they are not really needed
+ exclude group: "org.jgrapht", module: "jgrapht-core"
+ exclude group: "org.apfloat", module: "apfloat"
}
dependencies {
- // Note: jenkins-client is not well maintained and includes dependencies to libraries with critical security issues (e.g. CVE-2020-10683 for dom4j@1.6.1)
- // implementation "com.offbytwo.jenkins:jenkins-client:0.3.8"
- implementation files("libs/jenkins-client-0.4.1.jar")
- // The following 4 dependencies are explicitly integrated as transitive dependencies of jenkins-client-0.4.0.jar
+ // Required by Spring cloud
implementation "org.apache.httpcomponents.client5:httpclient5:5.4.1"
- implementation "org.apache.httpcomponents.core5:httpcore5:5.3.1"
+ implementation "org.apache.httpcomponents.core5:httpcore5:5.3.2"
implementation "org.apache.httpcomponents:httpmime:4.5.14"
- implementation("org.dom4j:dom4j:2.1.4") {
- // Note: avoid org.xml.sax.SAXNotRecognizedException: unrecognized feature http://xml.org/sax/features/external-general-entities
- // also see https://github.com/dom4j/dom4j/issues/99
- exclude module: "pull-parser"
- exclude module: "jaxen"
- exclude module: "xpp3"
- exclude module: "xsdlib"
- exclude module: "stax-api"
- exclude module: "jaxb-api"
- }
implementation "org.gitlab4j:gitlab4j-api:6.0.0-rc.8"
@@ -172,8 +182,10 @@ dependencies {
}
}
+ // Sentry depends on this
implementation "org.apache.logging.log4j:log4j-to-slf4j:2.24.3"
+ // Used for LTI (e.g. Moodle --> Artemis)
implementation "uk.ac.ox.ctl:spring-security-lti13:0.3.0"
// https://search.maven.org/artifact/org.eclipse.jgit/org.eclipse.jgit
@@ -189,7 +201,6 @@ dependencies {
// https://mvnrepository.com/artifact/net.sourceforge.plantuml/plantuml
implementation "net.sourceforge.plantuml:plantuml:1.2024.8"
- implementation "org.jasypt:jasypt:1.9.3"
implementation "me.xdrop:fuzzywuzzy:1.4.0"
implementation("org.yaml:snakeyaml") {
version {
@@ -205,19 +216,12 @@ dependencies {
// NOTE: the following six dependencies use the newer versions explicitly to avoid other dependencies to use older versions
implementation "ch.qos.logback:logback-classic:${logback_version}"
implementation "ch.qos.logback:logback-core:${logback_version}"
- // required by eureka client
- implementation "com.thoughtworks.xstream:xstream:1.4.21"
- // required by JPlag, should NOT be used in other places
- implementation "xerces:xercesImpl:2.12.2"
- // required by JPlag, should NOT be used in other places
- implementation "xalan:xalan:2.7.3"
- // required by JPlag, should NOT be used in other places
- implementation "xalan:serializer:2.7.3"
+
// required by Saml2, should NOT be used in other places
implementation "org.apache.santuario:xmlsec:4.0.3"
implementation "org.jsoup:jsoup:1.18.3"
- implementation "commons-codec:commons-codec:1.17.1" // needed for spring security saml2
+ implementation "commons-codec:commons-codec:1.17.2" // needed for spring security saml2
// use the latest version to avoid security vulnerabilities
implementation "org.springframework:spring-webmvc:${spring_framework_version}"
@@ -231,14 +235,7 @@ dependencies {
// use newest version of commons-compress to avoid security issues through outdated dependencies
implementation "org.apache.commons:commons-compress:1.27.1"
-
- // import JHipster dependencies BOM
- implementation platform("tech.jhipster:jhipster-dependencies:${jhipster_dependencies_version}")
-
implementation "tech.jhipster:jhipster-framework:${jhipster_dependencies_version}"
- implementation "org.springframework.boot:spring-boot-starter-cache:${spring_boot_version}"
- implementation "io.micrometer:micrometer-registry-prometheus:1.14.2"
- implementation "net.logstash.logback:logstash-logback-encoder:8.0"
// Defines low-level streaming API, and includes JSON-specific implementations
implementation "com.fasterxml.jackson.core:jackson-core:${fasterxml_version}"
@@ -256,20 +253,21 @@ dependencies {
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${fasterxml_version}"
+ // Required for synchronization between nodes and build agents (LocalCI)
implementation "com.hazelcast:hazelcast:${hazelcast_version}"
implementation "com.hazelcast:hazelcast-spring:${hazelcast_version}"
- implementation "com.hazelcast:hazelcast-hibernate53:5.2.0"
+ // Required for Hibernate multi node caching
+ runtimeOnly "com.hazelcast:hazelcast-hibernate53:5.2.0"
- implementation "javax.cache:cache-api:1.1.1"
implementation "org.hibernate.orm:hibernate-core:${hibernate_version}"
+ // Required for jdbc connection pooling to databases
implementation "com.zaxxer:HikariCP:6.2.1"
+ // Required for several dependencies
implementation "org.apache.commons:commons-text:1.13.0"
implementation "org.apache.commons:commons-math3:3.6.1"
- implementation "javax.transaction:javax.transaction-api:1.3"
-
implementation "org.liquibase:liquibase-core:${liquibase_version}"
implementation "org.springframework.boot:spring-boot-starter-validation:${spring_boot_version}"
@@ -295,9 +293,10 @@ dependencies {
implementation "org.springframework.cloud:spring-cloud-starter-config:${spring_cloud_version}"
implementation "org.springframework.cloud:spring-cloud-commons:${spring_cloud_version}"
- implementation "io.netty:netty-all:4.1.116.Final"
+ // required by the Websocket Broker Connection in WebsocketConfiguration (due to multi node setup support)
implementation "io.projectreactor.netty:reactor-netty:1.2.1"
implementation "org.springframework:spring-messaging:${spring_framework_version}"
+ // required for the connection to Hermes (push notifications)
implementation "org.springframework.retry:spring-retry:2.0.11"
implementation "org.springframework.security:spring-security-config:${spring_security_version}"
@@ -324,6 +323,7 @@ dependencies {
runtimeOnly "io.jsonwebtoken:jjwt-impl:${jwt_version}"
runtimeOnly "io.jsonwebtoken:jjwt-jackson:${jwt_version}"
+ // required by sshd-git
implementation "org.bouncycastle:bcpkix-jdk18on:1.79"
implementation "org.bouncycastle:bcprov-jdk18on:1.79"
@@ -332,8 +332,12 @@ dependencies {
implementation "org.zalando:problem-spring-web:0.29.1"
implementation "org.zalando:jackson-datatype-problem:0.27.1"
+ // Required by JPlag
implementation "com.ibm.icu:icu4j-charset:76.1"
+ // Required by exam session service
implementation "com.github.seancfoley:ipaddress:5.5.1"
+
+ // used for testing and Java Template Upgrade Service
implementation "org.apache.maven:maven-model:3.9.9"
implementation "org.apache.pdfbox:pdfbox:3.0.3"
implementation "org.apache.commons:commons-csv:1.12.0"
@@ -341,10 +345,6 @@ dependencies {
implementation "commons-fileupload:commons-fileupload:1.5"
implementation "net.lingala.zip4j:zip4j:2.11.5"
- implementation "org.jgrapht:jgrapht-core:1.5.2"
- // use the latest version explicitly to avoid security vulnerabilities (currently Artemis and JPlag rely on jgrapht 1.5.2 which relies on apfloat)
- implementation "org.apfloat:apfloat:1.14.0"
-
// use newest version of guava to avoid security issues through outdated dependencies
implementation "com.google.guava:guava:33.4.0-jre"
implementation "com.sun.activation:jakarta.activation:2.0.1"
@@ -420,7 +420,7 @@ dependencies {
testImplementation "com.h2database:h2:2.2.224"
// Lightweight JSON library needed for the internals of the MockRestServiceServer
- testImplementation "org.json:json:20241224"
+ testImplementation "org.json:json:20250107"
// NOTE: make sure this corresponds to the version used for JUnit in the testImplementation
testRuntimeOnly "org.junit.platform:junit-platform-launcher:${junit_platform_version}"
@@ -457,6 +457,12 @@ checkstyle {
maxErrors = 0
}
+gradleLint {
+ rules = ['dependency-parentheses']
+// criticalRules = ['unused-dependency'] // <-- this will fail the build in the event of a violation
+}
+
+
def isNonStable = { String version ->
def stableKeyword = ["RELEASE", "FINAL", "GA"].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r)?$/
@@ -491,3 +497,4 @@ tasks.named("dependencyUpdates").configure {
// 10) Clear Liquibase checksums: ./gradlew liquibaseClearChecksums
// 11) Create changelog between Java and DB ./gradlew liquibaseDiffChangeLog (make sure to set the correct username and password in liquibase.gradle)
// 12) Generate initial schema from DB ./gradlew liquibaseGenerateChangelog (make sure to set the correct username and password in liquibase.gradle)
+// 13) Find unused dependencies ./gradlew lintGradle -x webapp
diff --git a/gradle.properties b/gradle.properties
index 37430dbcce1e..c9f3fe32cd73 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -31,7 +31,7 @@ slf4j_version=2.0.16
sentry_version=7.20.0
liquibase_version=4.30.0
docker_java_version=3.4.1
-logback_version=1.5.15
+logback_version=1.5.16
java_parser_version=3.26.2
byte_buddy_version=1.15.11
netty_version=4.1.115.Final
@@ -49,7 +49,7 @@ gradle_node_plugin_version=7.1.0
apt_plugin_version=0.21
liquibase_plugin_version=3.0.1
modernizer_plugin_version=1.10.0
-spotless_plugin_version=6.25.0
+spotless_plugin_version=7.0.1
org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en \
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
diff --git a/libs/jenkins-client-0.4.1.jar b/libs/jenkins-client-0.4.1.jar
deleted file mode 100644
index 51d84492baf2..000000000000
Binary files a/libs/jenkins-client-0.4.1.jar and /dev/null differ
diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java b/src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
index 274b9e393ecb..a5fa0aaef44a 100644
--- a/src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
+++ b/src/main/java/de/tum/cit/aet/artemis/core/config/Constants.java
@@ -316,6 +316,11 @@ public final class Constants {
*/
public static final String PROFILE_BUILDAGENT = "buildagent";
+ /**
+ * The name of the Spring profile used to process build jobs in a Jenkins setup.
+ */
+ public static final String PROFILE_JENKINS = "jenkins";
+
/**
* The name of the Spring profile used for Artemis functionality.
*/
diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java
index 4ba55bc3c7dd..2971aa9936e0 100644
--- a/src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java
+++ b/src/main/java/de/tum/cit/aet/artemis/core/config/RestTemplateConfiguration.java
@@ -4,6 +4,7 @@
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_ATHENA;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE;
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_IRIS;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
import java.util.ArrayList;
@@ -13,6 +14,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
+import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
@@ -47,7 +49,7 @@ public RestTemplate gitlabRestTemplate(GitLabAuthorizationInterceptor gitlabInte
}
@Bean
- @Profile("jenkins")
+ @Profile(PROFILE_JENKINS)
public RestTemplate jenkinsRestTemplate(JenkinsAuthorizationInterceptor jenkinsInterceptor) {
return initializeRestTemplateWithInterceptors(jenkinsInterceptor, createRestTemplate());
}
@@ -91,7 +93,7 @@ public RestTemplate shortTimeoutGitlabRestTemplate(GitLabAuthorizationIntercepto
}
@Bean
- @Profile("jenkins")
+ @Profile(PROFILE_JENKINS)
public RestTemplate shortTimeoutJenkinsRestTemplate(JenkinsAuthorizationInterceptor jenkinsInterceptor) {
return initializeRestTemplateWithInterceptors(jenkinsInterceptor, createShortTimeoutRestTemplate());
}
@@ -115,7 +117,6 @@ public RestTemplate shortTimeoutHermesRestTemplate() {
// Note: for certain requests, e.g. the Athena submission selection, we would like to have even shorter timeouts.
// Therefore, we need additional rest templates. It is recommended to keep the timeout settings constant per rest template.
-
@Bean
@Profile(PROFILE_ATHENA)
public RestTemplate veryShortTimeoutAthenaRestTemplate(AthenaAuthorizationInterceptor athenaAuthorizationInterceptor) {
@@ -172,16 +173,19 @@ private RestTemplate createRestTemplate() {
}
private RestTemplate createShortTimeoutRestTemplate() {
- var requestFactory = new SimpleClientHttpRequestFactory();
- requestFactory.setReadTimeout(SHORT_READ_TIMEOUT);
- requestFactory.setConnectTimeout(SHORT_CONNECTION_TIMEOUT);
+ final var requestFactory = getSimpleClientHttpRequestFactory(SHORT_READ_TIMEOUT, SHORT_CONNECTION_TIMEOUT);
return new RestTemplate(requestFactory);
}
- private RestTemplate createVeryShortTimeoutRestTemplate() {
+ private static ClientHttpRequestFactory getSimpleClientHttpRequestFactory(int shortReadTimeout, int shortConnectionTimeout) {
var requestFactory = new SimpleClientHttpRequestFactory();
- requestFactory.setReadTimeout(VERY_SHORT_READ_TIMEOUT);
- requestFactory.setConnectTimeout(VERY_SHORT_CONNECTION_TIMEOUT);
+ requestFactory.setReadTimeout(shortReadTimeout);
+ requestFactory.setConnectTimeout(shortConnectionTimeout);
+ return requestFactory;
+ }
+
+ private RestTemplate createVeryShortTimeoutRestTemplate() {
+ final var requestFactory = getSimpleClientHttpRequestFactory(VERY_SHORT_READ_TIMEOUT, VERY_SHORT_CONNECTION_TIMEOUT);
return new RestTemplate(requestFactory);
}
}
diff --git a/src/main/java/de/tum/cit/aet/artemis/core/config/connector/JenkinsServerConfiguration.java b/src/main/java/de/tum/cit/aet/artemis/core/config/connector/JenkinsServerConfiguration.java
deleted file mode 100644
index c0da9667f954..000000000000
--- a/src/main/java/de/tum/cit/aet/artemis/core/config/connector/JenkinsServerConfiguration.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package de.tum.cit.aet.artemis.core.config.connector;
-
-import java.net.URISyntaxException;
-import java.net.URL;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-
-import com.offbytwo.jenkins.JenkinsServer;
-
-@Configuration
-@Profile("jenkins")
-public class JenkinsServerConfiguration {
-
- @Value("${artemis.continuous-integration.user}")
- private String jenkinsUser;
-
- @Value("${artemis.continuous-integration.password}")
- private String jenkinsPassword;
-
- @Value("${artemis.continuous-integration.url}")
- private URL jenkinsServerUrl;
-
- @Bean
- public JenkinsServer jenkinsServer() throws URISyntaxException {
- return new JenkinsServer(jenkinsServerUrl.toURI(), jenkinsUser, jenkinsPassword);
- }
-
-}
diff --git a/src/main/java/de/tum/cit/aet/artemis/core/util/UrlUtils.java b/src/main/java/de/tum/cit/aet/artemis/core/util/UrlUtils.java
index fdab2bec913f..2ffcd7a21c72 100644
--- a/src/main/java/de/tum/cit/aet/artemis/core/util/UrlUtils.java
+++ b/src/main/java/de/tum/cit/aet/artemis/core/util/UrlUtils.java
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.core.util;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -13,7 +14,7 @@ public class UrlUtils {
* Uses the given base URL and a list of path segments (i.e. /api/some/path would have [api, some, path] as the
* segments) in order to create the builder. The given arguments will be used to replace variable path segments.
* I.e. if you have a path of /api/some/<variable>/endpoint/<another variable>
and
- * args=["firstArg", 42]
, then the returned builder will be based on this path: /api/some/frstArg/endpoint/42
+ * args=["firstArg", 42]
, then the returned builder will be based on this path: /api/some/firstArg/endpoint/42
*
* @param baseUrl The URL to take as basis when building the endpoints path
* @param pathSegments The segments to be appended to the base URL
@@ -21,6 +22,39 @@ public class UrlUtils {
* @return A URI builder which combines all parameters into one base path for a REST API endpoint
*/
public static UriComponentsBuilder buildEndpoint(String baseUrl, List pathSegments, Object... args) {
+ final var parsedSegments = getParsedSegments(pathSegments, args);
+ return UriComponentsBuilder.fromUriString(baseUrl).pathSegment(parsedSegments);
+ }
+
+ /**
+ * Creates a {@link UriComponentsBuilder} that can be used for creating complex URLs for REST API endpoints.
+ * Uses the given base URL and a list of path segments (i.e. /api/some/path would have [api, some, path] as the
+ * segments) in order to create the builder. The given arguments will be used to replace variable path segments.
+ * I.e. if you have a path of /api/some/<variable>/endpoint/<another variable>
and
+ * args=["firstArg", 42]
, then the returned builder will be based on this path: /api/some/firstArg/endpoint/42
+ *
+ * @param baseUri The URI to take as basis when building the endpoints path
+ * @param pathSegments The segments to be appended to the base URL
+ * @param args Arguments that should replace the variable path segments
+ * @return A URI builder which combines all parameters into one base path for a REST API endpoint
+ */
+ public static UriComponentsBuilder buildEndpoint(URI baseUri, List pathSegments, Object... args) {
+ final var parsedSegments = getParsedSegments(pathSegments, args);
+ return UriComponentsBuilder.fromUri(baseUri).pathSegment(parsedSegments);
+ }
+
+ /**
+ * Creates a string array that can be used for creating complex URLs for REST API endpoints.
+ * Uses the given path segments (i.e. /api/some/path would have [api, some, path] as the
+ * segments) in order to create the string array. The given arguments will be used to replace variable path segments.
+ * I.e. if you have a path of /api/some/<variable>/endpoint/<another variable>
and
+ * args=["firstArg", 42]
, then the returned builder will be based on this path: /api/some/firstArg/endpoint/42
+ *
+ * @param pathSegments The segments to be appended to the base URL
+ * @param args Arguments that should replace the variable path segments
+ * @return a string array with the parsed segments
+ */
+ private static String[] getParsedSegments(List pathSegments, Object[] args) {
// Counts how many variable segments we have in the URL, e.g. like ["some static var", ""] has one variable segment
int segmentCtr = 0;
final var parsedSegments = new ArrayList();
@@ -41,7 +75,6 @@ public static UriComponentsBuilder buildEndpoint(String baseUrl, List pa
if (segmentCtr != args.length) {
throw new IllegalArgumentException("Unable to build endpoint. Too many arguments! " + Arrays.toString(args));
}
-
- return UriComponentsBuilder.fromHttpUrl(baseUrl).pathSegment(parsedSegments.toArray(String[]::new));
+ return parsedSegments.toArray(String[]::new);
}
}
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/InternalUrlService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/InternalUrlService.java
index 7df53789c6af..64f723bf5693 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/InternalUrlService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/InternalUrlService.java
@@ -104,7 +104,7 @@ public String replaceUrl(String urlToReplace, URL internalUrl) {
return null;
}
- UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(urlToReplace);
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(urlToReplace);
return builder.host(internalUrl.getHost()).port(internalUrl.getPort()).toUriString();
}
}
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java
index d03c14a5cbc5..886fa2622c8d 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java
@@ -999,7 +999,7 @@ public void checkIfProjectExists(ProgrammingExercise programmingExercise) {
* @return true if a project with the same ProjectKey or ProjectName already exists, otherwise false
*/
public boolean preCheckProjectExistsOnVCSOrCI(ProgrammingExercise programmingExercise, String courseShortName) {
- String projectKey = courseShortName + programmingExercise.getShortName().toUpperCase().replaceAll("\\s+", "");
+ String projectKey = (courseShortName + programmingExercise.getShortName().replaceAll("\\s+", "")).toUpperCase();
String projectName = courseShortName + " " + programmingExercise.getTitle();
log.debug("Project Key: {}", projectKey);
log.debug("Project Name: {}", projectName);
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java
index 9dd35b363709..303c22f7dd9f 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/aeolus/AeolusBuildPlanService.java
@@ -59,9 +59,6 @@ public class AeolusBuildPlanService {
@Value("${aeolus.token:#{null}}")
private String token;
- @Value("${artemis.continuous-integration.token:#{null}}")
- private String ciToken;
-
@Value("${artemis.continuous-integration.password:#{null}}")
private String ciPassword;
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlab/GitLabPersonalAccessTokenManagementService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlab/GitLabPersonalAccessTokenManagementService.java
index 5ea97d98e87d..c4a6bee76e0b 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlab/GitLabPersonalAccessTokenManagementService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/gitlab/GitLabPersonalAccessTokenManagementService.java
@@ -148,7 +148,7 @@ private void revokePersonalAccessToken(org.gitlab4j.api.models.User gitlabUser,
}
private void revokePersonalAccessToken(Long tokenId) {
- UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(gitlabApi.getGitLabServerUrl() + "/api/v4/personal_access_tokens");
+ UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(gitlabApi.getGitLabServerUrl() + "/api/v4/personal_access_tokens");
uriBuilder.pathSegment(String.valueOf(tokenId));
try {
@@ -161,7 +161,7 @@ private void revokePersonalAccessToken(Long tokenId) {
}
private GitLabPersonalAccessTokenListResponseDTO fetchPersonalAccessTokenId(Long userId) {
- UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(gitlabApi.getGitLabServerUrl() + "/api/v4/personal_access_tokens");
+ UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(gitlabApi.getGitLabServerUrl() + "/api/v4/personal_access_tokens");
uriBuilder.queryParam("search", PERSONAL_ACCESS_TOKEN_NAME);
uriBuilder.queryParam("user_id", userId);
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsAuthorizationInterceptor.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsAuthorizationInterceptor.java
index 448b585459c9..fa881b3ed1ee 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsAuthorizationInterceptor.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsAuthorizationInterceptor.java
@@ -1,7 +1,9 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
import java.io.IOException;
-import java.net.URL;
+import java.net.URI;
import jakarta.validation.constraints.NotNull;
@@ -22,7 +24,7 @@
import com.fasterxml.jackson.databind.JsonNode;
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
@Component
public class JenkinsAuthorizationInterceptor implements ClientHttpRequestInterceptor {
@@ -35,7 +37,7 @@ public class JenkinsAuthorizationInterceptor implements ClientHttpRequestInterce
private String password;
@Value("${artemis.continuous-integration.url}")
- private URL jenkinsURL;
+ private URI jenkinsServerUri;
@Value("${jenkins.use-crumb:#{true}}")
private boolean useCrumb;
@@ -62,7 +64,7 @@ private void setCrumb(final HttpHeaders headersToAuthenticate) {
final var entity = new HttpEntity<>(headers);
try {
- final var response = restTemplate.exchange(jenkinsURL.toString() + "/crumbIssuer/api/json", HttpMethod.GET, entity, JsonNode.class);
+ final var response = restTemplate.exchange(jenkinsServerUri.toString() + "/crumbIssuer/api/json", HttpMethod.GET, entity, JsonNode.class);
final var sessionId = response.getHeaders().get("Set-Cookie").getFirst();
headersToAuthenticate.add("Jenkins-Crumb", response.getBody().get("crumb").asText());
headersToAuthenticate.add("Cookie", sessionId);
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsEndpoints.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsEndpoints.java
index 96c8339f32fe..54acd2ea0eb9 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsEndpoints.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsEndpoints.java
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import java.net.URI;
import java.util.Arrays;
import java.util.List;
@@ -9,11 +10,28 @@
public enum JenkinsEndpoints {
- NEW_PLAN("job", "", "createItem"), NEW_FOLDER("createItem"), DELETE_FOLDER("job", "", "doDelete"),
- DELETE_JOB("job", "", "job", "", "doDelete"), PLAN_CONFIG("job", "", "job", "", "config.xml"),
- TRIGGER_BUILD("job", "", "job", "", "build"), ENABLE("job", "", "job", "", "enable"),
- TEST_RESULTS("job", "", "job", "", "lastBuild", "testResults", "api", "json"),
- LAST_BUILD("job", "", "job", "", "lastBuild", "api", "json");
+ // @formatter:off
+ // Build plan endpoints
+ NEW_PLAN("job", "", "createItem"),
+ NEW_FOLDER("createItem"),
+ DELETE_FOLDER("job", "", "doDelete"),
+ DELETE_JOB("job", "", "job", "", "doDelete"),
+ PLAN_CONFIG("job", "", "job", "", "config.xml"),
+ FOLDER_CONFIG("job", "", "config.xml"),
+ TRIGGER_BUILD("job", "", "job", "", "build"),
+ ENABLE("job", "", "job", "", "enable"),
+ LAST_BUILD("job", "", "job", "", "lastBuild", "api", "json"),
+ GET_FOLDER_JOB("job", "", "api", "json"),
+ GET_JOB("job", "", "job", "", "api", "json"),
+
+ // Health
+ HEALTH("login"),
+
+ // User management endpoints
+ GET_USER("user", "", "api", "json"),
+ DELETE_USER("user", "", "doDelete"),
+ CREATE_USER("securityRealm", "createAccountByAdmin");
+ // @formatter:on
private final List pathSegments;
@@ -21,7 +39,7 @@ public enum JenkinsEndpoints {
this.pathSegments = Arrays.asList(pathSegments);
}
- public UriComponentsBuilder buildEndpoint(String baseUrl, Object... args) {
- return UrlUtils.buildEndpoint(baseUrl, pathSegments, args);
+ public UriComponentsBuilder buildEndpoint(URI baseUri, Object... args) {
+ return UrlUtils.buildEndpoint(baseUri, pathSegments, args);
}
}
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInfoContributor.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInfoContributor.java
index bf07232a64b9..071873489e2f 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInfoContributor.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInfoContributor.java
@@ -1,6 +1,8 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
-import java.net.URL;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
+import java.net.URI;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.info.Info;
@@ -11,15 +13,15 @@
import de.tum.cit.aet.artemis.core.config.Constants;
@Component
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
public class JenkinsInfoContributor implements InfoContributor {
@Value("${artemis.continuous-integration.url}")
- private URL JENKINS_SERVER_URL;
+ private URI jenkinsServerUri;
@Override
public void contribute(Info.Builder builder) {
- final var buildPlanURLTemplate = JENKINS_SERVER_URL + "/job/{projectKey}/job/{buildPlanId}";
+ final var buildPlanURLTemplate = jenkinsServerUri + "/job/{projectKey}/job/{buildPlanId}";
builder.withDetail(Constants.INFO_BUILD_PLAN_URL_DETAIL, buildPlanURLTemplate);
// Store name of the continuous integration system
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInternalUrlService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInternalUrlService.java
index 69f1d5572473..4a1e6a4e1dc3 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInternalUrlService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsInternalUrlService.java
@@ -1,5 +1,7 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
import java.net.URL;
import java.util.Optional;
@@ -9,7 +11,7 @@
import de.tum.cit.aet.artemis.programming.service.InternalUrlService;
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
@Service
public class JenkinsInternalUrlService extends InternalUrlService {
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java
index 41b1254cbb7e..ec4a7dd3598a 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.BASH;
import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C;
import static de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage.C_PLUS_PLUS;
@@ -33,7 +34,7 @@
import de.tum.cit.aet.artemis.programming.service.ProgrammingLanguageFeatureService;
@Service
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
public class JenkinsProgrammingLanguageFeatureService extends ProgrammingLanguageFeatureService {
public JenkinsProgrammingLanguageFeatureService() {
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java
index 2730c9e82f9a..c787cae15d70 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsResultService.java
@@ -1,5 +1,6 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
import static de.tum.cit.aet.artemis.programming.domain.build.BuildLogStatisticsEntry.BuildJobPartDuration;
import java.time.ZonedDateTime;
@@ -22,7 +23,7 @@
import de.tum.cit.aet.artemis.programming.service.ci.AbstractContinuousIntegrationResultService;
import de.tum.cit.aet.artemis.programming.service.ci.notification.dto.TestResultsDTO;
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
@Service
public class JenkinsResultService extends AbstractContinuousIntegrationResultService {
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java
index 887bef4857b9..a411777b8534 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsService.java
@@ -1,7 +1,8 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
-import java.io.IOException;
-import java.net.URL;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
+import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -17,7 +18,6 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.offbytwo.jenkins.JenkinsServer;
import de.tum.cit.aet.artemis.core.exception.ContinuousIntegrationException;
import de.tum.cit.aet.artemis.core.exception.JenkinsException;
@@ -39,7 +39,7 @@
import de.tum.cit.aet.artemis.programming.service.jenkins.build_plan.JenkinsBuildPlanService;
import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobService;
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
@Service
public class JenkinsService extends AbstractContinuousIntegrationService {
@@ -48,15 +48,10 @@ public class JenkinsService extends AbstractContinuousIntegrationService {
private final ObjectMapper mapper = new ObjectMapper();
@Value("${artemis.continuous-integration.url}")
- protected URL serverUrl;
-
- @Value("${jenkins.use-crumb:#{true}}")
- private boolean useCrumb;
+ private URI jenkinsServerUri;
private final JenkinsBuildPlanService jenkinsBuildPlanService;
- private final JenkinsServer jenkinsServer;
-
private final JenkinsJobService jenkinsJobService;
private final JenkinsInternalUrlService jenkinsInternalUrlService;
@@ -69,11 +64,9 @@ public class JenkinsService extends AbstractContinuousIntegrationService {
private final ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository;
- public JenkinsService(JenkinsServer jenkinsServer, @Qualifier("shortTimeoutJenkinsRestTemplate") RestTemplate shortTimeoutRestTemplate,
- JenkinsBuildPlanService jenkinsBuildPlanService, JenkinsJobService jenkinsJobService, JenkinsInternalUrlService jenkinsInternalUrlService,
- Optional aeolusTemplateService, ProfileService profileService,
- ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) {
- this.jenkinsServer = jenkinsServer;
+ public JenkinsService(@Qualifier("shortTimeoutJenkinsRestTemplate") RestTemplate shortTimeoutRestTemplate, JenkinsBuildPlanService jenkinsBuildPlanService,
+ JenkinsJobService jenkinsJobService, JenkinsInternalUrlService jenkinsInternalUrlService, Optional aeolusTemplateService,
+ ProfileService profileService, ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) {
this.jenkinsBuildPlanService = jenkinsBuildPlanService;
this.jenkinsJobService = jenkinsJobService;
this.jenkinsInternalUrlService = jenkinsInternalUrlService;
@@ -153,12 +146,12 @@ public void updatePlanRepository(String buildProjectKey, String buildPlanKey, St
@Override
public void deleteProject(String projectKey) {
- jenkinsJobService.deleteJob(projectKey);
+ jenkinsJobService.deleteFolderJob(projectKey);
}
@Override
public void deleteBuildPlan(String projectKey, String planKey) {
- jenkinsBuildPlanService.deleteBuildPlan(projectKey, planKey);
+ jenkinsJobService.deleteJob(projectKey, planKey);
}
@Override
@@ -174,7 +167,8 @@ public String getPlanKey(Object requestBody) throws JenkinsException {
@Override
public Optional getWebHookUrl(String projectKey, String buildPlanId) {
- var urlString = serverUrl + "/project/" + projectKey + "/" + buildPlanId;
+ // TODO: use UriComponentsBuilder
+ var urlString = jenkinsServerUri + "/project/" + projectKey + "/" + buildPlanId;
return Optional.of(jenkinsInternalUrlService.toInternalCiUrl(urlString));
}
@@ -204,13 +198,13 @@ public ResponseEntity retrieveLatestArtifact(ProgrammingExerciseParticip
@Override
public String checkIfProjectExists(String projectKey, String projectName) {
try {
- final var job = jenkinsServer.getJob(projectKey);
- if (job == null || job.getUrl() == null || job.getUrl().isEmpty()) {
+ final var job = jenkinsJobService.getFolderJob(projectKey);
+ if (job == null || job.url() == null || job.url().isEmpty()) {
// means the project does not exist
return null;
}
else {
- return "The project " + projectKey + " already exists in the CI Server. Please choose a different short name!";
+ return "The project " + projectKey + " already exists in Jenkins. Please choose a different short name!";
}
}
catch (Exception emAll) {
@@ -237,10 +231,11 @@ public void removeAllDefaultProjectPermissions(String projectKey) {
@Override
public ConnectorHealth health() {
- Map additionalInfo = Map.of("url", serverUrl);
+ Map additionalInfo = Map.of("url", jenkinsServerUri);
try {
+ URI uri = JenkinsEndpoints.HEALTH.buildEndpoint(jenkinsServerUri).build(true).toUri();
// Note: we simply check if the login page is reachable
- shortTimeoutRestTemplate.getForObject(serverUrl + "/login", String.class);
+ shortTimeoutRestTemplate.getForObject(uri, String.class);
return new ConnectorHealth(true, additionalInfo);
}
catch (Exception emAll) {
@@ -251,9 +246,9 @@ public ConnectorHealth health() {
@Override
public void createProjectForExercise(ProgrammingExercise programmingExercise) throws ContinuousIntegrationException {
try {
- jenkinsServer.createFolder(programmingExercise.getProjectKey(), useCrumb);
+ jenkinsJobService.createFolder(programmingExercise.getProjectKey());
}
- catch (IOException e) {
+ catch (Exception e) {
log.error(e.getMessage(), e);
throw new JenkinsException("Error creating folder for exercise " + programmingExercise, e);
}
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsTriggerService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsTriggerService.java
index 8a837e105700..d2b132de1c55 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsTriggerService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsTriggerService.java
@@ -1,5 +1,7 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
@@ -7,7 +9,7 @@
import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationTriggerService;
import de.tum.cit.aet.artemis.programming.service.jenkins.build_plan.JenkinsBuildPlanService;
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
@Service
public class JenkinsTriggerService implements ContinuousIntegrationTriggerService {
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsUserManagementService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsUserManagementService.java
index 7fdd00a21cba..9939fcc576ae 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsUserManagementService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsUserManagementService.java
@@ -1,7 +1,9 @@
package de.tum.cit.aet.artemis.programming.service.jenkins;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
import java.io.IOException;
-import java.net.URL;
+import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -25,7 +27,6 @@
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
-import org.springframework.web.util.UriComponentsBuilder;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
@@ -41,13 +42,13 @@
import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobPermissionsService;
@Service
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
public class JenkinsUserManagementService implements CIUserManagementService {
private static final Logger log = LoggerFactory.getLogger(JenkinsUserManagementService.class);
@Value("${artemis.continuous-integration.url}")
- private URL jenkinsServerUrl;
+ private URI jenkinsServerUri;
@Value("${artemis.continuous-integration.user}")
private String jenkinsAdminUsername;
@@ -100,7 +101,7 @@ public void createUser(User user, String password) throws ContinuousIntegrationE
try {
// Create the Jenkins user
- var uri = UriComponentsBuilder.fromHttpUrl(jenkinsServerUrl.toString()).pathSegment("securityRealm", "createAccountByAdmin").build().toUri();
+ URI uri = JenkinsEndpoints.CREATE_USER.buildEndpoint(jenkinsServerUri).build(true).toUri();
restTemplate.exchange(uri, HttpMethod.POST, getCreateUserFormHttpEntity(user, password), Void.class);
// Adds the user to groups of existing programming exercises
@@ -152,7 +153,7 @@ public void deleteUser(User user) throws ContinuousIntegrationException {
}
try {
- var uri = UriComponentsBuilder.fromHttpUrl(jenkinsServerUrl.toString()).pathSegment("user", userLogin, "doDelete").build().toUri();
+ URI uri = JenkinsEndpoints.DELETE_USER.buildEndpoint(jenkinsServerUri, userLogin).build(true).toUri();
restTemplate.exchange(uri, HttpMethod.POST, null, Void.class);
removeUserFromGroups(userLogin, getUserWithGroups(user).getGroups());
}
@@ -240,8 +241,7 @@ public void updateUserAndGroups(String oldLogin, User user, String password, Set
@Override
public void addUserToGroups(String userLogin, Set groups) throws ContinuousIntegrationException {
var exercises = programmingExerciseRepository.findAllByInstructorOrEditorOrTAGroupNameIn(groups);
- log.info("Update Jenkins permissions for programming exercises: {}", exercises.stream().map(ProgrammingExercise::getProjectKey).toList());
- // TODO: in case we update a tutor group / role here, the tutor should NOT get access to exam exercises before the exam has finished
+ log.info("Update Jenkins permissions (add users to groups) for programming exercises: {}", exercises.stream().map(ProgrammingExercise::getProjectKey).toList());
exercises.forEach(exercise -> {
// The exercise's project key is also the name of the Jenkins job that groups all build plans
@@ -276,7 +276,6 @@ else if (groups.contains(course.getTeachingAssistantGroupName())) {
throw new JenkinsException("Cannot assign teaching assistant permissions to user: " + userLogin, e);
}
}
-
});
}
@@ -291,7 +290,7 @@ else if (groups.contains(course.getTeachingAssistantGroupName())) {
public void removeUserFromGroups(String userLogin, Set groups) throws ContinuousIntegrationException {
// Remove all permissions assigned to the user for each exercise that belongs to the specified groups.
var exercises = programmingExerciseRepository.findAllByInstructorOrEditorOrTAGroupNameIn(groups);
- log.info("Update Jenkins permissions for programming exercises: {}", exercises.stream().map(ProgrammingExercise::getProjectKey).toList());
+ log.info("Update Jenkins permissions (remove users from groups) for programming exercises: {}", exercises.stream().map(ProgrammingExercise::getProjectKey).toList());
exercises.forEach(exercise -> {
try {
// The exercise's projectkey is also the name of the Jenkins folder job which groups the student's, solution,
@@ -319,7 +318,8 @@ public void updateCoursePermissions(Course updatedCourse, String oldInstructorGr
// Remove all permissions assigned to the instructors and teaching assistants that do not belong to the course anymore.
var programmingExercises = programmingExerciseRepository.findAllProgrammingExercisesInCourseOrInExamsOfCourse(updatedCourse);
- log.info("Update Jenkins permissions for programming exercises: {}", programmingExercises.stream().map(ProgrammingExercise::getProjectKey).toList());
+ log.info("Update Jenkins permissions (update course permissions) for programming exercises: {}",
+ programmingExercises.stream().map(ProgrammingExercise::getProjectKey).toList());
removePermissionsFromInstructorsAndEditorsAndTAsForCourse(oldInstructorGroup, oldEditorGroup, oldTeachingAssistantGroup, programmingExercises);
// Assign teaching assistant and instructor permissions
@@ -393,7 +393,7 @@ private void removePermissionsFromInstructorsAndEditorsAndTAsForCourse(String in
*/
private JenkinsUserDTO getUser(String userLogin) throws ContinuousIntegrationException {
try {
- var uri = UriComponentsBuilder.fromHttpUrl(jenkinsServerUrl.toString()).pathSegment("user", userLogin, "api", "json").build().toUri();
+ URI uri = JenkinsEndpoints.GET_USER.buildEndpoint(jenkinsServerUri, userLogin).build(true).toUri();
return restTemplate.exchange(uri, HttpMethod.GET, null, JenkinsUserDTO.class).getBody();
}
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsXmlFileUtils.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsXmlFileUtils.java
index de506b3d2f78..095cea7b3484 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsXmlFileUtils.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsXmlFileUtils.java
@@ -30,7 +30,7 @@ public class JenkinsXmlFileUtils {
private static final Logger log = LoggerFactory.getLogger(JenkinsXmlFileUtils.class);
- public static Document readFromString(String xmlString) {
+ public static Document readFromString(@NotNull String xmlString) {
return parseDocument(xmlString);
}
@@ -60,7 +60,9 @@ public static Document readXmlFile(Resource resource, @Nullable Map aeolusBuildPlanService,
ProgrammingExerciseBuildConfigRepository programmingExerciseBuildConfigRepository) {
this.restTemplate = restTemplate;
- this.jenkinsServer = jenkinsServer;
this.jenkinsBuildPlanCreator = jenkinsBuildPlanCreator;
this.jenkinsJobService = jenkinsJobService;
this.userRepository = userRepository;
@@ -226,7 +213,7 @@ public void updateBuildPlanRepositories(String buildProjectKey, String buildPlan
// remove potential username from repo URI. Jenkins uses the Artemis Admin user and will fail if other usernames are in the URI
final var repoUri = newRepoUri.replaceAll("(https?://)(.*@)(.*)", "$1$3");
- final Document jobConfig = jenkinsJobService.getJobConfigForJobInFolder(buildProjectKey, buildPlanKey);
+ final Document jobConfig = jenkinsJobService.getJobConfig(buildProjectKey, buildPlanKey);
try {
JenkinsBuildPlanUtils.replaceScriptParameters(jobConfig, existingRepoUri, repoUri);
@@ -235,7 +222,7 @@ public void updateBuildPlanRepositories(String buildProjectKey, String buildPlan
log.error("Pipeline Script not found", e);
}
- postBuildPlanConfigChange(buildPlanKey, buildProjectKey, jobConfig);
+ jenkinsJobService.updateJob(buildProjectKey, buildPlanKey, jobConfig);
}
/**
@@ -262,25 +249,6 @@ private void updateBuildPlanURLs(ProgrammingExercise templateExercise, Programmi
}
}
- private void postBuildPlanConfigChange(String buildPlanKey, String buildProjectKey, Document jobConfig) {
- final var errorMessage = "Error trying to configure build plan in Jenkins " + buildPlanKey;
- try {
- URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(serverUrl.toString(), buildProjectKey, buildPlanKey).build(true).toUri();
-
- final var headers = new HttpHeaders();
- headers.setContentType(MediaType.APPLICATION_XML);
-
- String jobXmlString = JenkinsXmlFileUtils.writeToString(jobConfig);
- final var entity = new HttpEntity<>(jobXmlString, headers);
-
- restTemplate.exchange(uri, HttpMethod.POST, entity, String.class);
- }
- catch (RestClientException | TransformerException e) {
- log.error(errorMessage, e);
- throw new JenkinsException(errorMessage, e);
- }
- }
-
/**
* Returns the build plan key from the specified test results.
*
@@ -320,7 +288,7 @@ public String copyBuildPlan(ProgrammingExercise sourceExercise, String sourcePla
final var cleanTargetName = getCleanPlanName(targetPlanName);
final var sourcePlanKey = sourceProjectKey + "-" + sourcePlanName;
final var targetPlanKey = targetProjectKey + "-" + cleanTargetName;
- final var jobXml = jenkinsJobService.getJobConfigForJobInFolder(sourceProjectKey, sourcePlanKey);
+ final var jobXml = jenkinsJobService.getJobConfig(sourceProjectKey, sourcePlanKey);
updateBuildPlanURLs(sourceExercise, targetExercise, jobXml);
@@ -341,40 +309,15 @@ private String getCleanPlanName(String name) {
*/
public void triggerBuild(String projectKey, String planKey) {
try {
- jenkinsJobService.getJobInFolder(projectKey, planKey).build(useCrumb);
+ URI uri = JenkinsEndpoints.TRIGGER_BUILD.buildEndpoint(jenkinsServerUri, projectKey, planKey).build(true).toUri();
+ restTemplate.postForEntity(uri, new HttpEntity<>(null, new HttpHeaders()), Void.class);
}
- catch (JenkinsException | IOException e) {
+ catch (RestClientException e) {
log.error(e.getMessage(), e);
throw new JenkinsException("Error triggering build: " + planKey, e);
}
}
- /**
- * Deletes the build plan
- *
- * @param projectKey the project key of the plan
- * @param planKey the plan key
- */
- public void deleteBuildPlan(String projectKey, String planKey) {
- try {
- var folderJob = jenkinsJobService.getFolderJob(projectKey);
- if (folderJob != null) {
- jenkinsServer.deleteJob(folderJob, planKey, useCrumb);
- }
- }
- catch (HttpResponseException e) {
- // We don't throw an exception if the build doesn't exist in Jenkins (404 status)
- if (e.getStatusCode() != HttpStatus.SC_NOT_FOUND) {
- log.error(e.getMessage(), e);
- throw new JenkinsException("Error while trying to delete job in Jenkins: " + planKey, e);
- }
- }
- catch (IOException e) {
- log.error(e.getMessage(), e);
- throw new JenkinsException("Error while trying to delete job in Jenkins: " + planKey, e);
- }
- }
-
/**
* Retrieves the build status of the plan
*
@@ -384,28 +327,32 @@ public void deleteBuildPlan(String projectKey, String planKey) {
* @throws JenkinsException thrown in case of errors
*/
public ContinuousIntegrationService.BuildStatus getBuildStatusOfPlan(String projectKey, String planKey) throws JenkinsException {
- var job = jenkinsJobService.getJobInFolder(projectKey, planKey);
+ var job = jenkinsJobService.getJob(projectKey, planKey);
if (job == null) {
// Plan doesn't exist.
return ContinuousIntegrationService.BuildStatus.INACTIVE;
}
- if (job.isInQueue()) {
+ if (job.inQueue()) {
return ContinuousIntegrationService.BuildStatus.QUEUED;
}
try {
- var uri = UriComponentsBuilder.fromUriString(serverUrl.toString()).pathSegment("job", projectKey, "job", planKey, "lastBuild", "api", "json").build().toUri();
- var response = restTemplate.getForObject(uri, JsonNode.class);
- var isJobBuilding = response.get("building").asBoolean();
- return isJobBuilding ? ContinuousIntegrationService.BuildStatus.BUILDING : ContinuousIntegrationService.BuildStatus.INACTIVE;
+ URI uri = JenkinsEndpoints.LAST_BUILD.buildEndpoint(jenkinsServerUri, projectKey, planKey).build(true).toUri();
+ var buildStatus = restTemplate.getForObject(uri, JenkinsBuildStatusDTO.class);
+ return buildStatus != null && buildStatus.building ? ContinuousIntegrationService.BuildStatus.BUILDING : ContinuousIntegrationService.BuildStatus.INACTIVE;
}
- catch (NullPointerException | HttpClientErrorException e) {
+ catch (HttpClientErrorException e) {
log.error("Error while trying to fetch build status from Jenkins for {}: {}", planKey, e.getMessage());
return ContinuousIntegrationService.BuildStatus.INACTIVE;
}
}
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public record JenkinsBuildStatusDTO(boolean building) {
+ }
+
/**
* Checks if a project folder exists.
*
@@ -431,7 +378,7 @@ public boolean projectFolderExists(String projectKey) {
*/
public boolean buildPlanExists(String projectKey, String buildPlanId) {
try {
- var planExists = jenkinsJobService.getJobInFolder(projectKey, buildPlanId);
+ var planExists = jenkinsJobService.getJob(projectKey, buildPlanId);
return planExists != null;
}
catch (JenkinsException emAll) {
@@ -476,8 +423,8 @@ public void givePlanPermissions(ProgrammingExercise programmingExercise, String
*/
public void enablePlan(String projectKey, String planKey) {
try {
- var uri = UriComponentsBuilder.fromUriString(serverUrl.toString()).pathSegment("job", projectKey, "job", planKey, "enable").build(true).toUri();
- restTemplate.postForEntity(uri, null, String.class);
+ URI uri = JenkinsEndpoints.ENABLE.buildEndpoint(jenkinsServerUri, projectKey, planKey).build(true).toUri();
+ restTemplate.postForEntity(uri, new HttpEntity<>(null, new HttpHeaders()), Void.class);
}
catch (HttpClientErrorException e) {
throw new JenkinsException("Unable to enable plan " + planKey + "; statusCode=" + e.getStatusCode() + "; body=" + e.getResponseBodyAsString());
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobPermissionsService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobPermissionsService.java
index 1d7bd03b7029..9f33f4f35ebc 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobPermissionsService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobPermissionsService.java
@@ -1,14 +1,17 @@
package de.tum.cit.aet.artemis.programming.service.jenkins.jobs;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
import java.io.IOException;
import java.util.Set;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
@Service
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
public class JenkinsJobPermissionsService {
private final JenkinsJobService jenkinsJobService;
@@ -29,7 +32,7 @@ public JenkinsJobPermissionsService(JenkinsJobService jenkinsJobService) {
*/
public void addInstructorAndEditorAndTAPermissionsToUsersForJob(Set taLogins, Set editorLogins, Set instructorLogins, String folderName, String jobName)
throws IOException {
- var jobConfig = jenkinsJobService.getJobConfig(folderName, jobName);
+ Document jobConfig = jenkinsJobService.getJobConfig(folderName, jobName);
if (jobConfig == null) {
// Job doesn't exist so do nothing.
return;
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobService.java
index 334ad5dd8a17..fc820d12d71b 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/jobs/JenkinsJobService.java
@@ -1,37 +1,46 @@
package de.tum.cit.aet.artemis.programming.service.jenkins.jobs;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
+
import java.io.IOException;
+import java.net.URI;
import javax.xml.transform.TransformerException;
-import org.apache.http.client.HttpResponseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestClientException;
+import org.springframework.web.client.RestTemplate;
import org.w3c.dom.Document;
-import com.offbytwo.jenkins.JenkinsServer;
-import com.offbytwo.jenkins.model.FolderJob;
-import com.offbytwo.jenkins.model.JobWithDetails;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
import de.tum.cit.aet.artemis.core.exception.JenkinsException;
+import de.tum.cit.aet.artemis.programming.service.jenkins.JenkinsEndpoints;
import de.tum.cit.aet.artemis.programming.service.jenkins.JenkinsXmlFileUtils;
@Service
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
public class JenkinsJobService {
private static final Logger log = LoggerFactory.getLogger(JenkinsJobService.class);
- @Value("${jenkins.use-crumb:#{true}}")
- private boolean useCrumb;
+ private final RestTemplate restTemplate;
- private final JenkinsServer jenkinsServer;
+ @Value("${artemis.continuous-integration.url}")
+ private URI jenkinsServerUri;
- public JenkinsJobService(JenkinsServer jenkinsServer) {
- this.jenkinsServer = jenkinsServer;
+ public JenkinsJobService(@Qualifier("jenkinsRestTemplate") RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
}
/**
@@ -41,27 +50,36 @@ public JenkinsJobService(JenkinsServer jenkinsServer) {
* @param jobName the name of the job
* @return the job with details
*/
- public JobWithDetails getJobInFolder(String folderJobName, String jobName) {
+ public JobWithDetails getJob(String folderJobName, String jobName) {
if (folderJobName == null || jobName == null) {
log.warn("Cannot get the job, because projectKey {} or jobName {} is null", folderJobName, jobName);
return null;
}
- final var folder = getFolderJob(folderJobName);
- if (folder == null) {
+ try {
+ URI uri = JenkinsEndpoints.GET_JOB.buildEndpoint(jenkinsServerUri, folderJobName, jobName).build(true).toUri();
+ return restTemplate.getForObject(uri, JobWithDetails.class);
+ }
+ catch (HttpClientErrorException.NotFound notFound) {
log.warn("Cannot get the job {} in folder {} because it doesn't exist.", jobName, folderJobName);
return null;
}
-
- try {
- return jenkinsServer.getJob(folder, jobName);
- }
- catch (IOException e) {
+ catch (RestClientException e) {
log.error(e.getMessage(), e);
throw new JenkinsException(e.getMessage(), e);
}
}
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public record JobWithDetails(String name, String description, boolean inQueue) {
+ }
+
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ public record FolderJob(String name, String description, String url) {
+ }
+
/**
* Gets the folder job or null if it doesn't exist
*
@@ -70,13 +88,13 @@ public JobWithDetails getJobInFolder(String folderJobName, String jobName) {
*/
public FolderJob getFolderJob(String folderName) {
try {
- final var job = jenkinsServer.getJob(folderName);
- if (job == null) {
- return null;
- }
- return jenkinsServer.getFolderJob(job).orElse(null);
+ URI uri = JenkinsEndpoints.GET_FOLDER_JOB.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
+ return restTemplate.getForObject(uri, FolderJob.class);
}
- catch (IOException e) {
+ catch (HttpClientErrorException.NotFound notFound) {
+ return null;
+ }
+ catch (RestClientException e) {
log.error(e.getMessage(), e);
throw new JenkinsException(e.getMessage(), e);
}
@@ -89,21 +107,27 @@ public FolderJob getFolderJob(String folderName) {
* @param jobName the name of the job
* @return the xml document
*/
- public Document getJobConfigForJobInFolder(String folderName, String jobName) {
+ public Document getJobConfig(String folderName, String jobName) {
try {
var folder = getFolderJob(folderName);
if (folder == null) {
- throw new JenkinsException("The folder " + folderName + "does not exist.");
+ throw new JenkinsException("The folder " + folderName + " does not exist.");
}
- String xmlString = jenkinsServer.getJobXml(folder, jobName);
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, folderName, jobName).build(true).toUri();
+ String xmlString = restTemplate.getForObject(uri, String.class);
+
// Replace the old reference to the master and main branch by a reference to the default branch
xmlString = xmlString.replace("*/master", "**");
xmlString = xmlString.replace("*/main", "**");
return JenkinsXmlFileUtils.readFromString(xmlString);
}
- catch (IOException e) {
+ catch (HttpClientErrorException.NotFound notFound) {
+ log.error("Job with jobName {} in folder {} does not exist in Jenkins", jobName, folderName);
+ throw new JenkinsException(notFound.getMessage(), notFound);
+ }
+ catch (RestClientException e) {
log.error(e.getMessage(), e);
throw new JenkinsException(e.getMessage(), e);
}
@@ -117,14 +141,32 @@ public Document getJobConfigForJobInFolder(String folderName, String jobName) {
* @throws IOException in case of errors
*/
public Document getFolderConfig(String folderName) throws IOException {
- if (jenkinsServer.getJob(folderName) == null) {
+ if (getFolderJob(folderName) == null) {
return null;
}
- String folderXml = jenkinsServer.getJobXml(folderName);
+ URI uri = JenkinsEndpoints.FOLDER_CONFIG.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
+ String folderXml = restTemplate.getForObject(uri, String.class);
return JenkinsXmlFileUtils.readFromString(folderXml);
}
+ /**
+ * Creates a new folder in Jenkins.
+ *
+ * @param projectKey The name of the folder.
+ */
+ public void createFolder(String projectKey) {
+ //@formatter:off
+ URI uri = JenkinsEndpoints.NEW_FOLDER.buildEndpoint(jenkinsServerUri)
+ .queryParam("name", projectKey)
+ .queryParam("mode", "com.cloudbees.hudson.plugins.folder.Folder")
+ .queryParam("from", "")
+ .queryParam("Submit", "OK")
+ .build(true).toUri();
+ //@formatter:on
+ restTemplate.postForEntity(uri, new HttpEntity<>(null, new HttpHeaders()), Void.class);
+ }
+
/**
* Creates a job inside a folder
*
@@ -139,61 +181,79 @@ public void createJobInFolder(Document jobConfig, String folderName, String jobN
throw new JenkinsException("Cannot create job " + jobName + " because the folder " + folderName + " does not exist.");
}
- var existingJob = jenkinsServer.getJob(folder, jobName);
+ var existingJob = getJob(folderName, jobName);
if (existingJob != null) {
log.info("Build Plan {} already exists. Skipping creation of job.", jobName);
return;
}
- String configString = JenkinsXmlFileUtils.writeToString(jobConfig);
- jenkinsServer.createJob(folder, jobName, configString, useCrumb);
+ URI uri = JenkinsEndpoints.NEW_PLAN.buildEndpoint(jenkinsServerUri, folderName).queryParam("name", jobName).build(true).toUri();
+
+ final var headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_XML);
+ String jobXmlString = JenkinsXmlFileUtils.writeToString(jobConfig);
+ final var entity = new HttpEntity<>(jobXmlString, headers);
+
+ restTemplate.postForEntity(uri, entity, Void.class);
}
- catch (IOException | TransformerException e) {
+ catch (RestClientException | TransformerException e) {
log.error(e.getMessage(), e);
throw new JenkinsException(e.getMessage(), e);
}
}
/**
- * Gets the job config of a job that is inside a folder
+ * Updates a job.
*
- * @param folderName the name of the folder
- * @param jobName the name of the job
- * @return the job config as xml document or null if the job doesn't exist
- * @throws IOException in case of errors
+ * @param folderName optional folder name where the job resides (project key)
+ * @param jobName the name of the job (build plan key)
+ * @param jobConfig the updated job config
*/
- public Document getJobConfig(String folderName, String jobName) throws IOException {
- var job = jenkinsServer.getJob(folderName);
- if (job == null) {
- return null;
- }
+ public void updateJob(String folderName, String jobName, Document jobConfig) {
+ final var errorMessage = "Error trying to configure build plan in Jenkins " + jobName;
+ try {
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, folderName, jobName).build(true).toUri();
- var folder = jenkinsServer.getFolderJob(job);
+ final var headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_XML);
+ String jobXmlString = JenkinsXmlFileUtils.writeToString(jobConfig);
+ final var entity = new HttpEntity<>(jobXmlString, headers);
- String jobXml = jenkinsServer.getJobXml(folder.orElse(null), jobName);
- return JenkinsXmlFileUtils.readFromString(jobXml);
+ restTemplate.postForEntity(uri, entity, String.class);
+ }
+ catch (HttpClientErrorException.NotFound e) {
+ // We don't throw an exception if the project doesn't exist in Jenkins (404 status)
+ log.warn("updateJob {} does not exist in Jenkins: {}", jobName, e.getMessage());
+ throw new JenkinsException(errorMessage, e);
+ }
+ catch (RestClientException | TransformerException e) {
+ log.error(errorMessage, e);
+ throw new JenkinsException(errorMessage, e);
+ }
}
/**
- * Updates a job.
+ * Updates the xml description of the folder job.
*
- * @param folderName optional folder name where the job resides
- * @param jobName the name of the job
- * @param jobConfig the updated job config
+ * @param folderName the name of the folder
+ * @param folderConfig the xml document of the folder
* @throws IOException in case of errors
*/
- public void updateJob(String folderName, String jobName, Document jobConfig) throws IOException {
+ public void updateFolderJob(String folderName, Document folderConfig) throws IOException {
try {
- String configString = JenkinsXmlFileUtils.writeToString(jobConfig);
+ URI uri = JenkinsEndpoints.FOLDER_CONFIG.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
- if (folderName != null && !folderName.isEmpty()) {
- var job = jenkinsServer.getJob(folderName);
- var folder = jenkinsServer.getFolderJob(job);
- jenkinsServer.updateJob(folder.orElse(null), jobName, configString, useCrumb);
- }
- else {
- jenkinsServer.updateJob(jobName, configString, useCrumb);
- }
+ final var headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_XML);
+ String jobXmlString = JenkinsXmlFileUtils.writeToString(folderConfig);
+ final var entity = new HttpEntity<>(jobXmlString, headers);
+
+ restTemplate.postForEntity(uri, entity, Void.class);
+ }
+ catch (HttpClientErrorException.NotFound e) {
+ // We don't throw an exception if the project doesn't exist in Jenkins (404 status)
+ log.warn("updateFolderJob {} does not exist in Jenkins. Skipping deletion: {}", folderName, e.getMessage());
+ throw new JenkinsException("Error while trying to update folder job in Jenkins for " + folderName, e);
}
catch (TransformerException e) {
throw new IOException(e.getMessage(), e);
@@ -201,42 +261,43 @@ public void updateJob(String folderName, String jobName, Document jobConfig) thr
}
/**
- * Updates the xml description of the folder job.
+ * Deletes a job in Jenkins.
*
- * @param folderName the name of the folder
- * @param folderConfig the xml document of the folder
- * @throws IOException in case of errors
+ * @param folderName The name of the folder the job is in.
+ * @param jobName The name of the job itself.
*/
- public void updateFolderJob(String folderName, Document folderConfig) throws IOException {
+ public void deleteJob(String folderName, String jobName) {
try {
- String configString = JenkinsXmlFileUtils.writeToString(folderConfig);
- jenkinsServer.updateJob(folderName, configString, useCrumb);
+ URI uri = JenkinsEndpoints.DELETE_JOB.buildEndpoint(jenkinsServerUri, folderName, jobName).build(true).toUri();
+ restTemplate.postForEntity(uri, new HttpEntity<>(null, new HttpHeaders()), Void.class);
}
- catch (TransformerException e) {
- throw new IOException(e.getMessage(), e);
+ catch (HttpClientErrorException.NotFound e) {
+ // We don't throw an exception if the project doesn't exist in Jenkins (404 status)
+ log.warn("Job {} in folder {} does not exist in Jenkins. Skipping deletion: {}", jobName, folderName, e.getMessage());
+ }
+ catch (RestClientException e) {
+ log.error(e.getMessage(), e);
+ throw new JenkinsException("Error while trying to delete folder job in Jenkins for " + folderName, e);
}
}
/**
- * Deletes the job from Jenkins. Doesn't do anything if the job
- * doesn't exist.
+ * Deletes the job from Jenkins. Doesn't do anything if the job doesn't exist.
*
- * @param jobName the name of the job to delete.
+ * @param folderName the name of the folder (project) to delete.
*/
- public void deleteJob(String jobName) {
+ public void deleteFolderJob(String folderName) {
try {
- jenkinsServer.deleteJob(jobName, useCrumb);
+ URI uri = JenkinsEndpoints.DELETE_FOLDER.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
+ restTemplate.postForEntity(uri, new HttpEntity<>(null, new HttpHeaders()), Void.class);
}
- catch (HttpResponseException e) {
+ catch (HttpClientErrorException.NotFound e) {
// We don't throw an exception if the project doesn't exist in Jenkins (404 status)
- if (e.getStatusCode() != org.apache.http.HttpStatus.SC_NOT_FOUND) {
- log.error(e.getMessage(), e);
- throw new JenkinsException("Error while trying to delete job in Jenkins for " + jobName, e);
- }
+ log.warn("Folder job {} does not exist in Jenkins. Skipping deletion: {}", folderName, e.getMessage());
}
- catch (IOException e) {
+ catch (RestClientException e) {
log.error(e.getMessage(), e);
- throw new JenkinsException("Error while trying to delete job in Jenkins for " + jobName, e);
+ throw new JenkinsException("Error while trying to delete folder job in Jenkins for " + folderName, e);
}
}
}
diff --git a/src/main/kubernetes/artemis/secrets/artemis-secrets.yml b/src/main/kubernetes/artemis/secrets/artemis-secrets.yml
index 4d38f252c32c..9320449dd849 100644
--- a/src/main/kubernetes/artemis/secrets/artemis-secrets.yml
+++ b/src/main/kubernetes/artemis/secrets/artemis-secrets.yml
@@ -8,7 +8,6 @@ data:
artemis.continuous-integration.password: ""
artemis.continuous-integration.token: ""
artemis.lti.oauth-key: ""
- artemis.lti.oauth-secret: ""
artemis.user-management.external.password: "YWRtaW4=" # base64 encoded "admin"
artemis.user-management.internal-admin.password: "YXJ0ZW1pc19hZG1pbg==" # base64 encoded "artemis_admin"
artemis.user-management.ldap.password: ""
diff --git a/src/main/resources/config/application-artemis.yml b/src/main/resources/config/application-artemis.yml
index 4f54275e26d9..844ce17ed2ef 100644
--- a/src/main/resources/config/application-artemis.yml
+++ b/src/main/resources/config/application-artemis.yml
@@ -68,15 +68,15 @@ artemis:
# GitLab CI: not needed
# Jenkins: You have to specify the key from the credentials page in Jenkins under which the user and
# password for the VCS are stored
- vcs-credentials:
+ vcs-credentials: myCredentialsKey #must be changed to a secure value
# Key of the credentials for the Artemis notification token
# Jenkins: You have to specify the key from the credentials page in Jenkins under which the authentication-token is stored
- artemis-authentication-token-key:
+ artemis-authentication-token-key: myCredentialsKey #must be changed to a secure value
# The actual value of the notification token to check against in Artemis. This is the token that gets send with
# every request the CI system makes to Artemis containing a new result after a build.
# GitLab CI: The token value you use for the Server Notification Plugin
# Jenkins: The token value you use for the Server Notification Plugin and is stored under the notification-token credential above
- artemis-authentication-token-value:
+ artemis-authentication-token-value: myToken #must be changed to a secure value
build-timeout: 30 # Does cancel jenkins builds after 30 minutes to remove build that get stuck
notification-plugin: "ls1tum/artemis-notification-plugin:1.0.0" # Docker image for the generic notification plugin. This value is set in an CI variable in GitLab CI.
build-log:
diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml
index b4984ea6cf97..a002fcd338f8 100644
--- a/src/main/resources/config/application.yml
+++ b/src/main/resources/config/application.yml
@@ -211,7 +211,7 @@ spring:
main:
allow-bean-definition-overriding: false
allow-circular-references: false
- lazy-initialization: false # cannot set this to true (even if we would want, because then LocalCI does not process build results correctly)
+ lazy-initialization: false # cannot set this because of static inject in FilePathService
task:
execution:
thread-name-prefix: artemis-task-
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/authentication/InternalAuthenticationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/authentication/InternalAuthenticationIntegrationTest.java
index 087ad4d8dfaf..69b14ab0ba8d 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/authentication/InternalAuthenticationIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/authentication/InternalAuthenticationIntegrationTest.java
@@ -91,7 +91,7 @@ class InternalAuthenticationIntegrationTest extends AbstractSpringIntegrationJen
@BeforeEach
void setUp() {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
userUtilService.addUsers(TEST_PREFIX, 1, 0, 0, 0);
Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise();
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/authentication/UserJenkinsGitlabIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/authentication/UserJenkinsGitlabIntegrationTest.java
index 50ff7b932516..5944f729a03e 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/authentication/UserJenkinsGitlabIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/authentication/UserJenkinsGitlabIntegrationTest.java
@@ -66,7 +66,7 @@ class UserJenkinsGitlabIntegrationTest extends AbstractSpringIntegrationJenkinsG
void setUp() throws Exception {
userTestService.setup(TEST_PREFIX, this);
gitlabRequestMockProvider.enableMockingOfRequests();
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
}
@AfterEach
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/connector/AeolusRequestMockProvider.java b/src/test/java/de/tum/cit/aet/artemis/core/connector/AeolusRequestMockProvider.java
index 12ca6585f6c9..56ad679806e2 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/connector/AeolusRequestMockProvider.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/connector/AeolusRequestMockProvider.java
@@ -4,6 +4,7 @@
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.net.URL;
import java.util.HashMap;
@@ -72,7 +73,7 @@ public void mockSuccessfulPublishBuildPlan(AeolusTarget target, String expectedK
String json = objectMapper.writeValueAsString(responseBody);
mockServer.expect(requestTo(MatchesPattern.matchesPattern(uriPattern))).andExpect(method(HttpMethod.POST))
- .andRespond(withStatus(HttpStatus.OK).body(json).contentType(org.springframework.http.MediaType.APPLICATION_JSON));
+ .andRespond(withSuccess().body(json).contentType(org.springframework.http.MediaType.APPLICATION_JSON));
}
/**
@@ -110,11 +111,11 @@ public void mockGeneratePreview(AeolusTarget target) throws JsonProcessingExcept
String json = objectMapper.writeValueAsString(responseBody);
mockServer.expect(requestTo(MatchesPattern.matchesPattern(uriPattern))).andExpect(method(HttpMethod.POST))
- .andRespond(withStatus(HttpStatus.OK).body(json).contentType(org.springframework.http.MediaType.APPLICATION_JSON));
+ .andRespond(withSuccess().body(json).contentType(org.springframework.http.MediaType.APPLICATION_JSON));
}
public void mockAuthenticatedRequest(String uri, String token) {
- mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andExpect(header("Authorization", "Bearer " + token)).andRespond(withStatus(HttpStatus.OK));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andExpect(header("Authorization", "Bearer " + token)).andRespond(withSuccess());
}
/**
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java b/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java
index c551387bdb68..732f582317b1 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/connector/GitlabRequestMockProvider.java
@@ -22,6 +22,7 @@
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.net.URISyntaxException;
import java.net.URL;
@@ -365,7 +366,7 @@ public void mockCreationOfUser(String login) throws GitLabApiException, JsonProc
final var response = new ObjectMapper().writeValueAsString(accessTokenResponseDTO);
mockServer.expect(requestTo(gitLabApi.getGitLabServerUrl() + "/api/v4/users/" + userId + "/personal_access_tokens")).andExpect(method(HttpMethod.POST))
- .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(response));
+ .andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(response));
}
public void mockCreatePersonalAccessTokenError() throws GitLabApiException {
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/connector/JenkinsRequestMockProvider.java b/src/test/java/de/tum/cit/aet/artemis/core/connector/JenkinsRequestMockProvider.java
index 2adaa451e563..0ae3364343dc 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/connector/JenkinsRequestMockProvider.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/connector/JenkinsRequestMockProvider.java
@@ -1,26 +1,27 @@
package de.tum.cit.aet.artemis.core.connector;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_JENKINS;
import static de.tum.cit.aet.artemis.core.util.TestResourceUtils.loadFileFromResources;
+import static de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType.SOLUTION;
+import static de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType.TEMPLATE;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withBadRequest;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withResourceNotFound;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
+import java.net.URI;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
-import org.apache.http.client.HttpResponseException;
import org.hamcrest.Matchers;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,31 +35,26 @@
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;
-import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.offbytwo.jenkins.JenkinsServer;
-import com.offbytwo.jenkins.model.FolderJob;
-import com.offbytwo.jenkins.model.JobWithDetails;
-import com.offbytwo.jenkins.model.QueueReference;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
+import de.tum.cit.aet.artemis.programming.domain.build.BuildPlanType;
+import de.tum.cit.aet.artemis.programming.service.jenkins.JenkinsEndpoints;
import de.tum.cit.aet.artemis.programming.service.jenkins.dto.JenkinsUserDTO;
import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobPermissionsService;
+import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobService;
import de.tum.cit.aet.artemis.programming.test_repository.ProgrammingExerciseTestRepository;
@Component
-@Profile("jenkins")
+@Profile(PROFILE_JENKINS)
public class JenkinsRequestMockProvider {
@Value("${artemis.continuous-integration.url}")
- private URL jenkinsServerUrl;
-
- @Value("${jenkins.use-crumb:#{true}}")
- private boolean useCrumb;
+ private URI jenkinsServerUri;
private final RestTemplate restTemplate;
@@ -68,9 +64,6 @@ public class JenkinsRequestMockProvider {
private MockRestServiceServer shortTimeoutMockServer;
- // will be assigned in enableMockingOfRequests(), can be used like a MockitoSpyBean
- private JenkinsServer jenkinsServer;
-
// will be assigned in enableMockingOfRequests(), can be used like a MockitoSpyBean
private JenkinsJobPermissionsService jenkinsJobPermissionsService;
@@ -89,15 +82,13 @@ public JenkinsRequestMockProvider(@Qualifier("jenkinsRestTemplate") RestTemplate
// We remove JenkinsAuthorizationInterceptor because the tests hit the intercept() method
// which has its' own instance of RestTemplate (in order to get a crumb(. Since that template
// isn't mocked, it will throw an exception.
- // TODO: Find a way to either mock the interceptor or mock its RestTemplate
this.restTemplate.setInterceptors(List.of());
this.shortTimeoutRestTemplate.setInterceptors(List.of());
}
- public void enableMockingOfRequests(JenkinsServer jenkinsServer, JenkinsJobPermissionsService jenkinsJobPermissionsService) {
+ public void enableMockingOfRequests(JenkinsJobPermissionsService jenkinsJobPermissionsService) {
mockServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).bufferContent().build();
shortTimeoutMockServer = MockRestServiceServer.bindTo(shortTimeoutRestTemplate).ignoreExpectOrder(true).bufferContent().build();
- this.jenkinsServer = jenkinsServer;
this.jenkinsJobPermissionsService = jenkinsJobPermissionsService;
closeable = MockitoAnnotations.openMocks(this);
}
@@ -121,114 +112,175 @@ public void verifyMocks() {
mockServer.verify();
}
- public void mockCreateProjectForExercise(ProgrammingExercise exercise, boolean shouldFail) throws IOException {
- // TODO: we need to mockRetrieveArtifacts folder(...)
+ private String buildJobName(final String projectKey, final String planName) {
+ // the build plan ID can be provided either as the full name already (contains -), or only the participation ID suffix.
+ if (planName.contains("-")) {
+ return planName;
+ }
+ else {
+ return projectKey + "-" + planName;
+ }
+ }
+
+ public void mockCreateProjectForExercise(ProgrammingExercise exercise, boolean shouldFail) {
+ //@formatter:off
+ URI uri = JenkinsEndpoints.NEW_FOLDER.buildEndpoint(jenkinsServerUri)
+ .queryParam("name", exercise.getProjectKey())
+ .queryParam("mode", "com.cloudbees.hudson.plugins.folder.Folder")
+ .queryParam("from", "")
+ .queryParam("Submit", "OK")
+ .build(true).toUri();
+ //@formatter:on
if (shouldFail) {
- doThrow(IOException.class).when(jenkinsServer).createFolder(null, exercise.getProjectKey(), useCrumb);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withBadRequest());
}
else {
- doReturn(null).when(jenkinsServer).createFolder(null, exercise.getProjectKey(), useCrumb);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
}
public void mockCreateBuildPlan(String projectKey, String planKey, boolean jobAlreadyExists) throws IOException {
- var job = projectKey + "-" + planKey;
- mockCreateJobInFolder(projectKey, job, jobAlreadyExists);
- mockCreateBuildPlan(projectKey, job);
- }
+ final String job = buildJobName(projectKey, planKey);
- private void mockCreateBuildPlan(String projectKey, String job) throws IOException {
+ mockCreateJobInFolder(projectKey, job, jobAlreadyExists);
mockGivePlanPermissions(projectKey, job);
mockTriggerBuild(projectKey, job, false);
}
public void mockCreateCustomBuildPlan(String projectKey, String planKey) throws IOException {
- var job = projectKey + "-" + planKey;
- mockCreateBuildPlan(projectKey, job);
+ final String job = buildJobName(projectKey, planKey);
+ mockCreateBuildPlan(projectKey, job, false);
+ }
+
+ private void mockCreateJobInExistingFolder(String jobFolder, String job) throws IOException {
+ mockGetFolderJob(jobFolder);
+ mockGetJob(jobFolder, job, null, false);
+ mockCreateJob(jobFolder, job);
}
public void mockCreateJobInFolder(String jobFolder, String job, boolean jobAlreadyExists) throws IOException {
- var folderJob = new FolderJob();
+ var folderJob = jobAlreadyExists ? new JenkinsJobService.FolderJob(jobFolder, "description", "url") : null;
mockGetFolderJob(jobFolder, folderJob);
if (jobAlreadyExists) {
- var jobWithDetails = new JobWithDetails();
- doReturn(jobWithDetails).when(jenkinsServer).getJob(any(FolderJob.class), eq(job));
+ var jobWithDetails = new JenkinsJobService.JobWithDetails(job, "description", false);
+ // NOTE: this method also invokes mockGetFolderJob(...)
+ mockGetJob(jobFolder, job, jobWithDetails, false);
}
else {
- doReturn(null).when(jenkinsServer).getJob(any(FolderJob.class), eq(job));
- doReturn(null).when(jenkinsServer).createJob(any(FolderJob.class), eq(job), anyString(), eq(useCrumb));
+ mockGetJob(jobFolder, job, null, false);
+ mockCreateJob(jobFolder, job);
}
}
- public void mockGivePlanPermissions(String jobFolder, String job) throws IOException {
- mockGetJobConfig(jobFolder, job);
- mockUpdateJob(jobFolder, job);
- mockGetFolderConfig(jobFolder);
- doReturn(null).when(jenkinsServer).updateJob(eq(jobFolder), anyString(), eq(useCrumb));
+ public void mockCreateJob(String jobFolder, String job) {
+ URI uri = JenkinsEndpoints.NEW_PLAN.buildEndpoint(jenkinsServerUri, jobFolder).queryParam("name", job).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
- private void mockGetJobConfig(String folderName, String jobName) throws IOException {
- doReturn(new JobWithDetails()).when(jenkinsServer).getJob(folderName);
- doReturn(Optional.of(new FolderJob())).when(jenkinsServer).getFolderJob(any(JobWithDetails.class));
+ /**
+ * Mock equivalent of {@link JenkinsJobPermissionsService#addInstructorAndEditorAndTAPermissionsToUsersForJob(Set, Set, Set, String, String)}.
+ *
+ * @param folderName The folder the job is in.
+ * @param job The name of the job itself.
+ */
+ public void mockGivePlanPermissions(String folderName, String job) throws IOException {
+ // add permissions to job itself
+ mockGetJobConfig(folderName, job);
+ mockUpdatePlanRepository(folderName, job, false);
+ // add read permission to folder the job is in
+ mockAddInstructorAndEditorAndTAPermissionsToUsersForFolder(folderName, false);
+ }
+
+ public void mockGetJobConfig(String folderName, String jobName) throws IOException {
+ mockGetFolderJob(folderName);
+ mockGetJobConfigPlain(folderName, jobName);
+ }
+
+ /**
+ * Should only be used when explicitly only the single request is needed. Use {@link #mockGetJobConfig(String, String)} otherwise.
+ *
+ * @param folderName The name of the folder.
+ * @param jobName The name of the build plan itself.
+ */
+ public void mockGetJobConfigPlain(String folderName, String jobName) throws IOException {
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, folderName, jobName).build(true).toUri();
var mockXml = loadFileFromResources("test-data/jenkins-response/job-config.xml");
- doReturn(mockXml).when(jenkinsServer).getJobXml(any(FolderJob.class), eq(jobName));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(mockXml));
}
public void mockGetFolderConfig(String folderName) throws IOException {
- doReturn(new JobWithDetails()).when(jenkinsServer).getJob(folderName);
+ mockGetFolderJob(folderName);
+ mockGetFolderConfigPlain(folderName);
+ }
+
+ /**
+ * Should only be used when explicitly only the single GET request is needed. Use {@link #mockGetFolderConfig(String)} otherwise.
+ *
+ * @param folderName The name of the folder.
+ * @throws IOException Required due to serialization, should never occur.
+ */
+ public void mockGetFolderConfigPlain(String folderName) throws IOException {
+ URI uri = JenkinsEndpoints.FOLDER_CONFIG.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
var mockXml = loadFileFromResources("test-data/jenkins-response/job-config.xml");
- doReturn(mockXml).when(jenkinsServer).getJobXml(eq(folderName));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(mockXml).contentType(MediaType.APPLICATION_XML));
}
- private void mockUpdateJob(String folderName, String jobName) throws IOException {
- if (folderName != null && !folderName.isEmpty()) {
- doReturn(new JobWithDetails()).when(jenkinsServer).getJob(folderName);
- mockGetFolderJob(folderName, new FolderJob());
- doReturn(null).when(jenkinsServer).updateJob(any(FolderJob.class), eq(jobName), anyString(), eq(useCrumb));
- }
- else {
- doReturn(null).when(jenkinsServer).updateJob(eq(jobName), anyString(), eq(useCrumb));
- }
+ public void mockUpdateFolderConfigPlain(String folderName) throws IOException {
+ URI uri = JenkinsEndpoints.FOLDER_CONFIG.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
+ var mockXml = loadFileFromResources("test-data/jenkins-response/job-config.xml");
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess().body(mockXml).contentType(MediaType.APPLICATION_XML));
}
public void mockCheckIfProjectExists(ProgrammingExercise exercise, boolean exists, boolean shouldFail) throws IOException {
- var jobOrNull = exists ? mock(JobWithDetails.class) : null;
- if (jobOrNull != null) {
- doReturn("https://some-job-url.com").when(jobOrNull).getUrl();
- }
+ var projectKey = exercise.getProjectKey();
+ URI uri = JenkinsEndpoints.GET_FOLDER_JOB.buildEndpoint(jenkinsServerUri, projectKey).build(true).toUri();
+ var jobOrNull = exists ? new JenkinsJobService.FolderJob(projectKey, "description", "url") : null;
+ var response = mapper.writeValueAsString(jobOrNull);
if (shouldFail) {
- doThrow(IOException.class).when(jenkinsServer).getJob(exercise.getProjectKey());
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withBadRequest());
}
else {
- doReturn(jobOrNull).when(jenkinsServer).getJob(exercise.getProjectKey());
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(response));
}
}
public void mockCheckIfProjectExistsJobIsNull(ProgrammingExercise exercise) throws IOException {
- doReturn(null).when(jenkinsServer).getJob(exercise.getProjectKey());
+ mockGetFolderJob(exercise.getProjectKey(), null);
+ }
+
+ public void mockCopyBuildPlanFromTemplate(String sourceProjectKey, String targetProjectKey, String planKey) throws IOException {
+ mockCopyBuildPlanFromPlanType(sourceProjectKey, targetProjectKey, planKey, TEMPLATE, false);
+ }
+
+ public void mockCopyBuildPlanFromTemplateIntoExistingTargetFolder(String sourceProjectKey, String targetProjectKey, String planKey) throws IOException {
+ mockCopyBuildPlanFromPlanType(sourceProjectKey, targetProjectKey, planKey, TEMPLATE, true);
}
- public void mockCheckIfProjectExistsJobUrlEmptyOrNull(ProgrammingExercise exercise, boolean urlEmpty) throws IOException {
- var job = mock(JobWithDetails.class);
- doReturn(job).when(jenkinsServer).getJob(exercise.getProjectKey());
- doReturn(urlEmpty ? "" : null).when(job).getUrl();
+ public void mockCopyBuildPlanFromSolution(String sourceProjectKey, String targetProjectKey, String planKey) throws IOException {
+ mockCopyBuildPlanFromPlanType(sourceProjectKey, targetProjectKey, planKey, SOLUTION, false);
}
- public void mockCopyBuildPlan(String sourceProjectKey, String targetProjectKey) throws IOException {
- mockGetJobXmlForBuildPlanWith(sourceProjectKey, "");
- mockSaveJobXml(targetProjectKey);
+ private void mockCopyBuildPlanFromPlanType(String sourceProjectKey, String targetProjectKey, String planKey, BuildPlanType planType, boolean folderExists) throws IOException {
+ // the plan key has the form EXERCISE_ID-PARTICIPATION_ID
+ final String sourcePlanKey = sourceProjectKey + "-" + planType.getName();
+ mockGetJobXmlForBuildPlanWith(sourceProjectKey, sourcePlanKey, "");
+ mockSaveJobXml(targetProjectKey, planKey, folderExists);
}
- private void mockSaveJobXml(String targetProjectKey) throws IOException {
- mockGetFolderJob(targetProjectKey, new FolderJob());
- // copyBuildPlan uses #createJobInFolder()
- doReturn(null).when(jenkinsServer).getJob(any(), anyString());
- doReturn(null).when(jenkinsServer).createJob(any(), anyString(), anyString(), eq(useCrumb));
+ private void mockSaveJobXml(String targetProjectKey, String planKey, boolean folderExists) throws IOException {
+ mockGetFolderJob(targetProjectKey);
+ mockGetJob(targetProjectKey, planKey, null, false);
+ if (folderExists) {
+ mockCreateJobInExistingFolder(targetProjectKey, planKey);
+ }
+ else {
+ mockCreateBuildPlan(targetProjectKey, planKey, false);
+ }
}
- public void mockConfigureBuildPlan(ProgrammingExercise exercise, String username) throws URISyntaxException, IOException {
+ public void mockConfigureBuildPlan(ProgrammingExercise exercise, String username) throws IOException {
final var projectKey = exercise.getProjectKey();
final var planKey = projectKey + "-" + getCleanPlanName(username.toUpperCase());
mockUpdatePlanRepository(projectKey, planKey, true);
@@ -239,39 +291,44 @@ private String getCleanPlanName(String planName) {
return planName.toUpperCase().replaceAll("[^A-Z0-9]", "");
}
- public void mockUpdatePlanRepository(String projectKey, String planName, boolean useLegacyXml) throws IOException, URISyntaxException {
+ public void mockUpdatePlanRepository(String projectKey, String planName, boolean useLegacyXml) throws IOException {
var jobConfigXmlFilename = useLegacyXml ? "legacy-job-config.xml" : "job-config.xml";
var mockXml = loadFileFromResources("test-data/jenkins-response/" + jobConfigXmlFilename);
- mockGetFolderJob(projectKey, new FolderJob());
- mockGetJobXmlForBuildPlanWith(projectKey, mockXml);
-
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("job", projectKey, "job", planName, "config.xml").build().toUri();
+ mockGetFolderJob(projectKey);
+ mockGetJobXmlForBuildPlanWith(projectKey, planName, mockXml);
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
// build plan URL is updated after the repository URIs, so in this case, the URI is used twice
- mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withStatus(HttpStatus.OK));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
mockTriggerBuild(projectKey, planName, false);
mockTriggerBuild(projectKey, planName, false);
}
- public void mockUpdatePlanRepository(String projectKey, String planName, HttpStatus expectedHttpStatus) throws IOException, URISyntaxException {
+ public void mockUpdatePlanConfigPlain(String projectKey, String planName) {
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
+ }
+
+ public void mockUpdatePlanRepository(String projectKey, String planName, HttpStatus expectedHttpStatus) throws IOException {
var mockXml = loadFileFromResources("test-data/jenkins-response/job-config.xml");
- mockGetFolderJob(projectKey, new FolderJob());
- mockGetJobXmlForBuildPlanWith(projectKey, mockXml);
+ mockGetFolderJob(projectKey);
+ mockGetJobXmlForBuildPlanWith(projectKey, planName, mockXml);
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("job", projectKey, "job", planName, "config.xml").build().toUri();
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withStatus(expectedHttpStatus));
}
- private void mockGetJobXmlForBuildPlanWith(String projectKey, String xmlToReturn) throws IOException {
- mockGetFolderJob(projectKey, new FolderJob());
- doReturn(xmlToReturn).when(jenkinsServer).getJobXml(any(), any());
+ private void mockGetJobXmlForBuildPlanWith(String projectKey, String planName, String xmlToReturn) throws IOException {
+ mockGetFolderJob(projectKey);
+ URI uri = JenkinsEndpoints.PLAN_CONFIG.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(xmlToReturn));
}
- public void mockEnablePlan(String projectKey, String planKey, boolean planExistsInCi, boolean shouldFail) throws URISyntaxException {
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("job", projectKey, "job", planKey, "enable").build().toUri();
+ public void mockEnablePlan(String projectKey, String planKey, boolean planExistsInCi, boolean shouldFail) {
+ URI uri = JenkinsEndpoints.ENABLE.buildEndpoint(jenkinsServerUri, projectKey, planKey).build(true).toUri();
if (shouldFail) {
mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withStatus(HttpStatus.BAD_REQUEST));
}
@@ -283,28 +340,47 @@ public void mockEnablePlan(String projectKey, String planKey, boolean planExists
public void mockCopyBuildPlanForParticipation(ProgrammingExercise exercise, String username) throws IOException {
final var projectKey = exercise.getProjectKey();
- mockCopyBuildPlan(projectKey, projectKey);
+ final var planKey = projectKey + "-" + getCleanPlanName(username.toUpperCase());
+ mockCopyBuildPlanFromTemplateIntoExistingTargetFolder(projectKey, projectKey, planKey);
}
- public void mockGetJob(String projectKey, String jobName, JobWithDetails jobToReturn, boolean shouldFail) throws IOException {
- final var folder = new FolderJob();
+ public void mockGetJob(String projectKey, String jobName, JenkinsJobService.JobWithDetails jobToReturn, boolean shouldFail) throws IOException {
+ final var folder = new JenkinsJobService.FolderJob(projectKey, "description", "url");
mockGetFolderJob(projectKey, folder);
+ URI uri = JenkinsEndpoints.GET_JOB.buildEndpoint(jenkinsServerUri, projectKey, jobName).build(true).toUri();
if (!shouldFail) {
- doReturn(jobToReturn).when(jenkinsServer).getJob(folder, jobName);
+ var response = mapper.writeValueAsString(jobToReturn);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(response).contentType(MediaType.APPLICATION_JSON));
}
else {
- doThrow(IOException.class).when(jenkinsServer).getJob(folder, jobName);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withBadRequest());
}
}
- public void mockGetFolderJob(String folderName, FolderJob folderJobToReturn) throws IOException {
- final var jobWithDetails = new JobWithDetails();
- doReturn(jobWithDetails).when(jenkinsServer).getJob(folderName);
- doReturn(Optional.of(folderJobToReturn)).when(jenkinsServer).getFolderJob(jobWithDetails);
+ /**
+ * Should only be used when explicitly only the single request is needed. Use {@link #mockGetJob(String, String, JenkinsJobService.JobWithDetails, boolean)} otherwise.
+ *
+ * @param projectKey The name of the folder.
+ * @param jobName The name of the build plan itself.
+ * @param jobToReturn The job that is returned by the mocked API.
+ */
+ public void mockGetJobPlain(String projectKey, String jobName, JenkinsJobService.JobWithDetails jobToReturn) throws IOException {
+ URI uri = JenkinsEndpoints.GET_JOB.buildEndpoint(jenkinsServerUri, projectKey, jobName).build(true).toUri();
+ var response = mapper.writeValueAsString(jobToReturn);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(response).contentType(MediaType.APPLICATION_JSON));
+ }
+
+ public void mockGetFolderJob(String folderName) throws IOException {
+ mockGetFolderJob(folderName, new JenkinsJobService.FolderJob(folderName, "description", "url"));
+ }
+
+ public void mockGetFolderJob(String folderName, JenkinsJobService.FolderJob folderJobToReturn) throws IOException {
+ URI uri = JenkinsEndpoints.GET_FOLDER_JOB.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
+ var response = mapper.writeValueAsString(folderJobToReturn);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withSuccess().body(response).contentType(MediaType.APPLICATION_JSON));
}
- public void mockUpdateUserAndGroups(String oldLogin, User user, Set groupsToAdd, Set groupsToRemove, boolean userExistsInJenkins)
- throws IOException, URISyntaxException {
+ public void mockUpdateUserAndGroups(String oldLogin, User user, Set groupsToAdd, Set groupsToRemove, boolean userExistsInJenkins) throws IOException {
if (!oldLogin.equals(user.getLogin())) {
mockUpdateUserLogin(oldLogin, user);
}
@@ -312,16 +388,16 @@ public void mockUpdateUserAndGroups(String oldLogin, User user, Set grou
mockUpdateUser(user, userExistsInJenkins);
}
mockRemoveUserFromGroups(groupsToRemove, false);
- mockAddUsersToGroups(user.getLogin(), groupsToAdd, false);
+ mockAddUsersToGroups(groupsToAdd, false);
}
- private void mockUpdateUser(User user, boolean userExists) throws URISyntaxException, IOException {
+ private void mockUpdateUser(User user, boolean userExists) throws IOException {
mockGetUser(user.getLogin(), userExists, false);
mockDeleteUser(user, userExists, false);
mockCreateUser(user, false, false, false);
}
- private void mockUpdateUserLogin(String oldLogin, User user) throws IOException, URISyntaxException {
+ private void mockUpdateUserLogin(String oldLogin, User user) throws IOException {
if (oldLogin.equals(user.getLogin())) {
return;
}
@@ -333,20 +409,20 @@ private void mockUpdateUserLogin(String oldLogin, User user) throws IOException,
mockCreateUser(user, false, false, false);
}
- public void mockDeleteUser(User user, boolean userExistsInUserManagement, boolean shouldFailToDelete) throws IOException, URISyntaxException {
+ public void mockDeleteUser(User user, boolean userExistsInUserManagement, boolean shouldFailToDelete) throws IOException {
mockGetUser(user.getLogin(), userExistsInUserManagement, false);
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("user", user.getLogin(), "doDelete").build().toUri();
+ URI uri = JenkinsEndpoints.DELETE_USER.buildEndpoint(jenkinsServerUri, user.getLogin()).build(true).toUri();
var status = shouldFailToDelete ? HttpStatus.NOT_FOUND : HttpStatus.FOUND;
mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withStatus(status));
mockRemoveUserFromGroups(user.getGroups(), false);
}
- private void mockGetUser(String userLogin, boolean userExists, boolean shouldFailToGetUser) throws URISyntaxException, JsonProcessingException {
+ private void mockGetUser(String userLogin, boolean userExists, boolean shouldFailToGetUser) throws JsonProcessingException {
var jenkinsUser = new JenkinsUserDTO(userLogin, null, null);
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("user", userLogin, "api", "json").build().toUri();
+ URI uri = JenkinsEndpoints.GET_USER.buildEndpoint(jenkinsServerUri, userLogin).build(true).toUri();
if (userExists) {
mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET))
.andRespond(withStatus(HttpStatus.FOUND).body(mapper.writeValueAsString(jenkinsUser)).contentType(MediaType.APPLICATION_JSON));
@@ -359,7 +435,7 @@ else if (shouldFailToGetUser) {
}
}
- public void mockGetAnyUser(boolean shouldFail, int requestCount) throws URISyntaxException, JsonProcessingException {
+ public void mockGetAnyUser(boolean shouldFail, int requestCount) {
final var httpStatus = shouldFail ? HttpStatus.NOT_FOUND : HttpStatus.FOUND;
mockServer.expect(ExpectedCount.times(requestCount), requestTo(Matchers.endsWith("api/json"))).andRespond(withStatus(httpStatus));
}
@@ -385,29 +461,33 @@ private void mockRemovePermissionsFromUserOfFolder(String folderName, boolean sh
}
}
- public void mockCreateUser(User user, boolean userExistsInCi, boolean shouldFail, boolean shouldFailToGetUser) throws URISyntaxException, IOException {
+ public void mockCreateUser(User user, boolean userExistsInCi, boolean shouldFail, boolean shouldFailToGetUser) throws IOException {
mockGetUser(user.getLogin(), userExistsInCi, shouldFailToGetUser);
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("securityRealm", "createAccountByAdmin").build().toUri();
+ URI uri = JenkinsEndpoints.CREATE_USER.buildEndpoint(jenkinsServerUri).build(true).toUri();
var status = shouldFail ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.FOUND;
mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withStatus(status));
- mockAddUsersToGroups(user.getLogin(), user.getGroups(), false);
+ mockAddUsersToGroups(user.getGroups(), false);
}
- public void mockAddUsersToGroups(String login, Set groups, boolean shouldfail) throws IOException {
+ public void mockAddUsersToGroups(Set groups, boolean shouldFail) throws IOException {
var exercises = programmingExerciseRepository.findAllByInstructorOrEditorOrTAGroupNameIn(groups);
for (ProgrammingExercise exercise : exercises) {
- var jobName = exercise.getProjectKey();
+ var folderName = exercise.getProjectKey();
var course = exercise.getCourseViaExerciseGroupOrCourseMember();
if (groups.contains(course.getInstructorGroupName()) || groups.contains(course.getEditorGroupName()) || groups.contains(course.getTeachingAssistantGroupName())) {
- mockGetFolderConfig(jobName);
- if (shouldfail) {
- doThrow(IOException.class).when(jenkinsServer).updateJob(eq(jobName), anyString(), eq(useCrumb));
+ mockGetFolderConfig(folderName);
+ URI uri = JenkinsEndpoints.FOLDER_CONFIG.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
+
+ if (shouldFail) {
+ // updateJob
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withBadRequest().contentType(MediaType.APPLICATION_XML));
}
else {
- doReturn(null).when(jenkinsServer).updateJob(eq(jobName), anyString(), eq(useCrumb));
+ // updateJob
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess().contentType(MediaType.APPLICATION_XML));
}
}
}
@@ -450,66 +530,83 @@ private void mockAssignPermissionsToInstructorAndEditorAndTAsForCourse(Course co
private void mockAddInstructorAndEditorAndTAPermissionsToUsersForFolder(String folderName, boolean shouldFailToAdd) throws IOException {
mockGetFolderConfig(folderName);
+ URI uri = JenkinsEndpoints.FOLDER_CONFIG.buildEndpoint(jenkinsServerUri, folderName).build(true).toUri();
if (shouldFailToAdd) {
- doThrow(IOException.class).when(jenkinsServer).updateJob(eq(folderName), anyString(), eq(useCrumb));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withBadRequest());
}
else {
- doReturn(null).when(jenkinsServer).updateJob(eq(folderName), anyString(), eq(useCrumb));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
}
public void mockDeleteBuildPlan(String projectKey, String planName, boolean shouldFail) throws IOException {
- mockGetFolderJob(projectKey, new FolderJob());
+ mockGetFolderJob(projectKey);
+ URI uri = JenkinsEndpoints.DELETE_JOB.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
if (shouldFail) {
- doThrow(new HttpResponseException(400, "Bad Request")).when(jenkinsServer).deleteJob(any(FolderJob.class), eq(planName), eq(useCrumb));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withBadRequest());
}
else {
- doReturn(null).when(jenkinsServer).deleteJob(any(FolderJob.class), eq(planName), eq(useCrumb));
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
}
+ /**
+ * Should only be used when explicitly only the single POST request is needed. Use {@link #mockDeleteBuildPlan(String, String, boolean)} otherwise.
+ *
+ * @param projectKey The name of the folder.
+ * @param planName The name of the build plan itself.
+ */
+ public void mockDeleteBuildPlanPlain(String projectKey, String planName) {
+ URI uri = JenkinsEndpoints.DELETE_JOB.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
+ }
+
public void mockDeleteBuildPlanNotFound(String projectKey, String planName) throws IOException {
- mockGetFolderJob(projectKey, new FolderJob());
- doThrow(new HttpResponseException(404, "Not found")).when(jenkinsServer).deleteJob(any(FolderJob.class), eq(planName), eq(useCrumb));
+ mockGetFolderJob(projectKey);
+ URI uri = JenkinsEndpoints.DELETE_JOB.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withResourceNotFound());
}
public void mockDeleteBuildPlanFailWithException(String projectKey, String planName) throws IOException {
- mockGetFolderJob(projectKey, new FolderJob());
- doThrow(new IOException("IOException")).when(jenkinsServer).deleteJob(any(FolderJob.class), eq(planName), eq(useCrumb));
+ mockGetFolderJob(projectKey);
+ URI uri = JenkinsEndpoints.DELETE_JOB.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withBadRequest());
}
public void mockDeleteBuildPlanProject(String projectKey, boolean shouldFail) throws IOException {
+ URI uri = JenkinsEndpoints.DELETE_FOLDER.buildEndpoint(jenkinsServerUri, projectKey).build(true).toUri();
if (shouldFail) {
- doThrow(new HttpResponseException(400, "Bad Request")).when(jenkinsServer).deleteJob(projectKey, useCrumb);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withBadRequest());
}
else {
- doReturn(null).when(jenkinsServer).deleteJob(projectKey, useCrumb);
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
}
public void mockGetBuildStatus(String projectKey, String planName, boolean planExistsInCi, boolean planIsActive, boolean planIsBuilding, boolean failToGetLastBuild)
- throws IOException, URISyntaxException {
+ throws IOException {
if (!planExistsInCi) {
mockGetJob(projectKey, planName, null, false);
return;
}
- var jobWithDetails = mock(JobWithDetails.class);
+ boolean isQueued = planIsActive && !planIsBuilding;
+ var jobWithDetails = new JenkinsJobService.JobWithDetails(planName, "", isQueued);
mockGetJob(projectKey, planName, jobWithDetails, false);
- if (planIsActive && !planIsBuilding) {
- doReturn(true).when(jobWithDetails).isInQueue();
+ if (isQueued) {
return;
}
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("job", projectKey, "job", planName, "lastBuild", "api", "json").build().toUri();
+ URI uri = JenkinsEndpoints.LAST_BUILD.buildEndpoint(jenkinsServerUri, projectKey, planName).build(true).toUri();
final var body = new ObjectMapper().writeValueAsString(Map.of("building", planIsBuilding && planIsActive));
final var status = failToGetLastBuild ? HttpStatus.NOT_FOUND : HttpStatus.OK;
mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withStatus(status).body(body).contentType(MediaType.APPLICATION_JSON));
}
- public void mockHealth(boolean isRunning, HttpStatus httpStatus) throws URISyntaxException {
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("login").build().toUri();
+ public void mockHealth(boolean isRunning, HttpStatus httpStatus) {
+
+ URI uri = JenkinsEndpoints.HEALTH.buildEndpoint(jenkinsServerUri).build(true).toUri();
if (isRunning) {
shortTimeoutMockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET)).andRespond(withStatus(httpStatus).body("lol"));
}
@@ -519,16 +616,32 @@ public void mockHealth(boolean isRunning, HttpStatus httpStatus) throws URISynta
}
public void mockCheckIfBuildPlanExists(String projectKey, String buildPlanId, boolean buildPlanExists, boolean shouldFail) throws IOException {
- var toReturn = buildPlanExists ? new JobWithDetails() : null;
+ var toReturn = buildPlanExists ? new JenkinsJobService.JobWithDetails(buildPlanId, "description", false) : null;
mockGetJob(projectKey, buildPlanId, toReturn, shouldFail);
}
public void mockTriggerBuild(String projectKey, String buildPlanId, boolean triggerBuildFails) throws IOException {
- var jobWithDetails = mock(JobWithDetails.class);
- mockGetJob(projectKey, buildPlanId, jobWithDetails, triggerBuildFails);
+ mockGetJob(projectKey, buildPlanId, new JenkinsJobService.JobWithDetails(buildPlanId, "description", false), triggerBuildFails);
+ URI uri = JenkinsEndpoints.TRIGGER_BUILD.buildEndpoint(jenkinsServerUri, projectKey, buildPlanId).build(true).toUri();
+
if (!triggerBuildFails) {
- doReturn(new QueueReference("")).when(jobWithDetails).build();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
+ else {
+ // simulate a client exception, because this is caught in the actual production code
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withStatus(HttpStatus.BAD_REQUEST));
+ }
+ }
+
+ /**
+ * Should only be used when explicitly only the single request is needed. Use {@link #mockTriggerBuild(String, String, boolean)} otherwise.
+ *
+ * @param projectKey The name of the folder.
+ * @param buildPlanId The name of the build plan itself.
+ */
+ public void mockTriggerBuildPlain(String projectKey, String buildPlanId) {
+ URI uri = JenkinsEndpoints.TRIGGER_BUILD.buildEndpoint(jenkinsServerUri, projectKey, buildPlanId).build(true).toUri();
+ mockServer.expect(requestTo(uri)).andExpect(method(HttpMethod.POST)).andRespond(withSuccess());
}
public void mockGivePlanPermissionsThrowException(String projectKey, String projectKey1) throws IOException {
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/service/TelemetryServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/core/service/TelemetryServiceTest.java
index 1938c719ffa1..1ffc1ca37796 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/service/TelemetryServiceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/service/TelemetryServiceTest.java
@@ -6,7 +6,7 @@
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
-import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus;
+import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.testcontainers.shaded.org.awaitility.Awaitility.await;
import java.net.URI;
@@ -18,7 +18,6 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
@@ -62,7 +61,7 @@ void testSendTelemetry_TelemetryEnabled() throws Exception {
telemetryServiceSpy = spy(telemetryService);
mockServer.expect(ExpectedCount.once(), requestTo(new URI(destination + "/api/telemetry"))).andExpect(method(HttpMethod.POST))
.andExpect(request -> assertThat(request.getBody().toString()).contains("adminName"))
- .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!")));
+ .andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!")));
telemetryServiceSpy.sendTelemetry();
await().atMost(2, SECONDS).untilAsserted(() -> mockServer.verify());
@@ -74,7 +73,7 @@ void testSendTelemetry_TelemetryEnabledWithoutPersonalData() throws Exception {
telemetryServiceSpy = spy(telemetryService);
mockServer.expect(ExpectedCount.once(), requestTo(new URI(destination + "/api/telemetry"))).andExpect(method(HttpMethod.POST))
.andExpect(request -> assertThat(request.getBody().toString()).doesNotContain("adminName"))
- .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!")));
+ .andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!")));
telemetryServiceSpy.sendTelemetry();
await().atMost(2, SECONDS).untilAsserted(() -> mockServer.verify());
@@ -86,7 +85,7 @@ void testSendTelemetry_TelemetryDisabled() throws Exception {
telemetryServiceSpy = spy(telemetryService);
mockServer.expect(ExpectedCount.never(), requestTo(new URI(destination + "/api/telemetry"))).andExpect(method(HttpMethod.POST))
- .andRespond(withStatus(HttpStatus.OK).contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!")));
+ .andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body(mapper.writeValueAsString("Success!")));
telemetryServiceSpy.sendTelemetry();
await().atMost(2, SECONDS).untilAsserted(() -> mockServer.verify());
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/user/AccountResourceWithGitLabIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/core/user/AccountResourceWithGitLabIntegrationTest.java
index 6c2f0472be45..3449efff3b0d 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/user/AccountResourceWithGitLabIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/user/AccountResourceWithGitLabIntegrationTest.java
@@ -33,7 +33,7 @@ class AccountResourceWithGitLabIntegrationTest extends AbstractSpringIntegration
@BeforeEach
void setUp() {
gitlabRequestMockProvider.enableMockingOfRequests();
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
}
@AfterEach
diff --git a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java
index 3f4606f09e63..5ceacf867cab 100644
--- a/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java
+++ b/src/test/java/de/tum/cit/aet/artemis/core/user/util/UserTestService.java
@@ -222,8 +222,11 @@ public void deleteUsers(String currentUserLogin) throws Exception {
userTestRepository.deleteAll(userTestRepository.searchAllByLoginOrName(Pageable.unpaged(), TEST_PREFIX));
userUtilService.addUsers(TEST_PREFIX, 1, 1, 1, 1);
- var users = Set.of(userUtilService.getUserByLogin(TEST_PREFIX + "student1"), userUtilService.getUserByLogin(TEST_PREFIX + "tutor1"),
- userUtilService.getUserByLogin(TEST_PREFIX + "editor1"), userUtilService.getUserByLogin(TEST_PREFIX + "instructor1"));
+ var users = Stream.of("student1", "tutor1", "editor1", "instructor1").map(login -> {
+ final User user = userUtilService.getUserByLogin(TEST_PREFIX + login);
+ user.getGroups().clear();
+ return userTestRepository.save(user);
+ }).collect(Collectors.toSet());
var logins = users.stream().map(User::getLogin).toList();
request.delete("/api/admin/users", HttpStatus.OK, logins);
diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java
index 57368e33484b..a9e69ac1481f 100644
--- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamIntegrationTest.java
@@ -991,7 +991,7 @@ void testDeleteExamWithOneTestRuns() throws Exception {
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void testDeleteExamWithMultipleTestRuns() throws Exception {
gitlabRequestMockProvider.enableMockingOfRequests();
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
var exam = examUtilService.addExam(course1);
exam = examUtilService.addTextModelingProgrammingExercisesToExam(exam, true, true);
diff --git a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java
index c0bf3576b18b..d56b23a03dba 100644
--- a/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/exam/ExamParticipationIntegrationTest.java
@@ -756,7 +756,7 @@ private void lockAndAssessForSecondCorrection(Exam exam, Course course, List programmingExerciseService.checkIfProjectExists(programmingExercise));
-
- jenkinsRequestMockProvider.mockCheckIfProjectExistsJobUrlEmptyOrNull(programmingExercise, true);
- assertThatNoException().isThrownBy(() -> programmingExerciseService.checkIfProjectExists(programmingExercise));
-
- jenkinsRequestMockProvider.mockCheckIfProjectExistsJobUrlEmptyOrNull(programmingExercise, false);
- assertThatNoException().isThrownBy(() -> programmingExerciseService.checkIfProjectExists(programmingExercise));
}
@Test
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseIntegrationTestService.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseIntegrationTestService.java
index 415101303e07..f628d543c17f 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseIntegrationTestService.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseIntegrationTestService.java
@@ -2088,6 +2088,8 @@ void testResetOnlyRecreateBuildPlansSuccess() throws Exception {
mockDelegate.mockGetBuildPlan(programmingExercise.getProjectKey(), solutionBuildPlanName, true, true, false, false);
mockDelegate.mockDeleteBuildPlan(programmingExercise.getProjectKey(), templateBuildPlanName, false);
mockDelegate.mockDeleteBuildPlan(programmingExercise.getProjectKey(), solutionBuildPlanName, false);
+ mockDelegate.mockGetBuildPlanConfig(programmingExercise.getProjectKey(), templateBuildPlanName);
+ mockDelegate.mockGetBuildPlanConfig(programmingExercise.getProjectKey(), solutionBuildPlanName);
mockDelegate.mockConnectorRequestsForSetup(programmingExercise, false, false, false);
var resetOptions = new ProgrammingExerciseResetOptionsDTO(false, false, false, true);
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTemplateIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTemplateIntegrationTest.java
index 1244f782f744..b4b2e2709e20 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTemplateIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTemplateIntegrationTest.java
@@ -190,7 +190,7 @@ void setup() throws Exception {
doReturn(COMMIT_HASH_OBJECT_ID).when(gitService).getLastCommitHash(any());
Course course = courseUtilService.addEmptyCourse();
exercise = ProgrammingExerciseFactory.generateProgrammingExercise(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().plusDays(7), course);
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
exerciseRepo.configureRepos("exerciseLocalRepo", "exerciseOriginRepo");
@@ -204,7 +204,7 @@ void setup() throws Exception {
@AfterEach
void tearDown() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
programmingExerciseTestService.tearDown();
exerciseRepo.resetLocalRepo();
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTest.java
index 0b6748e8ed71..36df5a8e7680 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingExerciseTest.java
@@ -50,7 +50,7 @@ void init() {
}
void updateProgrammingExercise(ProgrammingExercise programmingExercise, String newProblem, String newTitle) throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
programmingExercise.setProblemStatement(newProblem);
programmingExercise.setTitle(newTitle);
@@ -171,7 +171,7 @@ void updateExerciseTestCasesZeroWeight(AssessmentType assessmentType) throws Exc
programmingExercise.setAssessmentType(assessmentType);
if (assessmentType == AssessmentType.AUTOMATIC) {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
jenkinsRequestMockProvider.mockCheckIfBuildPlanExists(programmingExercise.getProjectKey(), programmingExercise.getTemplateBuildPlanId(), true, false);
jenkinsRequestMockProvider.mockCheckIfBuildPlanExists(programmingExercise.getProjectKey(), programmingExercise.getSolutionBuildPlanId(), true, false);
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java
index 75fe4787b17b..44680b943f51 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest.java
@@ -46,7 +46,7 @@ class ProgrammingSubmissionAndResultGitlabJenkinsIntegrationTest extends Abstrac
@BeforeEach
void setUp() {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
userUtilService.addUsers(TEST_PREFIX, 3, 2, 0, 2);
@@ -257,8 +257,11 @@ void shouldSetSubmissionDateForBuildCorrectlyIfOnlyOnePushIsReceived() throws Ex
var firstCommitDate = ZonedDateTime.now().minusSeconds(60);
var secondCommitDate = ZonedDateTime.now().minusSeconds(30);
+ String projectKey = testService.programmingExercise.getProjectKey();
+
gitlabRequestMockProvider.mockGetDefaultBranch(defaultBranch);
gitlabRequestMockProvider.mockGetPushDate(testService.participation, Map.of(firstCommitHash, firstCommitDate, secondCommitHash, secondCommitDate));
+ jenkinsRequestMockProvider.mockTriggerBuild(projectKey, (projectKey + "-" + userLogin).toUpperCase(), false);
// First commit is pushed but not recorded
@@ -268,16 +271,16 @@ void shouldSetSubmissionDateForBuildCorrectlyIfOnlyOnePushIsReceived() throws Ex
// Build result for first commit is received
var firstBuildCompleteDate = ZonedDateTime.now();
var firstVcsDTO = new CommitDTO(firstCommitHash, uriService.getRepositorySlugFromRepositoryUri(testService.participation.getVcsRepositoryUri()), defaultBranch);
- var notificationDTOFirstCommit = createJenkinsNewResultNotification(testService.programmingExercise.getProjectKey(), userLogin, JAVA, List.of(), new ArrayList<>(),
- firstBuildCompleteDate, List.of(firstVcsDTO));
+ var notificationDTOFirstCommit = createJenkinsNewResultNotification(projectKey, userLogin, JAVA, List.of(), new ArrayList<>(), firstBuildCompleteDate,
+ List.of(firstVcsDTO));
postResult(notificationDTOFirstCommit, HttpStatus.OK);
// Build result for second commit is received
var secondBuildCompleteDate = ZonedDateTime.now();
var secondVcsDTO = new CommitDTO(secondCommitHash, uriService.getRepositorySlugFromRepositoryUri(testService.participation.getVcsRepositoryUri()), defaultBranch);
- var notificationDTOSecondCommit = createJenkinsNewResultNotification(testService.programmingExercise.getProjectKey(), userLogin, JAVA, List.of(), new ArrayList<>(),
- secondBuildCompleteDate, List.of(secondVcsDTO));
+ var notificationDTOSecondCommit = createJenkinsNewResultNotification(projectKey, userLogin, JAVA, List.of(), new ArrayList<>(), secondBuildCompleteDate,
+ List.of(secondVcsDTO));
postResult(notificationDTOSecondCommit, HttpStatus.OK);
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java
index 2d7d8e17a2e9..6deb3d409690 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/ProgrammingSubmissionIntegrationTest.java
@@ -95,7 +95,7 @@ void tearDown() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void triggerBuildStudent() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
doReturn(COMMIT_HASH_OBJECT_ID).when(gitService).getLastCommitHash(any());
String login = TEST_PREFIX + "student1";
@@ -135,7 +135,7 @@ void triggerBuildStudentSubmissionNotFound() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void triggerBuildInstructor() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
doReturn(COMMIT_HASH_OBJECT_ID).when(gitService).getLastCommitHash(any());
String login = TEST_PREFIX + "student1";
StudentParticipation participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login);
@@ -166,7 +166,7 @@ void triggerBuildInstructor() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void triggerBuildInstructor_cannotGetLastCommitHash() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
doThrow(EntityNotFoundException.class).when(gitService).getLastCommitHash(any());
String login = TEST_PREFIX + "student1";
StudentParticipation participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, login);
@@ -210,7 +210,7 @@ void triggerBuildStudentForbidden() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void triggerBuildForExerciseAsInstructor() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
doReturn(COMMIT_HASH_OBJECT_ID).when(gitService).getLastCommitHash(any());
String login1 = TEST_PREFIX + "student1";
@@ -282,7 +282,7 @@ void triggerBuildForExerciseStudentForbidden() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
void triggerBuildForParticipationsInstructorEmpty() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
String login1 = TEST_PREFIX + "student1";
String login2 = TEST_PREFIX + "student2";
String login3 = TEST_PREFIX + "student3";
@@ -360,7 +360,7 @@ void triggerFailedBuildResultPresentInCIOk() throws Exception {
var optionalParticipation = participationRepository.findById(submission.getParticipation().getId());
assertThat(optionalParticipation).isPresent();
final var participation = optionalParticipation.get();
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
final var programmingExerciseParticipation = ((ProgrammingExerciseParticipation) participation);
mockGetBuildPlan(programmingExerciseParticipation.getProgrammingExercise().getProjectKey(), participation.getBuildPlanId(), true, true, false, false);
// Mock again because we call the trigger request two times
@@ -393,7 +393,7 @@ void triggerFailedBuildSubmissionNotLatestButLastGradedNotFound() throws Excepti
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void triggerFailedBuild_CIException() throws Exception {
var participation = createExerciseWithSubmissionAndParticipation();
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
var repoUri = uriService.getRepositorySlugFromRepositoryUri(participation.getVcsRepositoryUri());
doReturn(participation.getVcsRepositoryUri()).when(versionControlService).getCloneRepositoryUri(exercise.getProjectKey(), repoUri);
@@ -430,7 +430,7 @@ void triggerFailedBuildForbiddenParticipationAccess() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void triggerFailedBuildEmptyLatestPendingSubmission() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
doReturn(COMMIT_HASH_OBJECT_ID).when(gitService).getLastCommitHash(any());
String login = TEST_PREFIX + "student1";
@@ -518,6 +518,12 @@ void testNotifyPush_isSetupCommit() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void testNotifyPush_studentCommitUpdatesSubmissionCount() throws Exception {
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
+
+ String buildPlanName = (exercise.getProjectKey() + "-" + TEST_PREFIX + "student1").toUpperCase();
+ jenkinsRequestMockProvider.mockTriggerBuild(exercise.getProjectKey(), buildPlanName, false);
+ jenkinsRequestMockProvider.mockTriggerBuild(exercise.getProjectKey(), buildPlanName, false);
+
var participation = participationUtilService.addStudentParticipationForProgrammingExercise(exercise, TEST_PREFIX + "student1");
Commit mockCommit = mock(Commit.class);
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryProgrammingExerciseParticipationJenkinsIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryProgrammingExerciseParticipationJenkinsIntegrationTest.java
index 325c4520311b..871573e46ffe 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryProgrammingExerciseParticipationJenkinsIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/RepositoryProgrammingExerciseParticipationJenkinsIntegrationTest.java
@@ -1,9 +1,6 @@
package de.tum.cit.aet.artemis.programming;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.time.ZonedDateTime;
@@ -16,14 +13,12 @@
import org.springframework.http.HttpStatus;
import org.springframework.security.test.context.support.WithMockUser;
-import com.offbytwo.jenkins.model.Build;
-import com.offbytwo.jenkins.model.JobWithDetails;
-
import de.tum.cit.aet.artemis.core.util.TestConstants;
import de.tum.cit.aet.artemis.exercise.domain.SubmissionType;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission;
import de.tum.cit.aet.artemis.programming.domain.build.BuildLogEntry;
+import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobService;
class RepositoryProgrammingExerciseParticipationJenkinsIntegrationTest extends AbstractProgrammingIntegrationJenkinsGitlabTest {
@@ -61,11 +56,10 @@ void testGetLatestBuildLogsFails() throws Exception {
programmingExerciseUtilService.addProgrammingSubmission(programmingExercise, submission, TEST_PREFIX + "student1");
- var jobWithDetails = mock(JobWithDetails.class);
- jenkinsRequestMockProvider.mockGetJob(programmingExercise.getProjectKey(), programmingExerciseParticipation.getBuildPlanId(), jobWithDetails, false);
- var lastBuild = mock(Build.class);
- doReturn(lastBuild).when(jobWithDetails).getLastBuild();
- doThrow(IOException.class).when(lastBuild).details();
+ var buildPlanId = programmingExerciseParticipation.getBuildPlanId();
+ var jobWithDetails = new JenkinsJobService.JobWithDetails(buildPlanId, "description", false);
+ jenkinsRequestMockProvider.mockGetJob(programmingExercise.getProjectKey(), buildPlanId, jobWithDetails, false);
+ jenkinsRequestMockProvider.mockGetBuildStatus(programmingExercise.getProjectKey(), buildPlanId, true, true, false, true);
var url = "/api/repository/" + programmingExerciseParticipation.getId() + "/buildlogs";
var buildLogs = request.getList(url, HttpStatus.OK, BuildLogEntry.class);
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusBuildScriptGenerationServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusBuildScriptGenerationServiceTest.java
index 73cc266c6a4b..228178d704f9 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusBuildScriptGenerationServiceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusBuildScriptGenerationServiceTest.java
@@ -26,23 +26,19 @@
import de.tum.cit.aet.artemis.programming.dto.aeolus.WindfileMetadata;
import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusBuildPlanService;
import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusBuildScriptGenerationService;
-import de.tum.cit.aet.artemis.programming.service.aeolus.AeolusTemplateService;
import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest;
class AeolusBuildScriptGenerationServiceTest extends AbstractSpringIntegrationLocalCILocalVCTest {
@Autowired
@Qualifier("aeolusRestTemplate")
- RestTemplate restTemplate;
+ private RestTemplate restTemplate;
@Autowired
- AeolusBuildPlanService aeolusBuildPlanService;
+ private AeolusBuildPlanService aeolusBuildPlanService;
@Autowired
- AeolusTemplateService aeolusTemplateService;
-
- @Autowired
- AeolusBuildScriptGenerationService aeolusBuildScriptGenerationService;
+ private AeolusBuildScriptGenerationService aeolusBuildScriptGenerationService;
@Autowired
private AeolusRequestMockProvider aeolusRequestMockProvider;
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusServiceTest.java
index 32520d1db703..6f9518dc8cc7 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusServiceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusServiceTest.java
@@ -48,19 +48,19 @@ class AeolusServiceTest extends AbstractSpringIntegrationIndependentTest {
@Autowired
@Qualifier("aeolusRestTemplate")
- RestTemplate restTemplate;
+ private RestTemplate restTemplate;
@Value("${aeolus.url}")
private URL aeolusUrl;
@Autowired
- AeolusBuildPlanService aeolusBuildPlanService;
+ private AeolusBuildPlanService aeolusBuildPlanService;
@Autowired
- AeolusTemplateService aeolusTemplateService;
+ private AeolusTemplateService aeolusTemplateService;
@Autowired
- AeolusBuildScriptGenerationService aeolusBuildScriptGenerationService;
+ private AeolusBuildScriptGenerationService aeolusBuildScriptGenerationService;
/**
* Initializes aeolusRequestMockProvider
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusTemplateResourceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusTemplateResourceTest.java
index ef5c18e18012..b21381598752 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusTemplateResourceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/aelous/AeolusTemplateResourceTest.java
@@ -17,7 +17,6 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import de.tum.cit.aet.artemis.core.user.util.UserUtilService;
-import de.tum.cit.aet.artemis.core.util.RequestUtilService;
import de.tum.cit.aet.artemis.programming.dto.aeolus.ScriptAction;
import de.tum.cit.aet.artemis.programming.dto.aeolus.Windfile;
import de.tum.cit.aet.artemis.shared.base.AbstractSpringIntegrationLocalCILocalVCTest;
@@ -26,9 +25,6 @@ class AeolusTemplateResourceTest extends AbstractSpringIntegrationLocalCILocalVC
private static final String TEST_PREFIX = "aeolusintegration";
- @Autowired
- protected RequestUtilService request;
-
@Autowired
private UserUtilService userUtilService;
@@ -37,18 +33,40 @@ void initTestCase() {
userUtilService.addUsers(TEST_PREFIX, 0, 0, 0, 1);
}
+ private record TestProvider(String templateKey, int expectedScriptActions) {
+ }
+
private static Stream templateProvider() {
- return Stream.of(new Object[][] { { "JAVA/PLAIN_GRADLE", 1 }, { "JAVA/PLAIN_GRADLE?sequentialRuns=true", 2 }, { "JAVA/PLAIN_GRADLE?staticAnalysis=true", 2 },
- { "JAVA/PLAIN_MAVEN", 1 }, { "JAVA/PLAIN_MAVEN?sequentialRuns=true", 2 }, { "JAVA/PLAIN_MAVEN?staticAnalysis=true", 2 }, { "JAVA/MAVEN_BLACKBOX", 5 },
- { "JAVA/MAVEN_BLACKBOX?staticAnalysis=true", 6 }, { "ASSEMBLER", 4 }, { "C/FACT", 2 }, { "C/GCC", 3 }, { "C/GCC?staticAnalysis=true", 3 }, { "KOTLIN", 1 },
- { "KOTLIN?sequentialRuns=true", 3 }, { "VHDL", 4 }, { "HASKELL", 1 }, { "HASKELL?sequentialRuns=true", 2 }, { "OCAML", 2 }, { "SWIFT/PLAIN", 1 },
- { "SWIFT/PLAIN?staticAnalysis=true", 2 } }).map(params -> Arguments.of(params[0], params[1]));
+ // @formatter:off
+ return Stream.of(
+ new TestProvider("JAVA/PLAIN_GRADLE", 1),
+ new TestProvider("JAVA/PLAIN_GRADLE?sequentialRuns=true", 2),
+ new TestProvider("JAVA/PLAIN_GRADLE?staticAnalysis=true", 2),
+ new TestProvider("JAVA/PLAIN_MAVEN", 1),
+ new TestProvider("JAVA/PLAIN_MAVEN?sequentialRuns=true", 2),
+ new TestProvider("JAVA/PLAIN_MAVEN?staticAnalysis=true", 2),
+ new TestProvider("JAVA/MAVEN_BLACKBOX", 5),
+ new TestProvider("JAVA/MAVEN_BLACKBOX?staticAnalysis=true", 6),
+ new TestProvider("ASSEMBLER", 4),
+ new TestProvider("C/FACT", 2),
+ new TestProvider("C/GCC", 3),
+ new TestProvider("C/GCC?staticAnalysis=true", 3),
+ new TestProvider("KOTLIN", 1),
+ new TestProvider("KOTLIN?sequentialRuns=true", 3),
+ new TestProvider("VHDL", 4),
+ new TestProvider("HASKELL", 1),
+ new TestProvider("HASKELL?sequentialRuns=true", 2),
+ new TestProvider("OCAML", 2),
+ new TestProvider("SWIFT/PLAIN", 1),
+ new TestProvider("SWIFT/PLAIN?staticAnalysis=true", 2)
+ ).map(provider -> Arguments.of(provider.templateKey(), provider.expectedScriptActions()));
+ // @formatter:on
}
@ParameterizedTest
@MethodSource("templateProvider")
@WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
- void testGetAeolusTemplateFile(String templateKey, Integer expectedScriptActions) throws Exception {
+ void testGetAeolusTemplateFile(String templateKey, int expectedScriptActions) throws Exception {
String template = request.get("/api/aeolus/templates/" + templateKey, HttpStatus.OK, String.class);
assertThat(template).isNotEmpty();
Windfile windfile = Windfile.deserialize(template);
@@ -152,7 +170,7 @@ void testGetNonExistingAeolusTemplateFile() throws Exception {
request.get("/api/aeolus/templates/JAVA/PLAIN_GRADLE?staticAnalysis=true&sequentialRuns=true", HttpStatus.NOT_FOUND, String.class);
}
- void assertWindfileIsCorrect(Windfile windfile, long expectedScriptActions) {
+ void assertWindfileIsCorrect(Windfile windfile, int expectedScriptActions) {
assertThat(windfile.api()).isEqualTo("v0.0.1");
assertThat(windfile.metadata().gitCredentials()).isNull();
assertThat(windfile.metadata().docker()).isNotNull();
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsAuthorizationInterceptorTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsAuthorizationInterceptorTest.java
index 6edf5327e20a..88ba767a272a 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsAuthorizationInterceptorTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsAuthorizationInterceptorTest.java
@@ -39,7 +39,7 @@ class JenkinsAuthorizationInterceptorTest extends AbstractProgrammingIntegration
*/
@BeforeEach
void initTestCase() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
mockRestServiceServer = MockRestServiceServer.bindTo(restTemplate).ignoreExpectOrder(true).bufferContent().build();
}
@@ -116,7 +116,7 @@ private HttpRequest mockHttpRequestWithHeaders() {
}
private void mockGetCrumb(String expectedBody, HttpStatus expectedStatus) throws URISyntaxException {
- final var uri = UriComponentsBuilder.fromUri(jenkinsServerUrl.toURI()).pathSegment("crumbIssuer/api/json").build().toUri();
+ final var uri = UriComponentsBuilder.fromUri(jenkinsServerUri).pathSegment("crumbIssuer/api/json").build().toUri();
var headers = new HttpHeaders();
headers.add("Set-Cookie", "some-session");
mockRestServiceServer.expect(requestTo(uri)).andExpect(method(HttpMethod.GET))
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobPermissionServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobPermissionServiceTest.java
index 66744e1a1e41..47e2952deae7 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobPermissionServiceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobPermissionServiceTest.java
@@ -28,7 +28,7 @@ class JenkinsJobPermissionServiceTest extends AbstractProgrammingIntegrationJenk
@BeforeEach
void initTestCase() {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
mockedJenkinsJobPermissionsUtils = mockStatic(JenkinsJobPermissionsUtils.class);
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobServiceTest.java
index 6688a0bb5371..47d9ddbd2bc8 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobServiceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsJobServiceTest.java
@@ -3,12 +3,8 @@
import static de.tum.cit.aet.artemis.programming.service.jenkins.JenkinsXmlFileUtils.getDocumentBuilderFactory;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
-import static org.mockito.Mockito.verify;
import java.io.IOException;
@@ -24,11 +20,10 @@
import org.springframework.security.test.context.support.WithMockUser;
import org.w3c.dom.Document;
-import com.offbytwo.jenkins.model.FolderJob;
-
import de.tum.cit.aet.artemis.core.exception.JenkinsException;
import de.tum.cit.aet.artemis.programming.AbstractProgrammingIntegrationJenkinsGitlabTest;
import de.tum.cit.aet.artemis.programming.service.jenkins.JenkinsXmlFileUtils;
+import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobService;
class JenkinsJobServiceTest extends AbstractProgrammingIntegrationJenkinsGitlabTest {
@@ -43,7 +38,7 @@ class JenkinsJobServiceTest extends AbstractProgrammingIntegrationJenkinsGitlabT
@BeforeEach
void initTestCase() throws Exception {
userUtilService.addUsers(TEST_PREFIX, 1, 0, 0, 0);
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
// create the document before the mock so that it still works correctly
invalidDocument = createEmptyDOMDocument();
@@ -65,34 +60,36 @@ void tearDown() throws Exception {
@Test
@WithMockUser(username = TEST_PREFIX + "student1")
void testCreateIfJobExists() throws IOException {
- jenkinsRequestMockProvider.mockCreateJobInFolder("JenkinsFolder", "JenkinsJob", true);
+ var job = new JenkinsJobService.JobWithDetails("JenkinsJob", "", false);
+ jenkinsRequestMockProvider.mockGetJob("JenkinsFolder", "JenkinsJob", job, false);
// This call shall not fail, since the job already exists ..
jenkinsJobService.createJobInFolder(validDocument, "JenkinsFolder", "JenkinsJob");
- // Create Job shouldn't be invoked on JenkinsServer because it exists
- verify(jenkinsServer, never()).createJob(any(), any(), any(), any());
+ // Create Job shouldn't be invoked because it exists
+ jenkinsRequestMockProvider.verifyMocks();
}
@Test
@WithMockUser(username = TEST_PREFIX + "student1")
void testCreateIfJobDoesNotExist() throws IOException {
- jenkinsRequestMockProvider.mockCreateJobInFolder("JenkinsFolder", "JenkinsJob", false);
+ jenkinsRequestMockProvider.mockGetJob("JenkinsFolder", "JenkinsJob", null, false);
+ jenkinsRequestMockProvider.mockCreateJob("JenkinsFolder", "JenkinsJob");
// This call shall not fail, since the job will be created ..
jenkinsJobService.createJobInFolder(validDocument, "JenkinsFolder", "JenkinsJob");
- // Create Job should be invoked on JenkinsServer
- verify(jenkinsServer).createJob(any(FolderJob.class), eq("JenkinsJob"), eq("JenkinsConfigStringMock"), any());
+ // Create Job should be invoked
+ jenkinsRequestMockProvider.verifyMocks();
}
@Test
@WithMockUser(username = TEST_PREFIX + "student1")
void testCreateJobInFolderJenkinsExceptionOnXmlError() throws IOException {
- jenkinsRequestMockProvider.mockGetFolderJob("JenkinsFolder", new FolderJob());
+ jenkinsRequestMockProvider.mockCreateJobInFolder("JenkinsFolder", "JenkinsJob", false);
assertThatExceptionOfType(JenkinsException.class).isThrownBy(() -> jenkinsJobService.createJobInFolder(invalidDocument, "JenkinsFolder", "JenkinsJob"));
}
@Test
@WithMockUser(username = TEST_PREFIX + "student1")
void testUpdateJobThrowIOExceptionOnXmlError() {
- assertThatIOException().isThrownBy(() -> jenkinsJobService.updateJob("JenkinsFolder", "JenkinsJob", invalidDocument));
+ assertThatExceptionOfType(JenkinsException.class).isThrownBy(() -> jenkinsJobService.updateJob("JenkinsFolder", "JenkinsJob", invalidDocument));
}
@Test
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsServiceTest.java b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsServiceTest.java
index c59f8a048f45..51888cd54820 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsServiceTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/service/JenkinsServiceTest.java
@@ -7,14 +7,10 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URISyntaxException;
import java.util.Optional;
import org.junit.jupiter.api.AfterEach;
@@ -28,8 +24,6 @@
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.util.StreamUtils;
-import com.offbytwo.jenkins.model.JobWithDetails;
-
import de.tum.cit.aet.artemis.core.exception.JenkinsException;
import de.tum.cit.aet.artemis.programming.AbstractProgrammingIntegrationJenkinsGitlabTest;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
@@ -37,6 +31,7 @@
import de.tum.cit.aet.artemis.programming.domain.ProgrammingLanguage;
import de.tum.cit.aet.artemis.programming.domain.build.BuildPlan;
import de.tum.cit.aet.artemis.programming.service.jenkins.build_plan.JenkinsBuildPlanUtils;
+import de.tum.cit.aet.artemis.programming.service.jenkins.jobs.JenkinsJobService;
class JenkinsServiceTest extends AbstractProgrammingIntegrationJenkinsGitlabTest {
@@ -47,7 +42,7 @@ class JenkinsServiceTest extends AbstractProgrammingIntegrationJenkinsGitlabTest
*/
@BeforeEach
void initTestCase() throws Exception {
- jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsServer, jenkinsJobPermissionsService);
+ jenkinsRequestMockProvider.enableMockingOfRequests(jenkinsJobPermissionsService);
gitlabRequestMockProvider.enableMockingOfRequests();
continuousIntegrationTestService.setup(TEST_PREFIX, this, continuousIntegrationService);
}
@@ -160,36 +155,20 @@ void testCreateBuildPlanForExerciseThrowsExceptionOnTemplateError(ProgrammingLan
@Test
@WithMockUser(roles = "INSTRUCTOR", username = TEST_PREFIX + "instructor1")
- void testImportBuildPlansThrowsExceptionOnGivePermissions() throws Exception {
+ void testDeleteBuildPlan() {
var programmingExercise = continuousIntegrationTestService.programmingExercise;
programmingExerciseUtilService.addTemplateParticipationForProgrammingExercise(programmingExercise);
programmingExerciseUtilService.addSolutionParticipationForProgrammingExercise(programmingExercise);
programmingExerciseUtilService.addTestCasesToProgrammingExercise(programmingExercise);
- jenkinsRequestMockProvider.mockCreateProjectForExercise(programmingExercise, false);
- jenkinsRequestMockProvider.mockCopyBuildPlan(programmingExercise.getProjectKey(), programmingExercise.getProjectKey());
- jenkinsRequestMockProvider.mockCopyBuildPlan(programmingExercise.getProjectKey(), programmingExercise.getProjectKey());
- jenkinsRequestMockProvider.mockGivePlanPermissionsThrowException(programmingExercise.getProjectKey(), programmingExercise.getProjectKey());
-
- assertThatExceptionOfType(JenkinsException.class).isThrownBy(() -> programmingExerciseImportService.importBuildPlans(programmingExercise, programmingExercise))
- .withMessageStartingWith("Cannot give assign permissions to plan");
- }
+ final String projectKey = programmingExercise.getProjectKey();
+ final String solutionJobName = projectKey + "-" + SOLUTION.getName();
- @Test
- @WithMockUser(roles = "INSTRUCTOR", username = TEST_PREFIX + "instructor1")
- void testDeleteBuildPlan() throws Exception {
- var programmingExercise = continuousIntegrationTestService.programmingExercise;
- programmingExerciseUtilService.addTemplateParticipationForProgrammingExercise(programmingExercise);
- programmingExerciseUtilService.addSolutionParticipationForProgrammingExercise(programmingExercise);
- programmingExerciseUtilService.addTestCasesToProgrammingExercise(programmingExercise);
+ jenkinsRequestMockProvider.mockDeleteBuildPlanPlain(projectKey, solutionJobName);
- jenkinsRequestMockProvider.mockCreateProjectForExercise(programmingExercise, false);
- jenkinsRequestMockProvider.mockCopyBuildPlan(programmingExercise.getProjectKey(), programmingExercise.getProjectKey());
- jenkinsRequestMockProvider.mockCopyBuildPlan(programmingExercise.getProjectKey(), programmingExercise.getProjectKey());
- jenkinsRequestMockProvider.mockGivePlanPermissionsThrowException(programmingExercise.getProjectKey(), programmingExercise.getProjectKey());
+ continuousIntegrationService.deleteBuildPlan(projectKey, solutionJobName);
- assertThatExceptionOfType(JenkinsException.class).isThrownBy(() -> programmingExerciseImportService.importBuildPlans(programmingExercise, programmingExercise))
- .withMessageStartingWith("Cannot give assign permissions to plan");
+ jenkinsRequestMockProvider.verifyMocks();
}
@Test
@@ -200,20 +179,38 @@ void testRecreateBuildPlanDeletedFolder() throws Exception {
programmingExerciseUtilService.addSolutionParticipationForProgrammingExercise(programmingExercise);
programmingExerciseUtilService.addTestCasesToProgrammingExercise(programmingExercise);
- final String templateJobName = programmingExercise.getProjectKey() + "-" + TEMPLATE.getName();
- final String solutionJobName = programmingExercise.getProjectKey() + "-" + SOLUTION.getName();
-
- jenkinsRequestMockProvider.mockCreateProjectForExercise(programmingExercise, false);
- jenkinsRequestMockProvider.mockGetJob(programmingExercise.getProjectKey(), templateJobName, null, false);
- jenkinsRequestMockProvider.mockDeleteBuildPlan(programmingExercise.getProjectKey(), templateJobName, false);
- jenkinsRequestMockProvider.mockDeleteBuildPlan(programmingExercise.getProjectKey(), solutionJobName, false);
- jenkinsRequestMockProvider.mockCreateBuildPlan(programmingExercise.getProjectKey(), TEMPLATE.getName(), false);
- jenkinsRequestMockProvider.mockCreateBuildPlan(programmingExercise.getProjectKey(), SOLUTION.getName(), false);
- when(jenkinsServer.getJob(programmingExercise.getProjectKey())).thenReturn(null).thenReturn(new JobWithDetails());
+ final String projectKey = programmingExercise.getProjectKey();
+ final String templateJobName = projectKey + "-" + TEMPLATE.getName();
+ final String solutionJobName = projectKey + "-" + SOLUTION.getName();
+ final JenkinsJobService.JobWithDetails dummyJob = new JenkinsJobService.JobWithDetails("name", "desc", false);
+ final JenkinsJobService.FolderJob dummyFolder = new JenkinsJobService.FolderJob("name", "desc", "");
+
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetFolderConfigPlain(projectKey);
+ jenkinsRequestMockProvider.mockDeleteBuildPlanPlain(projectKey, solutionJobName);
+ jenkinsRequestMockProvider.mockDeleteBuildPlanPlain(projectKey, templateJobName);
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetJobPlain(projectKey, templateJobName, dummyJob);
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetJobConfigPlain(projectKey, templateJobName);
+ jenkinsRequestMockProvider.mockUpdatePlanConfigPlain(projectKey, templateJobName);
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetFolderConfigPlain(projectKey);
+ jenkinsRequestMockProvider.mockUpdateFolderConfigPlain(projectKey);
+ jenkinsRequestMockProvider.mockTriggerBuildPlain(projectKey, templateJobName);
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetJobPlain(projectKey, solutionJobName, dummyJob);
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetJobConfigPlain(projectKey, solutionJobName);
+ jenkinsRequestMockProvider.mockUpdatePlanConfigPlain(projectKey, solutionJobName);
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey, dummyFolder);
+ jenkinsRequestMockProvider.mockGetFolderConfigPlain(projectKey);
+ jenkinsRequestMockProvider.mockUpdateFolderConfigPlain(projectKey);
+ jenkinsRequestMockProvider.mockTriggerBuildPlain(projectKey, solutionJobName);
continuousIntegrationService.recreateBuildPlansForExercise(programmingExercise);
- verify(jenkinsServer).createFolder(eq(null), eq(programmingExercise.getProjectKey()), any());
+ jenkinsRequestMockProvider.verifyMocks();
}
@Test
@@ -228,7 +225,7 @@ void testFailToUpdatePlanRepositoryInternalError() throws Exception {
testFailToUpdatePlanRepositoryRestClientException(HttpStatus.INTERNAL_SERVER_ERROR);
}
- private void testFailToUpdatePlanRepositoryRestClientException(HttpStatus expectedStatus) throws IOException, URISyntaxException {
+ private void testFailToUpdatePlanRepositoryRestClientException(HttpStatus expectedStatus) throws IOException {
var programmingExercise = continuousIntegrationTestService.programmingExercise;
programmingExerciseUtilService.addTemplateParticipationForProgrammingExercise(programmingExercise);
programmingExerciseUtilService.addSolutionParticipationForProgrammingExercise(programmingExercise);
@@ -294,7 +291,7 @@ void testCopyBuildPlan() throws IOException {
targetExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(buildConfigTarget));
targetExercise = programmingExerciseRepository.save(targetExercise);
- jenkinsRequestMockProvider.mockCopyBuildPlan(sourceExercise.getProjectKey(), targetExercise.getProjectKey());
+ jenkinsRequestMockProvider.mockCopyBuildPlanFromTemplate(sourceExercise.getProjectKey(), targetExercise.getProjectKey(), "");
continuousIntegrationService.copyBuildPlan(sourceExercise, "", targetExercise, "", "", true);
BuildPlan sourceBuildPlan = buildPlanRepository.findByProgrammingExercises_IdWithProgrammingExercisesElseThrow(sourceExercise.getId());
@@ -312,6 +309,8 @@ void testCopyLegacyBuildPlan() throws IOException {
ProgrammingExercise sourceExercise = new ProgrammingExercise();
course.addExercises(sourceExercise);
+ sourceExercise.setShortName("source");
+ sourceExercise.generateAndSetProjectKey();
var buildConfig = new ProgrammingExerciseBuildConfig();
sourceExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(buildConfig));
sourceExercise = programmingExerciseRepository.save(sourceExercise);
@@ -321,13 +320,15 @@ void testCopyLegacyBuildPlan() throws IOException {
ProgrammingExercise targetExercise = new ProgrammingExercise();
course.addExercises(targetExercise);
+ targetExercise.setShortName("target");
targetExercise.generateAndSetProjectKey();
var buildConfigTarget = new ProgrammingExerciseBuildConfig();
targetExercise.setBuildConfig(programmingExerciseBuildConfigRepository.save(buildConfigTarget));
targetExercise = programmingExerciseRepository.save(targetExercise);
- jenkinsRequestMockProvider.mockCopyBuildPlan(sourceExercise.getProjectKey(), targetExercise.getProjectKey());
+ String targetPlanName = targetExercise.getProjectKey() + "-" + TEMPLATE.getName();
+ jenkinsRequestMockProvider.mockCopyBuildPlanFromTemplate(sourceExercise.getProjectKey(), targetExercise.getProjectKey(), targetPlanName);
- continuousIntegrationService.copyBuildPlan(sourceExercise, "", targetExercise, "", "", true);
+ continuousIntegrationService.copyBuildPlan(sourceExercise, TEMPLATE.getName(), targetExercise, targetExercise.getProjectName(), TEMPLATE.getName(), true);
Optional targetBuildPlan = buildPlanRepository.findByProgrammingExercises_IdWithProgrammingExercises(targetExercise.getId());
assertThat(targetBuildPlan).isEmpty();
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/MockDelegate.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/MockDelegate.java
index 89b745f66689..715dd229854c 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/util/MockDelegate.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/MockDelegate.java
@@ -89,8 +89,6 @@ void mockFailUpdateCoursePermissionsInCi(Course updatedCourse, String oldInstruc
void mockFailToCreateUserInExternalUserManagement(User user, boolean failInVcs, boolean failInCi, boolean failToGetCiUser) throws Exception;
- void mockDeleteUserInUserManagement(User user, boolean userExistsInUserManagement, boolean failInVcs, boolean failInCi) throws Exception;
-
void mockCreateGroupInUserManagement(String groupName) throws Exception;
void mockDeleteGroupInUserManagement(String groupName) throws Exception;
@@ -109,6 +107,8 @@ void mockFailUpdateCoursePermissionsInCi(Course updatedCourse, String oldInstruc
void mockGetBuildPlan(String projectKey, String planName, boolean planExistsInCi, boolean planIsActive, boolean planIsBuilding, boolean failToGetBuild) throws Exception;
+ void mockGetBuildPlanConfig(String projectKey, String planName) throws Exception;
+
void mockHealthInCiService(boolean isRunning, HttpStatus httpStatus) throws Exception;
void mockConfigureBuildPlan(ProgrammingExerciseParticipation participation, String defaultBranch) throws Exception;
@@ -132,4 +132,6 @@ void mockFailUpdateCoursePermissionsInCi(Course updatedCourse, String oldInstruc
void mockDefaultBranch(ProgrammingExercise programmingExercise) throws IOException, GitLabApiException;
void mockUserExists(String username) throws Exception;
+
+ void mockGetCiProjectMissing(ProgrammingExercise exercise) throws IOException;
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java
index eb0268471ccf..fca1fd711142 100644
--- a/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java
+++ b/src/test/java/de/tum/cit/aet/artemis/programming/util/ProgrammingExerciseTestService.java
@@ -157,7 +157,7 @@
* Note: this class should be independent of the actual VCS and CIS and contains common test logic for scenarios:
* 1) Jenkins + Gitlab
* The local CI + local VC systems require a different setup as there are no requests to external systems and only minimal mocking is necessary. See
- * {@link ProgrammingExerciseLocalVCLocalCIIntegrationTest}.
+ * {@link de.tum.cit.aet.artemis.programming.icl.ProgrammingExerciseLocalVCLocalCIIntegrationTest}.
*/
@Service
public class ProgrammingExerciseTestService {
@@ -699,7 +699,7 @@ public void createProgrammingExercise_validExercise_withStaticCodeAnalysis(Progr
}
mockDelegate.mockConnectorRequestsForSetup(exercise, false, false, false);
exercise.setChannelName("testchannel-pe");
- var generatedExercise = request.postWithResponseBody("/api/programming-exercises/setup", exercise, ProgrammingExercise.class);
+ var generatedExercise = request.postWithResponseBody("/api/programming-exercises/setup", exercise, ProgrammingExercise.class, HttpStatus.CREATED);
exercise.setId(generatedExercise.getId());
assertThat(exercise).isEqualTo(generatedExercise);
@@ -1191,6 +1191,7 @@ public void importProgrammingExerciseAsPartOfExamImport() throws Exception {
// Mock requests
setupRepositoryMocks(sourceExercise, sourceExerciseRepo, sourceSolutionRepo, sourceTestRepo, sourceAuxRepo);
setupRepositoryMocks(exerciseToBeImported, exerciseRepo, solutionRepo, testRepo, auxRepo);
+ mockDelegate.mockGetCiProjectMissing(exerciseToBeImported);
mockDelegate.mockConnectorRequestsForImport(sourceExercise, exerciseToBeImported, false, false);
doReturn(false).when(versionControlService).checkIfProjectExists(any(), any());
// Import the exam
@@ -1974,8 +1975,8 @@ public List prepareStudentExamsForConduction(String testPrefix, Zon
Set peIds = exam.getExerciseGroups().get(6).getExercises().stream().map(Exercise::getId).collect(Collectors.toSet());
List programmingExercises = programmingExerciseTestRepository.findAllWithTemplateAndSolutionParticipationByIdIn(peIds);
exam.getExerciseGroups().get(6).setExercises(new HashSet<>(programmingExercises));
- for (var exercise : programmingExercises) {
+ for (var exercise : programmingExercises) {
setupRepositoryMocks(exercise);
for (var examUser : exam.getExamUsers()) {
var repo = new LocalRepository(defaultBranch);
@@ -1992,7 +1993,8 @@ public List prepareStudentExamsForConduction(String testPrefix, Zon
}
int noGeneratedParticipations = ExamPrepareExercisesTestUtil.prepareExerciseStart(request, exam, course);
- assertThat(noGeneratedParticipations).isEqualTo(registeredStudents.size() * exam.getExerciseGroups().size());
+ assertThat(noGeneratedParticipations).as("for each of the %d students there should be %d exercises", registeredStudents.size(), exam.getExerciseGroups().size())
+ .isEqualTo(registeredStudents.size() * exam.getExerciseGroups().size());
mockDelegate.resetMockProvider();
@@ -2289,17 +2291,18 @@ public void automaticCleanupBuildPlans() throws Exception {
var participation7a = createProgrammingParticipationWithSubmissionAndResult(exercise3, testPrefix + "student10", 80D, ZonedDateTime.now().minusDays(4), true);
var participation7b = createProgrammingParticipationWithSubmissionAndResult(exercise2, testPrefix + "student11", 80D, ZonedDateTime.now().minusDays(4), true);
- var participation8b = createProgrammingParticipationWithSubmissionAndResult(exercise4, testPrefix + "student12", 100D, ZonedDateTime.now().minusDays(6), true);
+ var participation8a = createProgrammingParticipationWithSubmissionAndResult(exercise4, testPrefix + "student12", 100D, ZonedDateTime.now().minusDays(6), true);
programmingExerciseStudentParticipationRepository.saveAll(Set.of(participation3a, participation3b, participation5b, participation6b));
await().untilAsserted(
() -> assertThat(programmingExerciseStudentParticipationRepository.findAllWithBuildPlanIdWithResults()).containsExactlyInAnyOrderElementsOf(List.of(participation1a,
- participation1b, participation2a, participation2b, participation3a, participation3b, participation4b, participation7a, participation7b, participation8b)));
+ participation1b, participation2a, participation2b, participation3a, participation3b, participation4b, participation7a, participation7b, participation8a)));
mockDelegate.mockDeleteBuildPlan(exercise.getProjectKey(), exercise.getProjectKey() + "-" + participation1a.getParticipantIdentifier().toUpperCase(), false);
mockDelegate.mockDeleteBuildPlan(exercise.getProjectKey(), exercise.getProjectKey() + "-" + participation2a.getParticipantIdentifier().toUpperCase(), false);
mockDelegate.mockDeleteBuildPlan(exercise.getProjectKey(), exercise.getProjectKey() + "-" + participation3a.getParticipantIdentifier().toUpperCase(), false);
mockDelegate.mockDeleteBuildPlan(exercise3.getProjectKey(), exercise3.getProjectKey() + "-" + participation7a.getParticipantIdentifier().toUpperCase(), false);
+ mockDelegate.mockDeleteBuildPlan(exercise4.getProjectKey(), exercise4.getProjectKey() + "-" + participation8a.getParticipantIdentifier().toUpperCase(), false);
automaticProgrammingExerciseCleanupService.cleanup(); // this call won't do it, because of the missing profile, we execute it anyway to cover at least some code
automaticProgrammingExerciseCleanupService.cleanupBuildPlansOnContinuousIntegrationServer();
@@ -2320,7 +2323,7 @@ public void automaticCleanupBuildPlans() throws Exception {
assertThat(programmingExerciseStudentParticipationRepository.findByIdElseThrow(participation7a.getId()).getBuildPlanId()).isNull();
assertThat(programmingExerciseStudentParticipationRepository.findByIdElseThrow(participation7b.getId()).getBuildPlanId()).isNotNull();
- assertThat(programmingExerciseStudentParticipationRepository.findByIdElseThrow(participation8b.getId()).getBuildPlanId()).isNotNull();
+ assertThat(programmingExerciseStudentParticipationRepository.findByIdElseThrow(participation8a.getId()).getBuildPlanId()).isNull();
}
private ProgrammingExerciseStudentParticipation createProgrammingParticipationWithSubmissionAndResult(ProgrammingExercise exercise, String studentLogin, double score,
diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/architecture/module/AbstractModuleRepositoryArchitectureTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/architecture/module/AbstractModuleRepositoryArchitectureTest.java
index fdc272e7877d..dc3cf5f1fa00 100644
--- a/src/test/java/de/tum/cit/aet/artemis/shared/architecture/module/AbstractModuleRepositoryArchitectureTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/shared/architecture/module/AbstractModuleRepositoryArchitectureTest.java
@@ -165,8 +165,7 @@ public void check(JavaMethod javaMethod, ConditionEvents events) {
@Test
void testOnlySpringTransactionalAnnotation() {
- ArchRule onlySpringTransactionalAnnotation = noMethodsOfThisModule().should().beAnnotatedWith(javax.transaction.Transactional.class).orShould()
- .beAnnotatedWith(jakarta.transaction.Transactional.class)
+ ArchRule onlySpringTransactionalAnnotation = noMethodsOfThisModule().should().beAnnotatedWith(jakarta.transaction.Transactional.class)
.because("Only Spring's Transactional annotation should be used as the usage of the other two is not reliable.");
onlySpringTransactionalAnnotation.check(allClasses);
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java
index caec288393af..0d37ac041a11 100644
--- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationGitlabCIGitlabSamlTest.java
@@ -12,6 +12,7 @@
import static org.mockito.Mockito.doThrow;
import static tech.jhipster.config.JHipsterConstants.SPRING_PROFILE_TEST;
+import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Set;
@@ -253,11 +254,6 @@ public void mockFailToCreateUserInExternalUserManagement(User user, boolean fail
gitlabRequestMockProvider.mockCreateVcsUser(user, failInVcs);
}
- @Override
- public void mockDeleteUserInUserManagement(User user, boolean userExistsInUserManagement, boolean failInVcs, boolean failInCi) throws GitLabApiException {
- gitlabRequestMockProvider.mockDeleteVcsUser(user.getLogin(), userExistsInUserManagement, failInVcs);
- }
-
@Override
public void mockUpdateCoursePermissions(Course updatedCourse, String oldInstructorGroup, String oldEditorGroup, String oldTeachingAssistantGroup) throws GitLabApiException {
gitlabRequestMockProvider.mockUpdateCoursePermissions(updatedCourse, oldInstructorGroup, oldEditorGroup, oldTeachingAssistantGroup);
@@ -314,6 +310,11 @@ public void mockGetBuildPlan(String projectKey, String planName, boolean planExi
// Unsupported action in GitLab CI setup
}
+ @Override
+ public void mockGetBuildPlanConfig(String projectKey, String planName) {
+ // Unsupported action in GitLab CI setup
+ }
+
@Override
public void mockHealthInCiService(boolean isRunning, HttpStatus httpStatus) throws URISyntaxException, JsonProcessingException {
gitlabRequestMockProvider.mockHealth(isRunning ? "ok" : "notok", httpStatus);
@@ -404,4 +405,9 @@ public void verifyMocks() {
public void mockUserExists(String username) throws Exception {
gitlabRequestMockProvider.mockUserExists(username, true);
}
+
+ @Override
+ public void mockGetCiProjectMissing(ProgrammingExercise exercise) throws IOException {
+ // not needed for GitLab
+ }
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java
index 4e2cc8f99c82..0ce1f4bc9e85 100644
--- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationIndependentTest.java
@@ -210,11 +210,6 @@ public void mockFailToCreateUserInExternalUserManagement(User user, boolean fail
log.debug("Called mockFailToCreateUserInExternalUserManagement with args {}, {}, {}, {}", user, failInVcs, failInCi, failToGetCiUser);
}
- @Override
- public void mockDeleteUserInUserManagement(User user, boolean userExistsInUserManagement, boolean failInVcs, boolean failInCi) {
- log.debug("Called mockDeleteUserInUserManagement with args {}, {}, {}, {}", user, userExistsInUserManagement, failInVcs, failInCi);
- }
-
@Override
public void mockCreateGroupInUserManagement(String groupName) {
log.debug("Called mockCreateGroupInUserManagement with args {}", groupName);
@@ -260,6 +255,11 @@ public void mockGetBuildPlan(String projectKey, String planName, boolean planExi
log.debug("Called mockGetBuildPlan with args {}, {}, {}, {}, {}, {}", projectKey, planName, planExistsInCi, planIsActive, planIsBuilding, failToGetBuild);
}
+ @Override
+ public void mockGetBuildPlanConfig(String projectKey, String planName) {
+ log.debug("Called mockGetBuildPlanConfig with args {}, {}", projectKey, planName);
+ }
+
@Override
public void mockHealthInCiService(boolean isRunning, HttpStatus httpStatus) {
log.debug("Called mockHealthInCiService with args {}, {}", isRunning, httpStatus);
@@ -319,4 +319,9 @@ public void mockDefaultBranch(ProgrammingExercise programmingExercise) {
public void mockUserExists(String username) {
log.debug("Called mockUserExists with args {}", username);
}
+
+ @Override
+ public void mockGetCiProjectMissing(ProgrammingExercise exercise) {
+ log.debug("Requested CI project {}", exercise.getProjectKey());
+ }
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationJenkinsGitlabTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationJenkinsGitlabTest.java
index 69d26da62033..40abd712ffd0 100644
--- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationJenkinsGitlabTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationJenkinsGitlabTest.java
@@ -32,8 +32,6 @@
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
-import com.offbytwo.jenkins.JenkinsServer;
-
import de.tum.cit.aet.artemis.assessment.web.ResultWebsocketService;
import de.tum.cit.aet.artemis.communication.service.notifications.GroupNotificationScheduleService;
import de.tum.cit.aet.artemis.core.connector.AeolusRequestMockProvider;
@@ -74,9 +72,6 @@ public abstract class AbstractSpringIntegrationJenkinsGitlabTest extends Abstrac
@MockitoSpyBean
protected GitLabService versionControlService;
- @MockitoSpyBean
- protected JenkinsServer jenkinsServer;
-
@MockitoSpyBean
protected JenkinsJobPermissionsService jenkinsJobPermissionsService;
@@ -104,7 +99,7 @@ public abstract class AbstractSpringIntegrationJenkinsGitlabTest extends Abstrac
@AfterEach
@Override
protected void resetSpyBeans() {
- Mockito.reset(continuousIntegrationService, versionControlService, jenkinsServer);
+ Mockito.reset(continuousIntegrationService, versionControlService);
super.resetSpyBeans();
}
@@ -201,9 +196,11 @@ private void mockCloneAndEnableAllBuildPlans(ProgrammingExercise sourceExercise,
String templateBuildPlanId = targetProjectKey + "-" + TEMPLATE.getName();
String solutionBuildPlanId = targetProjectKey + "-" + SOLUTION.getName();
+ jenkinsRequestMockProvider.mockGetFolderJob(targetProjectKey, null);
jenkinsRequestMockProvider.mockCreateProjectForExercise(exerciseToBeImported, false);
- jenkinsRequestMockProvider.mockCopyBuildPlan(sourceExercise.getProjectKey(), targetProjectKey);
- jenkinsRequestMockProvider.mockCopyBuildPlan(sourceExercise.getProjectKey(), targetProjectKey);
+ jenkinsRequestMockProvider.mockGetFolderJob(targetProjectKey);
+ jenkinsRequestMockProvider.mockCopyBuildPlanFromTemplate(sourceExercise.getProjectKey(), targetProjectKey, templateBuildPlanId);
+ jenkinsRequestMockProvider.mockCopyBuildPlanFromSolution(sourceExercise.getProjectKey(), targetProjectKey, solutionBuildPlanId);
jenkinsRequestMockProvider.mockGivePlanPermissions(targetProjectKey, templateBuildPlanId);
jenkinsRequestMockProvider.mockGivePlanPermissions(targetProjectKey, solutionBuildPlanId);
jenkinsRequestMockProvider.mockEnablePlan(targetProjectKey, templateBuildPlanId, planExistsInCi, shouldPlanEnableFail);
@@ -323,7 +320,7 @@ public void mockFetchCommitInfo(String projectKey, String repositorySlug, String
@Override
public void mockCopyBuildPlan(ProgrammingExerciseStudentParticipation participation) throws Exception {
final var projectKey = participation.getProgrammingExercise().getProjectKey();
- jenkinsRequestMockProvider.mockCopyBuildPlan(projectKey, projectKey);
+ jenkinsRequestMockProvider.mockCopyBuildPlanFromTemplate(projectKey, projectKey, participation.getBuildPlanId());
}
@Override
@@ -392,12 +389,6 @@ public void mockFailToCreateUserInExternalUserManagement(User user, boolean fail
jenkinsRequestMockProvider.mockCreateUser(user, false, failInCi, failToGetCiUser);
}
- @Override
- public void mockDeleteUserInUserManagement(User user, boolean userExistsInUserManagement, boolean failInVcs, boolean failInCi) throws Exception {
- gitlabRequestMockProvider.mockDeleteVcsUser(user.getLogin(), userExistsInUserManagement, failInVcs);
- jenkinsRequestMockProvider.mockDeleteUser(user, userExistsInUserManagement, failInCi);
- }
-
@Override
public void mockUpdateCoursePermissions(Course updatedCourse, String oldInstructorGroup, String oldEditorGroup, String oldTeachingAssistantGroup) throws Exception {
gitlabRequestMockProvider.mockUpdateCoursePermissions(updatedCourse, oldInstructorGroup, oldEditorGroup, oldTeachingAssistantGroup);
@@ -444,14 +435,14 @@ public void mockDeleteBuildPlanProject(String projectKey, boolean shouldFail) th
@Override
public void mockAddUserToGroupInUserManagement(User user, String group, boolean failInCi) throws Exception {
gitlabRequestMockProvider.mockUpdateVcsUser(user.getLogin(), user, Set.of(), Set.of(group), false);
- jenkinsRequestMockProvider.mockAddUsersToGroups(user.getLogin(), Set.of(group), failInCi);
+ jenkinsRequestMockProvider.mockAddUsersToGroups(Set.of(group), failInCi);
}
@Override
public void mockRemoveUserFromGroup(User user, String group, boolean failInCi) throws Exception {
gitlabRequestMockProvider.mockUpdateVcsUser(user.getLogin(), user, Set.of(group), Set.of(), false);
jenkinsRequestMockProvider.mockRemoveUserFromGroups(Set.of(group), failInCi);
- jenkinsRequestMockProvider.mockAddUsersToGroups(user.getLogin(), Set.of(group), false);
+ jenkinsRequestMockProvider.mockAddUsersToGroups(Set.of(group), false);
}
@Override
@@ -460,6 +451,13 @@ public void mockGetBuildPlan(String projectKey, String planName, boolean planExi
jenkinsRequestMockProvider.mockGetBuildStatus(projectKey, planName, planExistsInCi, planIsActive, planIsBuilding, failToGetBuild);
}
+ @Override
+ public void mockGetBuildPlanConfig(String projectKey, String planName) throws Exception {
+ jenkinsRequestMockProvider.mockGetFolderJob(projectKey);
+ jenkinsRequestMockProvider.mockGetFolderConfig(projectKey);
+ jenkinsRequestMockProvider.mockGetJobConfig(projectKey, planName);
+ }
+
@Override
public void mockHealthInCiService(boolean isRunning, HttpStatus httpStatus) throws Exception {
jenkinsRequestMockProvider.mockHealth(isRunning, httpStatus);
@@ -565,4 +563,9 @@ public void mockDeleteProgrammingExercise(ProgrammingExercise programmingExercis
}
gitlabRequestMockProvider.mockDeleteProject(projectKey, false);
}
+
+ @Override
+ public void mockGetCiProjectMissing(ProgrammingExercise exercise) throws IOException {
+ jenkinsRequestMockProvider.mockGetFolderJob(exercise.getProjectKey(), null);
+ }
}
diff --git a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java
index e123235d449e..2538b01761f7 100644
--- a/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/shared/base/AbstractSpringIntegrationLocalCILocalVCTest.java
@@ -505,11 +505,6 @@ public void mockFailToCreateUserInExternalUserManagement(User user, boolean fail
// Not implemented for local VC and local CI
}
- @Override
- public void mockDeleteUserInUserManagement(User user, boolean userExistsInUserManagement, boolean failInVcs, boolean failInCi) {
- // Not implemented for local VC and local CI
- }
-
@Override
public void mockCreateGroupInUserManagement(String groupName) {
// Not implemented for local VC and local CI
@@ -555,6 +550,11 @@ public void mockGetBuildPlan(String projectKey1, String planName, boolean planEx
// Not implemented for local VC and local CI
}
+ @Override
+ public void mockGetBuildPlanConfig(String projectKey, String planName) {
+ // not needed for localVCS/CI
+ }
+
@Override
public void mockHealthInCiService(boolean isRunning, HttpStatus httpStatus) {
// Not implemented for local VC and local CI
@@ -614,4 +614,9 @@ public void mockDefaultBranch(ProgrammingExercise programmingExercise) throws IO
public void mockUserExists(String username) throws Exception {
// Not implemented for local VC and local CI
}
+
+ @Override
+ public void mockGetCiProjectMissing(ProgrammingExercise exercise) {
+ // not relevant for local VC and local CI
+ }
}
diff --git a/src/test/resources/config/application-artemis.yml b/src/test/resources/config/application-artemis.yml
index bb921732c292..7ee6cb6f7597 100644
--- a/src/test/resources/config/application-artemis.yml
+++ b/src/test/resources/config/application-artemis.yml
@@ -49,7 +49,6 @@ artemis:
token: fake-token
url: https://continuous-integration.fake.fake
concurrent-build-size: 1
- secret-push-token: fake-token-hash
vcs-credentials: fake-key
artemis-authentication-token-key: fake-key
artemis-authentication-token-value: fake-token